OpenSim
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Properties Events Macros
DataSnapshotManager.cs
Go to the documentation of this file.
1 /*
2 * Copyright (c) Contributors, http://opensimulator.org/
3 * See CONTRIBUTORS.TXT for a full list of copyright holders.
4 *
5 * Redistribution and use in source and binary forms, with or without
6 * modification, are permitted provided that the following conditions are met:
7 * * Redistributions of source code must retain the above copyright
8 * notice, this list of conditions and the following disclaimer.
9 * * Redistributions in binary form must reproduce the above copyright
10 * notice, this list of conditions and the following disclaimer in the
11 * documentation and/or other materials provided with the distribution.
12 * * Neither the name of the OpenSimulator Project nor the
13 * names of its contributors may be used to endorse or promote products
14 * derived from this software without specific prior written permission.
15 *
16 * THIS SOFTWARE IS PROVIDED BY THE DEVELOPERS ``AS IS'' AND ANY
17 * EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
18 * WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
19 * DISCLAIMED. IN NO EVENT SHALL THE CONTRIBUTORS BE LIABLE FOR ANY
20 * DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES
21 * (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
22 * LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
23 * ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT
24 * (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
25 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
26 *
27 */
28 
29 using System;
30 using System.Collections.Generic;
31 using System.IO;
32 using System.Linq;
33 using System.Net;
34 using System.Reflection;
35 using System.Text;
36 using System.Xml;
37 using log4net;
38 using Nini.Config;
39 using OpenMetaverse;
40 using Mono.Addins;
41 using OpenSim.Framework;
42 using OpenSim.Region.DataSnapshot.Interfaces;
43 using OpenSim.Region.Framework.Interfaces;
44 using OpenSim.Region.Framework.Scenes;
45 
46 namespace OpenSim.Region.DataSnapshot
47 {
48  [Extension(Path = "/OpenSim/RegionModules", NodeName = "RegionModule", Id = "DataSnapshotManager")]
50  {
51  #region Class members
52  //Information from config
53  private bool m_enabled = false;
54  private bool m_configLoaded = false;
55  private List<string> m_disabledModules = new List<string>();
56  private Dictionary<string, string> m_gridinfo = new Dictionary<string, string>();
57  private string m_snapsDir = "DataSnapshot";
58  private string m_exposure_level = "minimum";
59 
60  //Lists of stuff we need
61  private List<Scene> m_scenes = new List<Scene>();
62  private List<IDataSnapshotProvider> m_dataproviders = new List<IDataSnapshotProvider>();
63 
64  //Various internal objects
65  private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType);
66  internal object m_syncInit = new object();
67 
68  //DataServices and networking
69  private string m_dataServices = "noservices";
70  public string m_listener_port = ConfigSettings.DefaultRegionHttpPort.ToString();
71  public string m_hostname = "127.0.0.1";
72  private UUID m_Secret = UUID.Random();
73  private bool m_servicesNotified = false;
74 
75  //Update timers
76  private int m_period = 20; // in seconds
77  private int m_maxStales = 500;
78  private int m_stales = 0;
79  private int m_lastUpdate = 0;
80 
81  //Program objects
82  private SnapshotStore m_snapStore = null;
83 
84  #endregion
85 
86  #region Properties
87 
88  public string ExposureLevel
89  {
90  get { return m_exposure_level; }
91  }
92 
93  public UUID Secret
94  {
95  get { return m_Secret; }
96  }
97 
98  #endregion
99 
100  #region Region Module interface
101 
102  public void Initialise(IConfigSource config)
103  {
104  if (!m_configLoaded)
105  {
106  m_configLoaded = true;
107  //m_log.Debug("[DATASNAPSHOT]: Loading configuration");
108  //Read from the config for options
109  lock (m_syncInit)
110  {
111  try
112  {
113  m_enabled = config.Configs["DataSnapshot"].GetBoolean("index_sims", m_enabled);
114  string gatekeeper = Util.GetConfigVarFromSections<string>(config, "GatekeeperURI",
115  new string[] { "Startup", "Hypergrid", "GridService" }, String.Empty);
116  // Legacy. Remove soon!
117  if (string.IsNullOrEmpty(gatekeeper))
118  {
119  IConfig conf = config.Configs["GridService"];
120  if (conf != null)
121  gatekeeper = conf.GetString("Gatekeeper", gatekeeper);
122  }
123  if (!string.IsNullOrEmpty(gatekeeper))
124  m_gridinfo.Add("gatekeeperURL", gatekeeper);
125 
126  m_gridinfo.Add(
127  "name", config.Configs["DataSnapshot"].GetString("gridname", "the lost continent of hippo"));
128  m_exposure_level = config.Configs["DataSnapshot"].GetString("data_exposure", m_exposure_level);
129  m_period = config.Configs["DataSnapshot"].GetInt("default_snapshot_period", m_period);
130  m_maxStales = config.Configs["DataSnapshot"].GetInt("max_changes_before_update", m_maxStales);
131  m_snapsDir = config.Configs["DataSnapshot"].GetString("snapshot_cache_directory", m_snapsDir);
132  m_listener_port = config.Configs["Network"].GetString("http_listener_port", m_listener_port);
133 
134  m_dataServices = config.Configs["DataSnapshot"].GetString("data_services", m_dataServices);
135  // New way of spec'ing data services, one per line
136  AddDataServicesVars(config.Configs["DataSnapshot"]);
137 
138  String[] annoying_string_array = config.Configs["DataSnapshot"].GetString("disable_modules", "").Split(".".ToCharArray());
139  foreach (String bloody_wanker in annoying_string_array)
140  {
141  m_disabledModules.Add(bloody_wanker);
142  }
143  m_lastUpdate = Environment.TickCount;
144  }
145  catch (Exception)
146  {
147  m_log.Warn("[DATASNAPSHOT]: Could not load configuration. DataSnapshot will be disabled.");
148  m_enabled = false;
149  return;
150  }
151 
152  }
153 
154  }
155 
156  }
157 
158  public void AddRegion(Scene scene)
159  {
160  if (!m_enabled)
161  return;
162 
163  m_log.DebugFormat("[DATASNAPSHOT]: Module added to Scene {0}.", scene.RegionInfo.RegionName);
164 
165  if (!m_servicesNotified)
166  {
167  m_hostname = scene.RegionInfo.ExternalHostName;
168  m_snapStore = new SnapshotStore(m_snapsDir, m_gridinfo, m_listener_port, m_hostname);
169 
170  //Hand it the first scene, assuming that all scenes have the same BaseHTTPServer
171  new DataRequestHandler(scene, this);
172 
173  if (m_dataServices != "" && m_dataServices != "noservices")
174  NotifyDataServices(m_dataServices, "online");
175 
176  m_servicesNotified = true;
177  }
178 
179  m_scenes.Add(scene);
180  m_snapStore.AddScene(scene);
181 
182  Assembly currentasm = Assembly.GetExecutingAssembly();
183 
184  foreach (Type pluginType in currentasm.GetTypes())
185  {
186  if (pluginType.IsPublic)
187  {
188  if (!pluginType.IsAbstract)
189  {
190  if (pluginType.GetInterface("IDataSnapshotProvider") != null)
191  {
192  IDataSnapshotProvider module = (IDataSnapshotProvider)Activator.CreateInstance(pluginType);
193  module.Initialize(scene, this);
194  module.OnStale += MarkDataStale;
195 
196  m_dataproviders.Add(module);
197  m_snapStore.AddProvider(module);
198 
199  m_log.Debug("[DATASNAPSHOT]: Added new data provider type: " + pluginType.Name);
200  }
201  }
202  }
203  }
204 
205  }
206 
207  public void RemoveRegion(Scene scene)
208  {
209  if (!m_enabled)
210  return;
211 
212  m_log.Info("[DATASNAPSHOT]: Region " + scene.RegionInfo.RegionName + " is being removed, removing from indexing");
213  Scene restartedScene = SceneForUUID(scene.RegionInfo.RegionID);
214 
215  m_scenes.Remove(restartedScene);
216  m_snapStore.RemoveScene(restartedScene);
217 
218  //Getting around the fact that we can't remove objects from a collection we are enumerating over
219  List<IDataSnapshotProvider> providersToRemove = new List<IDataSnapshotProvider>();
220 
221  foreach (IDataSnapshotProvider provider in m_dataproviders)
222  {
223  if (provider.GetParentScene == restartedScene)
224  {
225  providersToRemove.Add(provider);
226  }
227  }
228 
229  foreach (IDataSnapshotProvider provider in providersToRemove)
230  {
231  m_dataproviders.Remove(provider);
232  m_snapStore.RemoveProvider(provider);
233  }
234 
235  m_snapStore.RemoveScene(restartedScene);
236  }
237 
238  public void PostInitialise()
239  {
240  }
241 
242  public void RegionLoaded(Scene scene)
243  {
244  if (!m_enabled)
245  return;
246 
247  m_log.DebugFormat("[DATASNAPSHOT]: Marking scene {0} as stale.", scene.RegionInfo.RegionName);
248  m_snapStore.ForceSceneStale(scene);
249  }
250 
251  public void Close()
252  {
253  if (!m_enabled)
254  return;
255 
256  if (m_enabled && m_dataServices != "" && m_dataServices != "noservices")
257  NotifyDataServices(m_dataServices, "offline");
258  }
259 
260 
261  public string Name
262  {
263  get { return "External Data Generator"; }
264  }
265 
266  public Type ReplaceableInterface
267  {
268  get { return null; }
269  }
270 
271  #endregion
272 
273  #region Associated helper functions
274 
275  public Scene SceneForName(string name)
276  {
277  foreach (Scene scene in m_scenes)
278  if (scene.RegionInfo.RegionName == name)
279  return scene;
280 
281  return null;
282  }
283 
284  public Scene SceneForUUID(UUID id)
285  {
286  foreach (Scene scene in m_scenes)
287  if (scene.RegionInfo.RegionID == id)
288  return scene;
289 
290  return null;
291  }
292 
293  private void AddDataServicesVars(IConfig config)
294  {
295  // Make sure the services given this way aren't in m_dataServices already
296  List<string> servs = new List<string>(m_dataServices.Split(new char[] { ';' }));
297 
298  StringBuilder sb = new StringBuilder();
299  string[] keys = config.GetKeys();
300 
301  if (keys.Length > 0)
302  {
303  IEnumerable<string> serviceKeys = keys.Where(value => value.StartsWith("DATA_SRV_"));
304  foreach (string serviceKey in serviceKeys)
305  {
306  string keyValue = config.GetString(serviceKey, string.Empty).Trim();
307  if (!servs.Contains(keyValue))
308  sb.Append(keyValue).Append(";");
309  }
310  }
311 
312  m_dataServices = (m_dataServices == "noservices") ? sb.ToString() : sb.Append(m_dataServices).ToString();
313  }
314 
315  #endregion
316 
317  #region [Public] Snapshot storage functions
318 
322  public XmlDocument GetSnapshot(string regionName)
323  {
324  CheckStale();
325 
326  XmlDocument requestedSnap = new XmlDocument();
327  requestedSnap.AppendChild(requestedSnap.CreateXmlDeclaration("1.0", null, null));
328  requestedSnap.AppendChild(requestedSnap.CreateWhitespace("\r\n"));
329 
330  XmlNode regiondata = requestedSnap.CreateNode(XmlNodeType.Element, "regiondata", "");
331  try
332  {
333  if (string.IsNullOrEmpty(regionName))
334  {
335  XmlNode timerblock = requestedSnap.CreateNode(XmlNodeType.Element, "expire", "");
336  timerblock.InnerText = m_period.ToString();
337  regiondata.AppendChild(timerblock);
338 
339  regiondata.AppendChild(requestedSnap.CreateWhitespace("\r\n"));
340  foreach (Scene scene in m_scenes)
341  {
342  regiondata.AppendChild(m_snapStore.GetScene(scene, requestedSnap));
343  }
344  }
345  else
346  {
347  Scene scene = SceneForName(regionName);
348  regiondata.AppendChild(m_snapStore.GetScene(scene, requestedSnap));
349  }
350  requestedSnap.AppendChild(regiondata);
351  regiondata.AppendChild(requestedSnap.CreateWhitespace("\r\n"));
352  }
353  catch (XmlException e)
354  {
355  m_log.Warn("[DATASNAPSHOT]: XmlException while trying to load snapshot: " + e.ToString());
356  requestedSnap = GetErrorMessage(regionName, e);
357  }
358  catch (Exception e)
359  {
360  m_log.Warn("[DATASNAPSHOT]: Caught unknown exception while trying to load snapshot: " + e.StackTrace);
361  requestedSnap = GetErrorMessage(regionName, e);
362  }
363 
364 
365  return requestedSnap;
366  }
367 
368  private XmlDocument GetErrorMessage(string regionName, Exception e)
369  {
370  XmlDocument errorMessage = new XmlDocument();
371  XmlNode error = errorMessage.CreateNode(XmlNodeType.Element, "error", "");
372  XmlNode region = errorMessage.CreateNode(XmlNodeType.Element, "region", "");
373  region.InnerText = regionName;
374 
375  XmlNode exception = errorMessage.CreateNode(XmlNodeType.Element, "exception", "");
376  exception.InnerText = e.ToString();
377 
378  error.AppendChild(region);
379  error.AppendChild(exception);
380  errorMessage.AppendChild(error);
381 
382  return errorMessage;
383  }
384 
385  #endregion
386 
387  #region External data services
388  private void NotifyDataServices(string servicesStr, string serviceName)
389  {
390  Stream reply = null;
391  string delimStr = ";";
392  char [] delimiter = delimStr.ToCharArray();
393 
394  string[] services = servicesStr.Split(delimiter, StringSplitOptions.RemoveEmptyEntries);
395 
396  for (int i = 0; i < services.Length; i++)
397  {
398  string url = services[i].Trim();
399  using (RestClient cli = new RestClient(url))
400  {
401  cli.AddQueryParameter("service", serviceName);
402  cli.AddQueryParameter("host", m_hostname);
403  cli.AddQueryParameter("port", m_listener_port);
404  cli.AddQueryParameter("secret", m_Secret.ToString());
405  cli.RequestMethod = "GET";
406  try
407  {
408  reply = cli.Request(null);
409  }
410  catch (WebException)
411  {
412  m_log.Warn("[DATASNAPSHOT]: Unable to notify " + url);
413  }
414  catch (Exception e)
415  {
416  m_log.Warn("[DATASNAPSHOT]: Ignoring unknown exception " + e.ToString());
417  }
418 
419  byte[] response = new byte[1024];
420  // int n = 0;
421  try
422  {
423  // n = reply.Read(response, 0, 1024);
424  reply.Read(response, 0, 1024);
425  }
426  catch (Exception e)
427  {
428  m_log.WarnFormat("[DATASNAPSHOT]: Unable to decode reply from data service. Ignoring. {0}", e.StackTrace);
429  }
430  // This is not quite working, so...
431  // string responseStr = Util.UTF8.GetString(response);
432  m_log.Info("[DATASNAPSHOT]: data service " + url + " notified. Secret: " + m_Secret);
433  }
434  }
435  }
436  #endregion
437 
438  #region Latency-based update functions
439 
440  public void MarkDataStale(IDataSnapshotProvider provider)
441  {
442  //Behavior here: Wait m_period seconds, then update if there has not been a request in m_period seconds
443  //or m_maxStales has been exceeded
444  m_stales++;
445  }
446 
447  private void CheckStale()
448  {
449  // Wrap check
450  if (Environment.TickCount < m_lastUpdate)
451  {
452  m_lastUpdate = Environment.TickCount;
453  }
454 
455  if (m_stales >= m_maxStales)
456  {
457  if (Environment.TickCount - m_lastUpdate >= 20000)
458  {
459  m_stales = 0;
460  m_lastUpdate = Environment.TickCount;
461  MakeEverythingStale();
462  }
463  }
464  else
465  {
466  if (m_lastUpdate + 1000 * m_period < Environment.TickCount)
467  {
468  m_stales = 0;
469  m_lastUpdate = Environment.TickCount;
470  MakeEverythingStale();
471  }
472  }
473  }
474 
475  public void MakeEverythingStale()
476  {
477  m_log.Debug("[DATASNAPSHOT]: Marking all scenes as stale.");
478  foreach (Scene scene in m_scenes)
479  {
480  m_snapStore.ForceSceneStale(scene);
481  }
482  }
483  #endregion
484 
485  }
486 }
void MarkDataStale(IDataSnapshotProvider provider)
void PostInitialise()
This is called exactly once after all the shared region-modules have been instanciated and IRegionMod...
void Initialise(IConfigSource config)
This is called to initialize the region module. For shared modules, this is called exactly once...
void Close()
This is the inverse to Initialise. After a Close(), this instance won't be usable anymore...
void RegionLoaded(Scene scene)
This will be called once for every scene loaded. In a shared module this will be multiple times in on...
System.Collections.IEnumerable IEnumerable
void AddRegion(Scene scene)
This is called whenever a Scene is added. For shared modules, this can happen several times...
Implementation of a generic REST client
Definition: RestClient.cs:59
void RemoveRegion(Scene scene)
This is called whenever a Scene is removed. For shared modules, this can happen several times...