Removing contents of ThirdParty/3Di. The load balancer can now be found
at http://forge.opensimulator.org/gf/project/loadbalancer/ config key: svn.rmdir0.6.3-post-fixes
parent
7aa216d574
commit
fefe0ff3d9
File diff suppressed because it is too large
Load Diff
|
@ -1,12 +0,0 @@
|
|||
<Addin id="LoadBalancer" version="0.1">
|
||||
<Runtime>
|
||||
<Import assembly="OpenSim.ApplicationPlugins.LoadBalancer.dll" />
|
||||
</Runtime>
|
||||
<Dependencies>
|
||||
<Addin id="OpenSim" version="0.5" />
|
||||
<Addin id="RegionProxy" version="0.1" />
|
||||
</Dependencies>
|
||||
<Extension path="/OpenSim/Startup">
|
||||
<Plugin id="LoadBalancer" type="OpenSim.ApplicationPlugins.LoadBalancer.LoadBalancerPlugin" />
|
||||
</Extension>
|
||||
</Addin>
|
|
@ -1,225 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSim Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.LoadBalancer
|
||||
{
|
||||
public class AsynchronousClient
|
||||
{
|
||||
private static ManualResetEvent connectDone = new ManualResetEvent(false);
|
||||
private static ManualResetEvent sendDone = new ManualResetEvent(false);
|
||||
|
||||
public static Socket StartClient(string hostname, int port)
|
||||
{
|
||||
try
|
||||
{
|
||||
IPHostEntry ipHostInfo = Dns.GetHostEntry(hostname);
|
||||
IPAddress ipAddress = ipHostInfo.AddressList[0];
|
||||
IPEndPoint remoteEP = new IPEndPoint(ipAddress, port);
|
||||
|
||||
Socket client = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
client.BeginConnect(remoteEP, new AsyncCallback(ConnectCallback), client);
|
||||
connectDone.WaitOne();
|
||||
/*
|
||||
Send(client,"This is a test<EOF>");
|
||||
sendDone.WaitOne();
|
||||
Receive(client);
|
||||
receiveDone.WaitOne();
|
||||
client.Shutdown(SocketShutdown.Both);
|
||||
client.Close();
|
||||
*/
|
||||
return client;
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
throw new Exception("socket error !!");
|
||||
}
|
||||
}
|
||||
|
||||
private static void ConnectCallback(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket client = (Socket) ar.AsyncState;
|
||||
client.EndConnect(ar);
|
||||
Console.WriteLine("Socket connected to {0}", client.RemoteEndPoint.ToString());
|
||||
connectDone.Set();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
|
||||
public static void Send(Socket client, byte[] byteData)
|
||||
{
|
||||
client.BeginSend(byteData, 0, byteData.Length, 0, new AsyncCallback(SendCallback), client);
|
||||
}
|
||||
|
||||
private static void SendCallback(IAsyncResult ar)
|
||||
{
|
||||
try
|
||||
{
|
||||
Socket client = (Socket) ar.AsyncState;
|
||||
int bytesSent = client.EndSend(ar);
|
||||
if (bytesSent > 0)
|
||||
{
|
||||
//Console.WriteLine("Sent {0} bytes to server.", bytesSent);
|
||||
}
|
||||
sendDone.Set();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
public class InternalPacketHeader
|
||||
{
|
||||
public Guid agent_id;
|
||||
private byte[] buffer = new byte[32];
|
||||
public int numbytes;
|
||||
public int region_port;
|
||||
public int throttlePacketType;
|
||||
public int type;
|
||||
|
||||
public void FromBytes(byte[] bytes)
|
||||
{
|
||||
MemoryStream memstr = new MemoryStream(bytes);
|
||||
memstr.Seek(0, SeekOrigin.Begin);
|
||||
BinaryReader binread = new BinaryReader(memstr);
|
||||
|
||||
type = binread.ReadInt32();
|
||||
throttlePacketType = binread.ReadInt32();
|
||||
numbytes = binread.ReadInt32();
|
||||
agent_id = new Guid(binread.ReadBytes(16));
|
||||
region_port = binread.ReadInt32();
|
||||
|
||||
binread.Close();
|
||||
}
|
||||
|
||||
public byte[] ToBytes()
|
||||
{
|
||||
int i = 0;
|
||||
buffer[i++] = (byte) (type % 256);
|
||||
buffer[i++] = (byte) ((type >> 8) % 256);
|
||||
buffer[i++] = (byte) ((type >> 16) % 256);
|
||||
buffer[i++] = (byte) ((type >> 24) % 256);
|
||||
|
||||
buffer[i++] = (byte) (throttlePacketType % 256);
|
||||
buffer[i++] = (byte) ((throttlePacketType >> 8) % 256);
|
||||
buffer[i++] = (byte) ((throttlePacketType >> 16) % 256);
|
||||
buffer[i++] = (byte) ((throttlePacketType >> 24) % 256);
|
||||
|
||||
buffer[i++] = (byte) (numbytes % 256);
|
||||
buffer[i++] = (byte) ((numbytes >> 8) % 256);
|
||||
buffer[i++] = (byte) ((numbytes >> 16) % 256);
|
||||
buffer[i++] = (byte) ((numbytes >> 24) % 256);
|
||||
|
||||
// no endian care
|
||||
Buffer.BlockCopy(agent_id.ToByteArray(), 0, buffer, i, 16);
|
||||
i += 16;
|
||||
|
||||
buffer[i++] = (byte) (region_port % 256);
|
||||
buffer[i++] = (byte) ((region_port >> 8) % 256);
|
||||
buffer[i++] = (byte) ((region_port >> 16) % 256);
|
||||
buffer[i++] = (byte) ((region_port >> 24) % 256);
|
||||
|
||||
return buffer;
|
||||
}
|
||||
}
|
||||
|
||||
public class TcpClient
|
||||
{
|
||||
public static int internalPacketHeaderSize = 4 * 4 + 16 * 1;
|
||||
private Socket mConnection;
|
||||
|
||||
private string mHostname;
|
||||
private int mPort;
|
||||
|
||||
public TcpClient(string hostname, int port)
|
||||
{
|
||||
mHostname = hostname;
|
||||
mPort = port;
|
||||
mConnection = null;
|
||||
}
|
||||
|
||||
public void connect()
|
||||
{
|
||||
mConnection = AsynchronousClient.StartClient(mHostname, mPort);
|
||||
}
|
||||
|
||||
/*
|
||||
public void receive()
|
||||
{
|
||||
if (mConnection == null)
|
||||
{
|
||||
throw new Exception("client not initialized");
|
||||
}
|
||||
try
|
||||
{
|
||||
AsynchronousClient.Receive(this.mConnection);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
mConnection = null;
|
||||
}
|
||||
}
|
||||
*/
|
||||
|
||||
public void send(InternalPacketHeader header, byte[] packet)
|
||||
{
|
||||
lock (this)
|
||||
{
|
||||
if (mConnection == null)
|
||||
{
|
||||
// throw new Exception("client not initialized");
|
||||
connect();
|
||||
}
|
||||
|
||||
AsynchronousClient.Send(mConnection, header.ToBytes());
|
||||
|
||||
/*
|
||||
for (int i = 0; i < 10; i++)
|
||||
{
|
||||
Console.Write(packet[i] + " ");
|
||||
}
|
||||
Console.WriteLine("");
|
||||
*/
|
||||
AsynchronousClient.Send(mConnection, packet);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,219 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSim Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.IO;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Threading;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.LoadBalancer
|
||||
{
|
||||
public class StateObject
|
||||
{
|
||||
public const int BufferSize = 2048;
|
||||
public byte[] buffer = new byte[BufferSize];
|
||||
public InternalPacketHeader header = null;
|
||||
public MemoryStream ms_ptr = new MemoryStream();
|
||||
public Socket workSocket = null;
|
||||
}
|
||||
|
||||
public class AsynchronousSocketListener
|
||||
{
|
||||
public static ManualResetEvent allDone = new ManualResetEvent(false);
|
||||
public static string data = null;
|
||||
|
||||
#region KIRYU
|
||||
|
||||
#region Delegates
|
||||
|
||||
public delegate void PacketRecieveHandler(InternalPacketHeader header, byte[] buff);
|
||||
|
||||
#endregion
|
||||
|
||||
public static PacketRecieveHandler PacketHandler = null;
|
||||
|
||||
#endregion
|
||||
|
||||
public AsynchronousSocketListener()
|
||||
{
|
||||
}
|
||||
|
||||
public static void StartListening(int port)
|
||||
{
|
||||
IPHostEntry ipHostInfo = Dns.GetHostEntry(Dns.GetHostName());
|
||||
IPAddress ipAddress = ipHostInfo.AddressList[0];
|
||||
IPEndPoint localEndPoint = new IPEndPoint(ipAddress, port);
|
||||
|
||||
Socket listener = new Socket(AddressFamily.InterNetwork, SocketType.Stream, ProtocolType.Tcp);
|
||||
try
|
||||
{
|
||||
listener.Bind(localEndPoint);
|
||||
listener.Listen(100);
|
||||
while (true)
|
||||
{
|
||||
allDone.Reset();
|
||||
listener.BeginAccept(new AsyncCallback(AcceptCallback), listener);
|
||||
allDone.WaitOne();
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
Console.WriteLine(e.ToString());
|
||||
}
|
||||
/*
|
||||
Console.WriteLine("\nPress ENTER to continue...");
|
||||
Console.Read();
|
||||
*/
|
||||
}
|
||||
|
||||
public static void AcceptCallback(IAsyncResult ar)
|
||||
{
|
||||
allDone.Set();
|
||||
Socket listener = (Socket) ar.AsyncState;
|
||||
Socket handler = listener.EndAccept(ar);
|
||||
StateObject state = new StateObject();
|
||||
state.workSocket = handler;
|
||||
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
|
||||
}
|
||||
|
||||
public static void ReadCallback(IAsyncResult ar)
|
||||
{
|
||||
StateObject state = (StateObject) ar.AsyncState;
|
||||
Socket handler = state.workSocket;
|
||||
|
||||
try
|
||||
{
|
||||
int bytesRead = handler.EndReceive(ar);
|
||||
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "Received packet [{0}]", bytesRead);
|
||||
|
||||
if (bytesRead > 0)
|
||||
{
|
||||
state.ms_ptr.Write(state.buffer, 0, bytesRead);
|
||||
}
|
||||
else
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "Connection terminated");
|
||||
return;
|
||||
}
|
||||
|
||||
long rest_size = state.ms_ptr.Length;
|
||||
long current_pos = 0;
|
||||
while (rest_size > TcpClient.internalPacketHeaderSize)
|
||||
{
|
||||
if ((state.header == null) && (rest_size >= TcpClient.internalPacketHeaderSize))
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "Processing header");
|
||||
|
||||
// reading header
|
||||
state.header = new InternalPacketHeader();
|
||||
|
||||
byte[] headerbytes = new byte[TcpClient.internalPacketHeaderSize];
|
||||
state.ms_ptr.Position = current_pos;
|
||||
state.ms_ptr.Read(headerbytes, 0, TcpClient.internalPacketHeaderSize);
|
||||
state.ms_ptr.Seek(0, SeekOrigin.End);
|
||||
state.header.FromBytes(headerbytes);
|
||||
}
|
||||
|
||||
if ((state.header != null) && (rest_size >= state.header.numbytes + TcpClient.internalPacketHeaderSize))
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "Processing body");
|
||||
|
||||
// reading body
|
||||
byte[] packet = new byte[state.header.numbytes];
|
||||
state.ms_ptr.Position = current_pos + TcpClient.internalPacketHeaderSize;
|
||||
state.ms_ptr.Read(packet, 0, state.header.numbytes);
|
||||
|
||||
/*
|
||||
for (int i=0; i<state.header.numbytes; i++)
|
||||
{
|
||||
System.Console.Write(packet[i] + " ");
|
||||
}
|
||||
System.Console.WriteLine();
|
||||
*/
|
||||
|
||||
state.ms_ptr.Seek(0, SeekOrigin.End);
|
||||
// call loadbarancer function
|
||||
if (PacketHandler == null)
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "PacketHandler not found");
|
||||
}
|
||||
else
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", "calling PacketHandler");
|
||||
PacketHandler(state.header, packet);
|
||||
}
|
||||
|
||||
int read_size = state.header.numbytes + TcpClient.internalPacketHeaderSize;
|
||||
state.header = null;
|
||||
|
||||
rest_size -= read_size;
|
||||
current_pos += read_size;
|
||||
|
||||
if (rest_size < TcpClient.internalPacketHeaderSize)
|
||||
{
|
||||
byte[] rest_bytes = new byte[rest_size];
|
||||
state.ms_ptr.Position = read_size;
|
||||
state.ms_ptr.Read(rest_bytes, 0, (int) rest_size);
|
||||
state.ms_ptr.Close();
|
||||
state.ms_ptr = new MemoryStream();
|
||||
state.ms_ptr.Write(rest_bytes, 0, (int) rest_size);
|
||||
break;
|
||||
}
|
||||
}
|
||||
} // while (true)
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
//MainLog.Instance.Verbose("TCPSERVER", e.ToString());
|
||||
//MainLog.Instance.Verbose("TCPSERVER", e.StackTrace);
|
||||
}
|
||||
|
||||
handler.BeginReceive(state.buffer, 0, StateObject.BufferSize, 0, new AsyncCallback(ReadCallback), state);
|
||||
}
|
||||
}
|
||||
|
||||
public class TcpServer
|
||||
{
|
||||
private int mPort = 11000;
|
||||
|
||||
public TcpServer()
|
||||
{
|
||||
}
|
||||
|
||||
public TcpServer(int port)
|
||||
{
|
||||
mPort = port;
|
||||
}
|
||||
|
||||
public void start()
|
||||
{
|
||||
AsynchronousSocketListener.StartListening(mPort);
|
||||
}
|
||||
}
|
||||
}
|
|
@ -1,82 +0,0 @@
|
|||
INTRODUCTION
|
||||
|
||||
This folder contains code that implement:
|
||||
|
||||
1. Dynamic load balancing
|
||||
|
||||
OpenSim is allowing many regions to share a region server, but the optimal
|
||||
number of regions on each server depends on the load of each region, something
|
||||
which may change as time goes on. 3Di is working on a load balancer that
|
||||
allows the current load to be monitored and regions to be reassigned without
|
||||
requiring the servers to be restarted. To move a region, its state is
|
||||
serialized, and a new clone is created on the target server using this
|
||||
stream. The old region is then destroyed and the client viewer updated to use
|
||||
the new region address.
|
||||
|
||||
2. Region splitting
|
||||
|
||||
Currently each region can hold only a small number of avatars. To allow more
|
||||
avatars in each region, 3Di has implemented region splitting, in which several
|
||||
copies of a given region can be distributed across the region servers. Each
|
||||
sub-region updates a fraction of the avatars, and sends state updates to the
|
||||
other sub-regions.
|
||||
|
||||
IMPLEMENTATION
|
||||
|
||||
The code is organised as follows:
|
||||
|
||||
* LoadBalancer: communicates with other region servers and creates/destroys
|
||||
regions on command
|
||||
* RegionMonitor/MonitorGUI: provides a browser GUI, showing the state of the
|
||||
grid, and provides buttons for controlling region movement, splitting, and
|
||||
merging.
|
||||
* RegionMonitor/ServerPlugin: this is a region server plugin which
|
||||
communicates with the load balancer GUI to provide information
|
||||
on the identity and status of the regions on the grid
|
||||
* RegionProxy: maps messages from a clients to the true location of a region.
|
||||
|
||||
USAGE
|
||||
|
||||
In order to use these additions the following lines have to be added to
|
||||
OpenSim.ini:
|
||||
|
||||
proxy_offset = -1000
|
||||
proxy_url = http://10.8.1.50:9001
|
||||
serialize_dir = /mnt/temp/
|
||||
|
||||
If defined, proxy_offset defines how to calculate the true region port, e.g.
|
||||
if the XML defines the port as 9000 the actual port is 8000 if proxy_offset
|
||||
is -1000. The RegionProxy module will open a port at 9000 which the clients
|
||||
can connect to, and route all traffic from there to port 8000. This allows
|
||||
the region proxy to run on region server together with regions without
|
||||
blocking them by using the same port number.
|
||||
|
||||
The proxy location is defined in proxy_url. When splitting, the region state
|
||||
is stored on a file in the folder specified in serialize_dir. This has to be
|
||||
a shared folder which both region servers involved in the split have access to.
|
||||
|
||||
3. Monitor GUI
|
||||
|
||||
RegionMonitor/MonitorGUI is used to view status of all the managed Region
|
||||
servers, and send "Move", "Split", "Merge" commands to a specified Regions
|
||||
server.
|
||||
|
||||
MonitorGUI is a web-based application. You can access it through a web browser.
|
||||
Its back-end is written in perl. (CGI script)
|
||||
|
||||
Pre-requierments (CentOS, Fedora)
|
||||
|
||||
RPM package "perl-XML-RPC" and relevant packages.
|
||||
|
||||
Installation
|
||||
|
||||
1. Install Apache
|
||||
2. copy all the files undef "ThirdParty/3Di/RegionMonitor/MonitorGUI/htdocs" to
|
||||
"$APACHE_ROOT/htdocs"
|
||||
3. Configuration in "monitor.cgi"
|
||||
* 10th line, set the value to your "monitor.cgi"'s location.
|
||||
* 11th line, set the value to your Grid server.
|
||||
* 12th line, set your region proxy port number here.
|
||||
(ref. OpenSim.ini::NetWork::http_listener_port)
|
||||
* The code also works fine with mod_perl.
|
||||
|
|
@ -1,214 +0,0 @@
|
|||
package MonitorGUI::View;
|
||||
|
||||
use strict;
|
||||
|
||||
my @server_list;
|
||||
my $max_port;
|
||||
my $regions;
|
||||
|
||||
sub screen_header {
|
||||
return << "HEADER";
|
||||
<HTML>
|
||||
<HEAD>
|
||||
<STYLE TYPE="text/css">
|
||||
<!--
|
||||
a:link {font-size: 12pt; text-decoration:none; color:#0000ff ;}
|
||||
a:visited {font-size: 12pt; text-decoration:none; color:#ff0000 ;}
|
||||
a:active {font-size: 12pt; text-decoration:none; color:#00ff00 ;}
|
||||
a:hover {font-size: 12pt; text-decoration:underline; color:#ff00ff ;}
|
||||
td {font-size: 12pt;border:0;}
|
||||
th {background-color:#000000; font-size: 12pt;border:0; color:#FFFFFF; }
|
||||
tr {background-color:#FFFFFF; }
|
||||
b {font-size: 12pt;}
|
||||
//table {background-color:#000000; }
|
||||
-->
|
||||
</STYLE>
|
||||
<META http-equiv="content-type" content="text/html;charset=UTF-8" />
|
||||
<META name="refresh" content="300" />
|
||||
<TITLE>Region Monitor GUI, 3Di</TITLE>
|
||||
</HEAD>
|
||||
<BODY>
|
||||
HEADER
|
||||
}
|
||||
|
||||
sub screen_footer {
|
||||
return << "FOOTER";
|
||||
</BODY>
|
||||
</HTML>
|
||||
FOOTER
|
||||
}
|
||||
|
||||
sub html {
|
||||
my $grid_info = shift;
|
||||
my $regions_list = $grid_info->{"sim-profiles"};
|
||||
$regions = undef;
|
||||
foreach(@$regions_list) {
|
||||
my $ip = $_->{sim_ip} || "UNKNOWN";
|
||||
my $port = $_->{sim_port} || "UNKNOWN";
|
||||
$regions->{$ip}->{$port} = $_;
|
||||
if (!$regions->{max_port} || $regions->{max_port} < $port) {
|
||||
$regions->{max_port} = $port;
|
||||
}
|
||||
}
|
||||
@server_list = keys %$regions;
|
||||
$max_port = $regions->{max_port};
|
||||
my $html = "";
|
||||
foreach my $machine (@server_list) {
|
||||
next if ($machine eq "max_port");
|
||||
$html .= &_machine_view($machine, $regions->{$machine});
|
||||
}
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub _machine_view {
|
||||
my ($ip, $info) = @_;
|
||||
my $region_html = "";
|
||||
foreach my $region (keys %$info) {
|
||||
$region_html .= &_region_html($info->{$region});
|
||||
}
|
||||
my $html =<< "MACHINE_HTML";
|
||||
<h3>$ip</h3>
|
||||
$region_html
|
||||
<hr size=0 noshade />
|
||||
MACHINE_HTML
|
||||
}
|
||||
|
||||
sub _region_html {
|
||||
my $region_info = shift;
|
||||
my $name = $region_info->{name} || "UNKNOWN";
|
||||
my $x = $region_info->{x} || -1;
|
||||
my $y = $region_info->{y} || -1;
|
||||
my $ip = $region_info->{sim_ip} || "UNKNOWN";
|
||||
my $port = $region_info->{sim_port} || "UNKNOWN";
|
||||
my $get_scene_presence_filter = $region_info->{get_scene_presence_filter};
|
||||
my $get_scene_presence = $region_info->{get_scene_presence};
|
||||
my $get_avatar_filter = $region_info->{get_avatar_filter};
|
||||
my $get_avatar = $region_info->{get_avatar};
|
||||
my $avatar_names = $region_info->{avatar_names};
|
||||
my $action_forms = &_action_forms($region_info);
|
||||
my $html = <<"REGION_HTML";
|
||||
<strong>$name</strong><br/>
|
||||
$ip:$port | X: $x Y: $y<br/>
|
||||
<table border="0">
|
||||
<tr>
|
||||
<td>get_avatar</td>
|
||||
<td>$get_avatar</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>get_avatar_filter</td>
|
||||
<td>$get_avatar_filter</td>
|
||||
<td>$avatar_names</td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>get_scene_presence</td>
|
||||
<td>$get_scene_presence</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
<tr>
|
||||
<td>get_scene_presence_filter</td>
|
||||
<td>$get_scene_presence_filter</td>
|
||||
<td></td>
|
||||
</tr>
|
||||
</table>
|
||||
$action_forms
|
||||
REGION_HTML
|
||||
return $html;
|
||||
}
|
||||
|
||||
sub _action_forms {
|
||||
my $region_info = shift;
|
||||
my $ip = $region_info->{sim_ip};
|
||||
my $port = $region_info->{sim_port};
|
||||
my $default_input_port = $max_port + 1;
|
||||
my $move_to_options = "";
|
||||
my $split_to_options = "";
|
||||
my $merge_ip_options = "";
|
||||
foreach(@server_list) {
|
||||
next if ($_ eq "max_port");
|
||||
$merge_ip_options .= "<option value=\"$_\">$_\n";
|
||||
$split_to_options .= "<option value=\"$_\">$_\n";
|
||||
#next if ($_ eq $ip);
|
||||
$move_to_options .= "<option value=\"$_\">$_\n";
|
||||
}
|
||||
my $merge_port_options = "";
|
||||
my $merge_disabled = "disabled";
|
||||
|
||||
foreach(keys %{$regions->{$ip}}) {
|
||||
next if ($_ eq $port);
|
||||
$merge_disabled = "";
|
||||
}
|
||||
# for(9000..$max_port) { # TODO :
|
||||
# next if ($_ eq $port);
|
||||
# $merge_port_options .= "<option value=\"$_\">$_\n";
|
||||
# }
|
||||
my %port = ();
|
||||
foreach my $ip (keys %$regions) {
|
||||
next if ($ip eq "max_port");
|
||||
print STDERR "--" . $ip . "\n";
|
||||
foreach my $region_port (keys %{$regions->{$ip}}) {
|
||||
print STDERR "---" . $region_port . "\n";
|
||||
$port{$region_port} = 1;
|
||||
}
|
||||
}
|
||||
foreach (keys %port) {
|
||||
$merge_port_options .= "<option value=\"$_\">$_\n";
|
||||
$merge_disabled = "";
|
||||
}
|
||||
return << "ACTION_FORMS";
|
||||
<table>
|
||||
<tr>
|
||||
<form method="POST">
|
||||
<td>
|
||||
<input type="hidden" name="A" value="move" />
|
||||
<input type="hidden" name="from_ip" value="$ip" />
|
||||
<input type="hidden" name="from_port" value="$port" />
|
||||
<input type="submit" value="Move to" />
|
||||
<select name="to_ip">
|
||||
$move_to_options
|
||||
</select>:
|
||||
<input type="text" name="to_port" size="5" value="$default_input_port"/>
|
||||
</td>
|
||||
</form>
|
||||
|
||||
<td>
|
||||
|
|
||||
</td>
|
||||
|
||||
<form method="POST">
|
||||
<td>
|
||||
<input type="hidden" name="A" value="split" />
|
||||
<input type="hidden" name="from_ip" value="$ip" />
|
||||
<input type="hidden" name="from_port" value="$port" />
|
||||
<input type="submit" value="Split to" />
|
||||
<select name="to_ip">
|
||||
$split_to_options
|
||||
</select>:
|
||||
<input type="text" name="to_port" size="5" value="$default_input_port"/>
|
||||
</td>
|
||||
</form>
|
||||
|
||||
<td>
|
||||
|
|
||||
</td>
|
||||
|
||||
<form method="POST">
|
||||
<td>
|
||||
<input type="hidden" name="A" value="merge" />
|
||||
<input type="hidden" name="from_ip" value="$ip" />
|
||||
<input type="hidden" name="master_port" value="$port" />
|
||||
<input type="submit" value="Merge" $merge_disabled />
|
||||
<select name="slave_ip" $merge_disabled>
|
||||
$merge_ip_options
|
||||
</select>
|
||||
<select name="slave_port" $merge_disabled>
|
||||
$merge_port_options
|
||||
</select>
|
||||
</td>
|
||||
</form>
|
||||
</tr>
|
||||
</table>
|
||||
ACTION_FORMS
|
||||
}
|
||||
|
||||
1;
|
|
@ -1,91 +0,0 @@
|
|||
package MyCGI;
|
||||
|
||||
use strict;
|
||||
use CGI;
|
||||
|
||||
sub getParam {
|
||||
my $cgi;
|
||||
if ($ARGV[0]) {
|
||||
$cgi = new CGI($ARGV[0]);
|
||||
} else {
|
||||
$cgi = new CGI;
|
||||
}
|
||||
my @param_names = $cgi->param();
|
||||
my %param = ();
|
||||
foreach (@param_names) {
|
||||
$param{$_} = $cgi->param($_);
|
||||
}
|
||||
return \%param;
|
||||
}
|
||||
|
||||
sub getCookie {
|
||||
my $name = shift;
|
||||
my $cookie_value = &CGI::cookie($name);
|
||||
return &_parse($cookie_value);
|
||||
}
|
||||
|
||||
sub outputHtml {
|
||||
my ($charset, $html) = @_;
|
||||
print &CGI::header(-charset => $charset);
|
||||
print $html;
|
||||
}
|
||||
|
||||
sub outputXml {
|
||||
my ($charset, $xml) = @_;
|
||||
print &CGI::header( -type => 'text/xml', -charset => $charset );
|
||||
print $xml;
|
||||
}
|
||||
|
||||
sub makeCookieValue {
|
||||
my $param = shift;
|
||||
my @data = ();
|
||||
foreach(keys %$param) {
|
||||
push(@data, $_ . "=" . $param->{$_});
|
||||
}
|
||||
return join("&", @data);
|
||||
}
|
||||
|
||||
sub setCookie {
|
||||
my $param = shift;
|
||||
my $cookie = &CGI::cookie(
|
||||
-name => $param->{name} || return,
|
||||
-value => $param->{value},
|
||||
-domain => $param->{domain},
|
||||
-path => $param->{path},
|
||||
-expires => $param->{expires},
|
||||
);
|
||||
return &CGI::header(-cookie => $cookie);
|
||||
}
|
||||
|
||||
sub redirect {
|
||||
my $dest = shift;
|
||||
&CGI::redirect($dest);
|
||||
}
|
||||
|
||||
sub urlEncode {
|
||||
my $str = shift;
|
||||
$str =~ s/([^\w ])/'%'.unpack('H2', $1)/eg;
|
||||
$str =~ tr/ /+/;
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub urlDecode {
|
||||
my $str = shift;
|
||||
$str =~ tr/+/ /;
|
||||
$str =~ s/%([0-9A-Fa-f][0-9A-Fa-f])/pack('H2', $1)/eg;
|
||||
return $str;
|
||||
}
|
||||
|
||||
sub _parse {
|
||||
my $value = shift;
|
||||
my @pair = split(/&/, $value);
|
||||
my %data = ();
|
||||
foreach(@pair) {
|
||||
my ($name, $value) = split(/=/, $_);
|
||||
$data{$name} = $value;
|
||||
}
|
||||
return \%data;
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,100 +0,0 @@
|
|||
package XML::RPC;
|
||||
|
||||
use strict;
|
||||
use Carp;
|
||||
use RPC::XML;
|
||||
use RPC::XML::Parser;
|
||||
use RPC::XML::Client;
|
||||
|
||||
sub new {
|
||||
my ($this, $url) = @_;
|
||||
my %fields = (
|
||||
parser => new RPC::XML::Parser(),
|
||||
url => $url,
|
||||
);
|
||||
return bless \%fields, $this;
|
||||
}
|
||||
|
||||
sub receive {
|
||||
my ($this, $xmldata, $handler) = @_;
|
||||
my $response = undef;
|
||||
eval {
|
||||
my $request = $this->{parser}->parse($xmldata);
|
||||
my @args = map {$_->value} @{$request->args};
|
||||
$response = $handler->($request->{name}, @args);
|
||||
};
|
||||
if ($@) {
|
||||
my %error = (
|
||||
"error" => "ERROR",
|
||||
"message" => $@,
|
||||
);
|
||||
$response = \%error;
|
||||
}
|
||||
if ( ref($response) eq "RPC::XML::response" ) {
|
||||
return $response->as_string;
|
||||
}
|
||||
else {
|
||||
return RPC::XML::response->new($response)->as_string;
|
||||
}
|
||||
}
|
||||
|
||||
sub call {
|
||||
my ($this, $method_name, $param) = @_;
|
||||
if (!$this->{url}) {
|
||||
Carp::croak("XMLRPC: url not set for calling $method_name");
|
||||
}
|
||||
my $client = RPC::XML::Client->new($this->{url});
|
||||
my $request_param = undef;
|
||||
my $req = undef;
|
||||
if (ref $param eq "ARRAY") {
|
||||
$request_param = &_make_array_param($param);
|
||||
$req = RPC::XML::request->new(
|
||||
$method_name,
|
||||
@$request_param,
|
||||
);
|
||||
} elsif (ref $param eq "HASH"){
|
||||
$request_param = &_make_hash_param($param);
|
||||
$req = RPC::XML::request->new(
|
||||
$method_name,
|
||||
$request_param,
|
||||
);
|
||||
} else {
|
||||
Carp::croak("unexpected param type");
|
||||
}
|
||||
my $rpc_res = undef;
|
||||
eval {
|
||||
$rpc_res = $client->send_request($req);
|
||||
};
|
||||
if ($@) {
|
||||
Carp::croak("request " . $this->{url} . "/" . $method_name . " failed. $@" );
|
||||
}
|
||||
if (ref($rpc_res) eq "RPC::XML::struct") {
|
||||
my %res = map { $_ => $rpc_res->{$_}->value } keys %$rpc_res; # remember good perl !!
|
||||
return \%res;
|
||||
} elsif (ref($rpc_res) eq "RPC::XML::string") {
|
||||
return $rpc_res->value;
|
||||
} else {
|
||||
return undef;
|
||||
}
|
||||
}
|
||||
|
||||
sub _make_array_param {
|
||||
my $param = shift;
|
||||
my @array_param = ();
|
||||
foreach (@$param) {
|
||||
push @array_param, RPC::XML::string->new($_); # @@@ only string type
|
||||
}
|
||||
return \@array_param;
|
||||
}
|
||||
|
||||
sub _make_hash_param {
|
||||
my $param = shift;
|
||||
my %hash_param = ();
|
||||
foreach (keys %$param) {
|
||||
$hash_param{$_} = RPC::XML::string->new($param->{$_}); # @@@ only string type
|
||||
}
|
||||
return RPC::XML::struct->new(\%hash_param);
|
||||
}
|
||||
|
||||
1;
|
||||
|
|
@ -1,202 +0,0 @@
|
|||
#!/usr/bin/perl -w
|
||||
|
||||
use strict;
|
||||
use Carp;
|
||||
use MyCGI;
|
||||
use XML::RPC;
|
||||
use MonitorGUI::View;
|
||||
|
||||
use vars qw ($THIS_URL $GRID_SERVER_URL $DEFAULT_PROXY_PORT);
|
||||
$THIS_URL = "http://10.8.1.165/monitorgui/monitor.cgi";
|
||||
$GRID_SERVER_URL = "http://10.8.1.165/opensim/grid.cgi";
|
||||
$DEFAULT_PROXY_PORT = 9000;
|
||||
|
||||
my %ACTIONS = (
|
||||
# Region commands
|
||||
move => \&move_command,
|
||||
split => \&split_command,
|
||||
merge => \&merge_command,
|
||||
# display commands
|
||||
default => \&main_screen,
|
||||
refresh => \&refresh,
|
||||
);
|
||||
|
||||
# ##################
|
||||
# main
|
||||
my $param = &MyCGI::getParam;
|
||||
my $act = $param->{A} || "default";
|
||||
my $contents = "";
|
||||
if (!$ACTIONS{$act}) {
|
||||
&gui_error("404 NOT FOUND");
|
||||
} else {
|
||||
eval {
|
||||
$ACTIONS{$act}->($param);
|
||||
};
|
||||
if ($@) {
|
||||
&gui_error($@);
|
||||
}
|
||||
}
|
||||
|
||||
# #################
|
||||
# Region Commands
|
||||
sub move_command {
|
||||
my $param = shift;
|
||||
# from
|
||||
my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
|
||||
my $from_port = $param->{from_port} || Carp::croak("not enough params (from_port)");
|
||||
my $from_url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# to
|
||||
my $to_ip = $param->{to_ip} || Carp::croak("not enough params (to_ip)");
|
||||
my $to_port = $param->{to_port} || Carp::croak("not enough params (to_port)");
|
||||
my $to_url = "http://" . $param->{to_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# commands
|
||||
eval {
|
||||
&OpenSim::Utility::XMLRPCCall_array($from_url, "SerializeRegion", [$from_ip, $from_port]);
|
||||
&OpenSim::Utility::XMLRPCCall_array($to_url, "DeserializeRegion_Move", [$from_ip, $from_port, $to_ip, $to_port]);
|
||||
&OpenSim::Utility::XMLRPCCall_array($from_url, "TerminateRegion", [$from_port]);
|
||||
};
|
||||
if ($@) {
|
||||
print STDERR "Get Status Error: $@\n";
|
||||
}
|
||||
|
||||
# client refresh
|
||||
&redirect_refresh({wait=>5, force=>"$from_url|$to_url", msg=>"Move region $from_ip:$from_port from $from_url to $to_url"});
|
||||
}
|
||||
|
||||
sub split_command {
|
||||
my $param = shift;
|
||||
# from
|
||||
my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
|
||||
my $from_port = $param->{from_port} || Carp::croak("not enough params (from_port)");
|
||||
my $from_url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# to
|
||||
my $to_ip = $param->{to_ip} || Carp::croak("not enough params (to_ip)");
|
||||
my $to_port = $param->{to_port} || Carp::croak("not enough params (to_port)");
|
||||
my $to_url = "http://" . $param->{to_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# commands
|
||||
eval {
|
||||
&OpenSim::Utility::XMLRPCCall_array($from_url, "SerializeRegion", [$from_ip, $from_port]);
|
||||
&OpenSim::Utility::XMLRPCCall_array($to_url, "DeserializeRegion_Clone", [$from_ip, $from_port, $to_ip, $to_port]);
|
||||
};
|
||||
if ($@) {
|
||||
print STDERR "Get Status Error: $@\n";
|
||||
}
|
||||
|
||||
&redirect_refresh({wait=>5, force=>"$from_url", msg=>"Split region $from_ip:$from_port"});
|
||||
}
|
||||
|
||||
sub merge_command {
|
||||
my $param = shift;
|
||||
# from
|
||||
my $from_ip = $param->{from_ip} || Carp::croak("not enough params (from_ip)");
|
||||
my $url = "http://" . $param->{from_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# ports
|
||||
my $master_port = $param->{master_port} || Carp::croak("not enough params (master_port)");
|
||||
my $slave_ip = $param->{slave_ip} || Carp::croak("not enough params (slave_ip)");
|
||||
my $slave_port = $param->{slave_port} || Carp::croak("not enough params (slave_port)");
|
||||
my $slave_url = "http://" . $param->{slave_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
# commands
|
||||
eval {
|
||||
&XMLRPCCall_array($url, "MergeRegions", [$from_ip, $master_port]);
|
||||
&XMLRPCCall_array($slave_url, "TerminateRegion", [$slave_port]);
|
||||
};
|
||||
if ($@) {
|
||||
print STDERR "Get Status Error: $@\n";
|
||||
}
|
||||
&redirect_refresh({wait=>5, force=>"$url", msg=>"Merge region $from_ip:$master_port, $slave_port"});
|
||||
}
|
||||
|
||||
# #################
|
||||
# Display
|
||||
sub main_screen {
|
||||
my %xml_rpc_param = (
|
||||
# TODO: should be 0 - 65535 ?
|
||||
xmin => 999, ymin => 999, xmax => 1010, ymax => 1010,
|
||||
);
|
||||
my $res_obj = undef;
|
||||
eval {
|
||||
$res_obj = &XMLRPCCall($GRID_SERVER_URL, "map_block", \%xml_rpc_param);
|
||||
};
|
||||
if ($@) {
|
||||
&gui_error("map_block Error: " . $@);
|
||||
}
|
||||
my %copy_obj = %$res_obj;
|
||||
my $getstatus_failed = "<font color=\"red\">GetStatus Failed</font>";
|
||||
my $regions_list = $res_obj->{"sim-profiles"};
|
||||
foreach(@$regions_list) {
|
||||
if ($_->{sim_ip} && $_->{sim_port}) {
|
||||
my $url = "http://" . $_->{sim_ip} . ":" . $DEFAULT_PROXY_PORT;
|
||||
my $port = $_->{sim_port};
|
||||
my $res = undef;
|
||||
eval {
|
||||
$res = &XMLRPCCall_array($url, "GetStatus", [$port]);
|
||||
};
|
||||
if ($@) {
|
||||
print STDERR "Get Status Error: $@\n";
|
||||
}
|
||||
$_->{get_scene_presence_filter} = $res ? $res->{get_scene_presence_filter} : $getstatus_failed;
|
||||
$_->{get_scene_presence} = $res ? $res->{get_scene_presence} : $getstatus_failed;
|
||||
$_->{get_avatar_filter} = $res ? $res->{get_avatar_filter} : $getstatus_failed;
|
||||
$_->{get_avatar} = $res ? $res->{get_avatar} : $getstatus_failed;
|
||||
$_->{avatar_names} = $res ? $res->{avatar_names} : "NO USER";
|
||||
}
|
||||
}
|
||||
my $html = &MonitorGUI::View::html(\%copy_obj);
|
||||
&MyCGI::outputHtml("UTF-8", &MonitorGUI::View::screen_header . $html . &MonitorGUI::View::screen_footer);
|
||||
}
|
||||
|
||||
sub gui_error {
|
||||
my $msg = shift;
|
||||
&MyCGI::outputHtml("UTF-8", "<h1>ERROR</h1><hr />$msg");
|
||||
}
|
||||
|
||||
sub redirect_refresh {
|
||||
my $args = shift;
|
||||
my $wait = $args->{wait};
|
||||
my $force = $args->{force} || "";
|
||||
my $msg = $args->{msg} || "";
|
||||
my $param = "A=refresh&wait=$wait&ip=$force&msg=$msg";
|
||||
my $dist_url = $THIS_URL . "?" . $param;
|
||||
&MyCGI::redirect($dist_url);
|
||||
}
|
||||
|
||||
sub refresh {
|
||||
my $param = shift;
|
||||
my $msg = $param->{msg} || "";
|
||||
my $wait = $param->{wait} || 0;
|
||||
my $force = $param->{ip} || "";
|
||||
#my $jump_url = $force ? "$THIS_URL?A=force&ip=$force" : $THIS_URL;
|
||||
my $jump_url = $THIS_URL;
|
||||
my $html =<< "HTML";
|
||||
<html>
|
||||
<head>
|
||||
<meta http-equiv="Refresh" content="$wait;URL=$jump_url" />
|
||||
<title>Region Monitor GUI REFRESH</title>
|
||||
</head>
|
||||
<body>
|
||||
<h3>$msg</h3>
|
||||
<br>
|
||||
wait <font color="red"><b>$wait</b></font> sec for server to take effect ... <br>
|
||||
(* The page will jump to "Monitor Screen" automatically)
|
||||
</body>
|
||||
</html>
|
||||
HTML
|
||||
&MyCGI::outputHtml("UTF-8", $html);
|
||||
}
|
||||
|
||||
# ##################
|
||||
# Utility
|
||||
sub XMLRPCCall {
|
||||
my ($url, $methodname, $param) = @_;
|
||||
my $xmlrpc = new XML::RPC($url);
|
||||
my $result = $xmlrpc->call($methodname, $param);
|
||||
return $result;
|
||||
}
|
||||
|
||||
sub XMLRPCCall_array {
|
||||
my ($url, $methodname, $param) = @_;
|
||||
my $xmlrpc = new XML::RPC($url);
|
||||
my $result = $xmlrpc->call($methodname, @$param);
|
||||
return $result;
|
||||
}
|
||||
|
|
@ -1,21 +0,0 @@
|
|||
How to get this working on Linux/Apache:
|
||||
|
||||
Create a new directory /var/www/monitor and copy all files htdocs/* files there.
|
||||
|
||||
Include these lines in /etc/apache2/httpdocs
|
||||
---
|
||||
<Directory /var/www/monitor>
|
||||
Options +ExecCGI
|
||||
</Directory>
|
||||
AddHandler cgi-script .cgi
|
||||
---
|
||||
|
||||
Restart Apache: sudo /etc/init.d/apache2 restart
|
||||
|
||||
Check that the perl XML-RPC modules is available ("sudo apt-get install librcp-xml-perl" on Ubuntu)
|
||||
|
||||
Edit /var/www/monitor/monitor.cgi to update the IP addresses for the Grid server (TODO: improve this)
|
||||
|
||||
Start OpenSim in grid mode, use a browser to open http://localhost/monitor/monitor.cgi
|
||||
|
||||
|
|
@ -1,554 +0,0 @@
|
|||
/*
|
||||
* Copyright (c) Contributors, http://opensimulator.org/
|
||||
* See CONTRIBUTORS.TXT for a full list of copyright holders.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
* * Redistributions of source code must retain the above copyright
|
||||
* notice, this list of conditions and the following disclaimer.
|
||||
* * Redistributions in binary form must reproduce the above copyright
|
||||
* notice, this list of conditions and the following disclaimer in the
|
||||
* documentation and/or other materials provided with the distribution.
|
||||
* * Neither the name of the OpenSim Project nor the
|
||||
* names of its contributors may be used to endorse or promote products
|
||||
* derived from this software without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
|
||||
* EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||
* WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
|
||||
* DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
|
||||
* DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
|
||||
* (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
|
||||
* LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
|
||||
* ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
|
||||
* (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
|
||||
* SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
using System;
|
||||
using System.Collections;
|
||||
using System.Collections.Generic;
|
||||
using System.Net;
|
||||
using System.Net.Sockets;
|
||||
using System.Reflection;
|
||||
using log4net;
|
||||
using Mono.Addins;
|
||||
using Nwc.XmlRpc;
|
||||
using OpenSim.Framework;
|
||||
using OpenSim.Framework.Servers;
|
||||
|
||||
namespace OpenSim.ApplicationPlugins.RegionProxy
|
||||
{
|
||||
/* This module has an interface to OpenSim clients that is constant, and is responsible for relaying
|
||||
* messages to and from clients to the region objects. Since the region objects can be duplicated and
|
||||
* moved dynamically, the proxy provides methods for changing and adding regions. If more than one region
|
||||
* is associated with a client port, then the message will be broadcasted to all those regions.
|
||||
*
|
||||
* The client interface port may be blocked. While being blocked, all messages from the clients will be
|
||||
* stored in the proxy. Once the interface port is unblocked again, all stored messages will be resent
|
||||
* to the regions. This functionality is used when moving or cloning an region to make sure that no messages
|
||||
* are sent to the region while it is being reconfigured.
|
||||
*
|
||||
* The proxy opens a XmlRpc interface with these public methods:
|
||||
* - AddPort
|
||||
* - AddRegion
|
||||
* - ChangeRegion
|
||||
* - BlockClientMessages
|
||||
* - UnblockClientMessages
|
||||
*/
|
||||
|
||||
public class RegionProxyPlugin : IApplicationPlugin
|
||||
{
|
||||
private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
|
||||
private BaseHttpServer command_server;
|
||||
private ProxyServer proxy;
|
||||
|
||||
#region IApplicationPlugin Members
|
||||
// TODO: required by IPlugin, but likely not at all right
|
||||
string m_name = "RegionProxy";
|
||||
string m_version = "0.1";
|
||||
|
||||
public string Version { get { return m_version; } }
|
||||
public string Name { get { return m_name; } }
|
||||
|
||||
public void Initialise()
|
||||
{
|
||||
m_log.Info("[PROXY]: " + Name + " cannot be default-initialized!");
|
||||
throw new PluginNotInitialisedException (Name);
|
||||
}
|
||||
|
||||
public void Initialise(OpenSimBase openSim)
|
||||
{
|
||||
m_log.Info("[PROXY] Starting proxy");
|
||||
string proxyURL = openSim.ConfigSource.Source.Configs["Network"].GetString("proxy_url", "");
|
||||
if (proxyURL.Length == 0) return;
|
||||
|
||||
uint port = (uint) Int32.Parse(proxyURL.Split(new char[] {':'})[2]);
|
||||
command_server = new BaseHttpServer(port);
|
||||
command_server.Start();
|
||||
command_server.AddXmlRPCHandler("AddPort", AddPort);
|
||||
command_server.AddXmlRPCHandler("AddRegion", AddRegion);
|
||||
command_server.AddXmlRPCHandler("DeleteRegion", DeleteRegion);
|
||||
command_server.AddXmlRPCHandler("ChangeRegion", ChangeRegion);
|
||||
command_server.AddXmlRPCHandler("BlockClientMessages", BlockClientMessages);
|
||||
command_server.AddXmlRPCHandler("UnblockClientMessages", UnblockClientMessages);
|
||||
command_server.AddXmlRPCHandler("Stop", Stop);
|
||||
|
||||
proxy = new ProxyServer(m_log);
|
||||
}
|
||||
|
||||
public void Dispose()
|
||||
{
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
private XmlRpcResponse Stop(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
proxy.Stop();
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse AddPort(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
int clientPort = (int) request.Params[0];
|
||||
int regionPort = (int) request.Params[1];
|
||||
string regionUrl = (string) request.Params[2];
|
||||
proxy.AddPort(clientPort, regionPort, regionUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse AddRegion(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
int currentRegionPort = (int) request.Params[0];
|
||||
string currentRegionUrl = (string) request.Params[1];
|
||||
int newRegionPort = (int) request.Params[2];
|
||||
string newRegionUrl = (string) request.Params[3];
|
||||
proxy.AddRegion(currentRegionPort, currentRegionUrl, newRegionPort, newRegionUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse ChangeRegion(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
int currentRegionPort = (int) request.Params[0];
|
||||
string currentRegionUrl = (string) request.Params[1];
|
||||
int newRegionPort = (int) request.Params[2];
|
||||
string newRegionUrl = (string) request.Params[3];
|
||||
proxy.ChangeRegion(currentRegionPort, currentRegionUrl, newRegionPort, newRegionUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse DeleteRegion(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
int currentRegionPort = (int) request.Params[0];
|
||||
string currentRegionUrl = (string) request.Params[1];
|
||||
proxy.DeleteRegion(currentRegionPort, currentRegionUrl);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse BlockClientMessages(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
string regionUrl = (string) request.Params[0];
|
||||
int regionPort = (int) request.Params[1];
|
||||
proxy.BlockClientMessages(regionUrl, regionPort);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
|
||||
private XmlRpcResponse UnblockClientMessages(XmlRpcRequest request)
|
||||
{
|
||||
try
|
||||
{
|
||||
string regionUrl = (string) request.Params[0];
|
||||
int regionPort = (int) request.Params[1];
|
||||
proxy.UnblockClientMessages(regionUrl, regionPort);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
return new XmlRpcResponse();
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
public class ProxyServer
|
||||
{
|
||||
protected readonly ILog m_log;
|
||||
protected ProxyMap proxy_map = new ProxyMap();
|
||||
protected AsyncCallback receivedData;
|
||||
protected bool running;
|
||||
|
||||
public ProxyServer(ILog log)
|
||||
{
|
||||
m_log = log;
|
||||
running = false;
|
||||
receivedData = new AsyncCallback(OnReceivedData);
|
||||
}
|
||||
|
||||
public void BlockClientMessages(string regionUrl, int regionPort)
|
||||
{
|
||||
EndPoint client = proxy_map.GetClient(new IPEndPoint(IPAddress.Parse(regionUrl), regionPort));
|
||||
ProxyMap.RegionData rd = proxy_map.GetRegionData(client);
|
||||
rd.isBlocked = true;
|
||||
}
|
||||
|
||||
public void UnblockClientMessages(string regionUrl, int regionPort)
|
||||
{
|
||||
EndPoint client = proxy_map.GetClient(new IPEndPoint(IPAddress.Parse(regionUrl), regionPort));
|
||||
ProxyMap.RegionData rd = proxy_map.GetRegionData(client);
|
||||
|
||||
rd.isBlocked = false;
|
||||
while (rd.storedMessages.Count > 0)
|
||||
{
|
||||
StoredMessage msg = (StoredMessage) rd.storedMessages.Dequeue();
|
||||
//m_log.Verbose("[PROXY]"+"Resending blocked message from {0}", msg.senderEP);
|
||||
SendMessage(msg.buffer, msg.length, msg.senderEP, msg.sd);
|
||||
}
|
||||
}
|
||||
|
||||
public void AddRegion(int oldRegionPort, string oldRegionUrl, int newRegionPort, string newRegionUrl)
|
||||
{
|
||||
//m_log.Verbose("[PROXY]"+"AddRegion {0} {1}", oldRegionPort, newRegionPort);
|
||||
EndPoint client = proxy_map.GetClient(new IPEndPoint(IPAddress.Parse(oldRegionUrl), oldRegionPort));
|
||||
ProxyMap.RegionData data = proxy_map.GetRegionData(client);
|
||||
data.regions.Add(new IPEndPoint(IPAddress.Parse(newRegionUrl), newRegionPort));
|
||||
}
|
||||
|
||||
public void ChangeRegion(int oldRegionPort, string oldRegionUrl, int newRegionPort, string newRegionUrl)
|
||||
{
|
||||
//m_log.Verbose("[PROXY]"+"ChangeRegion {0} {1}", oldRegionPort, newRegionPort);
|
||||
EndPoint client = proxy_map.GetClient(new IPEndPoint(IPAddress.Parse(oldRegionUrl), oldRegionPort));
|
||||
ProxyMap.RegionData data = proxy_map.GetRegionData(client);
|
||||
data.regions.Clear();
|
||||
data.regions.Add(new IPEndPoint(IPAddress.Parse(newRegionUrl), newRegionPort));
|
||||
}
|
||||
|
||||
public void DeleteRegion(int oldRegionPort, string oldRegionUrl)
|
||||
{
|
||||
m_log.InfoFormat("[PROXY]" + "DeleteRegion {0} {1}", oldRegionPort, oldRegionUrl);
|
||||
EndPoint regionEP = new IPEndPoint(IPAddress.Parse(oldRegionUrl), oldRegionPort);
|
||||
EndPoint client = proxy_map.GetClient(regionEP);
|
||||
ProxyMap.RegionData data = proxy_map.GetRegionData(client);
|
||||
data.regions.Remove(regionEP);
|
||||
}
|
||||
|
||||
public void AddPort(int clientPort, int regionPort, string regionUrl)
|
||||
{
|
||||
running = true;
|
||||
|
||||
//m_log.Verbose("[PROXY]"+"AddPort {0} {1}", clientPort, regionPort);
|
||||
IPEndPoint clientEP = new IPEndPoint(IPAddress.Parse("127.0.0.1"), clientPort);
|
||||
proxy_map.Add(clientEP, new IPEndPoint(IPAddress.Parse(regionUrl), regionPort));
|
||||
|
||||
ServerData sd = new ServerData();
|
||||
sd.clientEP = new IPEndPoint(clientEP.Address, clientEP.Port);
|
||||
|
||||
OpenPort(sd);
|
||||
}
|
||||
|
||||
protected void OpenPort(ServerData sd)
|
||||
{
|
||||
// sd.clientEP must be set before calling this function
|
||||
|
||||
ClosePort(sd);
|
||||
|
||||
try
|
||||
{
|
||||
m_log.InfoFormat("[PROXY] Opening special UDP socket on {0}", sd.clientEP);
|
||||
sd.serverIP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), ((IPEndPoint) sd.clientEP).Port);
|
||||
sd.server = new Socket(AddressFamily.InterNetwork, SocketType.Dgram, ProtocolType.Udp);
|
||||
sd.server.Bind(sd.serverIP);
|
||||
|
||||
sd.senderEP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0);
|
||||
//receivedData = new AsyncCallback(OnReceivedData);
|
||||
sd.server.BeginReceiveFrom(sd.recvBuffer, 0, sd.recvBuffer.Length, SocketFlags.None, ref sd.senderEP, receivedData, sd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
m_log.ErrorFormat("[PROXY] Failed to (re)open socket {0}", sd.clientEP);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
}
|
||||
|
||||
protected static void ClosePort(ServerData sd)
|
||||
{
|
||||
// Close the port if it exists and is open
|
||||
if (sd.server == null) return;
|
||||
|
||||
try
|
||||
{
|
||||
sd.server.Shutdown(SocketShutdown.Both);
|
||||
sd.server.Close();
|
||||
}
|
||||
catch (Exception)
|
||||
{
|
||||
}
|
||||
}
|
||||
|
||||
public void Stop()
|
||||
{
|
||||
running = false;
|
||||
m_log.InfoFormat("[PROXY] Stopping the proxy server");
|
||||
}
|
||||
|
||||
|
||||
protected virtual void OnReceivedData(IAsyncResult result)
|
||||
{
|
||||
if (!running) return;
|
||||
|
||||
ServerData sd = (ServerData) result.AsyncState;
|
||||
sd.senderEP = new IPEndPoint(IPAddress.Parse("0.0.0.0"), 0);
|
||||
|
||||
try
|
||||
{
|
||||
int numBytes = sd.server.EndReceiveFrom(result, ref sd.senderEP);
|
||||
if (numBytes > 0)
|
||||
{
|
||||
SendMessage(sd.recvBuffer, numBytes, sd.senderEP, sd);
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
// OpenPort(sd); // reopen the port just in case
|
||||
m_log.ErrorFormat("[PROXY] EndReceiveFrom failed in {0}", sd.clientEP);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
}
|
||||
|
||||
WaitForNextMessage(sd);
|
||||
}
|
||||
|
||||
protected void WaitForNextMessage(ServerData sd)
|
||||
{
|
||||
bool error = true;
|
||||
while (error)
|
||||
{
|
||||
error = false;
|
||||
try
|
||||
{
|
||||
sd.server.BeginReceiveFrom(sd.recvBuffer, 0, sd.recvBuffer.Length, SocketFlags.None, ref sd.senderEP, receivedData, sd);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
error = true;
|
||||
m_log.ErrorFormat("[PROXY] BeginReceiveFrom failed, retrying... {0}", sd.clientEP);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
OpenPort(sd);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
protected void SendMessage(byte[] buffer, int length, EndPoint senderEP, ServerData sd)
|
||||
{
|
||||
int numBytes = length;
|
||||
|
||||
//m_log.ErrorFormat("[PROXY] Got message from {0} in thread {1}, size {2}", senderEP, sd.clientEP, numBytes);
|
||||
EndPoint client = proxy_map.GetClient(senderEP);
|
||||
|
||||
if (client == null)
|
||||
{
|
||||
// This message comes from a client object, forward it to the the region(s)
|
||||
ProxyCodec.EncodeProxyMessage(buffer, ref numBytes, senderEP);
|
||||
ProxyMap.RegionData rd = proxy_map.GetRegionData(sd.clientEP);
|
||||
foreach (EndPoint region in rd.regions)
|
||||
{
|
||||
if (rd.isBlocked)
|
||||
{
|
||||
rd.storedMessages.Enqueue(new StoredMessage(buffer, length, numBytes, senderEP, sd));
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
sd.server.SendTo(buffer, numBytes, SocketFlags.None, region);
|
||||
//m_log.InfoFormat("[PROXY] Sending client message from {0} to {1}", senderEP, region);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OpenPort(sd); // reopen the port just in case
|
||||
m_log.ErrorFormat("[PROXY] Failed sending client message from {0} to {1}", senderEP, region);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
else
|
||||
{
|
||||
try
|
||||
{
|
||||
client = ProxyCodec.DecodeProxyMessage(buffer, ref numBytes);
|
||||
try
|
||||
{
|
||||
// This message comes from a region object, forward it to the its client
|
||||
sd.server.SendTo(buffer, numBytes, SocketFlags.None, client);
|
||||
//m_log.InfoFormat("[PROXY] Sending region message from {0} to {1}, size {2}", senderEP, client, numBytes);
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OpenPort(sd); // reopen the port just in case
|
||||
m_log.ErrorFormat("[PROXY] Failed sending region message from {0} to {1}", senderEP, client);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
catch (Exception e)
|
||||
{
|
||||
OpenPort(sd); // reopen the port just in case
|
||||
m_log.ErrorFormat("[PROXY] Failed decoding region message from {0}", senderEP);
|
||||
m_log.Error("[PROXY]" + e.Message);
|
||||
m_log.Error("[PROXY]" + e.StackTrace);
|
||||
return;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#region Nested type: ProxyMap
|
||||
|
||||
protected class ProxyMap
|
||||
{
|
||||
private Dictionary<EndPoint, RegionData> map;
|
||||
|
||||
public ProxyMap()
|
||||
{
|
||||
map = new Dictionary<EndPoint, RegionData>();
|
||||
}
|
||||
|
||||
public void Add(EndPoint client, EndPoint region)
|
||||
{
|
||||
if (map.ContainsKey(client))
|
||||
{
|
||||
map[client].regions.Add(region);
|
||||
}
|
||||
else
|
||||
{
|
||||
RegionData regions = new RegionData();
|
||||
map.Add(client, regions);
|
||||
regions.regions.Add(region);
|
||||
}
|
||||
}
|
||||
|
||||
public RegionData GetRegionData(EndPoint client)
|
||||
{
|
||||
return map[client];
|
||||
}
|
||||
|
||||
public EndPoint GetClient(EndPoint region)
|
||||
{
|
||||
foreach (KeyValuePair<EndPoint, RegionData> pair in map)
|
||||
{
|
||||
if (pair.Value.regions.Contains(region))
|
||||
{
|
||||
return pair.Key;
|
||||
}
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
#region Nested type: RegionData
|
||||
|
||||
public class RegionData
|
||||
{
|
||||
public bool isBlocked = false;
|
||||
public List<EndPoint> regions = new List<EndPoint>();
|
||||
public Queue storedMessages = new Queue();
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: ServerData
|
||||
|
||||
protected class ServerData
|
||||
{
|
||||
public EndPoint clientEP;
|
||||
public byte[] recvBuffer = new byte[4096];
|
||||
public EndPoint senderEP;
|
||||
public Socket server;
|
||||
public IPEndPoint serverIP;
|
||||
|
||||
public ServerData()
|
||||
{
|
||||
server = null;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
|
||||
#region Nested type: StoredMessage
|
||||
|
||||
protected class StoredMessage
|
||||
{
|
||||
public byte[] buffer;
|
||||
public int length;
|
||||
public ServerData sd;
|
||||
public EndPoint senderEP;
|
||||
|
||||
public StoredMessage(byte[] buffer, int length, int maxLength, EndPoint senderEP, ServerData sd)
|
||||
{
|
||||
this.buffer = new byte[maxLength];
|
||||
this.length = length;
|
||||
for (int i = 0; i < length; i++) this.buffer[i] = buffer[i];
|
||||
this.senderEP = senderEP;
|
||||
this.sd = sd;
|
||||
}
|
||||
}
|
||||
|
||||
#endregion
|
||||
}
|
||||
}
|
|
@ -1,11 +0,0 @@
|
|||
<Addin id="RegionProxy" version="0.1">
|
||||
<Runtime>
|
||||
<Import assembly="OpenSim.ApplicationPlugins.RegionProxy.dll" />
|
||||
</Runtime>
|
||||
<Dependencies>
|
||||
<Addin id="OpenSim" version="0.5" />
|
||||
</Dependencies>
|
||||
<Extension path="/OpenSim/Startup">
|
||||
<Plugin id="RegionProxy" type="OpenSim.ApplicationPlugins.RegionProxy.RegionProxyPlugin" />
|
||||
</Extension>
|
||||
</Addin>
|
Loading…
Reference in New Issue