websitepanel/WebsitePanel.VmConfig/Sources/WebsitePanel.VmConfig/VmConfigService.cs
2011-07-13 16:07:32 -07:00

497 lines
17 KiB
C#

// Copyright (c) 2011, Outercurve Foundation.
// All rights reserved.
//
// 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 Outercurve Foundation 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 COPYRIGHT HOLDERS AND CONTRIBUTORS "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 COPYRIGHT HOLDER OR 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.Configuration;
using System.Collections.Generic;
using System.ComponentModel;
using System.Diagnostics;
using System.ServiceProcess;
using System.Management;
using System.Timers;
using System.Text;
using Microsoft.Win32;
namespace WebsitePanel.VmConfig
{
public partial class VmConfigService : ServiceBase
{
internal const string RegistryInputKey = "SOFTWARE\\Microsoft\\Virtual Machine\\External";
internal const string RegistryOutputKey = "SOFTWARE\\Microsoft\\Virtual Machine\\Guest";
internal const string TaskPrefix = "WSP-";
internal const string CurrentTaskName = "WSP-CurrentTask";
internal const string RAM_SUMMARY_KEY = "VM-RAM-Summary";
internal const string HDD_SUMMARY_KEY = "VM-HDD-Summary";
private Dictionary<string, string> provisioningModules;
private Timer idleTimer;
private Timer pollTimer;
private Timer summaryTimer;
private bool rebootRequired = false;
private System.Threading.Thread mainThread;
public VmConfigService()
{
InitializeComponent();
}
protected override void OnStart(string[] args)
{
//start main procedure in separate thread
mainThread = new System.Threading.Thread(new System.Threading.ThreadStart(Start));
mainThread.Start();
}
private void Start()
{
//log
ServiceLog.WriteApplicationStart();
// delay (for sync with KVP exchange service)
DelayOnStart();
//init
InitializeProvisioningModules();
InitializeTimers();
// start timer
StartSummaryTimer();
//run tasks
ProcessTasks();
}
protected override void OnStop()
{
if (this.mainThread.IsAlive)
{
this.mainThread.Abort();
}
this.mainThread.Join();
ServiceLog.WriteApplicationStop();
}
private void DelayOnStart()
{
int startupDelay = 0;
if (Int32.TryParse(ConfigurationManager.AppSettings["Service.StartupDelay"], out startupDelay)
&& startupDelay > 0)
{
ServiceLog.WriteStart("Delay on service start-up");
System.Threading.Thread.Sleep(startupDelay);
ServiceLog.WriteEnd("Delay on service start-up");
}
}
private void DeleteOldResults()
{
// get the list of input tasks
string[] strTasks = RegistryUtils.GetRegistryKeyValueNames(RegistryInputKey);
List<string> tasks = new List<string>();
foreach (string strTask in strTasks)
{
if (!string.IsNullOrEmpty(strTask) && strTask.StartsWith(TaskPrefix) && strTask != CurrentTaskName)
{
//save only WebsitePanel tasks
tasks.Add(strTask);
}
}
// get the list of task results
int deletedResults = 0;
string[] strResults = RegistryUtils.GetRegistryKeyValueNames(RegistryOutputKey);
foreach (string strResult in strResults)
{
if (!string.IsNullOrEmpty(strResult) && strResult.StartsWith(TaskPrefix) && strResult != CurrentTaskName)
{
// check if task result exists in the input tasks
if (!tasks.Contains(strResult))
{
DeleteRegistryKeyValue(RegistryOutputKey, strResult);
ServiceLog.WriteInfo(string.Format("Deleted activity result: {0}", strResult));
deletedResults++;
}
}
}
if(deletedResults > 0)
ServiceLog.WriteEnd(string.Format("{0} result(s) deleted", deletedResults));
}
private void InitializeProvisioningModules()
{
ServiceLog.WriteStart("Loading provisioning modules...");
provisioningModules = new Dictionary<string, string>();
Configuration config = ConfigurationManager.OpenExeConfiguration(ConfigurationUserLevel.None);
ModuleSettingsSection section = config.Sections["moduleSettings"] as ModuleSettingsSection;
if ( section != null)
{
foreach (string key in section.Modules.AllKeys)
{
provisioningModules.Add(key, section.Modules[key].Value);
}
}
else
ServiceLog.WriteError("Modules configuration section not found");
ServiceLog.WriteEnd(string.Format("{0} module(s) loaded", provisioningModules.Count));
}
private void InitializeTimers()
{
// idle timer
idleTimer = new Timer();
idleTimer.AutoReset = false;
double idleInterval;
if (!Double.TryParse(ConfigurationManager.AppSettings["Service.ExitIdleInterval"], out idleInterval))
{
ServiceLog.WriteError("Invalid configuration parameter: Service.ExitIdleInterval");
idleInterval = 600000;
}
idleTimer.Interval = idleInterval;
idleTimer.Enabled = false;
idleTimer.Elapsed += new ElapsedEventHandler(OnIdleTimerElapsed);
// poll timer
pollTimer = new Timer();
pollTimer.AutoReset = false;
double pollInterval;
if (!Double.TryParse(ConfigurationManager.AppSettings["Service.RegistryPollInterval"], out pollInterval))
{
ServiceLog.WriteError("Invalid configuration parameter: Service.RegistryPollInterval");
pollInterval = 60000;
}
pollTimer.Interval = pollInterval;
pollTimer.Enabled = false;
pollTimer.Elapsed += new ElapsedEventHandler(OnPollTimerElapsed);
// system symmary timer
summaryTimer = new Timer();
double summaryInterval;
if (!Double.TryParse(ConfigurationManager.AppSettings["Service.SystemSummaryInterval"], out summaryInterval))
{
ServiceLog.WriteError("Invalid configuration parameter: Service.SystemSummaryInterval");
summaryInterval = 15000;
}
summaryTimer.Interval = summaryInterval;
summaryTimer.Enabled = false;
summaryTimer.Elapsed += new ElapsedEventHandler(OnSummaryTimerElapsed);
}
private void OnIdleTimerElapsed(object sender, ElapsedEventArgs e)
{
base.Stop();
}
private void OnPollTimerElapsed(object sender, ElapsedEventArgs e)
{
ProcessTasks();
}
private void OnSummaryTimerElapsed(object sender, ElapsedEventArgs e)
{
GetSystemSummary();
}
private void GetSystemSummary()
{
const UInt64 Size1KB = 0x400;
const UInt64 Size1GB = 0x40000000;
// check free/total RAM
WmiUtils wmi = new WmiUtils("root\\cimv2");
ManagementObjectCollection objOses = wmi.ExecuteQuery("SELECT * FROM Win32_OperatingSystem");
foreach (ManagementObject objOs in objOses)
{
UInt64 freeRam = Convert.ToUInt64(objOs["FreePhysicalMemory"]) / Size1KB;
UInt64 totalRam = Convert.ToUInt64(objOs["TotalVisibleMemorySize"]) / Size1KB;
// write to the registry
RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, RAM_SUMMARY_KEY,
String.Format("{0}:{1}", freeRam, totalRam));
objOs.Dispose();
}
// check local HDD logical drives
ManagementObjectCollection objDisks = wmi.ExecuteQuery("SELECT * FROM Win32_LogicalDisk WHERE DriveType = 3");
StringBuilder sb = new StringBuilder();
bool first = true;
foreach (ManagementObject objDisk in objDisks)
{
if(!first)
sb.Append(";");
sb.Append(objDisk["DeviceID"]);
sb.Append(Convert.ToInt32(Convert.ToDouble(objDisk["FreeSpace"]) / (double)Size1GB)).Append(":");
sb.Append(Convert.ToInt32(Convert.ToDouble(objDisk["Size"]) / (double)Size1GB));
first = false;
objDisk.Dispose();
}
// write HDD info
RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, HDD_SUMMARY_KEY, sb.ToString());
// dispose resources
objOses.Dispose();
objDisks.Dispose();
}
private void ProcessTasks()
{
// delete old results
DeleteOldResults();
//process all tasks
while (true)
{
//load completed tasks results
string[] strResults = RegistryUtils.GetRegistryKeyValueNames(RegistryOutputKey);
List<string> results = new List<string>();
foreach (string strResult in strResults)
{
if (!string.IsNullOrEmpty(strResult) && strResult.StartsWith(TaskPrefix) && strResult != CurrentTaskName)
{
//save only WebsitePanel tasks
results.Add(strResult);
}
}
// sorted list of tasks - will be sorted by the TaskID (time in ticks)
SortedList<long, string> tasks = new SortedList<long, string>();
//load task definitions from input registry key
string[] strTasks = RegistryUtils.GetRegistryKeyValueNames(RegistryInputKey);
foreach (string strTask in strTasks)
{
if (results.Contains(strTask))
continue; // skip completed tasks
if (!string.IsNullOrEmpty(strTask) && strTask.StartsWith(TaskPrefix))
{
// extract Task ID parameter
int idx = strTask.LastIndexOf('-');
if(idx == -1)
continue;
string strTaskId = strTask.Substring(idx + 1);
long taskId = 0;
try
{
taskId = Int64.Parse(strTaskId);
}
catch
{
continue; // wrong task format
}
//save only WebsitePanel tasks
if(!tasks.ContainsKey(taskId))
tasks.Add(taskId, strTask);
}
}
if (tasks.Count == 0)
{
if (rebootRequired)
{
ServiceLog.WriteInfo("Reboot required");
RebootSystem();
return;
}
//start timers
StartPollTimer(); //starts task processing after poll interval
StartIdleTimer(); //stops service if idle
//no tasks - exit!
return;
}
else
{
//stop idle timer as we need to process tasks
StopIdleTimer();
}
ExecutionContext context = null;
foreach (long tid in tasks.Keys)
{
//find first correct task
string taskDefinition = tasks[tid];
string taskParameters = RegistryUtils.GetRegistryKeyStringValue(RegistryInputKey, taskDefinition);
if (taskDefinition.LastIndexOf("-") == -1 || taskDefinition.LastIndexOf('-') == taskDefinition.Length - 1)
{
ServiceLog.WriteError(string.Format("Task was deleted from queue as its definition is invalid : {0}", taskDefinition));
DeleteRegistryKeyValue(RegistryInputKey, taskDefinition);
//go to next task
continue;
}
string taskName = taskDefinition.Substring(0, taskDefinition.LastIndexOf("-")).Substring(TaskPrefix.Length);
string taskId = taskDefinition.Substring(taskDefinition.LastIndexOf('-') + 1);
if (!provisioningModules.ContainsKey(taskName))
{
ServiceLog.WriteError(string.Format("Task was deleted from queue as its definition was not found : {0}", taskName));
DeleteRegistryKeyValue(RegistryInputKey, taskDefinition);
//go to next task
continue;
}
//prepare execution context for correct task
context = new ExecutionContext();
context.ActivityID = taskId;
context.ActivityName = taskName;
ParseParameters(context.Parameters, taskParameters);
context.ActivityDefinition = taskDefinition;
break;
}
if (context != null)
{
string type = provisioningModules[context.ActivityName];
ExecutionResult res = null;
DateTime start = DateTime.Now;
try
{
//load module and run task
ServiceLog.WriteStart(string.Format("Starting '{0}' module...", context.ActivityName));
context.Progress = 0;
res = ModuleLoader.Run(type, ref context);
context.Progress = 100;
ServiceLog.WriteEnd(string.Format("'{0}' module finished.", context.ActivityName));
}
catch (Exception ex)
{
ServiceLog.WriteError("Unhandled exception:", ex);
res = new ExecutionResult();
res.ResultCode = -1;
res.ErrorMessage = string.Format("Unhandled exception : {0}", ex);
}
DateTime end = DateTime.Now;
SaveExecutionResult(context.ActivityDefinition, res, start, end);
//DeleteRegistryKeyValue(RegistryInputKey, context.ActivityDefinition);
if (res.RebootRequired)
rebootRequired = true;
}
}
}
private void ParseParameters(Dictionary<string, string> dictionary, string parameters)
{
if (dictionary == null)
throw new ArgumentNullException("dictionary");
if (string.IsNullOrEmpty(parameters))
return;
string[] pairs = parameters.Split('|');
foreach (string pair in pairs)
{
string[] parts = pair.Split(new char[] { '=' }, 2);
if (parts.Length != 2)
continue;
if (!dictionary.ContainsKey(parts[0]))
dictionary.Add(parts[0], parts[1]);
}
}
private void DeleteRegistryKeyValue(string key, string valueName)
{
try
{
RegistryUtils.DeleteRegistryKeyValue(key, valueName);
}
catch (Exception ex)
{
ServiceLog.WriteError("Registry error:", ex);
}
}
private void SaveExecutionResult(string name, ExecutionResult res, DateTime started, DateTime ended)
{
StringBuilder builder = new StringBuilder();
builder.AppendFormat("ResultCode={0}|", res.ResultCode);
builder.AppendFormat("RebootRequired={0}|", res.RebootRequired);
builder.AppendFormat("ErrorMessage={0}|", res.ErrorMessage);
builder.AppendFormat("Value={0}|", res.Value);
builder.AppendFormat("Started={0}|", started.ToString("yyyyMMddHHmmss"));
builder.AppendFormat("Ended={0}", started.ToString("yyyyMMddHHmmss"));
RegistryUtils.SetRegistryKeyStringValue(RegistryOutputKey, name, builder.ToString());
}
private void StopIdleTimer()
{
idleTimer.Stop();
}
private void StartIdleTimer()
{
if ( idleTimer.Interval > 1 )
idleTimer.Start();
}
private void StartPollTimer()
{
if ( pollTimer.Interval > 1 )
pollTimer.Start();
}
private void StartSummaryTimer()
{
if (summaryTimer.Interval > 1)
summaryTimer.Start();
}
private void RebootSystem()
{
try
{
ServiceLog.WriteStart("RebootSystem");
WmiUtils wmi = new WmiUtils("root\\cimv2");
ManagementObjectCollection objOses = wmi.ExecuteQuery("SELECT * FROM Win32_OperatingSystem");
foreach (ManagementObject objOs in objOses)
{
objOs.Scope.Options.EnablePrivileges = true;
objOs.InvokeMethod("Reboot", null);
}
ServiceLog.WriteEnd("RebootSystem");
}
catch (Exception ex)
{
ServiceLog.WriteError("Reboot System error:", ex);
}
}
}
}