From 85654f82a515df99d01dd2d2f3b619747a6cc5db Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 12:25:04 -0500 Subject: [PATCH 01/48] First cut of AutoBackupModule; only compile-tested so far --- .../Resources/OptionalModules.addin.xml | 1 + .../World/AutoBackup/AutoBackupModule.cs | 540 ++++++++++++++++++ 2 files changed, 541 insertions(+) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs diff --git a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml index 5eea286fd7..869134354f 100644 --- a/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml +++ b/OpenSim/Region/OptionalModules/Resources/OptionalModules.addin.xml @@ -13,5 +13,6 @@ + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs new file mode 100644 index 0000000000..ed21e41c58 --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -0,0 +1,540 @@ +/* + * 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 OpenSimulator 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.Timers; +using System.Diagnostics; +using System.Reflection; +using System.Collections.Generic; +using log4net; +using Nini; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; + + +/* + * Config Settings Documentation. + * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + * This is the only required option for enabling auto-backup; the other options have sane defaults. + * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + * The number of minutes between each backup attempt. + * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + * AutoBackupBusyCheck: True/False. Default: True. + * If True, we will only take an auto-backup if a set of conditions are met. + * These conditions are heuristics to try and avoid taking a backup when the sim is busy. + * AutoBackupScript: String. Default: not specified (disabled). + * File path to an executable script or binary to run when an automatic backup is taken. + * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + * argv[1] of the executed file/script will be the file name of the generated OAR. + * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + * AutoBackupNaming: string. Default: Time. + * One of three strings (case insensitive): + * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + * AutoBackupDir: String. Default: "." (the current directory). + * A directory (absolute or relative) where backups should be saved. + * */ + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + + public enum NamingType + { + TIME, + SEQUENTIAL, + OVERWRITE + }; + + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase + { + + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + //AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + private readonly IScene m_scene; + private bool m_enabled = false; + private NamingType m_naming = NamingType.TIME; + private Timer m_timer = null; + private bool m_busycheck = true; + private string m_script = null; + private string m_dir = "."; + + public AutoBackupModuleState(IScene scene) + { + m_scene = scene; + if(scene == null) + throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + } + + public void SetEnabled(bool b) + { + m_enabled = b; + } + + public bool GetEnabled() + { + return m_enabled; + } + + public Timer GetTimer() + { + return m_timer; + } + + public void SetTimer(Timer t) + { + m_timer = t; + } + + public bool GetBusyCheck() + { + return m_busycheck; + } + + public void SetBusyCheck(bool b) + { + m_busycheck = b; + } + + + public string GetScript() + { + return m_script; + } + + public void SetScript(string s) + { + m_script = s; + } + + public string GetBackupDir() + { + return m_dir; + } + + public void SetBackupDir(string s) + { + m_dir = s; + } + + public NamingType GetNamingType() + { + return m_naming; + } + + public void SetNamingType(NamingType n) + { + m_naming = n; + } + } + + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + //Also helps if you don't want AutoBackup at all + readonly Dictionary states = new Dictionary(4); + readonly Dictionary timers = new Dictionary(1); + readonly Dictionary> timerMap = new Dictionary>(1); + + public AutoBackupModule () + { + + } + + #region IRegionModuleBase implementation + void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) + { + //I have no overall config settings to care about. + } + + void IRegionModuleBase.Close () + { + //We don't want any timers firing while the sim's coming down; strange things may happen. + StopAllTimers(); + } + + void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) + { + //NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) + { + AutoBackupModuleState abms = states[scene]; + Timer timer = abms.GetTimer(); + List list = timerMap[timer]; + list.Remove(scene); + if(list.Count == 0) + { + timerMap.Remove(timer); + timers.Remove(timer.Interval); + timer.Close(); + } + } + + void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) + { + //This really ought not to happen, but just in case, let's pretend it didn't... + if(scene == null) + return; + + AutoBackupModuleState st = new AutoBackupModuleState(scene); + states.Add(scene, st); + + //Read the config settings and set variables. + IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + st.SetEnabled(config.GetBoolean("AutoBackup", false)); + if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + return; + + //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = config.GetDouble("AutoBackupInterval", 720); + if(timers.ContainsKey(interval)) + { + st.SetTimer(timers[interval]); + } + else + { + st.SetTimer(new Timer(interval)); + timers.Add(interval, st.GetTimer()); + st.GetTimer().Elapsed += HandleElapsed; + } + + //Add the current region to the list of regions tied to this timer. + if(timerMap.ContainsKey(st.GetTimer())) + { + timerMap[st.GetTimer()].Add(scene); + } + else + { + List scns = new List(1); + timerMap.Add(st.GetTimer(), scns); + } + + st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + + //Set file naming algorithm + string namingtype = config.GetString("AutoBackupNaming", "Time"); + if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.TIME); + } + else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.SEQUENTIAL); + } + else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + st.SetNamingType(NamingType.OVERWRITE); + } + else + { + m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType(NamingType.TIME); + } + + st.SetScript(config.GetString("AutoBackupScript", null)); + st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + + //Let's give the user *one* convenience and auto-mkdir + if(st.GetBackupDir() != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); + if(!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch(Exception e) + { + m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + void HandleElapsed (object sender, ElapsedEventArgs e) + { + bool heuristicsRun = false; + bool heuristicsPassed = false; + foreach(IScene scene in timerMap[(Timer)sender]) + { + AutoBackupModuleState state = states[scene]; + bool heuristics = state.GetBusyCheck(); + + //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if((heuristics && heuristicsRun && heuristicsPassed) + || !heuristics) + { + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + if(savePath == null) + { + m_log.Warn("savePath is null in HandleElapsed"); + continue; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + else if(heuristics && heuristicsRun && !heuristicsPassed) + { + continue; + } + //Logical Deduction: heuristics are on but haven't been run + else + { + heuristicsPassed = RunHeuristics(); + heuristicsRun = true; + if(!heuristicsPassed) + continue; + } + } + } + + string IRegionModuleBase.Name { + get { + return "AutoBackupModule"; + } + } + + Type IRegionModuleBase.ReplaceableInterface { + get { + return null; + } + } + + #endregion + #region ISharedRegionModule implementation + void ISharedRegionModule.PostInitialise () + { + //I don't care right now. + } + + #endregion + + //Is this even needed? + public bool IsSharedModule + { + get { return true; } + } + + private string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch(naming) + { + case NamingType.OVERWRITE: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + return path.FullName; + case NamingType.TIME: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.SEQUENTIAL: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + break; + } + + return path.FullName; + } + + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. + //(Terrible reference to ) + //This format may turn out to be too unwieldy to keep... + //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + //Sequential numbers, right? Ugh. Almost makes TOO much sense. + private string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + //Get the next logical file name + //I really shouldn't put fields here, but for now.... ;) + private string m_dirName = null; + private string m_regionName = null; + private string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + m_dirName = dirName; + m_regionName = regionName; + long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); + biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + + uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if(uniqueFile.Exists) + { + //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! + //Now you get to pay the performance cost :) + uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + } + + return uniqueFile.FullName; + } + + private bool RunHeuristics() + { + return true; + } + + private void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if(scriptName == null || scriptName.Length <= 0) + return; + + try + { + FileInfo fi = new FileInfo(scriptName); + if(fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch(Exception e) + { + m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach(Timer t in timerMap.Keys) + { + t.Close(); + } + } + + /* Find the largest value for which the predicate returns true. + * We use a bisection algorithm (half interval) to make the algorithm scalable. + * The worst-case complexity is about O(log(n)^2) in practice. + * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. + * The number of predicate invocations only hits a few hundred when the maximized value + * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. + * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. + * The Predicate parameter must be a function that accepts a long and returns a bool. + * */ + public long HalfIntervalMaximize(long start, Predicate pred) + { + long prev = start, curr = start, biggest = 0; + + if(start < 0) + throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + + do + { + if(pred(curr)) + { + if(curr > biggest) + { + biggest = curr; + } + prev = curr; + if(curr == 0) + { + //Special case because 0 * 2 = 0 :) + curr = 1; + } + else + { + //Look deeper + curr *= 2; + } + } + else + { + // We went too far, back off halfway + curr = (curr + prev) / 2; + } + } + while(curr - prev > 0); + + return biggest; + } + + public bool FileExistsTest(long num) + { + FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + return test.Exists; + } + + + //Very slow, hence why we try the HalfIntervalMaximize first! + public FileInfo UniqueFileSearchLinear(long start) + { + long l = start; + FileInfo retval = null; + do + { + retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } + while(retval.Exists); + + return retval; + } +} + +} + From dfa63ff0313610e1c2d262f7e660af94134159f6 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 20:15:38 -0500 Subject: [PATCH 02/48] Let GetNextFile do all the string-building work for SEQUENTIAL. --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ed21e41c58..f8d9060ea1 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -364,7 +364,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + "_" + GetNextFile(baseDir, regionName) + ".oar"); + path = new FileInfo(GetNextFile(baseDir, regionName)); return path.FullName; default: m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); From 99e82602826e7d100d04a4bb229188be240db1ad Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:08:19 -0500 Subject: [PATCH 03/48] Add [Modules] option for unconditionally disabling entire module globally (for easy configuration) --- .../World/AutoBackup/AutoBackupModule.cs | 25 ++++++++++++++++++- 1 file changed, 24 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index f8d9060ea1..7593b95e02 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,6 +40,9 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. + * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * In the [Modules] section: + * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! * EACH REGION in e.g. Regions/Regions.ini can have the following options: * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. @@ -166,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () { @@ -175,11 +179,24 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region IRegionModuleBase implementation void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { - //I have no overall config settings to care about. + //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) + { + m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); + if (m_Enabled) + { + m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + + } } void IRegionModuleBase.Close () { + if(!m_Enabled) + return; + //We don't want any timers firing while the sim's coming down; strange things may happen. StopAllTimers(); } @@ -191,6 +208,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + AutoBackupModuleState abms = states[scene]; Timer timer = abms.GetTimer(); List list = timerMap[timer]; @@ -205,6 +225,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { + if(!m_Enabled) + return; + //This really ought not to happen, but just in case, let's pretend it didn't... if(scene == null) return; From 06a4810d210fcbfe1a859317698f791129ecdaf1 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:29:45 -0500 Subject: [PATCH 04/48] Fix config source by taking it from Initialize --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7593b95e02..68cf2192c6 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -169,6 +169,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup readonly Dictionary states = new Dictionary(4); readonly Dictionary timers = new Dictionary(1); readonly Dictionary> timerMap = new Dictionary>(1); + private IConfigSource m_configSource = null; private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! public AutoBackupModule () @@ -180,6 +181,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + m_configSource = source; IConfig moduleConfig = source.Configs["Modules"]; if (moduleConfig != null) { @@ -236,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs[scene.RegionInfo.RegionName]; + IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; From 3c9bf5c476eb294748b53e0c65e7880c26c108ab Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:47:05 -0500 Subject: [PATCH 05/48] Fix config source stuff harder (debug console prints only; no fix yet) --- .../World/AutoBackup/AutoBackupModule.cs | 11 +++++++++++ 1 file changed, 11 insertions(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68cf2192c6..ebad12ffc0 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,11 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); + AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + if(config == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); + foreach(IConfig c in m_configSource.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); + } + throw new NullReferenceException("This is debug code"); + } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. return; From e5c08a553c4cbf0485d7eb93fc891e002ae04254 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 22:55:32 -0500 Subject: [PATCH 06/48] More debug messages --- .../World/AutoBackup/AutoBackupModule.cs | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ebad12ffc0..21e48df736 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -248,6 +248,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); } + + if(scene.Config != null) + { + m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); + IConfigSource tmp = scene.Config; + foreach(IConfig d in tmp.Configs) + { + m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); + } + } throw new NullReferenceException("This is debug code"); } st.SetEnabled(config.GetBoolean("AutoBackup", false)); From 7eac0af5590a450752a9fd2c57abe7d1a6d84c4c Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:07:54 -0500 Subject: [PATCH 07/48] Fix an actual bug in the timer interval calculation (minutes -> msec) --- .../World/AutoBackup/AutoBackupModule.cs | 18 +++++++++++++++--- 1 file changed, 15 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 21e48df736..489971814c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -240,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup states.Add(scene, st); //Read the config settings and set variables. - IConfig config = m_configSource.Configs[scene.RegionInfo.RegionName]; + IConfig config = scene.Config.Configs["Startup"]; if(config == null) { m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); @@ -258,11 +258,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); } } - throw new NullReferenceException("This is debug code"); + throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. + { return; + } + else + { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = config.GetDouble("AutoBackupInterval", 720); @@ -272,7 +278,13 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } else { - st.SetTimer(new Timer(interval)); + //0 or negative interval == do nothing. + if(interval <= 0.0) + { + st.SetEnabled(false); + return; + } + st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; } From c2658c2f14d75c0c90f40c3673eb35677bcf2cb7 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 19 Feb 2011 23:14:43 -0500 Subject: [PATCH 08/48] More relevant console messages, and maybe fix config problem --- .../World/AutoBackup/AutoBackupModule.cs | 25 +++++-------------- 1 file changed, 6 insertions(+), 19 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 489971814c..4735620da7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -234,35 +234,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; - m_log.Info("[AUTO BACKUP MODULE]: RegionLoaded for region: " + scene.RegionInfo.RegionName); - AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["Startup"]; + IConfig config = scene.Config.Configs["AutoBackupModule"]; if(config == null) { - m_log.Warn("[AUTO BACKUP MODULE]: Can't get config settings! Here are the IConfigs available:"); - foreach(IConfig c in m_configSource.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + c.Name); - } - - if(scene.Config != null) - { - m_log.Warn("[AUTO BACKUP MODULE]: And in scene.Config:"); - IConfigSource tmp = scene.Config; - foreach(IConfig d in tmp.Configs) - { - m_log.Warn("[AUTO BACKUP MODULE]: " + d.Name); - } - } - throw new NullReferenceException("This is debug code"); //This crashes the whole process -- not good + //No config settings for this, let's just give up. + st.SetEnabled(false); + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + return; } st.SetEnabled(config.GetBoolean("AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { + m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } else From 2da9bb3ca2fbd4294409b74733f30b79bd48e2ec Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:31:10 -0500 Subject: [PATCH 09/48] Try to fix config one more time. Note that the way we specify settings has changed significantly here. --- .../World/AutoBackup/AutoBackupModule.cs | 25 +++++++++++-------- 1 file changed, 14 insertions(+), 11 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4735620da7..0869b0c523 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -40,10 +40,13 @@ using OpenSim.Region.Framework.Interfaces; /* * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have one option: + * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: * In the [Modules] section: * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION in e.g. Regions/Regions.ini can have the following options: + * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + * VERY IMPORTANT: You must create the key name as follows: . + * Example: My region is named Foo. + * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -190,7 +193,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); } - } } @@ -234,19 +236,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(scene == null) return; + string sRegionName = scene.RegionInfo.RegionName; AutoBackupModuleState st = new AutoBackupModuleState(scene); states.Add(scene, st); //Read the config settings and set variables. - IConfig config = scene.Config.Configs["AutoBackupModule"]; + IConfig config = m_configSource.Configs["AutoBackupModule"]; if(config == null) { - //No config settings for this, let's just give up. + //No config settings for any regions, let's just give up. st.SetEnabled(false); m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean("AutoBackup", false)); + st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); @@ -258,7 +261,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble("AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); @@ -287,10 +290,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timerMap.Add(st.GetTimer(), scns); } - st.SetBusyCheck(config.GetBoolean("AutoBackupBusyCheck", true)); + st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString("AutoBackupNaming", "Time"); + string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { st.SetNamingType(NamingType.TIME); @@ -309,8 +312,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetNamingType(NamingType.TIME); } - st.SetScript(config.GetString("AutoBackupScript", null)); - st.SetBackupDir(config.GetString("AutoBackupDir", ".")); + st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir if(st.GetBackupDir() != ".") From 7fa8ed0c47b3b0a5c87cd27b2620aeef05a7fe86 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:48:02 -0500 Subject: [PATCH 10/48] Config works, but timer isn't firing. Hmm. --- .../World/AutoBackup/AutoBackupModule.cs | 12 +++++++----- 1 file changed, 7 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 0869b0c523..68c8301651 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -246,25 +246,26 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //No config settings for any regions, let's just give up. st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is NOT AutoBackup enabled."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } else { - m_log.Info("[AUTO BACKUP MODULE]: Region " + scene.RegionInfo.RegionName + " is AutoBackup ENABLED."); + m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720); + double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; if(timers.ContainsKey(interval)) { st.SetTimer(timers[interval]); + m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); } else { @@ -274,9 +275,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval * 60000)); //Milliseconds -> minutes + st.SetTimer(new Timer(interval)); //Milliseconds -> minutes timers.Add(interval, st.GetTimer()); st.GetTimer().Elapsed += HandleElapsed; + m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. From bb1f03abc6a3c12b4f511e4f212f5673c4d31dc4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:57:48 -0500 Subject: [PATCH 11/48] Make timer auto-respawn. --- .../OptionalModules/World/AutoBackup/AutoBackupModule.cs | 9 ++++++--- 1 file changed, 6 insertions(+), 3 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 68c8301651..58b93c05ef 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -275,9 +275,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetEnabled(false); return; } - st.SetTimer(new Timer(interval)); //Milliseconds -> minutes - timers.Add(interval, st.GetTimer()); - st.GetTimer().Elapsed += HandleElapsed; + Timer tim = new Timer(interval); + st.SetTimer(tim); //Milliseconds -> minutes + timers.Add(interval, tim); + tim.Elapsed += HandleElapsed; + tim.AutoReset = True; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } @@ -351,6 +353,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { IRegionArchiverModule iram = scene.RequestModuleInterface(); string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); if(savePath == null) { m_log.Warn("savePath is null in HandleElapsed"); From cb049aa15f64fec5ba4b08aedddf98b68ab5044e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 08:58:42 -0500 Subject: [PATCH 12/48] s/True/true --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 58b93c05ef..ba5e24bf87 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -279,7 +279,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup st.SetTimer(tim); //Milliseconds -> minutes timers.Add(interval, tim); tim.Elapsed += HandleElapsed; - tim.AutoReset = True; + tim.AutoReset = true; m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } From d3511ca592eff780e6d707680ac1d20a24b9fc58 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Fri, 25 Feb 2011 09:03:11 -0500 Subject: [PATCH 13/48] Start the timer. (Could it be that simple?) --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 + 1 file changed, 1 insertion(+) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index ba5e24bf87..77126c7fb2 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -280,6 +280,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Add(interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; + tim.Start(); m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } From 34b6904939dd551aafa68ea8e02dc5177d395b3b Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 26 Feb 2011 22:09:19 -0500 Subject: [PATCH 14/48] First working commit of AutoBackupModule. It seems to do something! Heuristics are still TODO, but this is alpha 1. --- .../World/AutoBackup/AutoBackupModule.cs | 34 +++++++++++++------ 1 file changed, 23 insertions(+), 11 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 77126c7fb2..e3686acc35 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -281,7 +281,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start(); - m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. @@ -292,6 +292,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup else { List scns = new List(1); + scns.Add(scene); timerMap.Add(st.GetTimer(), scns); } @@ -343,6 +344,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { bool heuristicsRun = false; bool heuristicsPassed = false; + if(!timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + } foreach(IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; @@ -352,16 +357,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); - m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("savePath is null in HandleElapsed"); - continue; - } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + doRegionBackup(scene); } //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! else if(heuristics && heuristicsRun && !heuristicsPassed) @@ -375,9 +371,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup heuristicsRun = true; if(!heuristicsPassed) continue; + doRegionBackup(scene); } } } + + void doRegionBackup(IScene scene) + { + AutoBackupModuleState state = states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + if(savePath == null) + { + m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, null); + ExecuteScript(state.GetScript(), savePath); + } string IRegionModuleBase.Name { get { From a01c44e74de669ea2643b8bfe76a7e78ca4740a4 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:04:54 -0500 Subject: [PATCH 15/48] Be smarter about stopping timers. Cleanup formatting. Use a boolean flag to tell timers that fire after IRegionModuleBase.Close() is called that they should not execute. Also, I used MonoDevelop's auto-formatting feature to format the code uniformly. No guarantee about variable names though. --- .../World/AutoBackup/AutoBackupModule.cs | 502 ++++++++---------- 1 file changed, 225 insertions(+), 277 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e3686acc35..54b9b09cb3 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -73,20 +73,19 @@ using OpenSim.Region.Framework.Interfaces; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - + public enum NamingType { TIME, SEQUENTIAL, OVERWRITE - }; - + } + public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase { - - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - + + private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); + //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { @@ -97,84 +96,87 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_busycheck = true; private string m_script = null; private string m_dir = "."; - - public AutoBackupModuleState(IScene scene) + + public AutoBackupModuleState (IScene scene) { m_scene = scene; - if(scene == null) - throw new NullReferenceException("Required parameter missing for AutoBackupModuleState constructor"); + if (scene == null) + throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); } - - public void SetEnabled(bool b) + + public void SetEnabled (bool b) { - m_enabled = b; + m_enabled = b; } - - public bool GetEnabled() + + public bool GetEnabled () { - return m_enabled; + return m_enabled; } - - public Timer GetTimer() + + public Timer GetTimer () { - return m_timer; + return m_timer; } - - public void SetTimer(Timer t) + + public void SetTimer (Timer t) { - m_timer = t; + m_timer = t; } - - public bool GetBusyCheck() + + public bool GetBusyCheck () { - return m_busycheck; + return m_busycheck; } - - public void SetBusyCheck(bool b) + + public void SetBusyCheck (bool b) { - m_busycheck = b; + m_busycheck = b; } - - - public string GetScript() + + + public string GetScript () { - return m_script; + return m_script; } - - public void SetScript(string s) + + public void SetScript (string s) { - m_script = s; + m_script = s; } - - public string GetBackupDir() + + public string GetBackupDir () { - return m_dir; + return m_dir; } - - public void SetBackupDir(string s) + + public void SetBackupDir (string s) { - m_dir = s; + m_dir = s; } - - public NamingType GetNamingType() + + public NamingType GetNamingType () { return m_naming; } - - public void SetNamingType(NamingType n) + + public void SetNamingType (NamingType n) { - m_naming = n; + m_naming = n; } } - + //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary(4); - readonly Dictionary timers = new Dictionary(1); - readonly Dictionary> timerMap = new Dictionary>(1); + readonly Dictionary states = new Dictionary (4); + readonly Dictionary timers = new Dictionary (1); + readonly Dictionary> timerMap = new Dictionary> (1); private IConfigSource m_configSource = null; - private bool m_Enabled = false; //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - + private bool m_Enabled = false; + //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed = false; + //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. public AutoBackupModule () { @@ -185,24 +187,22 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) - { - m_Enabled = moduleConfig.GetBoolean("AutoBackupModule", false); - if (m_Enabled) - { - m_log.Info("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); - } - } + IConfig moduleConfig = source.Configs["Modules"]; + if (moduleConfig != null) { + m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); + if (m_Enabled) { + m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + } + } } void IRegionModuleBase.Close () { - if(!m_Enabled) + if (!m_Enabled) return; //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers(); + StopAllTimers (); } void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) @@ -212,327 +212,286 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer(); + Timer timer = abms.GetTimer (); List list = timerMap[timer]; - list.Remove(scene); - if(list.Count == 0) - { - timerMap.Remove(timer); - timers.Remove(timer.Interval); - timer.Close(); + list.Remove (scene); + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); } } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) { - if(!m_Enabled) + if (!m_Enabled) return; //This really ought not to happen, but just in case, let's pretend it didn't... - if(scene == null) + if (scene == null) return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState(scene); - states.Add(scene, st); + AutoBackupModuleState st = new AutoBackupModuleState (scene); + states.Add (scene, st); //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; - if(config == null) - { + if (config == null) { //No config settings for any regions, let's just give up. - st.SetEnabled(false); - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (false); + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; } - st.SetEnabled(config.GetBoolean(sRegionName + ".AutoBackup", false)); - if(!st.GetEnabled()) //If you don't want AutoBackup, we stop. - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); + st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + //If you don't want AutoBackup, we stop. + if (!st.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); return; - } - else - { - m_log.Info("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + } else { + m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble(sRegionName + ".AutoBackupInterval", 720) * 60000; - if(timers.ContainsKey(interval)) - { - st.SetTimer(timers[interval]); - m_log.Debug("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); - } - else - { + double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + if (timers.ContainsKey (interval)) { + st.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + } else { //0 or negative interval == do nothing. - if(interval <= 0.0) - { - st.SetEnabled(false); + if (interval <= 0.0) { + st.SetEnabled (false); return; } - Timer tim = new Timer(interval); - st.SetTimer(tim); //Milliseconds -> minutes - timers.Add(interval, tim); - tim.Elapsed += HandleElapsed; + Timer tim = new Timer (interval); + st.SetTimer (tim); + //Milliseconds -> minutes + timers.Add (interval, tim); + tim.Elapsed += HandleElapsed; tim.AutoReset = true; - tim.Start(); + tim.Start (); //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if(timerMap.ContainsKey(st.GetTimer())) - { - timerMap[st.GetTimer()].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - timerMap.Add(st.GetTimer(), scns); + if (timerMap.ContainsKey (st.GetTimer ())) { + timerMap[st.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (st.GetTimer (), scns); } - st.SetBusyCheck(config.GetBoolean(sRegionName + ".AutoBackupBusyCheck", true)); + st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); //Set file naming algorithm - string namingtype = config.GetString(sRegionName + ".AutoBackupNaming", "Time"); - if(namingtype.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.TIME); - } - else if(namingtype.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.SEQUENTIAL); - } - else if(namingtype.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - st.SetNamingType(NamingType.OVERWRITE); - } - else - { - m_log.Warn("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType(NamingType.TIME); + string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); + if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.TIME); + } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.SEQUENTIAL); + } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + st.SetNamingType (NamingType.OVERWRITE); + } else { + m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); + st.SetNamingType (NamingType.TIME); } - st.SetScript(config.GetString(sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir(config.GetString(sRegionName + ".AutoBackupDir", ".")); + st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); + st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); //Let's give the user *one* convenience and auto-mkdir - if(st.GetBackupDir() != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(st.GetBackupDir()); - if(!dirinfo.Exists) - { - dirinfo.Create(); + if (st.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); } - } - catch(Exception e) - { - m_log.Warn("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir() + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); } } } void HandleElapsed (object sender, ElapsedEventArgs e) { + if (m_closed) + return; bool heuristicsRun = false; bool heuristicsPassed = false; - if(!timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender.ToString()); + if (!timerMap.ContainsKey ((Timer)sender)) { + m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach(IScene scene in timerMap[(Timer)sender]) - { + foreach (IScene scene in timerMap[(Timer)sender]) { AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck(); + bool heuristics = state.GetBusyCheck (); //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if((heuristics && heuristicsRun && heuristicsPassed) - || !heuristics) - { - doRegionBackup(scene); - } + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { + doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - else if(heuristics && heuristicsRun && !heuristicsPassed) - { + } else if (heuristics && heuristicsRun && !heuristicsPassed) { continue; - } //Logical Deduction: heuristics are on but haven't been run - else - { - heuristicsPassed = RunHeuristics(); + } else { + heuristicsPassed = RunHeuristics (); heuristicsRun = true; - if(!heuristicsPassed) + if (!heuristicsPassed) continue; - doRegionBackup(scene); + doRegionBackup (scene); } } } - - void doRegionBackup(IScene scene) + + void doRegionBackup (IScene scene) { AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, state.GetBackupDir(), state.GetNamingType()); + IRegionArchiverModule iram = scene.RequestModuleInterface (); + string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); - if(savePath == null) - { - m_log.Warn("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + if (savePath == null) { + m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, null); - ExecuteScript(state.GetScript(), savePath); + iram.ArchiveRegion (savePath, null); + ExecuteScript (state.GetScript (), savePath); } string IRegionModuleBase.Name { - get { - return "AutoBackupModule"; - } + get { return "AutoBackupModule"; } } Type IRegionModuleBase.ReplaceableInterface { - get { - return null; - } + get { return null; } } - + #endregion #region ISharedRegionModule implementation void ISharedRegionModule.PostInitialise () { //I don't care right now. } - + #endregion - + //Is this even needed? - public bool IsSharedModule - { - get { return true; } - } - - private string BuildOarPath(string regionName, string baseDir, NamingType naming) + public bool IsSharedModule { + get { return true; } + } + + private string BuildOarPath (string regionName, string baseDir, NamingType naming) { FileInfo path = null; - switch(naming) - { + switch (naming) { case NamingType.OVERWRITE: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); return path.FullName; case NamingType.TIME: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString() + ".oar"); + path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); return path.FullName; case NamingType.SEQUENTIAL: - path = new FileInfo(GetNextFile(baseDir, regionName)); + path = new FileInfo (GetNextFile (baseDir, regionName)); return path.FullName; default: - m_log.Warn("VERY BAD: Unhandled case element " + naming.ToString()); + m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); break; } return path.FullName; } - + //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. //(Terrible reference to ) //This format may turn out to be too unwieldy to keep... //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString() + private string GetTimeString () { - StringWriter sw = new StringWriter(); - sw.Write("_"); + StringWriter sw = new StringWriter (); + sw.Write ("_"); DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); + sw.Write (now.Year); + sw.Write ("y_"); + sw.Write (now.Month); + sw.Write ("M_"); + sw.Write (now.Day); + sw.Write ("d_"); + sw.Write (now.Hour); + sw.Write ("h_"); + sw.Write (now.Minute); + sw.Write ("m_"); + sw.Write (now.Second); + sw.Write ("s"); + sw.Flush (); + string output = sw.ToString (); + sw.Close (); return output; } - + //Get the next logical file name //I really shouldn't put fields here, but for now.... ;) private string m_dirName = null; private string m_regionName = null; - private string GetNextFile(string dirName, string regionName) + private string GetNextFile (string dirName, string regionName) { FileInfo uniqueFile = null; m_dirName = dirName; m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize(1, FileExistsTest); - biggestExistingFile++; //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - - uniqueFile = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if(uniqueFile.Exists) - { + long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); + if (uniqueFile.Exists) { //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear(biggestExistingFile); + uniqueFile = UniqueFileSearchLinear (biggestExistingFile); } return uniqueFile.FullName; } - - private bool RunHeuristics() + + private bool RunHeuristics () { return true; } - - private void ExecuteScript(string scriptName, string savePath) + + private void ExecuteScript (string scriptName, string savePath) { //Fast path out - if(scriptName == null || scriptName.Length <= 0) + if (scriptName == null || scriptName.Length <= 0) return; - try - { - FileInfo fi = new FileInfo(scriptName); - if(fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); + try { + FileInfo fi = new FileInfo (scriptName); + if (fi.Exists) { + ProcessStartInfo psi = new ProcessStartInfo (scriptName); psi.Arguments = savePath; psi.CreateNoWindow = true; - Process proc = Process.Start(psi); + Process proc = Process.Start (psi); proc.ErrorDataReceived += HandleProcErrorDataReceived; } - } - catch(Exception e) - { - m_log.Warn("Exception encountered when trying to run script for oar backup " + savePath, e); + } catch (Exception e) { + m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); } } void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) { - m_log.Warn("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); + m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); } - - private void StopAllTimers() + + private void StopAllTimers () { - foreach(Timer t in timerMap.Keys) - { - t.Close(); + foreach (Timer t in timerMap.Keys) { + t.Close (); } + m_closed = true; } - + /* Find the largest value for which the predicate returns true. * We use a bisection algorithm (half interval) to make the algorithm scalable. * The worst-case complexity is about O(log(n)^2) in practice. @@ -542,65 +501,54 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. * The Predicate parameter must be a function that accepts a long and returns a bool. * */ - public long HalfIntervalMaximize(long start, Predicate pred) + public long HalfIntervalMaximize (long start, Predicate pred) { long prev = start, curr = start, biggest = 0; - if(start < 0) - throw new IndexOutOfRangeException("Start value for HalfIntervalMaximize must be non-negative"); + if (start < 0) + throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - do - { - if(pred(curr)) - { - if(curr > biggest) - { + do { + if (pred (curr)) { + if (curr > biggest) { biggest = curr; } prev = curr; - if(curr == 0) - { + if (curr == 0) { //Special case because 0 * 2 = 0 :) curr = 1; - } - else - { + } else { //Look deeper curr *= 2; } - } - else - { + } else { // We went too far, back off halfway curr = (curr + prev) / 2; } - } - while(curr - prev > 0); + } while (curr - prev > 0); return biggest; } - - public bool FileExistsTest(long num) + + public bool FileExistsTest (long num) { - FileInfo test = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); + FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); return test.Exists; } - - + + //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear(long start) + public FileInfo UniqueFileSearchLinear (long start) { long l = start; FileInfo retval = null; - do - { - retval = new FileInfo(m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } - while(retval.Exists); + do { + retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); + } while (retval.Exists); return retval; } -} + } } From 018645f9f843d1e807a69a63b7dd82c294885eff Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:45:50 -0500 Subject: [PATCH 16/48] First pass at busy heuristics. Compile-tested only. --- .../World/AutoBackup/AutoBackupModule.cs | 69 +++++++++++++++++-- 1 file changed, 63 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 54b9b09cb3..98127b720c 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -35,7 +35,9 @@ using log4net; using Nini; using Nini.Config; using OpenSim.Framework; +using OpenSim.Framework.Statistics; using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; /* @@ -319,6 +321,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void HandleElapsed (object sender, ElapsedEventArgs e) { + //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + //check whether the region is too busy! Especially on sims with LOTS of regions. + //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... if (m_closed) return; bool heuristicsRun = false; @@ -333,15 +341,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; - //Logical Deduction: heuristics are on but haven't been run + //Logical Deduction: heuristics are on but haven't been run } else { - heuristicsPassed = RunHeuristics (); + heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; - if (!heuristicsPassed) + if (!heuristicsPassed) { + m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; + } doRegionBackup (scene); } } @@ -349,6 +360,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup void doRegionBackup (IScene scene) { + if (scene.RegionStatus != RegionStatus.Up) { + //We won't backup a region that isn't operating normally. + m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + return; + } + AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); @@ -454,9 +471,49 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - private bool RunHeuristics () + /* + * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + * */ + private bool RunHeuristics (IScene region) { - return true; + try { + return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); + } catch (Exception e) { + m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + return false; + } + } + + /* + * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunTimeDilationHeuristic (IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /* + * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + * */ + private bool RunAgentLimitHeuristic (IScene region) + { + string regionName = region.RegionInfo.RegionName; + try { + Scene scene = (Scene)region; + //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); + } catch (InvalidCastException ice) { + m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + return true; + //Non-obstructionist safest answer... + } } private void ExecuteScript (string scriptName, string savePath) From b3c42e952f24a5c280a3691e1f75912e21c77323 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 28 Feb 2011 11:54:07 -0500 Subject: [PATCH 17/48] Fix small bug with remove region; update settings docs. Our impl of IRegionModuleBase.RemoveRegion didn't remove the scene from the states map. --- .../World/AutoBackup/AutoBackupModule.cs | 14 ++++++++------ 1 file changed, 8 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 98127b720c..766034256b 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -71,6 +71,10 @@ using OpenSim.Region.Framework.Scenes; * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. * AutoBackupDir: String. Default: "." (the current directory). * A directory (absolute or relative) where backups should be saved. + * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + * If the time dilation is below this value, don't take a backup right now. + * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + * If the number of agents is greater than this value, don't take a backup right now. * */ namespace OpenSim.Region.OptionalModules.World.AutoBackup @@ -91,7 +95,6 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { - private readonly IScene m_scene; private bool m_enabled = false; private NamingType m_naming = NamingType.TIME; private Timer m_timer = null; @@ -99,11 +102,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private string m_script = null; private string m_dir = "."; - public AutoBackupModuleState (IScene scene) + public AutoBackupModuleState () { - m_scene = scene; - if (scene == null) - throw new NullReferenceException ("Required parameter missing for AutoBackupModuleState constructor"); + } public void SetEnabled (bool b) @@ -226,6 +227,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup timers.Remove (timer.Interval); timer.Close (); } + states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -238,7 +240,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return; string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (scene); + AutoBackupModuleState st = new AutoBackupModuleState (); states.Add (scene, st); //Read the config settings and set variables. From 4974a1ce69fb3a1d2937c7de7ba93079a918eb3a Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 12:34:26 -0400 Subject: [PATCH 18/48] AutoBackup: Support region-independent settings too. --- .../World/AutoBackup/AutoBackupModule.cs | 234 +++++++++++++----- 1 file changed, 176 insertions(+), 58 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 766034256b..37a2d97ac3 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -49,6 +49,8 @@ using OpenSim.Region.Framework.Scenes; * VERY IMPORTANT: You must create the key name as follows: . * Example: My region is named Foo. * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + * Region-specific settings take precedence. * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. * This is the only required option for enabling auto-backup; the other options have sane defaults. * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. @@ -121,6 +123,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { return m_timer; } + + public double GetIntervalMinutes () + { + if(m_timer == null) + { + return -1.0; + } + else + { + return m_timer.Interval / 60000.0; + } + } public void SetTimer (Timer t) { @@ -167,6 +181,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { m_naming = n; } + + public string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; + retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; + return retval; + } } //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. @@ -180,6 +207,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private bool m_closed = false; //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); + public AutoBackupModule () { @@ -194,9 +223,20 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (moduleConfig != null) { m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); if (m_Enabled) { - m_log.Info ("[AUTO BACKUP MODULE]: AutoBackupModule enabled"); + m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } } + + Timer defTimer = new Timer(720 * 60000); + defaultState.SetTimer(defTimer); + timers.Add (720*60000, defTimer); + defTimer.Elapsed += HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start (); + + AutoBackupModuleState abms = ParseConfig(null, false); + m_log.Debug("[AUTO BACKUP]: Config for default"); + m_log.Debug(abms.ToString()); } void IRegionModuleBase.Close () @@ -238,87 +278,162 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //This really ought not to happen, but just in case, let's pretend it didn't... if (scene == null) return; - - string sRegionName = scene.RegionInfo.RegionName; - AutoBackupModuleState st = new AutoBackupModuleState (); - states.Add (scene, st); - - //Read the config settings and set variables. + + AutoBackupModuleState abms = ParseConfig(scene, true); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug(abms.ToString()); + } + + AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if(parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + //Read the config settings and set variables. IConfig config = m_configSource.Configs["AutoBackupModule"]; if (config == null) { - //No config settings for any regions, let's just give up. - st.SetEnabled (false); - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + state = defaultState; //defaultState would be disabled too if the section doesn't exist. + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; } - st.SetEnabled (config.GetBoolean (sRegionName + ".AutoBackup", false)); + + bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); + if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state + { + state = new AutoBackupModuleState(); + state.SetEnabled (tmpEnabled); + } + //If you don't want AutoBackup, we stop. - if (!st.GetEnabled ()) { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is NOT AutoBackup enabled."); - return; + if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; } else { - m_log.Info ("[AUTO BACKUP MODULE]: Region " + sRegionName + " is AutoBackup ENABLED."); + m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); } //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (sRegionName + ".AutoBackupInterval", 720) * 60000; + double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; + if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) + { + state = new AutoBackupModuleState(); + } + if (timers.ContainsKey (interval)) { - st.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP MODULE]: Reusing timer for " + interval + " msec for region " + sRegionName); + if(state != null) + state.SetTimer (timers[interval]); + m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); } else { //0 or negative interval == do nothing. - if (interval <= 0.0) { - st.SetEnabled (false); - return; + if (interval <= 0.0 && state != null) { + state.SetEnabled (false); + return state; } Timer tim = new Timer (interval); - st.SetTimer (tim); + state.SetTimer (tim); //Milliseconds -> minutes timers.Add (interval, tim); tim.Elapsed += HandleElapsed; tim.AutoReset = true; tim.Start (); - //m_log.Debug("[AUTO BACKUP MODULE]: New timer for " + interval + " msec for region " + sRegionName); + //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (st.GetTimer ())) { - timerMap[st.GetTimer ()].Add (scene); + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); } else { List scns = new List (1); scns.Add (scene); - timerMap.Add (st.GetTimer (), scns); + timerMap.Add (state.GetTimer (), scns); } - st.SetBusyCheck (config.GetBoolean (sRegionName + ".AutoBackupBusyCheck", true)); + bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); + if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetBusyCheck (tmpBusyCheck); + } //Set file naming algorithm - string namingtype = config.GetString (sRegionName + ".AutoBackupNaming", "Time"); - if (namingtype.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.TIME); - } else if (namingtype.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.SEQUENTIAL); - } else if (namingtype.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - st.SetNamingType (NamingType.OVERWRITE); + string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.TIME; + } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.SEQUENTIAL; + } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { + tmpNamingType = NamingType.OVERWRITE; } else { - m_log.Warn ("Unknown naming type specified for region " + scene.RegionInfo.RegionName + ": " + namingtype); - st.SetNamingType (NamingType.TIME); + m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); + tmpNamingType = NamingType.TIME; } - st.SetScript (config.GetString (sRegionName + ".AutoBackupScript", null)); - st.SetBackupDir (config.GetString (sRegionName + ".AutoBackupDir", ".")); - - //Let's give the user *one* convenience and auto-mkdir - if (st.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (st.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + st.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } + if(state == null && tmpNamingType != defaultState.GetNamingType()) + { + state = new AutoBackupModuleState(); } + + if(state != null) + { + state.SetNamingType(tmpNamingType); + } + + string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); + if(state == null && tmpScript != defaultState.GetScript()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetScript (tmpScript); + } + + string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); + if(state == null && tmpBackupDir != defaultState.GetBackupDir()) + { + state = new AutoBackupModuleState(); + } + + if(state != null) + { + state.SetBackupDir (tmpBackupDir); + //Let's give the user *one* convenience and auto-mkdir + if (state.GetBackupDir () != ".") { + try { + DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); + if (!dirinfo.Exists) { + dirinfo.Create (); + } + } catch (Exception e) { + m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); + } + } + } + + return state; } void HandleElapsed (object sender, ElapsedEventArgs e) @@ -336,7 +451,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!timerMap.ContainsKey ((Timer)sender)) { m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); } - foreach (IScene scene in timerMap[(Timer)sender]) { + + List tmap = timerMap[(Timer)sender]; + if(tmap != null && tmap.Count > 0) + foreach (IScene scene in tmap) { AutoBackupModuleState state = states[scene]; bool heuristics = state.GetBusyCheck (); @@ -345,14 +463,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup doRegionBackup (scene); //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; //Logical Deduction: heuristics are on but haven't been run } else { heuristicsPassed = RunHeuristics (scene); heuristicsRun = true; if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP MODULE]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); + m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); continue; } doRegionBackup (scene); @@ -364,16 +482,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup { if (scene.RegionStatus != RegionStatus.Up) { //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP MODULE]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); + m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); return; } AutoBackupModuleState state = states[scene]; IRegionArchiverModule iram = scene.RequestModuleInterface (); string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP MODULE]: savePath = " + savePath); + //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); if (savePath == null) { - m_log.Warn ("[AUTO BACKUP MODULE]: savePath is null in HandleElapsed"); + m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } iram.ArchiveRegion (savePath, null); @@ -481,7 +599,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup try { return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP MODULE]: Exception in RunHeuristics", e); + m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); return false; } } @@ -512,7 +630,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP MODULE]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); + m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); return true; //Non-obstructionist safest answer... } From 4ad05fb01d038b31738438d02850972e79739f30 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:12:26 -0400 Subject: [PATCH 19/48] Fix some NREs on certain paths. --- .../World/AutoBackup/AutoBackupModule.cs | 28 +++++++++++++++---- 1 file changed, 22 insertions(+), 6 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 37a2d97ac3..3d156ca076 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -324,6 +324,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup //If you don't want AutoBackup, we stop. if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); @@ -346,6 +347,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.SetEnabled (false); return state; } + if(state == null) + state = new AutoBackupModuleState(); Timer tim = new Timer (interval); state.SetTimer (tim); //Milliseconds -> minutes @@ -357,12 +360,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } + } + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); From 95a6ee0a3793b72deb29fe6e853d59afc2ca631e Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 11 Apr 2011 13:20:46 -0400 Subject: [PATCH 20/48] Fix NREs harder. --- .../World/AutoBackup/AutoBackupModule.cs | 39 +++++++++++-------- 1 file changed, 23 insertions(+), 16 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 3d156ca076..e52e9cb5f7 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -225,6 +225,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (m_Enabled) { m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); } + else { + m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); + return; + } } Timer defTimer = new Timer(720 * 60000); @@ -360,24 +364,27 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } //Add the current region to the list of regions tied to this timer. - if(state != null) + if(scene != null) { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); + if(state != null) + { + if (timerMap.ContainsKey (state.GetTimer ())) { + timerMap[state.GetTimer ()].Add (scene); + } else { + List scns = new List (1); + scns.Add (scene); + timerMap.Add (state.GetTimer (), scns); + } } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); + else + { + if(timerMap.ContainsKey(defaultState.GetTimer())) { + timerMap[defaultState.GetTimer()].Add(scene); + } else { + List scns = new List (1); + scns.Add(scene); + timerMap.Add(defaultState.GetTimer(), scns); + } } } From 049bce4d003f6854868306c7d98f7b5a501cb047 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 12 Apr 2011 01:14:21 -0400 Subject: [PATCH 21/48] Fixup the global defaults config parsing code. --- .../World/AutoBackup/AutoBackupModule.cs | 40 ++++++++++++------- 1 file changed, 25 insertions(+), 15 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index e52e9cb5f7..364697b3f1 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -238,7 +238,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup defTimer.AutoReset = true; defTimer.Start (); - AutoBackupModuleState abms = ParseConfig(null, false); + AutoBackupModuleState abms = ParseConfig(null, true); m_log.Debug("[AUTO BACKUP]: Config for default"); m_log.Debug(abms.ToString()); } @@ -262,16 +262,23 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (!m_Enabled) return; - AutoBackupModuleState abms = states[scene]; - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); + if(states.ContainsKey(scene)) + { + AutoBackupModuleState abms = states[scene]; + + //Remove this scene out of the timer map list + Timer timer = abms.GetTimer (); + List list = timerMap[timer]; + list.Remove (scene); + + //Shut down the timer if this was the last scene for the timer + if (list.Count == 0) { + timerMap.Remove (timer); + timers.Remove (timer.Interval); + timer.Close (); + } + states.Remove(scene); } - states.Remove(scene); } void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) @@ -283,9 +290,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if (scene == null) return; - AutoBackupModuleState abms = ParseConfig(scene, true); + AutoBackupModuleState abms = ParseConfig(scene, false); m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug(abms.ToString()); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) @@ -322,13 +329,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state { state = new AutoBackupModuleState(); - state.SetEnabled (tmpEnabled); + } + + if(state != null) + { + state.SetEnabled (tmpEnabled); } //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || !state.GetEnabled ()) { + if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - state = defaultState; return state; } else { m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); From 082fad6dd28513e38120c9d272aeed385de8208f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:29:13 -0400 Subject: [PATCH 22/48] Fix most issues raised by justincc: http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 1509 ++++++++--------- .../World/AutoBackup/AutoBackupModuleState.cs | 109 ++ bin/OpenSimDefaults.ini | 4 + 3 files changed, 858 insertions(+), 764 deletions(-) create mode 100644 OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 364697b3f1..a4dbea406a 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,764 +1,745 @@ -/* - * 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 OpenSimulator 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.Timers; -using System.Diagnostics; -using System.Reflection; -using System.Collections.Generic; -using log4net; -using Nini; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Framework.Statistics; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - - -/* - * Config Settings Documentation. - * At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: - * In the [Modules] section: - * AutoBackupModule: True/False. Default: False. If True, use the auto backup module. Otherwise it will be disabled regardless of what settings are in Regions.ini! - * EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. - * VERY IMPORTANT: You must create the key name as follows: . - * Example: My region is named Foo. - * If I wanted to specify the "AutoBackupInterval" key below, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. - * Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. - * Region-specific settings take precedence. - * AutoBackup: True/False. Default: False. If True, activate auto backup functionality. - * This is the only required option for enabling auto-backup; the other options have sane defaults. - * If False, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. - * AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). - * The number of minutes between each backup attempt. - * If a negative or zero value is given, it is equivalent to setting AutoBackup = False. - * AutoBackupBusyCheck: True/False. Default: True. - * If True, we will only take an auto-backup if a set of conditions are met. - * These conditions are heuristics to try and avoid taking a backup when the sim is busy. - * AutoBackupScript: String. Default: not specified (disabled). - * File path to an executable script or binary to run when an automatic backup is taken. - * The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. - * Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! - * argv[1] of the executed file/script will be the file name of the generated OAR. - * If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. - * AutoBackupNaming: string. Default: Time. - * One of three strings (case insensitive): - * "Time": Current timestamp is appended to file name. An existing file will never be overwritten. - * "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. - * "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. - * AutoBackupDir: String. Default: "." (the current directory). - * A directory (absolute or relative) where backups should be saved. - * AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. - * If the time dilation is below this value, don't take a backup right now. - * AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. - * If the number of agents is greater than this value, don't take a backup right now. - * */ - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - - public enum NamingType - { - TIME, - SEQUENTIAL, - OVERWRITE - } - - public class AutoBackupModule : ISharedRegionModule, IRegionModuleBase - { - - private static readonly ILog m_log = LogManager.GetLogger (MethodBase.GetCurrentMethod ().DeclaringType); - - //AutoBackupModuleState: Auto-Backup state for one region (scene). - public class AutoBackupModuleState - { - private bool m_enabled = false; - private NamingType m_naming = NamingType.TIME; - private Timer m_timer = null; - private bool m_busycheck = true; - private string m_script = null; - private string m_dir = "."; - - public AutoBackupModuleState () - { - - } - - public void SetEnabled (bool b) - { - m_enabled = b; - } - - public bool GetEnabled () - { - return m_enabled; - } - - public Timer GetTimer () - { - return m_timer; - } - - public double GetIntervalMinutes () - { - if(m_timer == null) - { - return -1.0; - } - else - { - return m_timer.Interval / 60000.0; - } - } - - public void SetTimer (Timer t) - { - m_timer = t; - } - - public bool GetBusyCheck () - { - return m_busycheck; - } - - public void SetBusyCheck (bool b) - { - m_busycheck = b; - } - - - public string GetScript () - { - return m_script; - } - - public void SetScript (string s) - { - m_script = s; - } - - public string GetBackupDir () - { - return m_dir; - } - - public void SetBackupDir (string s) - { - m_dir = s; - } - - public NamingType GetNamingType () - { - return m_naming; - } - - public void SetNamingType (NamingType n) - { - m_naming = n; - } - - public string ToString() - { - string retval = ""; - - retval += "[AUTO BACKUP]: AutoBackup: " + (GetEnabled() ? "ENABLED" : "DISABLED") + "\n"; - retval += "[AUTO BACKUP]: Interval: " + GetIntervalMinutes() + " minutes" + "\n"; - retval += "[AUTO BACKUP]: Do Busy Check: " + (GetBusyCheck() ? "Yes" : "No") + "\n"; - retval += "[AUTO BACKUP]: Naming Type: " + GetNamingType().ToString() + "\n"; - retval += "[AUTO BACKUP]: Backup Dir: " + GetBackupDir() + "\n"; - retval += "[AUTO BACKUP]: Script: " + GetScript() + "\n"; - return retval; - } - } - - //Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - //Also helps if you don't want AutoBackup at all - readonly Dictionary states = new Dictionary (4); - readonly Dictionary timers = new Dictionary (1); - readonly Dictionary> timerMap = new Dictionary> (1); - private IConfigSource m_configSource = null; - private bool m_Enabled = false; - //Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed = false; - //True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - //Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - readonly AutoBackupModuleState defaultState = new AutoBackupModuleState(); - - public AutoBackupModule () - { - - } - - #region IRegionModuleBase implementation - void IRegionModuleBase.Initialise (Nini.Config.IConfigSource source) - { - //Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - m_configSource = source; - IConfig moduleConfig = source.Configs["Modules"]; - if (moduleConfig != null) { - m_Enabled = moduleConfig.GetBoolean ("AutoBackupModule", false); - if (m_Enabled) { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else { - m_log.Info ("[AUTO BACKUP]: AutoBackupModule disabled"); - return; - } - } - - Timer defTimer = new Timer(720 * 60000); - defaultState.SetTimer(defTimer); - timers.Add (720*60000, defTimer); - defTimer.Elapsed += HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start (); - - AutoBackupModuleState abms = ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Config for default"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close () - { - if (!m_Enabled) - return; - - //We don't want any timers firing while the sim's coming down; strange things may happen. - StopAllTimers (); - } - - void IRegionModuleBase.AddRegion (Framework.Scenes.Scene scene) - { - //NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - if(states.ContainsKey(scene)) - { - AutoBackupModuleState abms = states[scene]; - - //Remove this scene out of the timer map list - Timer timer = abms.GetTimer (); - List list = timerMap[timer]; - list.Remove (scene); - - //Shut down the timer if this was the last scene for the timer - if (list.Count == 0) { - timerMap.Remove (timer); - timers.Remove (timer.Interval); - timer.Close (); - } - states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded (Framework.Scenes.Scene scene) - { - if (!m_Enabled) - return; - - //This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - return; - - AutoBackupModuleState abms = ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - AutoBackupModuleState ParseConfig (IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if(parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - //Read the config settings and set variables. - IConfig config = m_configSource.Configs["AutoBackupModule"]; - if (config == null) { - state = defaultState; //defaultState would be disabled too if the section doesn't exist. - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean (prepend + "AutoBackup", defaultState.GetEnabled()); - if(state == null && tmpEnabled != defaultState.GetEnabled()) //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetEnabled (tmpEnabled); - } - - //If you don't want AutoBackup, we stop. - if ((state == null && !defaultState.GetEnabled()) || (state != null && !state.GetEnabled ())) { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } else { - m_log.Info ("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - //Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = config.GetDouble (prepend + "AutoBackupInterval", defaultState.GetIntervalMinutes()) * 60000.0; - if(state == null && interval != defaultState.GetIntervalMinutes() * 60000.0) - { - state = new AutoBackupModuleState(); - } - - if (timers.ContainsKey (interval)) { - if(state != null) - state.SetTimer (timers[interval]); - m_log.Debug ("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + sRegionLabel); - } else { - //0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) { - state.SetEnabled (false); - return state; - } - if(state == null) - state = new AutoBackupModuleState(); - Timer tim = new Timer (interval); - state.SetTimer (tim); - //Milliseconds -> minutes - timers.Add (interval, tim); - tim.Elapsed += HandleElapsed; - tim.AutoReset = true; - tim.Start (); - //m_log.Debug("[AUTO BACKUP]: New timer for " + interval + " msec for region " + sRegionName); - } - - //Add the current region to the list of regions tied to this timer. - if(scene != null) - { - if(state != null) - { - if (timerMap.ContainsKey (state.GetTimer ())) { - timerMap[state.GetTimer ()].Add (scene); - } else { - List scns = new List (1); - scns.Add (scene); - timerMap.Add (state.GetTimer (), scns); - } - } - else - { - if(timerMap.ContainsKey(defaultState.GetTimer())) { - timerMap[defaultState.GetTimer()].Add(scene); - } else { - List scns = new List (1); - scns.Add(scene); - timerMap.Add(defaultState.GetTimer(), scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean (prepend + "AutoBackupBusyCheck", defaultState.GetBusyCheck()); - if(state == null && tmpBusyCheck != defaultState.GetBusyCheck()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBusyCheck (tmpBusyCheck); - } - - //Set file naming algorithm - string stmpNamingType = config.GetString (prepend + "AutoBackupNaming", defaultState.GetNamingType().ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals ("Time", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.TIME; - } else if (stmpNamingType.Equals ("Sequential", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.SEQUENTIAL; - } else if (stmpNamingType.Equals ("Overwrite", StringComparison.CurrentCultureIgnoreCase)) { - tmpNamingType = NamingType.OVERWRITE; - } else { - m_log.Warn ("Unknown naming type specified for region " + sRegionLabel + ": " + stmpNamingType); - tmpNamingType = NamingType.TIME; - } - - if(state == null && tmpNamingType != defaultState.GetNamingType()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetNamingType(tmpNamingType); - } - - string tmpScript = config.GetString (prepend + "AutoBackupScript", defaultState.GetScript()); - if(state == null && tmpScript != defaultState.GetScript()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetScript (tmpScript); - } - - string tmpBackupDir = config.GetString (prepend + "AutoBackupDir", "."); - if(state == null && tmpBackupDir != defaultState.GetBackupDir()) - { - state = new AutoBackupModuleState(); - } - - if(state != null) - { - state.SetBackupDir (tmpBackupDir); - //Let's give the user *one* convenience and auto-mkdir - if (state.GetBackupDir () != ".") { - try { - DirectoryInfo dirinfo = new DirectoryInfo (state.GetBackupDir ()); - if (!dirinfo.Exists) { - dirinfo.Create (); - } - } catch (Exception e) { - m_log.Warn ("BAD NEWS. You won't be able to save backups to directory " + state.GetBackupDir () + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", e); - } - } - } - - return state; - } - - void HandleElapsed (object sender, ElapsedEventArgs e) - { - //TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - //XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - //check whether the region is too busy! Especially on sims with LOTS of regions. - //Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - // but would allow us to be semantically correct while being easier on perf. - //Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (m_closed) - return; - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!timerMap.ContainsKey ((Timer)sender)) { - m_log.Debug ("Code-up error: timerMap doesn't contain timer " + sender.ToString ()); - } - - List tmap = timerMap[(Timer)sender]; - if(tmap != null && tmap.Count > 0) - foreach (IScene scene in tmap) { - AutoBackupModuleState state = states[scene]; - bool heuristics = state.GetBusyCheck (); - - //Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) { - doRegionBackup (scene); - //Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } else if (heuristics && heuristicsRun && !heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - //Logical Deduction: heuristics are on but haven't been run - } else { - heuristicsPassed = RunHeuristics (scene); - heuristicsRun = true; - if (!heuristicsPassed) { - m_log.Info ("[AUTO BACKUP]: Heuristics: too busy to backup " + scene.RegionInfo.RegionName + " right now."); - continue; - } - doRegionBackup (scene); - } - } - } - - void doRegionBackup (IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) { - //We won't backup a region that isn't operating normally. - m_log.Warn ("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + " because its status is " + scene.RegionStatus.ToString ()); - return; - } - - AutoBackupModuleState state = states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface (); - string savePath = BuildOarPath (scene.RegionInfo.RegionName, state.GetBackupDir (), state.GetNamingType ()); - //m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) { - m_log.Warn ("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - iram.ArchiveRegion (savePath, null); - ExecuteScript (state.GetScript (), savePath); - } - - string IRegionModuleBase.Name { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface { - get { return null; } - } - - #endregion - #region ISharedRegionModule implementation - void ISharedRegionModule.PostInitialise () - { - //I don't care right now. - } - - #endregion - - //Is this even needed? - public bool IsSharedModule { - get { return true; } - } - - private string BuildOarPath (string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) { - case NamingType.OVERWRITE: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName); - return path.FullName; - case NamingType.TIME: - path = new FileInfo (baseDir + Path.DirectorySeparatorChar + regionName + GetTimeString () + ".oar"); - return path.FullName; - case NamingType.SEQUENTIAL: - path = new FileInfo (GetNextFile (baseDir, regionName)); - return path.FullName; - default: - m_log.Warn ("VERY BAD: Unhandled case element " + naming.ToString ()); - break; - } - - return path.FullName; - } - - //Welcome to the TIME STRING. 4 CORNER INTEGERS, CUBES 4 QUAD MEMORY -- No 1 Integer God. - //(Terrible reference to ) - //This format may turn out to be too unwieldy to keep... - //Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - //Sequential numbers, right? Ugh. Almost makes TOO much sense. - private string GetTimeString () - { - StringWriter sw = new StringWriter (); - sw.Write ("_"); - DateTime now = DateTime.Now; - sw.Write (now.Year); - sw.Write ("y_"); - sw.Write (now.Month); - sw.Write ("M_"); - sw.Write (now.Day); - sw.Write ("d_"); - sw.Write (now.Hour); - sw.Write ("h_"); - sw.Write (now.Minute); - sw.Write ("m_"); - sw.Write (now.Second); - sw.Write ("s"); - sw.Flush (); - string output = sw.ToString (); - sw.Close (); - return output; - } - - //Get the next logical file name - //I really shouldn't put fields here, but for now.... ;) - private string m_dirName = null; - private string m_regionName = null; - private string GetNextFile (string dirName, string regionName) - { - FileInfo uniqueFile = null; - m_dirName = dirName; - m_regionName = regionName; - long biggestExistingFile = HalfIntervalMaximize (1, FileExistsTest); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + biggestExistingFile + ".oar"); - if (uniqueFile.Exists) { - //Congratulations, your strange deletion patterns fooled my half-interval search into picking an existing file! - //Now you get to pay the performance cost :) - uniqueFile = UniqueFileSearchLinear (biggestExistingFile); - } - - return uniqueFile.FullName; - } - - /* - * Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - * */ - private bool RunHeuristics (IScene region) - { - try { - return RunTimeDilationHeuristic (region) && RunAgentLimitHeuristic (region); - } catch (Exception e) { - m_log.Warn ("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /* - * If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - * then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - * AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunTimeDilationHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= m_configSource.Configs["AutoBackupModule"].GetFloat (regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /* - * If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - * then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - * AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - * Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - * */ - private bool RunAgentLimitHeuristic (IScene region) - { - string regionName = region.RegionInfo.RegionName; - try { - Scene scene = (Scene)region; - //TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount () <= m_configSource.Configs["AutoBackupModule"].GetInt (regionName + ".AutoBackupAgentThreshold", 10); - } catch (InvalidCastException ice) { - m_log.Debug ("[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!"); - return true; - //Non-obstructionist safest answer... - } - } - - private void ExecuteScript (string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - return; - - try { - FileInfo fi = new FileInfo (scriptName); - if (fi.Exists) { - ProcessStartInfo psi = new ProcessStartInfo (scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start (psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } catch (Exception e) { - m_log.Warn ("Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - void HandleProcErrorDataReceived (object sender, DataReceivedEventArgs e) - { - m_log.Warn ("ExecuteScript hook " + ((Process)sender).ProcessName + " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers () - { - foreach (Timer t in timerMap.Keys) { - t.Close (); - } - m_closed = true; - } - - /* Find the largest value for which the predicate returns true. - * We use a bisection algorithm (half interval) to make the algorithm scalable. - * The worst-case complexity is about O(log(n)^2) in practice. - * Only for extremely small values (under 10) do you notice it taking more iterations than a linear search. - * The number of predicate invocations only hits a few hundred when the maximized value - * is in the tens of millions, so prepare for the predicate to be invoked between 10 and 100 times. - * And of course it is fantastic with powers of 2, which are densely packed in values under 100 anyway. - * The Predicate parameter must be a function that accepts a long and returns a bool. - * */ - public long HalfIntervalMaximize (long start, Predicate pred) - { - long prev = start, curr = start, biggest = 0; - - if (start < 0) - throw new IndexOutOfRangeException ("Start value for HalfIntervalMaximize must be non-negative"); - - do { - if (pred (curr)) { - if (curr > biggest) { - biggest = curr; - } - prev = curr; - if (curr == 0) { - //Special case because 0 * 2 = 0 :) - curr = 1; - } else { - //Look deeper - curr *= 2; - } - } else { - // We went too far, back off halfway - curr = (curr + prev) / 2; - } - } while (curr - prev > 0); - - return biggest; - } - - public bool FileExistsTest (long num) - { - FileInfo test = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + num + ".oar"); - return test.Exists; - } - - - //Very slow, hence why we try the HalfIntervalMaximize first! - public FileInfo UniqueFileSearchLinear (long start) - { - long l = start; - FileInfo retval = null; - do { - retval = new FileInfo (m_dirName + Path.DirectorySeparatorChar + m_regionName + "_" + (l++) + ".oar"); - } while (retval.Exists); - - return retval; - } - } - -} - +#pragma warning disable 1587 +/// +/// 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 OpenSimulator 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.Generic; +using System.Diagnostics; +using System.Globalization; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +/// +/// Config Settings Documentation. +/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: +/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. +/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . +/// Example: My region is named Foo. +/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. +/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. +/// Region-specific settings take precedence. +/// +/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. +/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! +/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. +/// This is the only required option for enabling auto-backup; the other options have sane defaults. +/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. +/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. +/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). +/// The number of minutes between each backup attempt. +/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. +/// AutoBackupBusyCheck: True/False. Default: True. +/// If True, we will only take an auto-backup if a set of conditions are met. +/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. +/// AutoBackupScript: String. Default: not specified (disabled). +/// File path to an executable script or binary to run when an automatic backup is taken. +/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. +/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! +/// argv[1] of the executed file/script will be the file name of the generated OAR. +/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. +/// AutoBackupNaming: string. Default: Time. +/// One of three strings (case insensitive): +/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. +/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. +/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. +/// AutoBackupDir: String. Default: "." (the current directory). +/// A directory (absolute or relative) where backups should be saved. +/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. +/// If the time dilation is below this value, don't take a backup right now. +/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. +/// If the number of agents is greater than this value, don't take a backup right now. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + + /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. + /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_states = + new Dictionary(1); + + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + private bool m_closed; + + private IConfigSource m_configSource; + + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + void IRegionModuleBase.Initialise(IConfigSource source) + { + /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + /// We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + void IRegionModuleBase.AddRegion(Scene scene) + { + /// NO-OP. Wait for the region to be loaded. + } + + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + /// Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + /// Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + /// This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + void ISharedRegionModule.PostInitialise() + { + /// I don't care right now. + } + + #endregion + + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + /// Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + /// defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + /// If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + /// 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + /// Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + /// Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + /// Let's give the user *one* convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region + /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + /// check whether the region is too busy! Especially on sims with LOTS of regions. + /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + /// but would allow us to be semantically correct while being easier on perf. + /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + /// Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + /// We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + iram.ArchiveRegion(savePath, Guid.NewGuid(), null); + ExecuteScript(state.Script, savePath); + } + + /// This format may turn out to be too unwieldy to keep... + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Sequential numbers, right? Ugh. Almost makes TOO much sense. + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + /// + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! + /// + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + /// Non-obstructionist safest answer... + } + } + + private static void ExecuteScript(string scriptName, string savePath) + { + //Fast path out + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + /// All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + /// Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs new file mode 100644 index 0000000000..1b348af5eb --- /dev/null +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -0,0 +1,109 @@ +#pragma warning disable 1587 +/// +/// 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 OpenSimulator 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. +/// + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + public class AutoBackupModuleState + { + public AutoBackupModuleState() + { + this.Enabled = false; + this.BackupDir = "."; + this.BusyCheck = true; + this.Timer = null; + this.NamingType = NamingType.Time; + this.Script = null; + } + + public bool Enabled + { + get; + set; + } + + public System.Timers.Timer Timer + { + get; + set; + } + + public double IntervalMinutes + { + get + { + if (this.Timer == null) + { + return -1.0; + } + else + { + return this.Timer.Interval / 60000.0; + } + } + } + + public bool BusyCheck + { + get; + set; + } + + public string Script + { + get; + set; + } + + public string BackupDir + { + get; + set; + } + + public NamingType NamingType + { + get; + set; + } + + public new string ToString() + { + string retval = ""; + + retval += "[AUTO BACKUP]: AutoBackup: " + (Enabled ? "ENABLED" : "DISABLED") + "\n"; + retval += "[AUTO BACKUP]: Interval: " + IntervalMinutes + " minutes" + "\n"; + retval += "[AUTO BACKUP]: Do Busy Check: " + (BusyCheck ? "Yes" : "No") + "\n"; + retval += "[AUTO BACKUP]: Naming Type: " + NamingType.ToString() + "\n"; + retval += "[AUTO BACKUP]: Backup Dir: " + BackupDir + "\n"; + retval += "[AUTO BACKUP]: Script: " + Script + "\n"; + return retval; + } + } +} + diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 96ffb7ea6b..db4bb6ae8f 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -1277,6 +1277,10 @@ [GridService] ;; default standalone, overridable in StandaloneCommon.ini StorageProvider = "OpenSim.Data.Null.dll:NullRegionData" + +[AutoBackupModule] + ;; default is module is disabled at the top level + AutoBackupModuleEnabled = false [Modules] Include-modules = "addon-modules/*/config/*.ini" From 440d54a52158057a93d8bd08c5e46290e69d8f3f Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Sat, 23 Apr 2011 18:32:08 -0400 Subject: [PATCH 23/48] AutoBackup: Removed unneeded imports. --- .../Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index a4dbea406a..7c0a5c6c57 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -29,7 +29,6 @@ using System; using System.Collections.Generic; using System.Diagnostics; -using System.Globalization; using System.IO; using System.Reflection; using System.Timers; From c82c7e6ed9ecf4858d5afffa635013bd14e90bdf Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Tue, 26 Apr 2011 11:42:06 -0400 Subject: [PATCH 24/48] Wait for OnOarFileSaved event callback before executing script We want to execute the (optional) user script after I/O is done on the oar. I wasn't aware that ArchiveRegion is asynchronous -- now I am. Should fully resolve comment 0018290 at http://opensimulator.org/mantis/view.php?id=5440 --- .../World/AutoBackup/AutoBackupModule.cs | 23 ++++++++++++------- .../World/AutoBackup/AutoBackupModuleState.cs | 14 +++++++++++ 2 files changed, 29 insertions(+), 8 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 7c0a5c6c57..bd4893c08e 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -94,18 +94,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup private static readonly ILog m_log = LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - /// True means IRegionModuleBase.Close() was called on us, and we should stop operation ASAP. - /// Used to prevent elapsing timers after Close() is called from trying to start an autobackup while the sim is shutting down. - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. /// Also helps if you don't want AutoBackup at all + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); private readonly Dictionary m_states = new Dictionary(1); - private readonly Dictionary> m_timerMap = new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); private bool m_enabled; @@ -528,8 +524,19 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); return; } - iram.ArchiveRegion(savePath, Guid.NewGuid(), null); - ExecuteScript(state.Script, savePath); + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + void EventManager_OnOarFileSaved(Guid guid, string message) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } /// This format may turn out to be too unwieldy to keep... diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 1b348af5eb..7fecfa435a 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -26,11 +26,17 @@ /// SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. /// +using System; +using System.Collections.Generic; + + namespace OpenSim.Region.OptionalModules.World.AutoBackup { /// AutoBackupModuleState: Auto-Backup state for one region (scene). public class AutoBackupModuleState { + private Dictionary m_liveRequests = null; + public AutoBackupModuleState() { this.Enabled = false; @@ -41,6 +47,14 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.Script = null; } + public Dictionary LiveRequests + { + get { + return this.m_liveRequests ?? + (this.m_liveRequests = new Dictionary(1)); + } + } + public bool Enabled { get; From 2aab033aaa41f2ebef96e725b76e0153a673b448 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 02:20:50 -0400 Subject: [PATCH 25/48] First pass at fixing justincc's feedback v2 ( http://opensimulator.org/mantis/view.php?id=5440 ) Fixing everything here (I think) except the per-region config. That's next. --- .../World/AutoBackup/AutoBackupModule.cs | 1577 +++++++++-------- .../World/AutoBackup/AutoBackupModuleState.cs | 59 +- 2 files changed, 868 insertions(+), 768 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index bd4893c08e..4a9615d1bc 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -1,751 +1,848 @@ -#pragma warning disable 1587 -/// -/// 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 OpenSimulator 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.Generic; -using System.Diagnostics; -using System.IO; -using System.Reflection; -using System.Timers; -using System.Text.RegularExpressions; -using log4net; -using Nini.Config; -using OpenSim.Framework; -using OpenSim.Region.Framework.Interfaces; -using OpenSim.Region.Framework.Scenes; - -/// -/// Config Settings Documentation. -/// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: -/// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. -/// IMPORTANT: You may optionally specify the key name as follows for a per-region key: . -/// Example: My region is named Foo. -/// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. -/// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. -/// Region-specific settings take precedence. -/// -/// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. -/// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! -/// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. -/// This is the only required option for enabling auto-backup; the other options have sane defaults. -/// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. -/// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. -/// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). -/// The number of minutes between each backup attempt. -/// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. -/// AutoBackupBusyCheck: True/False. Default: True. -/// If True, we will only take an auto-backup if a set of conditions are met. -/// These conditions are heuristics to try and avoid taking a backup when the sim is busy. -/// AutoBackupScript: String. Default: not specified (disabled). -/// File path to an executable script or binary to run when an automatic backup is taken. -/// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. -/// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! -/// argv[1] of the executed file/script will be the file name of the generated OAR. -/// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. -/// AutoBackupNaming: string. Default: Time. -/// One of three strings (case insensitive): -/// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. -/// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. -/// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. -/// AutoBackupDir: String. Default: "." (the current directory). -/// A directory (absolute or relative) where backups should be saved. -/// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. -/// If the time dilation is below this value, don't take a backup right now. -/// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. -/// If the number of agents is greater than this value, don't take a backup right now. -/// - -namespace OpenSim.Region.OptionalModules.World.AutoBackup -{ - public enum NamingType - { - Time, - Sequential, - Overwrite - } - - public class AutoBackupModule : ISharedRegionModule - { - private static readonly ILog m_log = - LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); - - /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all - private readonly Dictionary m_pendingSaves = new Dictionary(1); - private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); - private readonly Dictionary m_states = - new Dictionary(1); - private readonly Dictionary> m_timerMap = - new Dictionary>(1); - private readonly Dictionary m_timers = new Dictionary(1); - - private bool m_enabled; - +/* + * 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 OpenSimulator 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.Generic; +using System.Diagnostics; +using System.IO; +using System.Reflection; +using System.Timers; +using System.Text.RegularExpressions; +using log4net; +using Nini.Config; +using OpenSim.Framework; +using OpenSim.Region.Framework.Interfaces; +using OpenSim.Region.Framework.Scenes; + +namespace OpenSim.Region.OptionalModules.World.AutoBackup +{ + /// + /// Choose between ways of naming the backup files that are generated. + /// + /// Time: OARs are named by a timestamp. + /// Sequential: OARs are named by counting (Region_1.oar, Region_2.oar, etc.) + /// Overwrite: Only one file per region is created; it's overwritten each time a backup is made. + public enum NamingType + { + Time, + Sequential, + Overwrite + } + + /// + /// AutoBackupModule: save OAR region backups to disk periodically + /// + /// + /// Config Settings Documentation. + /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: + /// EACH REGION, in OpenSim.ini, can have the following settings under the [AutoBackupModule] section. + /// IMPORTANT: You may optionally specify the key name as follows for a per-region key: [Region Name].[Key Name] + /// Example: My region is named Foo. + /// If I wanted to specify the "AutoBackupInterval" key just for this region, I would name my key "Foo.AutoBackupInterval", under the [AutoBackupModule] section of OpenSim.ini. + /// Instead of specifying them on a per-region basis, you can also omit the region name to specify the default setting for all regions. + /// Region-specific settings take precedence. + /// + /// AutoBackupModuleEnabled: True/False. Default: False. If True, use the auto backup module. This setting does not support per-region basis. + /// All other settings under [AutoBackupModule] are ignored if AutoBackupModuleEnabled is false, even per-region settings! + /// AutoBackup: True/False. Default: False. If True, activate auto backup functionality. + /// This is the only required option for enabling auto-backup; the other options have sane defaults. + /// If False for a particular region, the auto-backup module becomes a no-op for the region, and all other AutoBackup* settings are ignored. + /// If False globally (the default), only regions that specifically override this with "FooRegion.AutoBackup = true" will get AutoBackup functionality. + /// AutoBackupInterval: Double, non-negative value. Default: 720 (12 hours). + /// The number of minutes between each backup attempt. + /// If a negative or zero value is given, it is equivalent to setting AutoBackup = False. + /// AutoBackupBusyCheck: True/False. Default: True. + /// If True, we will only take an auto-backup if a set of conditions are met. + /// These conditions are heuristics to try and avoid taking a backup when the sim is busy. + /// AutoBackupScript: String. Default: not specified (disabled). + /// File path to an executable script or binary to run when an automatic backup is taken. + /// The file should really be (Windows) an .exe or .bat, or (Linux/Mac) a shell script or binary. + /// Trying to "run" directories, or things with weird file associations on Win32, might cause unexpected results! + /// argv[1] of the executed file/script will be the file name of the generated OAR. + /// If the process can't be spawned for some reason (file not found, no execute permission, etc), write a warning to the console. + /// AutoBackupNaming: string. Default: Time. + /// One of three strings (case insensitive): + /// "Time": Current timestamp is appended to file name. An existing file will never be overwritten. + /// "Sequential": A number is appended to the file name. So if RegionName_x.oar exists, we'll save to RegionName_{x+1}.oar next. An existing file will never be overwritten. + /// "Overwrite": Always save to file named "${AutoBackupDir}/RegionName.oar", even if we have to overwrite an existing file. + /// AutoBackupDir: String. Default: "." (the current directory). + /// A directory (absolute or relative) where backups should be saved. + /// AutoBackupDilationThreshold: float. Default: 0.5. Lower bound on time dilation required for BusyCheck heuristics to pass. + /// If the time dilation is below this value, don't take a backup right now. + /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. + /// If the number of agents is greater than this value, don't take a backup right now + /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. + /// Also helps if you don't want AutoBackup at all. + /// + public class AutoBackupModule : ISharedRegionModule + { + private static readonly ILog m_log = + LogManager.GetLogger(MethodBase.GetCurrentMethod().DeclaringType); + private readonly Dictionary m_pendingSaves = new Dictionary(1); + private readonly AutoBackupModuleState m_defaultState = new AutoBackupModuleState(); + private readonly Dictionary m_states = + new Dictionary(1); + private readonly Dictionary> m_timerMap = + new Dictionary>(1); + private readonly Dictionary m_timers = new Dictionary(1); + + private bool m_enabled; + + /// /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! - private bool m_closed; - - private IConfigSource m_configSource; - - public bool IsSharedModule + /// + private bool m_closed; + + private IConfigSource m_configSource; + + /// + /// Required by framework. + /// + public bool IsSharedModule + { + get { return true; } + } + + #region ISharedRegionModule Members + + /// + /// Identifies the module to the system. + /// + string IRegionModuleBase.Name + { + get { return "AutoBackupModule"; } + } + + /// + /// We don't implement an interface, this is a single-use module. + /// + Type IRegionModuleBase.ReplaceableInterface + { + get { return null; } + } + + /// + /// Called once in the lifetime of the module at startup. + /// + /// The input config source for OpenSim.ini. + void IRegionModuleBase.Initialise(IConfigSource source) + { + // Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module + this.m_configSource = source; + IConfig moduleConfig = source.Configs["AutoBackupModule"]; + if (moduleConfig == null) + { + this.m_enabled = false; + return; + } + else + { + this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); + if (this.m_enabled) + { + m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); + } + else + { + return; + } + } + + Timer defTimer = new Timer(43200000); + this.m_defaultState.Timer = defTimer; + this.m_timers.Add(43200000, defTimer); + defTimer.Elapsed += this.HandleElapsed; + defTimer.AutoReset = true; + defTimer.Start(); + + AutoBackupModuleState abms = this.ParseConfig(null, true); + m_log.Debug("[AUTO BACKUP]: Here is the default config:"); + m_log.Debug(abms.ToString()); + } + + /// + /// Called once at de-init (sim shutting down). + /// + void IRegionModuleBase.Close() + { + if (!this.m_enabled) + { + return; + } + + // We don't want any timers firing while the sim's coming down; strange things may happen. + this.StopAllTimers(); + } + + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// + /// + void IRegionModuleBase.AddRegion(Scene scene) + { + } + + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// + /// The scene (region) to stop performing AutoBackup on. + void IRegionModuleBase.RemoveRegion(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + if (this.m_states.ContainsKey(scene)) + { + AutoBackupModuleState abms = this.m_states[scene]; + + // Remove this scene out of the timer map list + Timer timer = abms.Timer; + List list = this.m_timerMap[timer]; + list.Remove(scene); + + // Shut down the timer if this was the last scene for the timer + if (list.Count == 0) + { + this.m_timerMap.Remove(timer); + this.m_timers.Remove(timer.Interval); + timer.Close(); + } + this.m_states.Remove(scene); + } + } + + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// + /// The scene to (possibly) perform AutoBackup on. + void IRegionModuleBase.RegionLoaded(Scene scene) + { + if (!this.m_enabled) + { + return; + } + + // This really ought not to happen, but just in case, let's pretend it didn't... + if (scene == null) + { + return; + } + + AutoBackupModuleState abms = this.ParseConfig(scene, false); + m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); + m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); + } + + /// + /// Currently a no-op. + /// + void ISharedRegionModule.PostInitialise() + { + } + + #endregion + + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. + private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) + { + string sRegionName; + string sRegionLabel; + string prepend; + AutoBackupModuleState state; + + if (parseDefault) + { + sRegionName = null; + sRegionLabel = "DEFAULT"; + prepend = ""; + state = this.m_defaultState; + } + else + { + sRegionName = scene.RegionInfo.RegionName; + sRegionLabel = sRegionName; + prepend = sRegionName + "."; + state = null; + } + + // Read the config settings and set variables. + IConfig config = this.m_configSource.Configs["AutoBackupModule"]; + if (config == null) + { + // defaultState would be disabled too if the section doesn't exist. + state = this.m_defaultState; + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); + return state; + } + + bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + if (state == null && tmpEnabled != this.m_defaultState.Enabled) + //Varies from default state + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Enabled = tmpEnabled; + } + + // If you don't want AutoBackup, we stop. + if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) + { + return state; + } + else + { + m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); + } + + // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. + double interval = + config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* + 60000.0; + if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) + { + state = new AutoBackupModuleState(); + } + + if (this.m_timers.ContainsKey(interval)) + { + if (state != null) + { + state.Timer = this.m_timers[interval]; + } + m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + + sRegionLabel); + } + else + { + // 0 or negative interval == do nothing. + if (interval <= 0.0 && state != null) + { + state.Enabled = false; + return state; + } + if (state == null) + { + state = new AutoBackupModuleState(); + } + Timer tim = new Timer(interval); + state.Timer = tim; + //Milliseconds -> minutes + this.m_timers.Add(interval, tim); + tim.Elapsed += this.HandleElapsed; + tim.AutoReset = true; + tim.Start(); + } + + // Add the current region to the list of regions tied to this timer. + if (scene != null) + { + if (state != null) + { + if (this.m_timerMap.ContainsKey(state.Timer)) + { + this.m_timerMap[state.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(state.Timer, scns); + } + } + else + { + if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) + { + this.m_timerMap[this.m_defaultState.Timer].Add(scene); + } + else + { + List scns = new List(1); + scns.Add(scene); + this.m_timerMap.Add(this.m_defaultState.Timer, scns); + } + } + } + + bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", + this.m_defaultState.BusyCheck); + if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BusyCheck = tmpBusyCheck; + } + + // Set file naming algorithm + string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", + this.m_defaultState.NamingType.ToString()); + NamingType tmpNamingType; + if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Time; + } + else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Sequential; + } + else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) + { + tmpNamingType = NamingType.Overwrite; + } + else + { + m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + + stmpNamingType); + tmpNamingType = NamingType.Time; + } + + if (state == null && tmpNamingType != this.m_defaultState.NamingType) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.NamingType = tmpNamingType; + } + + string tmpScript = config.GetString(prepend + "AutoBackupScript", + this.m_defaultState.Script); + if (state == null && tmpScript != this.m_defaultState.Script) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.Script = tmpScript; + } + + string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) + { + state = new AutoBackupModuleState(); + } + + if (state != null) + { + state.BackupDir = tmpBackupDir; + // Let's give the user some convenience and auto-mkdir + if (state.BackupDir != ".") + { + try + { + DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); + if (!dirinfo.Exists) + { + dirinfo.Create(); + } + } + catch (Exception e) + { + m_log.Warn( + "BAD NEWS. You won't be able to save backups to directory " + + state.BackupDir + + " because it doesn't exist or there's a permissions issue with it. Here's the exception.", + e); + } + } + } + + return state; + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// + /// + private void HandleElapsed(object sender, ElapsedEventArgs e) + { + // TODO: heuristic thresholds are per-region, so we should probably run heuristics once per region + // XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to + // check whether the region is too busy! Especially on sims with LOTS of regions. + // Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, + // but would allow us to be semantically correct while being easier on perf. + // Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... + // Alternative 3: Don't support per-region heuristics at all; just accept them as a global only parameter. + // Since this is pretty experimental, I haven't decided which alternative makes the most sense. + if (this.m_closed) + { + return; + } + bool heuristicsRun = false; + bool heuristicsPassed = false; + if (!this.m_timerMap.ContainsKey((Timer) sender)) + { + m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); + } + + List tmap = this.m_timerMap[(Timer) sender]; + if (tmap != null && tmap.Count > 0) + { + foreach (IScene scene in tmap) + { + AutoBackupModuleState state = this.m_states[scene]; + bool heuristics = state.BusyCheck; + + // Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. + if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) + { + this.DoRegionBackup(scene); + // Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! + } + else if (heuristicsRun) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + // Logical Deduction: heuristics are on but haven't been run + } + else + { + heuristicsPassed = this.RunHeuristics(scene); + heuristicsRun = true; + if (!heuristicsPassed) + { + m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + + scene.RegionInfo.RegionName + " right now."); + continue; + } + this.DoRegionBackup(scene); + } + } + } + } + + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// + /// + private void DoRegionBackup(IScene scene) + { + if (scene.RegionStatus != RegionStatus.Up) + { + // We won't backup a region that isn't operating normally. + m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + + " because its status is " + scene.RegionStatus); + return; + } + + AutoBackupModuleState state = this.m_states[scene]; + IRegionArchiverModule iram = scene.RequestModuleInterface(); + string savePath = BuildOarPath(scene.RegionInfo.RegionName, + state.BackupDir, + state.NamingType); + if (savePath == null) + { + m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); + return; + } + Guid guid = Guid.NewGuid(); + m_pendingSaves.Add(guid, scene); + state.LiveRequests.Add(guid, savePath); + ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); + iram.ArchiveRegion(savePath, guid, null); + } + + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// + /// + void EventManager_OnOarFileSaved(Guid guid, string message) { - get { return true; } - } - - #region ISharedRegionModule Members - - string IRegionModuleBase.Name - { - get { return "AutoBackupModule"; } - } - - Type IRegionModuleBase.ReplaceableInterface - { - get { return null; } - } - - void IRegionModuleBase.Initialise(IConfigSource source) - { - /// Determine if we have been enabled at all in OpenSim.ini -- this is part and parcel of being an optional module - this.m_configSource = source; - IConfig moduleConfig = source.Configs["AutoBackupModule"]; - if (moduleConfig == null) + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) { - this.m_enabled = false; - return; + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); } - else - { - this.m_enabled = moduleConfig.GetBoolean("AutoBackupModuleEnabled", false); - if (this.m_enabled) - { - m_log.Info("[AUTO BACKUP]: AutoBackupModule enabled"); - } - else - { - return; - } - } - - Timer defTimer = new Timer(43200000); - this.m_defaultState.Timer = defTimer; - this.m_timers.Add(43200000, defTimer); - defTimer.Elapsed += this.HandleElapsed; - defTimer.AutoReset = true; - defTimer.Start(); - - AutoBackupModuleState abms = this.ParseConfig(null, true); - m_log.Debug("[AUTO BACKUP]: Here is the default config:"); - m_log.Debug(abms.ToString()); - } - - void IRegionModuleBase.Close() - { - if (!this.m_enabled) - { - return; - } - - /// We don't want any timers firing while the sim's coming down; strange things may happen. - this.StopAllTimers(); - } - - void IRegionModuleBase.AddRegion(Scene scene) - { - /// NO-OP. Wait for the region to be loaded. - } - - void IRegionModuleBase.RemoveRegion(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - if (this.m_states.ContainsKey(scene)) - { - AutoBackupModuleState abms = this.m_states[scene]; - - /// Remove this scene out of the timer map list - Timer timer = abms.Timer; - List list = this.m_timerMap[timer]; - list.Remove(scene); - - /// Shut down the timer if this was the last scene for the timer - if (list.Count == 0) - { - this.m_timerMap.Remove(timer); - this.m_timers.Remove(timer.Interval); - timer.Close(); - } - this.m_states.Remove(scene); - } - } - - void IRegionModuleBase.RegionLoaded(Scene scene) - { - if (!this.m_enabled) - { - return; - } - - /// This really ought not to happen, but just in case, let's pretend it didn't... - if (scene == null) - { - return; - } - - AutoBackupModuleState abms = this.ParseConfig(scene, false); - m_log.Debug("[AUTO BACKUP]: Config for " + scene.RegionInfo.RegionName); - m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); - } - - void ISharedRegionModule.PostInitialise() - { - /// I don't care right now. - } - - #endregion - - private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) - { - string sRegionName; - string sRegionLabel; - string prepend; - AutoBackupModuleState state; - - if (parseDefault) - { - sRegionName = null; - sRegionLabel = "DEFAULT"; - prepend = ""; - state = this.m_defaultState; - } - else - { - sRegionName = scene.RegionInfo.RegionName; - sRegionLabel = sRegionName; - prepend = sRegionName + "."; - state = null; - } - - /// Read the config settings and set variables. - IConfig config = this.m_configSource.Configs["AutoBackupModule"]; - if (config == null) - { - /// defaultState would be disabled too if the section doesn't exist. - state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); - if (state == null && tmpEnabled != this.m_defaultState.Enabled) - //Varies from default state - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Enabled = tmpEnabled; - } - - /// If you don't want AutoBackup, we stop. - if ((state == null && !this.m_defaultState.Enabled) || (state != null && !state.Enabled)) - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); - return state; - } - else - { - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is AutoBackup ENABLED."); - } - - /// Borrow an existing timer if one exists for the same interval; otherwise, make a new one. - double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; - if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) - { - state = new AutoBackupModuleState(); - } - - if (this.m_timers.ContainsKey(interval)) - { - if (state != null) - { - state.Timer = this.m_timers[interval]; - } - m_log.Debug("[AUTO BACKUP]: Reusing timer for " + interval + " msec for region " + - sRegionLabel); - } - else - { - /// 0 or negative interval == do nothing. - if (interval <= 0.0 && state != null) - { - state.Enabled = false; - return state; - } - if (state == null) - { - state = new AutoBackupModuleState(); - } - Timer tim = new Timer(interval); - state.Timer = tim; - //Milliseconds -> minutes - this.m_timers.Add(interval, tim); - tim.Elapsed += this.HandleElapsed; - tim.AutoReset = true; - tim.Start(); - } - - /// Add the current region to the list of regions tied to this timer. - if (scene != null) - { - if (state != null) - { - if (this.m_timerMap.ContainsKey(state.Timer)) - { - this.m_timerMap[state.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(state.Timer, scns); - } - } - else - { - if (this.m_timerMap.ContainsKey(this.m_defaultState.Timer)) - { - this.m_timerMap[this.m_defaultState.Timer].Add(scene); - } - else - { - List scns = new List(1); - scns.Add(scene); - this.m_timerMap.Add(this.m_defaultState.Timer, scns); - } - } - } - - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); - if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BusyCheck = tmpBusyCheck; - } - - /// Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); - NamingType tmpNamingType; - if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Time; - } - else if (stmpNamingType.Equals("Sequential", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Sequential; - } - else if (stmpNamingType.Equals("Overwrite", StringComparison.CurrentCultureIgnoreCase)) - { - tmpNamingType = NamingType.Overwrite; - } - else - { - m_log.Warn("Unknown naming type specified for region " + sRegionLabel + ": " + - stmpNamingType); - tmpNamingType = NamingType.Time; - } - - if (state == null && tmpNamingType != this.m_defaultState.NamingType) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.NamingType = tmpNamingType; - } - - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); - if (state == null && tmpScript != this.m_defaultState.Script) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.Script = tmpScript; - } - - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); - if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) - { - state = new AutoBackupModuleState(); - } - - if (state != null) - { - state.BackupDir = tmpBackupDir; - /// Let's give the user *one* convenience and auto-mkdir - if (state.BackupDir != ".") - { - try - { - DirectoryInfo dirinfo = new DirectoryInfo(state.BackupDir); - if (!dirinfo.Exists) - { - dirinfo.Create(); - } - } - catch (Exception e) - { - m_log.Warn( - "BAD NEWS. You won't be able to save backups to directory " + - state.BackupDir + - " because it doesn't exist or there's a permissions issue with it. Here's the exception.", - e); - } - } - } - - return state; - } - - private void HandleElapsed(object sender, ElapsedEventArgs e) - { - /// TODO?: heuristic thresholds are per-region, so we should probably run heuristics once per region - /// XXX: Running heuristics once per region could add undue performance penalty for something that's supposed to - /// check whether the region is too busy! Especially on sims with LOTS of regions. - /// Alternative: make heuristics thresholds global to the module rather than per-region. Less flexible, - /// but would allow us to be semantically correct while being easier on perf. - /// Alternative 2: Run heuristics once per unique set of heuristics threshold parameters! Ay yi yi... - if (this.m_closed) - { - return; - } - bool heuristicsRun = false; - bool heuristicsPassed = false; - if (!this.m_timerMap.ContainsKey((Timer) sender)) - { - m_log.Debug("Code-up error: timerMap doesn't contain timer " + sender); - } - - List tmap = this.m_timerMap[(Timer) sender]; - if (tmap != null && tmap.Count > 0) - { - foreach (IScene scene in tmap) - { - AutoBackupModuleState state = this.m_states[scene]; - bool heuristics = state.BusyCheck; - - /// Fast path: heuristics are on; already ran em; and sim is fine; OR, no heuristics for the region. - if ((heuristics && heuristicsRun && heuristicsPassed) || !heuristics) - { - this.DoRegionBackup(scene); - /// Heuristics are on; ran but we're too busy -- keep going. Maybe another region will have heuristics off! - } - else if (heuristicsRun) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - /// Logical Deduction: heuristics are on but haven't been run - } - else - { - heuristicsPassed = this.RunHeuristics(scene); - heuristicsRun = true; - if (!heuristicsPassed) - { - m_log.Info("[AUTO BACKUP]: Heuristics: too busy to backup " + - scene.RegionInfo.RegionName + " right now."); - continue; - } - this.DoRegionBackup(scene); - } - } - } - } - - private void DoRegionBackup(IScene scene) - { - if (scene.RegionStatus != RegionStatus.Up) - { - /// We won't backup a region that isn't operating normally. - m_log.Warn("[AUTO BACKUP]: Not backing up region " + scene.RegionInfo.RegionName + - " because its status is " + scene.RegionStatus); - return; - } - - AutoBackupModuleState state = this.m_states[scene]; - IRegionArchiverModule iram = scene.RequestModuleInterface(); - string savePath = BuildOarPath(scene.RegionInfo.RegionName, - state.BackupDir, - state.NamingType); - /// m_log.Debug("[AUTO BACKUP]: savePath = " + savePath); - if (savePath == null) - { - m_log.Warn("[AUTO BACKUP]: savePath is null in HandleElapsed"); - return; - } - Guid guid = Guid.NewGuid(); - m_pendingSaves.Add(guid, scene); - state.LiveRequests.Add(guid, savePath); - ((Scene) scene).EventManager.OnOarFileSaved += new EventManager.OarFileSaved(EventManager_OnOarFileSaved); - iram.ArchiveRegion(savePath, guid, null); - } - - void EventManager_OnOarFileSaved(Guid guid, string message) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } - - /// This format may turn out to be too unwieldy to keep... + } + + /// This format may turn out to be too unwieldy to keep... /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? - /// Sequential numbers, right? Ugh. Almost makes TOO much sense. - private static string GetTimeString() - { - StringWriter sw = new StringWriter(); - sw.Write("_"); - DateTime now = DateTime.Now; - sw.Write(now.Year); - sw.Write("y_"); - sw.Write(now.Month); - sw.Write("M_"); - sw.Write(now.Day); - sw.Write("d_"); - sw.Write(now.Hour); - sw.Write("h_"); - sw.Write(now.Minute); - sw.Write("m_"); - sw.Write(now.Second); - sw.Write("s"); - sw.Flush(); - string output = sw.ToString(); - sw.Close(); - return output; - } - - /// - /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. - /// - private bool RunHeuristics(IScene region) - { - try - { - return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); - } - catch (Exception e) - { - m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); - return false; - } - } - - /// + /// Sequential numbers, right? We support those, too! + private static string GetTimeString() + { + StringWriter sw = new StringWriter(); + sw.Write("_"); + DateTime now = DateTime.Now; + sw.Write(now.Year); + sw.Write("y_"); + sw.Write(now.Month); + sw.Write("M_"); + sw.Write(now.Day); + sw.Write("d_"); + sw.Write(now.Hour); + sw.Write("h_"); + sw.Write(now.Minute); + sw.Write("m_"); + sw.Write(now.Second); + sw.Write("s"); + sw.Flush(); + string output = sw.ToString(); + sw.Close(); + return output; + } + + /// Return value of true ==> not too busy; false ==> too busy to backup an OAR right now, or error. + private bool RunHeuristics(IScene region) + { + try + { + return this.RunTimeDilationHeuristic(region) && this.RunAgentLimitHeuristic(region); + } + catch (Exception e) + { + m_log.Warn("[AUTO BACKUP]: Exception in RunHeuristics", e); + return false; + } + } + + /// /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunTimeDilationHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - return region.TimeDilation >= - this.m_configSource.Configs["AutoBackupModule"].GetFloat( - regionName + ".AutoBackupDilationThreshold", 0.5f); - } - - /// + /// + /// + /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. + private bool RunTimeDilationHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + return region.TimeDilation >= + this.m_configSource.Configs["AutoBackupModule"].GetFloat( + regionName + ".AutoBackupDilationThreshold", 0.5f); + } + + /// /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// Return value of "true" ==> not too busy. Return value of "false" ==> too busy! - /// - private bool RunAgentLimitHeuristic(IScene region) - { - string regionName = region.RegionInfo.RegionName; - try - { - Scene scene = (Scene) region; - /// TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... - return scene.GetRootAgentCount() <= - this.m_configSource.Configs["AutoBackupModule"].GetInt( - regionName + ".AutoBackupAgentThreshold", 10); - } - catch (InvalidCastException ice) - { - m_log.Debug( - "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", - ice); - return true; - /// Non-obstructionist safest answer... - } - } - - private static void ExecuteScript(string scriptName, string savePath) - { - //Fast path out - if (scriptName == null || scriptName.Length <= 0) - { - return; - } - - try - { - FileInfo fi = new FileInfo(scriptName); - if (fi.Exists) - { - ProcessStartInfo psi = new ProcessStartInfo(scriptName); - psi.Arguments = savePath; - psi.CreateNoWindow = true; - Process proc = Process.Start(psi); - proc.ErrorDataReceived += HandleProcErrorDataReceived; - } - } - catch (Exception e) - { - m_log.Warn( - "Exception encountered when trying to run script for oar backup " + savePath, e); - } - } - - private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) - { - m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + - " is yacking on stderr: " + e.Data); - } - - private void StopAllTimers() - { - foreach (Timer t in this.m_timerMap.Keys) - { - t.Close(); - } - this.m_closed = true; - } - - private static string GetNextFile(string dirName, string regionName) - { - FileInfo uniqueFile = null; - long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); - biggestExistingFile++; - //We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. - uniqueFile = - new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + - biggestExistingFile + ".oar"); - return uniqueFile.FullName; - } - - private static string BuildOarPath(string regionName, string baseDir, NamingType naming) - { - FileInfo path = null; - switch (naming) - { - case NamingType.Overwrite: - path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); - return path.FullName; - case NamingType.Time: - path = - new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + - GetTimeString() + ".oar"); - return path.FullName; - case NamingType.Sequential: - /// All codepaths in GetNextFile should return a file name ending in .oar - path = new FileInfo(GetNextFile(baseDir, regionName)); - return path.FullName; - default: - m_log.Warn("VERY BAD: Unhandled case element " + naming); - break; - } - - return null; - } - - private static long GetNextOarFileNumber(string dirName, string regionName) - { - long retval = 1; - - DirectoryInfo di = new DirectoryInfo(dirName); - FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); - Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); - - if (fi.LongLength > 0) - { - long subtract = 1L; - bool worked = false; - Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); - - while (!worked && subtract <= fi.LongLength) - { - /// Pick the file with the last natural ordering - string biggestFileName = fi[fi.LongLength - subtract].Name; - MatchCollection matches = reg.Matches(biggestFileName); - long l = 1; - if (matches.Count > 0 && matches[0].Groups.Count > 0) - { - try - { - long.TryParse(matches[0].Groups[1].Value, out l); - retval = l; - worked = true; - } - catch (FormatException fe) - { - m_log.Warn( - "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", - fe); - subtract++; - } - } - else - { - subtract++; - } - } - } - return retval; - } - } -} - - + /// + /// + /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. + private bool RunAgentLimitHeuristic(IScene region) + { + string regionName = region.RegionInfo.RegionName; + try + { + Scene scene = (Scene) region; + // TODO: Why isn't GetRootAgentCount() a method in the IScene interface? Seems generally useful... + return scene.GetRootAgentCount() <= + this.m_configSource.Configs["AutoBackupModule"].GetInt( + regionName + ".AutoBackupAgentThreshold", 10); + } + catch (InvalidCastException ice) + { + m_log.Debug( + "[AUTO BACKUP]: I NEED MAINTENANCE: IScene is not a Scene; can't get root agent count!", + ice); + return true; + // Non-obstructionist safest answer... + } + } + + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// + /// + private static void ExecuteScript(string scriptName, string savePath) + { + // Do nothing if there's no script. + if (scriptName == null || scriptName.Length <= 0) + { + return; + } + + try + { + FileInfo fi = new FileInfo(scriptName); + if (fi.Exists) + { + ProcessStartInfo psi = new ProcessStartInfo(scriptName); + psi.Arguments = savePath; + psi.CreateNoWindow = true; + Process proc = Process.Start(psi); + proc.ErrorDataReceived += HandleProcErrorDataReceived; + } + } + catch (Exception e) + { + m_log.Warn( + "Exception encountered when trying to run script for oar backup " + savePath, e); + } + } + + /// + /// Called if a running script process writes to stderr. + /// + /// + /// + private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) + { + m_log.Warn("ExecuteScript hook " + ((Process) sender).ProcessName + + " is yacking on stderr: " + e.Data); + } + + /// + /// Quickly stop all timers from firing. + /// + private void StopAllTimers() + { + foreach (Timer t in this.m_timerMap.Keys) + { + t.Close(); + } + this.m_closed = true; + } + + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// + /// + private static string GetNextFile(string dirName, string regionName) + { + FileInfo uniqueFile = null; + long biggestExistingFile = GetNextOarFileNumber(dirName, regionName); + biggestExistingFile++; + // We don't want to overwrite the biggest existing file; we want to write to the NEXT biggest. + uniqueFile = + new FileInfo(dirName + Path.DirectorySeparatorChar + regionName + "_" + + biggestExistingFile + ".oar"); + return uniqueFile.FullName; + } + + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. + /// + private static string BuildOarPath(string regionName, string baseDir, NamingType naming) + { + FileInfo path = null; + switch (naming) + { + case NamingType.Overwrite: + path = new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + ".oar"); + return path.FullName; + case NamingType.Time: + path = + new FileInfo(baseDir + Path.DirectorySeparatorChar + regionName + + GetTimeString() + ".oar"); + return path.FullName; + case NamingType.Sequential: + // All codepaths in GetNextFile should return a file name ending in .oar + path = new FileInfo(GetNextFile(baseDir, regionName)); + return path.FullName; + default: + m_log.Warn("VERY BAD: Unhandled case element " + naming); + break; + } + + return null; + } + + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// + /// + private static long GetNextOarFileNumber(string dirName, string regionName) + { + long retval = 1; + + DirectoryInfo di = new DirectoryInfo(dirName); + FileInfo[] fi = di.GetFiles(regionName, SearchOption.TopDirectoryOnly); + Array.Sort(fi, (f1, f2) => StringComparer.CurrentCultureIgnoreCase.Compare(f1.Name, f2.Name)); + + if (fi.LongLength > 0) + { + long subtract = 1L; + bool worked = false; + Regex reg = new Regex(regionName + "_([0-9])+" + ".oar"); + + while (!worked && subtract <= fi.LongLength) + { + // Pick the file with the last natural ordering + string biggestFileName = fi[fi.LongLength - subtract].Name; + MatchCollection matches = reg.Matches(biggestFileName); + long l = 1; + if (matches.Count > 0 && matches[0].Groups.Count > 0) + { + try + { + long.TryParse(matches[0].Groups[1].Value, out l); + retval = l; + worked = true; + } + catch (FormatException fe) + { + m_log.Warn( + "[AUTO BACKUP]: Error: Can't parse long value from file name to determine next OAR backup file number!", + fe); + subtract++; + } + } + else + { + subtract++; + } + } + } + return retval; + } + } +} + + diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs index 7fecfa435a..2db718c413 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModuleState.cs @@ -1,30 +1,29 @@ -#pragma warning disable 1587 -/// -/// 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 OpenSimulator 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. -/// +/* + * 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 OpenSimulator 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.Generic; @@ -32,7 +31,11 @@ using System.Collections.Generic; namespace OpenSim.Region.OptionalModules.World.AutoBackup { - /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// AutoBackupModuleState: Auto-Backup state for one region (scene). + /// If you use this class in any way outside of AutoBackupModule, you should treat the class as opaque. + /// Since it is not part of the framework, you really should not rely upon it outside of the AutoBackupModule implementation. + /// + /// public class AutoBackupModuleState { private Dictionary m_liveRequests = null; From 0995fedcaca9a921488929ee40f68c71fbba7a70 Mon Sep 17 00:00:00 2001 From: Sean McNamara Date: Mon, 2 May 2011 04:32:31 -0400 Subject: [PATCH 26/48] AutoBackupModule: Implement per-region settings in Regions.ini. --- .../World/AutoBackup/AutoBackupModule.cs | 291 +++++++++++------- 1 file changed, 186 insertions(+), 105 deletions(-) diff --git a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs index 4a9615d1bc..ce9a4481ea 100644 --- a/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs +++ b/OpenSim/Region/OptionalModules/World/AutoBackup/AutoBackupModule.cs @@ -55,7 +55,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// /// AutoBackupModule: save OAR region backups to disk periodically - /// + /// /// /// Config Settings Documentation. /// At the TOP LEVEL, e.g. in OpenSim.ini, we have the following options: @@ -96,7 +96,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup /// AutoBackupAgentThreshold: int. Default: 10. Upper bound on # of agents in region required for BusyCheck heuristics to pass. /// If the number of agents is greater than this value, don't take a backup right now /// Save memory by setting low initial capacities. Minimizes impact in common cases of all regions using same interval, and instances hosting 1 ~ 4 regions. - /// Also helps if you don't want AutoBackup at all. + /// Also helps if you don't want AutoBackup at all. /// public class AutoBackupModule : ISharedRegionModule { @@ -110,17 +110,18 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup new Dictionary>(1); private readonly Dictionary m_timers = new Dictionary(1); + private delegate T DefaultGetter(string settingName, T defaultValue); private bool m_enabled; - /// - /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! + /// + /// Whether the shared module should be enabled at all. NOT the same as m_Enabled in AutoBackupModuleState! /// private bool m_closed; private IConfigSource m_configSource; - /// - /// Required by framework. + /// + /// Required by framework. /// public bool IsSharedModule { @@ -129,25 +130,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #region ISharedRegionModule Members - /// - /// Identifies the module to the system. + /// + /// Identifies the module to the system. /// string IRegionModuleBase.Name { get { return "AutoBackupModule"; } } - /// - /// We don't implement an interface, this is a single-use module. + /// + /// We don't implement an interface, this is a single-use module. /// Type IRegionModuleBase.ReplaceableInterface { get { return null; } } - /// - /// Called once in the lifetime of the module at startup. - /// + /// + /// Called once in the lifetime of the module at startup. + /// /// The input config source for OpenSim.ini. void IRegionModuleBase.Initialise(IConfigSource source) { @@ -184,8 +185,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug(abms.ToString()); } - /// - /// Called once at de-init (sim shutting down). + /// + /// Called once at de-init (sim shutting down). /// void IRegionModuleBase.Close() { @@ -198,17 +199,17 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.StopAllTimers(); } - /// - /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. - /// + /// + /// Currently a no-op for AutoBackup because we have to wait for region to be fully loaded. + /// /// void IRegionModuleBase.AddRegion(Scene scene) { } - /// - /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. - /// + /// + /// Here we just clean up some resources and stop the OAR backup (if any) for the given scene. + /// /// The scene (region) to stop performing AutoBackup on. void IRegionModuleBase.RemoveRegion(Scene scene) { @@ -237,10 +238,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Most interesting/complex code paths in AutoBackup begin here. - /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. - /// + /// + /// Most interesting/complex code paths in AutoBackup begin here. + /// We read lots of Nini config, maybe set a timer, add members to state tracking Dictionaries, etc. + /// /// The scene to (possibly) perform AutoBackup on. void IRegionModuleBase.RegionLoaded(Scene scene) { @@ -260,8 +261,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup m_log.Debug((abms == null ? "DEFAULT" : abms.ToString())); } - /// - /// Currently a no-op. + /// + /// Currently a no-op. /// void ISharedRegionModule.PostInitialise() { @@ -269,12 +270,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup #endregion - /// - /// Set up internal state for a given scene. Fairly complex code. - /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. - /// - /// The scene to look at. - /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). + /// + /// Set up internal state for a given scene. Fairly complex code. + /// When this method returns, we've started auto-backup timers, put members in Dictionaries, and created a State object for this scene. + /// + /// The scene to look at. + /// Whether this call is intended to figure out what we consider the "default" config (applied to all regions unless overridden by per-region settings). /// An AutoBackupModuleState contains most information you should need to know relevant to auto-backup, as applicable to a single region. private AutoBackupModuleState ParseConfig(IScene scene, bool parseDefault) { @@ -299,16 +300,16 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Read the config settings and set variables. + IConfig regionConfig = (scene != null ? scene.Config.Configs[sRegionName] : null); IConfig config = this.m_configSource.Configs["AutoBackupModule"]; if (config == null) { // defaultState would be disabled too if the section doesn't exist. state = this.m_defaultState; - m_log.Info("[AUTO BACKUP]: Region " + sRegionLabel + " is NOT AutoBackup enabled."); return state; } - bool tmpEnabled = config.GetBoolean(prepend + "AutoBackup", this.m_defaultState.Enabled); + bool tmpEnabled = ResolveBoolean("AutoBackup", this.m_defaultState.Enabled, config, regionConfig); if (state == null && tmpEnabled != this.m_defaultState.Enabled) //Varies from default state { @@ -332,8 +333,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup // Borrow an existing timer if one exists for the same interval; otherwise, make a new one. double interval = - config.GetDouble(prepend + "AutoBackupInterval", this.m_defaultState.IntervalMinutes)* - 60000.0; + this.ResolveDouble("AutoBackupInterval", this.m_defaultState.IntervalMinutes, + config, regionConfig) * 60000.0; if (state == null && interval != this.m_defaultState.IntervalMinutes*60000.0) { state = new AutoBackupModuleState(); @@ -400,8 +401,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - bool tmpBusyCheck = config.GetBoolean(prepend + "AutoBackupBusyCheck", - this.m_defaultState.BusyCheck); + bool tmpBusyCheck = ResolveBoolean("AutoBackupBusyCheck", + this.m_defaultState.BusyCheck, config, regionConfig); if (state == null && tmpBusyCheck != this.m_defaultState.BusyCheck) { state = new AutoBackupModuleState(); @@ -413,8 +414,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } // Set file naming algorithm - string stmpNamingType = config.GetString(prepend + "AutoBackupNaming", - this.m_defaultState.NamingType.ToString()); + string stmpNamingType = ResolveString("AutoBackupNaming", + this.m_defaultState.NamingType.ToString(), config, regionConfig); NamingType tmpNamingType; if (stmpNamingType.Equals("Time", StringComparison.CurrentCultureIgnoreCase)) { @@ -445,8 +446,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.NamingType = tmpNamingType; } - string tmpScript = config.GetString(prepend + "AutoBackupScript", - this.m_defaultState.Script); + string tmpScript = ResolveString("AutoBackupScript", + this.m_defaultState.Script, config, regionConfig); if (state == null && tmpScript != this.m_defaultState.Script) { state = new AutoBackupModuleState(); @@ -457,7 +458,7 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup state.Script = tmpScript; } - string tmpBackupDir = config.GetString(prepend + "AutoBackupDir", "."); + string tmpBackupDir = ResolveString("AutoBackupDir", ".", config, regionConfig); if (state == null && tmpBackupDir != this.m_defaultState.BackupDir) { state = new AutoBackupModuleState(); @@ -491,10 +492,90 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return state; } - /// - /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. - /// - /// + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private bool ResolveBoolean(string settingName, bool defaultValue, IConfig global, IConfig local) + { + if(local != null) + { + return local.GetBoolean(settingName, global.GetBoolean(settingName, defaultValue)); + } + else + { + return global.GetBoolean(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private double ResolveDouble(string settingName, double defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetDouble(settingName, global.GetDouble(settingName, defaultValue)); + } + else + { + return global.GetDouble(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private int ResolveInt(string settingName, int defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetInt(settingName, global.GetInt(settingName, defaultValue)); + } + else + { + return global.GetInt(settingName, defaultValue); + } + } + + /// + /// Helper function for ParseConfig. + /// + /// + /// + /// + /// + /// + private string ResolveString(string settingName, string defaultValue, IConfig global, IConfig local) + { + if (local != null) + { + return local.GetString(settingName, global.GetString(settingName, defaultValue)); + } + else + { + return global.GetString(settingName, defaultValue); + } + } + + /// + /// Called when any auto-backup timer expires. This starts the code path for actually performing a backup. + /// + /// /// private void HandleElapsed(object sender, ElapsedEventArgs e) { @@ -554,9 +635,9 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). - /// + /// + /// Save an OAR, register for the callback for when it's done, then call the AutoBackupScript (if applicable). + /// /// private void DoRegionBackup(IScene scene) { @@ -585,25 +666,25 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup iram.ArchiveRegion(savePath, guid, null); } - /// - /// Called by the Event Manager when the OnOarFileSaved event is fired. - /// - /// + /// + /// Called by the Event Manager when the OnOarFileSaved event is fired. + /// + /// /// void EventManager_OnOarFileSaved(Guid guid, string message) - { - // Ignore if the OAR save is being done by some other part of the system - if (m_pendingSaves.ContainsKey(guid)) - { - AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; - ExecuteScript(abms.Script, abms.LiveRequests[guid]); - m_pendingSaves.Remove(guid); - abms.LiveRequests.Remove(guid); - } + { + // Ignore if the OAR save is being done by some other part of the system + if (m_pendingSaves.ContainsKey(guid)) + { + AutoBackupModuleState abms = m_states[(m_pendingSaves[guid])]; + ExecuteScript(abms.Script, abms.LiveRequests[guid]); + m_pendingSaves.Remove(guid); + abms.LiveRequests.Remove(guid); + } } /// This format may turn out to be too unwieldy to keep... - /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? + /// Besides, that's what ctimes are for. But then how do I name each file uniquely without using a GUID? /// Sequential numbers, right? We support those, too! private static string GetTimeString() { @@ -642,12 +723,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), - /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). - /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". - /// - /// + /// + /// If the time dilation right at this instant is less than the threshold specified in AutoBackupDilationThreshold (default 0.5), + /// then we return false and trip the busy heuristic's "too busy" path (i.e. don't save an OAR). + /// AutoBackupDilationThreshold is a _LOWER BOUND_. Lower Time Dilation is bad, so if you go lower than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got worse time dilation than the threshold. private bool RunTimeDilationHeuristic(IScene region) { @@ -657,12 +738,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup regionName + ".AutoBackupDilationThreshold", 0.5f); } - /// - /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), - /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). - /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". - /// - /// + /// + /// If the root agent count right at this instant is less than the threshold specified in AutoBackupAgentThreshold (default 10), + /// then we return false and trip the busy heuristic's "too busy" path (i.e., don't save an OAR). + /// AutoBackupAgentThreshold is an _UPPER BOUND_. Higher Agent Count is bad, so if you go higher than our threshold, it's "too busy". + /// + /// /// Returns true if we're not too busy; false means we've got more agents on the sim than the threshold. private bool RunAgentLimitHeuristic(IScene region) { @@ -685,12 +766,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Run the script or executable specified by the "AutoBackupScript" config setting. - /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. - /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. - /// - /// + /// + /// Run the script or executable specified by the "AutoBackupScript" config setting. + /// Of course this is a security risk if you let anyone modify OpenSim.ini and they want to run some nasty bash script. + /// But there are plenty of other nasty things that can be done with an untrusted OpenSim.ini, such as running high threat level scripting functions. + /// + /// /// private static void ExecuteScript(string scriptName, string savePath) { @@ -719,10 +800,10 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup } } - /// - /// Called if a running script process writes to stderr. - /// - /// + /// + /// Called if a running script process writes to stderr. + /// + /// /// private static void HandleProcErrorDataReceived(object sender, DataReceivedEventArgs e) { @@ -730,8 +811,8 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup " is yacking on stderr: " + e.Data); } - /// - /// Quickly stop all timers from firing. + /// + /// Quickly stop all timers from firing. /// private void StopAllTimers() { @@ -742,11 +823,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup this.m_closed = true; } - /// - /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. - /// - /// - /// + /// + /// Determine the next unique filename by number, for "Sequential" AutoBackupNamingType. + /// + /// + /// /// private static string GetNextFile(string dirName, string regionName) { @@ -760,12 +841,12 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return uniqueFile.FullName; } - /// - /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. - /// - /// Name of the region to save. - /// Absolute or relative path to the directory where the file should reside. - /// The naming scheme for the file name. + /// + /// Top-level method for creating an absolute path to an OAR backup file based on what naming scheme the user wants. + /// + /// Name of the region to save. + /// Absolute or relative path to the directory where the file should reside. + /// The naming scheme for the file name. /// private static string BuildOarPath(string regionName, string baseDir, NamingType naming) { @@ -792,11 +873,11 @@ namespace OpenSim.Region.OptionalModules.World.AutoBackup return null; } - /// - /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). - /// - /// - /// + /// + /// Helper function for Sequential file naming type (see BuildOarPath and GetNextFile). + /// + /// + /// /// private static long GetNextOarFileNumber(string dirName, string regionName) { From bc49a0bc5df9e600af1e291fad3719949a592685 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:09:08 +0100 Subject: [PATCH 27/48] Add "dump asset" command to the asset service for debugging purposes. This command dumps the asset with the given id to a file with the same name. --- OpenSim/Services/AssetService/AssetService.cs | 43 +++++++++++++++++++ 1 file changed, 43 insertions(+) diff --git a/OpenSim/Services/AssetService/AssetService.cs b/OpenSim/Services/AssetService/AssetService.cs index e1f90b6e18..25a91f2762 100644 --- a/OpenSim/Services/AssetService/AssetService.cs +++ b/OpenSim/Services/AssetService/AssetService.cs @@ -26,9 +26,12 @@ */ using System; +using System.Collections.Generic; +using System.IO; using System.Reflection; using Nini.Config; using log4net; +using NDesk.Options; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Data; @@ -60,6 +63,13 @@ namespace OpenSim.Services.AssetService "delete asset", "delete asset ", "Delete asset from database", HandleDeleteAsset); + + MainConsole.Instance.Commands.AddCommand("kfs", false, + "dump asset", + "dump asset ", + "Dump asset to a file", + "The filename is the same as the ID given.", + HandleDumpAsset); if (m_AssetLoader != null) { @@ -189,6 +199,39 @@ namespace OpenSim.Services.AssetService return false; } + + void HandleDumpAsset(string module, string[] args) + { + if (args.Length < 3) + { + MainConsole.Instance.Output("Usage is dump asset "); + return; + } + + string rawAssetId = args[2]; + UUID assetId; + + if (!UUID.TryParse(rawAssetId, out assetId)) + { + MainConsole.Instance.OutputFormat("ERROR: {0} is not a valid ID format", rawAssetId); + return; + } + + AssetBase asset = m_Database.GetAsset(assetId); + if (asset == null) + { + MainConsole.Instance.OutputFormat("ERROR: No asset found with ID {0}", assetId); + return; + } + + using (FileStream fs = new FileStream(rawAssetId, FileMode.CreateNew)) + { + using (BinaryWriter bw = new BinaryWriter(fs)) + { + bw.Write(asset.Data); + } + } + } void HandleShowDigest(string module, string[] args) { From 46baadbb65ac82260f46e580addda2f45efee120 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:22:19 +0100 Subject: [PATCH 28/48] remove the NDesk.Options using since didn't end up needing it --- OpenSim/Services/AssetService/AssetService.cs | 1 - 1 file changed, 1 deletion(-) diff --git a/OpenSim/Services/AssetService/AssetService.cs b/OpenSim/Services/AssetService/AssetService.cs index 25a91f2762..80d58e18e4 100644 --- a/OpenSim/Services/AssetService/AssetService.cs +++ b/OpenSim/Services/AssetService/AssetService.cs @@ -31,7 +31,6 @@ using System.IO; using System.Reflection; using Nini.Config; using log4net; -using NDesk.Options; using OpenSim.Framework; using OpenSim.Framework.Console; using OpenSim.Data; From 8755a48cde6ee77f421bef07e8b95cf8b68a76ed Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 00:34:04 +0100 Subject: [PATCH 29/48] fix command display for debugging 'emergency-monitoring' --- .../OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs index db17d8faf7..bdebbfbb8e 100644 --- a/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs +++ b/OpenSim/Region/OptionalModules/Agent/UDP/Linden/LindenUDPInfoModule.cs @@ -107,7 +107,7 @@ namespace OpenSim.Region.CoreModules.UDP.Linden scene.AddCommand( this, "emergency-monitoring", - "Go on/off emergency monitoring mode", + "emergency-monitoring", "Go on/off emergency monitoring mode", "Go on/off emergency monitoring mode", EmergencyMonitoring); From 8ca793875318efc8db3339b25bf7fa5ddeeac218 Mon Sep 17 00:00:00 2001 From: BlueWall Date: Sun, 1 May 2011 14:44:09 -0400 Subject: [PATCH 30/48] Adding ssl support Adding ssl support for "Out of Band" applications such as the remote admin module or Robust services --- OpenSim/Framework/MainServer.cs | 5 ++ OpenSim/Framework/NetworkServersInfo.cs | 15 ++++ .../Servers/HttpServer/BaseHttpServer.cs | 14 ++++ .../ClientStack/RegionApplicationBase.cs | 16 ++++ OpenSim/Server/Base/HttpServerBase.cs | 77 ++++++++++++++++++- bin/OpenSimDefaults.ini | 14 ++++ bin/Robust.ini.example | 21 +++++ 7 files changed, 160 insertions(+), 2 deletions(-) diff --git a/OpenSim/Framework/MainServer.cs b/OpenSim/Framework/MainServer.cs index 0515b166e2..a3e0a26bb7 100644 --- a/OpenSim/Framework/MainServer.cs +++ b/OpenSim/Framework/MainServer.cs @@ -52,6 +52,11 @@ namespace OpenSim.Framework return GetHttpServer(port,null); } + public static void AddHttpServer(BaseHttpServer server) + { + m_Servers.Add(server.Port, server); + } + public static IHttpServer GetHttpServer(uint port, IPAddress ipaddr) { if (port == 0) diff --git a/OpenSim/Framework/NetworkServersInfo.cs b/OpenSim/Framework/NetworkServersInfo.cs index b25f8b9f60..5bb4111c90 100644 --- a/OpenSim/Framework/NetworkServersInfo.cs +++ b/OpenSim/Framework/NetworkServersInfo.cs @@ -49,6 +49,12 @@ namespace OpenSim.Framework public string HttpSSLCN = ""; public uint httpSSLPort = 9001; + // "Out of band" managemnt https + public bool ssl_listener = false; + public uint https_port = 0; + public string cert_path = String.Empty; + public string cert_pass = String.Empty; + public string MessagingURL = String.Empty; public NetworkServersInfo() @@ -86,6 +92,15 @@ namespace OpenSim.Framework secureInventoryServer = config.Configs["Network"].GetBoolean("secure_inventory_server", true); MessagingURL = config.Configs["Network"].GetString("messaging_server_url", string.Empty); + + // "Out of band management https" + ssl_listener = config.Configs["Network"].GetBoolean("https_listener",false); + if( ssl_listener) + { + cert_path = config.Configs["Network"].GetString("cert_path",String.Empty); + cert_pass = config.Configs["Network"].GetString("cert_pass",String.Empty); + https_port = (uint)config.Configs["Network"].GetInt("https_port", 0); + } } } } diff --git a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs index ba89e2117b..598e5d17cd 100644 --- a/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs +++ b/OpenSim/Framework/Servers/HttpServer/BaseHttpServer.cs @@ -32,6 +32,7 @@ using System.Collections.Specialized; using System.IO; using System.Net; using System.Net.Sockets; +using System.Security.Cryptography.X509Certificates; using System.Reflection; using System.Globalization; using System.Text; @@ -72,6 +73,7 @@ namespace OpenSim.Framework.Servers.HttpServer protected uint m_port; protected uint m_sslport; protected bool m_ssl; + private X509Certificate2 m_cert; protected bool m_firstcaps = true; protected string m_SSLCommonName = ""; @@ -123,6 +125,14 @@ namespace OpenSim.Framework.Servers.HttpServer } } + public BaseHttpServer(uint port, bool ssl, string CPath, string CPass) : this (port, ssl) + { + if (m_ssl) + { + m_cert = new X509Certificate2(CPath, CPass); + } + } + /// /// Add a stream handler to the http server. If the handler already exists, then nothing happens. /// @@ -1683,6 +1693,7 @@ namespace OpenSim.Framework.Servers.HttpServer try { //m_httpListener = new HttpListener(); + NotSocketErrors = 0; if (!m_ssl) { @@ -1702,6 +1713,9 @@ namespace OpenSim.Framework.Servers.HttpServer { //m_httpListener.Prefixes.Add("https://+:" + (m_sslport) + "/"); //m_httpListener.Prefixes.Add("http://+:" + m_port + "/"); + m_httpListener2 = CoolHTTPListener.Create(IPAddress.Any, (int)m_port, m_cert); + m_httpListener2.ExceptionThrown += httpServerException; + m_httpListener2.LogWriter = httpserverlog; } m_httpListener2.RequestReceived += OnRequest; diff --git a/OpenSim/Region/ClientStack/RegionApplicationBase.cs b/OpenSim/Region/ClientStack/RegionApplicationBase.cs index ea1317a19c..6e3a58e63a 100644 --- a/OpenSim/Region/ClientStack/RegionApplicationBase.cs +++ b/OpenSim/Region/ClientStack/RegionApplicationBase.cs @@ -96,6 +96,22 @@ namespace OpenSim.Region.ClientStack MainServer.Instance = m_httpServer; + // "OOB" Server + if (m_networkServersInfo.ssl_listener) + { + BaseHttpServer server = null; + server = new BaseHttpServer( + m_networkServersInfo.https_port, m_networkServersInfo.ssl_listener, m_networkServersInfo.cert_path, + m_networkServersInfo.cert_pass); + // Add the server to m_Servers + if(server != null) + { + m_log.InfoFormat("[REGION SERVER]: Starting HTTPS server on port {0}", server.Port); + MainServer.AddHttpServer(server); + server.Start(); + } + } + base.StartupSpecific(); } diff --git a/OpenSim/Server/Base/HttpServerBase.cs b/OpenSim/Server/Base/HttpServerBase.cs index 9e4593e19e..bb5ce96757 100644 --- a/OpenSim/Server/Base/HttpServerBase.cs +++ b/OpenSim/Server/Base/HttpServerBase.cs @@ -97,16 +97,76 @@ namespace OpenSim.Server.Base if (port == 0) { - System.Console.WriteLine("Port number not specified or 0, server can't start"); + Thread.CurrentThread.Abort(); } + // + bool ssl_main = networkConfig.GetBoolean("https_main",false); + bool ssl_listener = networkConfig.GetBoolean("https_listener",false); m_consolePort = (uint)networkConfig.GetInt("ConsolePort", 0); m_Port = port; + // + // This is where to make the servers: + // + // + // Make the base server according to the port, etc. + // ADD: Possibility to make main server ssl + // Then, check for https settings and ADD a server to + // m_Servers + // + if ( !ssl_main ) + { + m_HttpServer = new BaseHttpServer(port); - m_HttpServer = new BaseHttpServer(port); + } + else + { + string cert_path = networkConfig.GetString("cert_path",String.Empty); + if ( cert_path == String.Empty ) + { + System.Console.WriteLine("Path to X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + string cert_pass = networkConfig.GetString("cert_pass",String.Empty); + if ( cert_pass == String.Empty ) + { + System.Console.WriteLine("Password for X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + m_HttpServer = new BaseHttpServer(port, ssl_main, cert_path, cert_pass); + } MainServer.Instance = m_HttpServer; + + // If https_listener = true, then add an ssl listener on the https_port... + if ( ssl_listener == true ) { + + uint https_port = (uint)networkConfig.GetInt("https_port", 0); + + string cert_path = networkConfig.GetString("cert_path",String.Empty); + if ( cert_path == String.Empty ) + { + System.Console.WriteLine("Path to X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + string cert_pass = networkConfig.GetString("cert_pass",String.Empty); + if ( cert_pass == String.Empty ) + { + System.Console.WriteLine("Password for X509 certificate is missing, server can't start."); + Thread.CurrentThread.Abort(); + } + // Add our https_server + BaseHttpServer server = null; + server = new BaseHttpServer(https_port, ssl_listener, cert_path, cert_pass); + if (server != null) + { + m_Log.InfoFormat("[SERVER]: Starting HTTPS server on port {0}", https_port); + m_Servers.Add(https_port,server); + } + else + System.Console.WriteLine(String.Format("Failed to start HTTPS server on port {0}",https_port)); + } } protected override void Initialise() @@ -114,6 +174,19 @@ namespace OpenSim.Server.Base m_Log.InfoFormat("[SERVER]: Starting HTTP server on port {0}", m_HttpServer.Port); m_HttpServer.Start(); + if (m_Servers.Count > 0) + { + foreach (BaseHttpServer s in m_Servers.Values) + { + if (!s.UseSSL) + m_Log.InfoFormat("[SERVER]: Starting HTTP server on port {0}", s.Port); + else + m_Log.InfoFormat("[SERVER]: Starting HTTPS server on port {0}", s.Port); + + s.Start(); + } + } + if (MainConsole.Instance is RemoteConsole) { if (m_consolePort == 0) diff --git a/bin/OpenSimDefaults.ini b/bin/OpenSimDefaults.ini index 6d2d54daca..2e192f1178 100644 --- a/bin/OpenSimDefaults.ini +++ b/bin/OpenSimDefaults.ini @@ -291,6 +291,20 @@ http_listener_sslport = 9001 ; Use this port for SSL connections http_listener_ssl_cert = "" ; Currently unused, but will be used for OSHttpServer + ; HTTPS for "Out of band" management applications such as the remote + ; admin module + ; + ; Create https_listener = "True" will create a listener on the port + ; specified. Provide the path to your server certificate along with it's + ; password + ; https_listener = False + ; Set our listener to this port + ; https_port = 0 + ; Path to X509 certificate + ; cert_path = "path/to/cert.p12" + ; Password for cert + ; cert_pass = "password" + ; Hostname to use in llRequestURL/llRequestSecureURL ; if not defined - default machine name is being used ; (on Windows this mean NETBIOS name - useably only inside local network) diff --git a/bin/Robust.ini.example b/bin/Robust.ini.example index 047e9eeed6..cc018f8867 100644 --- a/bin/Robust.ini.example +++ b/bin/Robust.ini.example @@ -21,6 +21,27 @@ ServiceConnectors = "8003/OpenSim.Server.Handlers.dll:AssetServiceConnector,8003 [Network] port = 8003 + + ; HTTPS for "Out of band" management applications such as the remote admin + ; module. May specify https_main = True to make the main http server + ; use https or "False" to make the main server HTTP + ; https_main = False + ; + ; Create https_listener = "True" will create a listener on the port + ; specified. Provide the path to your server certificate along with it's + ; password + ; https_listener = False + ; + ; Set our listener to this port + ; https_port = 0 + ; + ; Path to X509 certificate + ; cert_path = "path/to/cert.p12" + ; + ; Password for cert + ; cert_pass = "password" + + ; * The following are for the remote console ; * They have no effect for the local or basic console types ; * Leave commented to diable logins to the console From e4e95706d52fad2bab4a725955449f6bdb523a29 Mon Sep 17 00:00:00 2001 From: BlueWall Date: Mon, 2 May 2011 14:35:44 -0400 Subject: [PATCH 31/48] Add support for llRequestSecureURL() if ssl is enabled --- .../Scripting/LSLHttp/UrlModule.cs | 61 +++++++++++++++++-- 1 file changed, 56 insertions(+), 5 deletions(-) diff --git a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs index 9b565ed85b..a552a28912 100644 --- a/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs +++ b/OpenSim/Region/CoreModules/Scripting/LSLHttp/UrlModule.cs @@ -78,7 +78,9 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp private int m_TotalUrls = 100; + private uint https_port = 0; private IHttpServer m_HttpServer = null; + private IHttpServer m_HttpsServer = null; private string m_ExternalHostNameForLSL = ""; @@ -100,6 +102,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp public void Initialise(IConfigSource config) { m_ExternalHostNameForLSL = config.Configs["Network"].GetString("ExternalHostNameForLSL", System.Environment.MachineName); + bool ssl_enabled = config.Configs["Network"].GetBoolean("https_listener",false); + if (ssl_enabled) + { + https_port = (uint) config.Configs["Network"].GetInt("https_port",0); + } } public void PostInitialise() @@ -113,6 +120,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp // There can only be one // m_HttpServer = MainServer.Instance; + // + // We can use the https if it is enabled + if (https_port > 0) + { + m_HttpsServer = MainServer.GetHttpServer(https_port); + } } scene.RegisterModuleInterface(this); @@ -171,7 +184,40 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp { UUID urlcode = UUID.Random(); - engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + if (m_HttpsServer == null) + { + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + return urlcode; + } + + lock (m_UrlMap) + { + if (m_UrlMap.Count >= m_TotalUrls) + { + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_DENIED", "" }); + return urlcode; + } + string url = "https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + "/lslhttps/" + urlcode.ToString() + "/"; + + UrlData urlData = new UrlData(); + urlData.hostID = host.UUID; + urlData.itemID = itemID; + urlData.engine = engine; + urlData.url = url; + urlData.urlcode = urlcode; + urlData.requests = new Dictionary(); + + + m_UrlMap[url] = urlData; + + string uri = "/lslhttps/" + urlcode.ToString() + "/"; + + m_HttpsServer.AddPollServiceHTTPHandler(uri,HandleHttpPoll, + new PollServiceEventArgs(HttpRequestHandler,HasEvents, GetEvents, NoEvents, + urlcode)); + + engine.PostScriptEvent(itemID, "http_request", new Object[] { urlcode.ToString(), "URL_REQUEST_GRANTED", url }); + } return urlcode; } @@ -345,7 +391,7 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp } private Hashtable GetEvents(UUID requestID, UUID sessionID, string request) { - UrlData url = null; + UrlData url = null; RequestData requestData = null; lock (m_RequestMap) @@ -391,11 +437,12 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp lock (request) { string uri = request["uri"].ToString(); - + bool is_ssl = uri.Contains("lslhttps"); + try { Hashtable headers = (Hashtable)request["headers"]; - + // string uri_full = "http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri;// "/lslhttp/" + urlcode.ToString() + "/"; int pos1 = uri.IndexOf("/");// /lslhttp @@ -409,7 +456,11 @@ namespace OpenSim.Region.CoreModules.Scripting.LSLHttp pathInfo = uri.Substring(pos3); - UrlData url = m_UrlMap["http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp]; + UrlData url = null; + if (!is_ssl) + url = m_UrlMap["http://" + m_ExternalHostNameForLSL + ":" + m_HttpServer.Port.ToString() + uri_tmp]; + else + url = m_UrlMap["https://" + m_ExternalHostNameForLSL + ":" + m_HttpsServer.Port.ToString() + uri_tmp]; //for llGetHttpHeader support we need to store original URI here //to make x-path-info / x-query-string / x-script-url / x-remote-ip headers From fd44540c023e7df35308a40df9e61d7f9273eba4 Mon Sep 17 00:00:00 2001 From: "Justin Clark-Casey (justincc)" Date: Fri, 6 May 2011 01:06:28 +0100 Subject: [PATCH 32/48] add descriptive explanations for region restart functionality --- .../CoreModules/World/Region/RestartModule.cs | 19 +++++++++++++------ .../Agent/UDP/Linden/LindenUDPInfoModule.cs | 1 - 2 files changed, 13 insertions(+), 7 deletions(-) diff --git a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs index ab6a598ace..e983239b4a 100644 --- a/OpenSim/Region/CoreModules/World/Region/RestartModule.cs +++ b/OpenSim/Region/CoreModules/World/Region/RestartModule.cs @@ -64,19 +64,26 @@ namespace OpenSim.Region.CoreModules.World.Region public void AddRegion(Scene scene) { m_Scene = scene; + scene.RegisterModuleInterface(this); MainConsole.Instance.Commands.AddCommand("RestartModule", false, "region restart bluebox", - "region restart bluebox