diff --git a/src/Agni/Agni.csproj b/src/Agni/Agni.csproj
new file mode 100644
index 0000000..a9847a7
--- /dev/null
+++ b/src/Agni/Agni.csproj
@@ -0,0 +1,115 @@
+
+
+
+ netstandard2.0
+ Agni OS Main Assembly
+
+
+
+
+ ..\..\out\Debug\
+ ..\..\out\Debug\Agni.xml
+ true
+
+
+
+ ..\..\out\Release\
+ ..\..\out\Release\Agni.xml
+ true
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ ..\lib\nfx\NFX.dll
+
+
+ ..\lib\nfx\NFX.Wave.dll
+
+
+ ..\lib\nfx\NFX.Web.dll
+
+
+
+
+
+
+
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+ Always
+
+
+
+
diff --git a/src/Agni/AgniException.cs b/src/Agni/AgniException.cs
new file mode 100644
index 0000000..3558f3f
--- /dev/null
+++ b/src/Agni/AgniException.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+using NFX;
+
+namespace Agni
+{
+ ///
+ /// Marker interfaces for all Agni exceptions
+ ///
+ public interface IAgniException
+ {
+ }
+
+ ///
+ /// Base exception thrown by the framework
+ ///
+ [Serializable]
+ public class AgniException : NFXException, IAgniException
+ {
+ public const string SENDER_FLD_NAME = "AE-S";
+ public const string TOPIC_FLD_NAME = "AE-T";
+
+ public static string DefaultSender;
+ public static string DefaultTopic;
+
+ public readonly string Sender;
+ public readonly string Topic;
+
+
+ public AgniException()
+ {
+ Sender = DefaultSender;
+ Topic = DefaultTopic;
+ }
+
+ public AgniException(int code)
+ {
+ Code = code;
+ Sender = DefaultSender;
+ Topic = DefaultTopic;
+ }
+
+ public AgniException(int code, string message) : this(message, null, code, null, null) {}
+ public AgniException(string message) : this(message, null, 0, null, null) { }
+ public AgniException(string message, Exception inner) : this(message, inner, 0, null, null) { }
+
+ public AgniException(string message, Exception inner, int code, string sender, string topic) : base(message, inner)
+ {
+ Code = code;
+ Sender = sender ?? DefaultSender;
+ Topic = topic ?? DefaultTopic;
+ }
+
+ protected AgniException(SerializationInfo info, StreamingContext context) : base(info, context)
+ {
+ Sender = info.GetString(SENDER_FLD_NAME);
+ Topic = info.GetString(TOPIC_FLD_NAME);
+ }
+
+ public override void GetObjectData(SerializationInfo info, StreamingContext context)
+ {
+ if (info == null)
+ throw new NFXException(StringConsts.ARGUMENT_ERROR + GetType().Name + ".GetObjectData(info=null)");
+ info.AddValue(SENDER_FLD_NAME, Sender);
+ info.AddValue(TOPIC_FLD_NAME, Topic);
+ base.GetObjectData(info, context);
+ }
+ }
+}
diff --git a/src/Agni/AgniExtensions.cs b/src/Agni/AgniExtensions.cs
new file mode 100644
index 0000000..324c077
--- /dev/null
+++ b/src/Agni/AgniExtensions.cs
@@ -0,0 +1,199 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Glue;
+using NFX.Environment;
+
+namespace Agni
+{
+ public static class AgniExtensions
+ {
+ ///
+ /// Tries to resolve mnemonic name of the Agni service into port, i.e. "hgov" into int port number
+ ///
+ public static Node ToResolvedServiceNode(this string connectString, bool appTerminal = false)
+ {
+ return ToResolvedServiceNode(new Node(connectString), appTerminal);
+ }
+
+ ///
+ /// Tries to resolve mnemonic name of the Agni service into port, i.e. "hgov" into int port number
+ ///
+ public static Node ToResolvedServiceNode(this Node node, bool appTerminal = false)
+ {
+ if (!node.Assigned ) return node;
+
+ if (node.Binding.IsNullOrWhiteSpace())
+ node = new Node("{0}://{1}:{2}".Args(SysConsts.DEFAULT_BINDING, node.Host, node.Service));
+
+ int p;
+ if (int.TryParse(node.Service, out p)) return node;
+
+ var sync = node.Binding.Trim().EqualsIgnoreCase(SysConsts.SYNC_BINDING);
+ var port = ServiceNameToPort(node.Service, sync, appTerminal);
+ return new Node("{0}://{1}:{2}".Args(node.Binding, node.Host, port));
+ }
+
+ ///
+ /// Translates mnemonic name of the major service (i.e. "hgov") into its default port
+ ///
+ public static int ServiceNameToPort(string service, bool sync, bool appTerminal)
+ {
+ var result = SysConsts.DEFAULT_ZONE_GOV_SVC_SYNC_PORT;
+ if (service.IsNotNullOrWhiteSpace())
+ {
+ service = service.Trim().ToLowerInvariant();
+ if (service=="ahgov" ||
+ service=="hgov" ||
+ service=="hgv" ||
+ service=="h") result = SysConsts.DEFAULT_HOST_GOV_SVC_SYNC_PORT;
+
+ else if (service=="azgov" ||
+ service=="zgov" ||
+ service=="zgv" ||
+ service=="z") result = SysConsts.DEFAULT_ZONE_GOV_SVC_SYNC_PORT;
+
+ else if (service=="agdida" ||
+ service == "gdida" ||
+ service == "gdid" ||
+ service=="id" ||
+ service=="g") result = SysConsts.DEFAULT_GDID_AUTH_SVC_SYNC_PORT;
+
+ else if (service=="aws" ||
+ service=="www" ||
+ service=="web" ||
+ service=="http") result = SysConsts.DEFAULT_AWS_SVC_SYNC_PORT;
+
+ else if (service=="aph" ||
+ service=="proc" ||
+ service=="ph") result = SysConsts.DEFAULT_PH_SVC_SYNC_PORT;
+
+ else if (service=="log") result = SysConsts.DEFAULT_LOG_SVC_SYNC_PORT;
+
+ else if (service=="telem" ||
+ service=="telemetry" ||
+ service=="tlm") result = SysConsts.DEFAULT_TELEMETRY_SVC_SYNC_PORT;
+
+ else if (service=="wm" ||
+ service=="msg" ||
+ service=="wmsg") result = SysConsts.DEFAULT_WEB_MESSAGE_SYSTEM_SVC_APPTERM_PORT;
+
+ else if (service=="ash" || service=="sh")
+ {
+ if (appTerminal) return SysConsts.DEFAULT_ASH_APPTERM_PORT;
+ else throw new AgniException("Not supported ASH service");
+ }
+ else throw new AgniException("Not supported service: `{0}`".Args(service));
+ }
+
+ return appTerminal ? result + SysConsts.APP_TERMINAL_PORT_OFFSET : sync ? result : result+1;
+ }
+
+
+ ///
+ /// Checks two strings for region paths that reference the same regional entity disregarding entity extensions (such as '.r' or '.noc').
+ /// Note: this method DOES NOT check whether this path resolves to actual catalog entity as it only compares names.
+ /// This function should be used in conjunction with GetRegionPathHashCode() while implementing Equals/GetHashCode.
+ /// Ignores dynamic host name suffixes
+ ///
+ public static bool IsSameRegionPath(this string path1, string path2)
+ {
+ if (path1==null || path2==null) return false;
+
+ var segs1 = path1.Split('/').Where(s=>s.IsNotNullOrWhiteSpace()).ToList();
+ var segs2 = path2.Split('/').Where(s=>s.IsNotNullOrWhiteSpace()).ToList();
+
+ if (segs1.Count!=segs2.Count) return false;
+
+ for(var i=0; i0) seg1 = seg1.Substring(0, si).Trim();
+ si = seg2.LastIndexOf(Metabase.Metabank.HOST_DYNAMIC_SUFFIX_SEPARATOR); if (si>0) seg2 = seg2.Substring(0, si).Trim();
+ }
+
+ var di = seg1.LastIndexOf('.'); if (di>0) seg1 = seg1.Substring(0, di);
+ di = seg2.LastIndexOf('.'); if (di>0) seg2 = seg2.Substring(0, di);
+
+ if (!seg1.EqualsIgnoreCase( seg2 )) return false;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Deletes region extensions (such as '.r' or '.noc') from path
+ /// Ignores dynamic host name suffixes
+ ///
+ public static string StripPathOfRegionExtensions(this string path)
+ {
+ if (path==null) return null;
+
+ var segs = path.Split('/').Where(s=>s.IsNotNullOrWhiteSpace()).ToList();
+ var result = new StringBuilder();
+ for(var i=0; i0) seg = seg.Substring(0, si).Trim();
+ }
+
+ var di = seg.LastIndexOf('.');
+ if (di>0) seg = seg.Substring(0, di);
+
+ if (i>0) result.Append('/');
+ result.Append(seg.Trim());
+ }
+
+ return result.ToString();
+ }
+
+ ///
+ /// Computes has code for region path string disregarding case and extensions (such as '.r' or '.noc')
+ /// Note: This function should be used in conjunction with IsSameRegionPath() while implementing Equals/GetHashCode
+ /// Ignores dynamic host name suffixes
+ ///
+ public static int GetRegionPathHashCode(this string path)
+ {
+ var stripped = path.StripPathOfRegionExtensions();
+ if (stripped==null) return 0;
+ return stripped.ToLowerInvariant().GetHashCode();
+ }
+
+
+ ///
+ /// Returns true if the supplied string is a valid name
+ /// a name that does not contain SysConsts.NAME_INVALID_CHARS
+ ///
+ public static bool IsValidName(this string name)
+ {
+ if (name==null) return false;
+
+ var started = false;
+ var ws = false;
+
+ for(var i=0; i
+ /// Provides a shortcut access to app-global Agni context
+ ///
+ public static class AgniSystem
+ {
+ private static BuildInformation s_CoreBuildInfo;
+
+ ///
+ /// Returns BuildInformation object for the core agni assembly
+ ///
+ public static BuildInformation CoreBuildInfo
+ {
+ get
+ {
+ //multithreading: 2nd copy is ok
+ if (s_CoreBuildInfo == null)
+ s_CoreBuildInfo = new BuildInformation(typeof(AgniSystem).Assembly);
+
+ return s_CoreBuildInfo;
+ }
+ }
+
+ private static string s_MetabaseApplicationName;
+
+ ///
+ /// Every agni application MUST ASSIGN THIS property at its entry point ONCE. Example: void Main(string[]args){ AgniSystem.MetabaseApplicationName = "MyApp1";...
+ ///
+ public static string MetabaseApplicationName
+ {
+ get { return s_MetabaseApplicationName; }
+ set
+ {
+ if (s_MetabaseApplicationName != null || value.IsNullOrWhiteSpace())
+ throw new AgniException(StringConsts.METABASE_APP_NAME_ASSIGNMENT_ERROR);
+ s_MetabaseApplicationName = value;
+ }
+ }
+
+
+ ///
+ /// Returns instance of agni application container that this AgniSystem services
+ ///
+ public static IAgniApplication Application
+ {
+ get { return (App.Instance as IAgniApplication) ?? (IAgniApplication)AgniNOPApplication.Instance; }
+ }
+
+ ///
+ /// Denotes system application/process type that this app container has, i.e.: HostGovernor, WebServer, etc.
+ ///
+ public static SystemApplicationType SystemApplicationType { get { return Application.SystemApplicationType; } }
+
+ ///
+ /// Returns current instance
+ ///
+ public static IAgniSystem Instance { get { return Application.TheSystem; } }
+
+ ///
+ /// Returns true when AgniSystem is active non-NOP instance
+ ///
+ public static bool Available { get { return Instance != null && Instance.Available; } }
+
+ ///
+ /// References application configuration root used to boot this application instance
+ ///
+ public static IConfigSectionNode BootConfigRoot { get { return Application.BootConfigRoot; } }
+
+ ///
+ /// Host name of this machine as determined at boot. This is a shortcut to Agni.AppModel.BootConfLoader.HostName
+ ///
+ public static string HostName { get { return BootConfLoader.HostName; } }
+
+
+ ///
+ /// True if this host is dynamic
+ ///
+ public static bool DynamicHost { get { return BootConfLoader.DynamicHost; } }
+
+
+ ///
+ /// Returns parent zone governor host name or null if this is the top-level host in Agni.
+ /// This is a shortcut to Agni.AppModel.BootConfLoader.ParentZoneGovernorPrimaryHostName
+ ///
+ public static string ParentZoneGovernorPrimaryHostName { get { return BootConfLoader.ParentZoneGovernorPrimaryHostName; } }
+
+
+ ///
+ /// NOC name for this host as determined at boot
+ ///
+ public static string NOCName { get { return NOCMetabaseSection.Name; } }
+
+
+ ///
+ /// True when metabase is mounted!=null
+ ///
+ public static bool IsMetabase { get { return BootConfLoader.Metabase != null; } }
+
+
+ ///
+ /// Returns metabank instance that interfaces the metabase as determined at application boot.
+ /// If metabase is null then exception is thrown. Use IsMetabase to test for null instead
+ ///
+ public static Metabank Metabase
+ {
+ get
+ {
+ var result = BootConfLoader.Metabase;
+
+ if (result == null)
+ {
+ var trace = new System.Diagnostics.StackTrace(false);
+ throw new AgniException(StringConsts.METABASE_NOT_AVAILABLE_ERROR.Args(trace.ToString()));
+ }
+
+ return result;
+ }
+ }
+
+ ///
+ /// Returns Metabank.SectionHost (metabase's information about this host)
+ ///
+ public static Metabank.SectionHost HostMetabaseSection { get { return Metabase.CatalogReg.NavigateHost(HostName); } }
+
+ ///
+ /// Returns Metabank.SectionNOC (metabase's information about the NOC this host is in)
+ ///
+ public static Metabank.SectionNOC NOCMetabaseSection { get { return HostMetabaseSection.NOC; } }
+
+
+ ///
+ /// Returns Agni distributed lock manager
+ ///
+ public static Locking.ILockManager LockManager { get { return Application.LockManager; } }
+
+ ///
+ /// References distributed GDID provider
+ ///
+ public static IGDIDProvider GDIDProvider { get { return Application.GDIDProvider; } }
+
+ ///
+ /// Returns Agni distributed process manager
+ ///
+ public static Workers.IProcessManager ProcessManager { get { return Application.ProcessManager; } }
+
+ ///
+ /// Returns Agni distributed dynamic host manager
+ ///
+ public static Dynamic.IHostManager DynamicHostManager { get { return Application.DynamicHostManager; } }
+ }
+}
diff --git a/src/Agni/AppModel/AgniNOPApplication.cs b/src/Agni/AppModel/AgniNOPApplication.cs
new file mode 100644
index 0000000..d6a368e
--- /dev/null
+++ b/src/Agni/AppModel/AgniNOPApplication.cs
@@ -0,0 +1,47 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using Agni.Dynamic;
+using Agni.Workers;
+using NFX.ApplicationModel;
+using NFX.DataAccess;
+using NFX.Environment;
+
+namespace Agni.AppModel
+{
+ ///
+ /// Represents an application that consists of pure-nop providers, consequently
+ /// this application does not log, does not store data and does not do anything else
+ /// still satisfying its contract
+ ///
+ public class AgniNOPApplication : NOPApplication, IAgniApplication
+ {
+ private static AgniNOPApplication s_Instance = new AgniNOPApplication();
+
+ protected AgniNOPApplication() : base() {}
+
+ ///
+ /// Returns a singlelton instance of the AgniNOPApplication
+ ///
+ public static new AgniNOPApplication Instance { get { return s_Instance; } }
+
+ public string MetabaseApplicationName { get { return string.Empty; } }
+
+ public IAgniSystem TheSystem { get { return NOPAgniSystem.Instance; } }
+
+ public IConfigSectionNode BootConfigRoot { get { return m_Configuration.Root; } }
+
+ public bool ConfiguredFromLocalBootConfig { get { return false; } }
+
+ public SystemApplicationType SystemApplicationType { get { return SystemApplicationType.Unspecified; } }
+
+ public Locking.ILockManager LockManager { get { return Locking.NOPLockManager.Instance; } }
+
+ public IGDIDProvider GDIDProvider { get { throw new NotSupportedException("NOPApp.GDIDProvider"); } }
+
+ public IProcessManager ProcessManager { get { throw new NotSupportedException("NOPApp.ProcessManager"); } }
+
+ public IHostManager DynamicHostManager { get { throw new NotSupportedException("NOPApp.HostManager"); } }
+ }
+}
diff --git a/src/Agni/AppModel/AgniServiceApplication.cs b/src/Agni/AppModel/AgniServiceApplication.cs
new file mode 100644
index 0000000..80990be
--- /dev/null
+++ b/src/Agni/AppModel/AgniServiceApplication.cs
@@ -0,0 +1,358 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Log;
+using NFX.ApplicationModel;
+using NFX.ServiceModel;
+using NFX.Environment;
+using NFX.DataAccess;
+using NFX.Wave;
+
+using Agni.Identification;
+
+namespace Agni.AppModel
+{
+ ///
+ /// Provides base implementation of IAgniApplication for applications like services and console apps.
+ /// This class IS thread safe
+ ///
+ public class AgniServiceApplication : ServiceBaseApplication, IAgniApplication
+ {
+ #region CONSTS
+
+ public const string CONFIG_WEB_MANAGER_SECTION = "web-manager";
+
+ public const string CONFIG_LOCK_MANAGER_SECTION = "lock-manager";
+
+ public const string CONFIG_PROCESS_MANAGER_SECTION = "process-manager";
+
+ public const string CONFIG_HOST_MANAGER_SECTION = "host-manager";
+
+ #endregion
+
+ #region .ctor
+
+ public AgniServiceApplication(SystemApplicationType sysAppType, string[] args, ConfigSectionNode rootConfig)
+ : base(BootConfLoader.SetSystemApplicationType(sysAppType, args), rootConfig)
+ {}
+
+ protected override void Destructor()
+ {
+ BootConfLoader.Unload();
+ base.Destructor();
+ }
+
+ #endregion
+
+ #region Fields
+
+ protected IAgniSystem m_TheSystem;
+ private ConfigSectionNode m_BootConfigRoot;
+
+ private WaveServer m_WebManagerServer;
+
+ private Locking.ILockManagerImplementation m_LockManager;
+ private GDIDGenerator m_GDIDProvider;
+ private Workers.IProcessManagerImplementation m_ProcessManager;
+ private Dynamic.IHostManagerImplementation m_DynamicHostManager;
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// References a singleton instance of AgniServiceApplication
+ ///
+ public static AgniServiceApplication Instance { get{ return App.Instance as AgniServiceApplication; } }
+
+ ///
+ /// Denotes system application/process type that this app container has, i.e.: HostGovernor, WebServer, etc.
+ /// The value is set in .ctor and kept in BootConfLoader.SystemApplicationType
+ ///
+ public SystemApplicationType SystemApplicationType { get {return BootConfLoader.SystemApplicationType; } }
+
+
+ public string MetabaseApplicationName { get{ return Agni.AgniSystem.MetabaseApplicationName; } }
+
+
+ public IConfigSectionNode BootConfigRoot { get { return m_BootConfigRoot; } }
+ public IAgniSystem TheSystem { get{ return m_TheSystem;} }
+
+ internal WaveServer WebManagerServer{ get{return m_WebManagerServer;}}
+
+ public Locking.ILockManager LockManager { get{ return m_LockManager ?? Locking.NOPLockManager.Instance; } }
+
+ public IGDIDProvider GDIDProvider { get { return m_GDIDProvider; } }
+
+ public Workers.IProcessManager ProcessManager { get { return m_ProcessManager; } }
+
+ public Dynamic.IHostManager DynamicHostManager { get { return m_DynamicHostManager; } }
+ #endregion
+
+ #region Protected
+
+ protected override Configuration GetConfiguration()
+ {
+ var localConfig = base.GetConfiguration();
+
+ BootConfLoader.ProcessAllExistingIncludes(localConfig.Root, null, "boot");
+
+ m_BootConfigRoot = localConfig.Root;
+
+ var cmdArgs = new string[]{};
+
+ if (CommandArgs.Configuration is CommandArgsConfiguration)
+ cmdArgs = ((CommandArgsConfiguration)this.CommandArgs.Configuration).Arguments;
+
+ return BootConfLoader.Load(cmdArgs, localConfig);
+ }
+
+ protected override void DoInitApplication()
+ {
+ base.DoInitApplication();
+
+ var FROM = GetType().FullName+".DoInitApplication()";
+
+ var csvc = new AgniSystemBase(this);
+ csvc.Start();
+
+ m_TheSystem = csvc;
+ var metabase = BootConfLoader.Metabase;
+
+ try
+ {
+ m_GDIDProvider = new GDIDGenerator("Agni", this);
+
+ foreach(var ah in metabase.GDIDAuthorities)
+ {
+ m_GDIDProvider.AuthorityHosts.Register(ah);
+ WriteLog(MessageType.Info, FROM+"{GDIDProvider init}", "Registered GDID authority host: "+ah.ToString());
+ }
+
+ WriteLog(MessageType.Info, FROM, "GDIProvider made");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM+"{GDIDProvider init}", error.ToMessageWithType());
+ try
+ {
+ m_GDIDProvider.Dispose();
+ }
+ catch{ }
+
+ m_GDIDProvider = null;
+ }
+
+ var wmSection = ConfigRoot[CONFIG_WEB_MANAGER_SECTION];
+ if (wmSection.Exists && wmSection.AttrByName(CONFIG_ENABLED_ATTR).ValueAsBool(false))
+ try
+ {
+ m_WebManagerServer = new WaveServer();
+ m_WebManagerServer.Configure(wmSection);
+ m_WebManagerServer.Start();
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM+"{WebManagerServer start}", error.ToMessageWithType());
+ try
+ {
+ m_WebManagerServer.Dispose();
+ }
+ catch{}
+
+ m_WebManagerServer = null;
+ }
+
+ var lockSection = ConfigRoot[CONFIG_LOCK_MANAGER_SECTION];
+ try
+ {
+ m_LockManager = FactoryUtils.MakeAndConfigure(lockSection, typeof(Locking.LockManager));
+
+ WriteLog(MessageType.Info, FROM, "Lock Manager made");
+
+ if (m_LockManager is Service)
+ {
+ ((Service)m_LockManager).Start();
+ WriteLog(MessageType.Info, FROM, "Lock Manager STARTED");
+ }
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM+"{LockManager start}", error.ToMessageWithType());
+ try
+ {
+ m_LockManager.Dispose();
+ }
+ catch{}
+
+ m_LockManager = null;
+ }
+
+ var procSection = ConfigRoot[CONFIG_PROCESS_MANAGER_SECTION];
+ try
+ {
+ m_ProcessManager = FactoryUtils.MakeAndConfigure(procSection, typeof(Workers.ProcessManager), new object[] { this });
+
+ WriteLog(MessageType.Info, FROM, "Process Manager made");
+
+ if (m_ProcessManager is Service)
+ {
+ ((Service)m_ProcessManager).Start();
+ WriteLog(MessageType.Info, FROM, "Process Manager STARTED");
+ }
+ }
+ catch (Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM+"{ProcessManager start}", error.ToMessageWithType());
+ try
+ {
+ m_ProcessManager.Dispose();
+ }
+ catch{}
+
+ m_ProcessManager = null;
+ }
+
+ var hostSection = ConfigRoot[CONFIG_HOST_MANAGER_SECTION];
+ try
+ {
+ m_DynamicHostManager = FactoryUtils.MakeAndConfigure(procSection, typeof(Dynamic.HostManager), new object[] { this });
+
+ WriteLog(MessageType.Info, FROM, "Dynamic Host Manager made");
+
+ if (m_DynamicHostManager is Service)
+ {
+ ((Service)m_DynamicHostManager).Start();
+ WriteLog(MessageType.Info, FROM, "Dynamic Host Manager STARTED");
+ }
+ }
+ catch (Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM+ "{HostManager start}", error.ToMessageWithType());
+ try
+ {
+ m_DynamicHostManager.Dispose();
+ }
+ catch{}
+
+ m_DynamicHostManager = null;
+ }
+ }
+
+ protected override void DoCleanupApplication()
+ {
+ var FROM = GetType().FullName+".DoCleanupApplication()";
+
+ if (m_DynamicHostManager != null)
+ {
+ WriteLog(MessageType.Info, FROM, "Finalizing Dynamic Host Manager");
+ try
+ {
+ if (m_DynamicHostManager is Service)
+ {
+ ((Service)m_DynamicHostManager).SignalStop();
+ ((Service)m_DynamicHostManager).WaitForCompleteStop();
+ WriteLog(MessageType.Info, FROM, "Dynamic Host Manager STOPPED");
+ }
+
+ DisposableObject.DisposeAndNull(ref m_DynamicHostManager);
+ WriteLog(MessageType.Info, FROM, "Dynamic Host Manager DISPOSED");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.Error, FROM, "ERROR finalizing Dynamic Host Manager: " + error.ToMessageWithType());
+ }
+ }
+
+ if (m_ProcessManager!=null)
+ {
+ WriteLog(MessageType.Info, FROM, "Finalizing Process Manager");
+ try
+ {
+ if (m_ProcessManager is Service)
+ {
+ ((Service)m_ProcessManager).SignalStop();
+ ((Service)m_ProcessManager).WaitForCompleteStop();
+ WriteLog(MessageType.Info, FROM, "Process Manager STOPPED");
+ }
+
+ DisposableObject.DisposeAndNull(ref m_ProcessManager);
+ WriteLog(MessageType.Info, FROM, "Process Manager DISPOSED");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.Error, FROM, "ERROR finalizing Process Manager: " + error.ToMessageWithType());
+ }
+ }
+
+ if (m_LockManager!=null)
+ {
+ WriteLog(MessageType.Info, FROM, "Finalizing Lock Manager");
+ try
+ {
+ if (m_LockManager is Service)
+ {
+ ((Service)m_LockManager).SignalStop();
+ ((Service)m_LockManager).WaitForCompleteStop();
+ WriteLog(MessageType.Info, FROM, "Lock Manager STOPPED");
+ }
+
+ DisposableObject.DisposeAndNull(ref m_LockManager);
+ WriteLog(MessageType.Info, FROM, "lock manager DISPOSED");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.Error, FROM, "ERROR finalizing Lock Manager: " + error.ToMessageWithType());
+ }
+ }
+
+ if (m_WebManagerServer!=null)
+ {
+ WriteLog(MessageType.Info, FROM, "Finalizing Web Manager Server");
+ try
+ {
+ DisposableObject.DisposeAndNull(ref m_WebManagerServer);
+ WriteLog(MessageType.Info, FROM, "Web Manager Server DISPOSED");
+ }
+ catch (Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM, "ERROR finalizing Web Manager Server: " + error.ToMessageWithType());
+ }
+ }
+
+ if (m_GDIDProvider!=null)
+ {
+ WriteLog(MessageType.Info, FROM, "Finalizing GDIDProvider");
+ try
+ {
+ DisposableObject.DisposeAndNull(ref m_GDIDProvider);
+ WriteLog(MessageType.Info, FROM, "GDIDProvider DISPOSED");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.Error, FROM, "ERROR finalizing GDIDProvider: " + error.ToMessageWithType());
+ }
+ }
+
+ //Turn off Node - must be right before shutdown
+ WriteLog(MessageType.Info, FROM, "Finalizing TheSystem");
+ try
+ {
+ DisposableObject.DisposeAndNull(ref m_TheSystem);
+ WriteLog(MessageType.Info, FROM, "TheSystem DISPOSED");
+ }
+ catch(Exception error)
+ {
+ WriteLog(MessageType.CatastrophicError, FROM, "ERROR finalizing TheSystem: " + error.ToMessageWithType());
+ }
+
+ // Shutdown - must be last
+ base.DoCleanupApplication();
+ }
+
+ #endregion
+ }
+}
diff --git a/src/Agni/AppModel/AgniSystemBase.cs b/src/Agni/AppModel/AgniSystemBase.cs
new file mode 100644
index 0000000..399a0e3
--- /dev/null
+++ b/src/Agni/AppModel/AgniSystemBase.cs
@@ -0,0 +1,49 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.ServiceModel;
+
+namespace Agni.AppModel
+{
+ ///
+ /// Provides base AgniSystem implementation
+ ///
+ public class AgniSystemBase : Service, IAgniSystem
+ {
+ public const string AGNI_COMPONENT_NAME = "agni";
+
+ #region .ctor
+ internal AgniSystemBase(IAgniApplication app) : base(app)
+ {
+
+ }
+ #endregion
+
+ public override string ComponentCommonName { get { return AGNI_COMPONENT_NAME; } }
+
+ public bool Available { get { return false; } }
+
+ IOperationalStatus IAgniSystem.Status
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+
+ #region Protected
+ protected override void DoStart()
+ {
+ base.DoStart();
+ }
+
+ protected override void DoWaitForCompleteStop()
+ {
+ base.DoWaitForCompleteStop();
+ }
+ #endregion
+ }
+}
diff --git a/src/Agni/AppModel/BootConfLoader.cs b/src/Agni/AppModel/BootConfLoader.cs
new file mode 100644
index 0000000..5a53b72
--- /dev/null
+++ b/src/Agni/AppModel/BootConfLoader.cs
@@ -0,0 +1,475 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Security;
+using NFX.Environment;
+using NFX.IO.FileSystem;
+using NFX.ApplicationModel;
+
+using Agni.Identification;
+using Agni.Metabase;
+
+namespace Agni.AppModel
+{
+ ///
+ /// Gets the boot configuration for app container.
+ /// Reads the local process config to determine where the metabase is and what file system to use to connect to it.
+ /// Once metabase connection is established get all information form there identifying the host by agni/Host/$name.
+ /// If the name of the host is not set in config, then take it from AGNI_HOST_NAME environment var. If that name is blank then
+ /// take host name from: DEFAULT_WORLD_GLOBAL_ZONE_PATH+LOCAL_COMPUTER_NAME (NetBIOSName)
+ ///
+ public static class BootConfLoader
+ {
+ #region CONSTS
+ public const string ENV_VAR_METABASE_FS_ROOT = "AGNI_METABASE_FS_ROOT";
+ public const string ENV_VAR_METABASE_FS_TYPE = "AGNI_METABASE_FS_TYPE";
+ public const string ENV_VAR_METABASE_FS_CSTRING = "AGNI_METABASE_FS_CSTRING";
+
+ public const string ENV_VAR_HOST_NAME = "AGNI_HOST_NAME";
+
+ ///
+ /// Used to append to local machine name if ENV_VAR_HOST_NAME is not set, then this value is concatenated with local machine name
+ ///
+ public const string DEFAULT_HOST_ZONE_PATH = SysConsts.DEFAULT_WORLD_GLOBAL_ZONE_PATH;
+
+ public const string CONFIG_AGNI_SECTION = "agni";
+ public const string CONFIG_HOST_SECTION = "host";
+ public const string CONFIG_METABASE_SECTION = "metabase";
+ public const string CONFIG_FS_SECTION = "file-system";
+ public const string CONFIG_SESSION_CONNECT_PARAMS_SECTION = "session-connect-params";
+ public const string CONFIG_APPLICATION_NAME_ATTR = "app-name";
+
+
+ ///
+ /// Switch for agni options
+ ///
+ public const string CMD_ARG_AGNI_SWITCH = "agni";
+
+ ///
+ /// Used to specify application name from command line
+ ///
+ public const string CMD_ARG_APP_NAME = "app-name";
+
+ ///
+ /// Specifies host name in metabase, i.e. "/USA/East/Cle/A/I/wmed0001"
+ ///
+ public const string CONFIG_HOST_NAME_ATTR = "name";
+
+ ///
+ /// Root of file system
+ ///
+ public const string CONFIG_ROOT_ATTR = "root";
+
+
+ public const string LOG_FROM_BOOTLOADER = "AppBootLoader";
+
+
+ #endregion
+
+ private static SystemApplicationType s_SystemApplicationType;
+ private static bool s_Loaded;
+ private static Exception s_LoadException;
+ private static string s_HostName;
+ private static string s_DynamicHostNameSuffix;
+ private static Metabank s_Metabase;
+ private static string s_ParentZoneGovernorPrimaryHostName;
+
+ ///
+ /// Internal hack to compensate for c# inability to call .ctor within .ctor body
+ ///
+ internal static string[] SetSystemApplicationType(SystemApplicationType appType, string[] args)
+ {
+ s_SystemApplicationType = appType;
+ return args;
+ }
+
+ ///
+ /// Application container system type
+ ///
+ public static SystemApplicationType SystemApplicationType { get { return s_SystemApplicationType; } }
+
+ ///
+ /// Returns true after configuration has loaded
+ ///
+ public static bool Loaded { get{ return s_Loaded;} }
+
+ ///
+ /// Returns exception (if any) has occured during application config loading process
+ ///
+ public static Exception LoadException { get{ return s_LoadException;} }
+
+
+ ///
+ /// Host name as determined at boot
+ ///
+ public static string HostName { get { return s_HostName ?? string.Empty;} }
+
+
+ ///
+ /// For dynamic hosts, host name suffix as determined at boot. It is the last part of HostName for dynamic hosts
+ /// including the separation character
+ ///
+ public static string DynamicHostNameSuffix { get { return s_DynamicHostNameSuffix ?? string.Empty;} }
+
+
+ ///
+ /// True when metabase section host declares this host as dynamic and HostName ends with DynamicHostNameSuffix
+ ///
+ public static bool DynamicHost {get { return s_DynamicHostNameSuffix.IsNotNullOrWhiteSpace();}}
+
+
+ ///
+ /// Returns primary zone governer parent host as determined at boot or null if this is the top-level host
+ ///
+ public static string ParentZoneGovernorPrimaryHostName { get { return s_ParentZoneGovernorPrimaryHostName;}}
+
+
+
+ ///
+ /// Metabase as determined at boot or null in case of failure
+ ///
+ public static Metabank Metabase { get { return s_Metabase;} }
+
+
+
+ private class TestDisposer : IDisposable { public void Dispose(){ BootConfLoader.Unload();} }
+
+
+ internal static void SetDomainInvariantCulture()
+ {
+ System.Globalization.CultureInfo.DefaultThreadCurrentCulture =
+ System.Globalization.CultureInfo.InvariantCulture;
+ }
+
+ public static IDisposable LoadForTest(SystemApplicationType appType, Metabank mbase, string host, string dynamicHostNameSuffix = null)
+ {
+ SetDomainInvariantCulture();
+ s_Loaded = true;
+ s_SystemApplicationType = appType;
+ s_Metabase = mbase;
+ s_HostName = host;
+
+ if (dynamicHostNameSuffix.IsNotNullOrWhiteSpace())
+ s_DynamicHostNameSuffix = Metabank.HOST_DYNAMIC_SUFFIX_SEPARATOR + dynamicHostNameSuffix;
+ else
+ {
+ var sh = mbase.CatalogReg.NavigateHost(host);
+ if (sh.Dynamic)
+ s_DynamicHostNameSuffix = thisMachineDynamicNameSuffix();
+ }
+
+ if (s_DynamicHostNameSuffix.IsNotNullOrWhiteSpace())
+ s_HostName = s_HostName + s_DynamicHostNameSuffix;
+
+ SystemVarResolver.Bind();
+
+ Configuration.ProcesswideConfigNodeProviderType = typeof(Metabase.MetabankFileConfigNodeProvider);
+
+ return new TestDisposer();
+ }
+
+ private static string thisMachineDynamicNameSuffix()
+ {
+ //no spaces between
+ return "{0}{1}".Args(Metabank.HOST_DYNAMIC_SUFFIX_SEPARATOR, NFX.OS.Computer.UniqueNetworkSignature.Trim());//trim() as a safeguard
+ }
+
+
+ private static void writeLog(this ServiceBaseApplication app, NFX.Log.MessageType type, string text)
+ {
+ app.Log.Write( new NFX.Log.Message{
+ Type = type,
+ From = LOG_FROM_BOOTLOADER,
+ Text = "Entering agni app bootloader..."
+ });
+
+ }
+
+
+ ///
+ /// Loads initial application container configuration (app container may re-read it in future using metabase) per supplied local one also connecting the metabase
+ ///
+ public static Configuration Load(string[] cmdArgs, Configuration bootConfig)
+ {
+ if (s_Loaded)
+ throw new AgniException(StringConsts.APP_LOADER_ALREADY_LOADED_ERROR);
+
+ SetDomainInvariantCulture();
+
+ SystemVarResolver.Bind();
+
+ Configuration.ProcesswideConfigNodeProviderType = typeof(Metabase.MetabankFileConfigNodeProvider);
+
+ try
+ {
+ Configuration result = null;
+
+ //init Boot app container
+ using(var bootApp = new ServiceBaseApplication(cmdArgs, bootConfig.Root))
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Entering agni app bootloader...");
+
+ determineHostName(bootApp);
+
+ NFX.Log.Message.DefaultHostName = s_HostName;
+
+ mountMetabank(bootApp);
+
+ Metabank.SectionHost zoneGov;
+ bool isDynamicHost;
+ result = getEffectiveAppConfigAndZoneGovernor(bootApp, out zoneGov, out isDynamicHost);
+
+ if (zoneGov!=null) s_ParentZoneGovernorPrimaryHostName = zoneGov.RegionPath;
+
+ if (isDynamicHost)
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Info, "The meatabase host '{0}' is dynamic".Args(s_HostName));
+ s_DynamicHostNameSuffix = thisMachineDynamicNameSuffix();
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Obtained actual host dynamic suffix: '{0}' ".Args(s_DynamicHostNameSuffix));
+ s_HostName = s_HostName + s_DynamicHostNameSuffix;//no spaces between
+ bootApp.writeLog(NFX.Log.MessageType.Info, "The actual dynamic instance host name is: '{0}'".Args(s_HostName));
+
+ NFX.Log.Message.DefaultHostName = s_HostName;
+ }
+
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "...exiting agni app bootloader");
+ }
+
+ return result;
+ }
+ catch(Exception error)
+ {
+ s_LoadException = error;
+ throw new AgniException(StringConsts.APP_LOADER_ERROR + error.ToMessageWithType(), error);
+ }
+ finally
+ {
+ s_Loaded = true;
+ }
+ }
+
+ public static void ProcessAllExistingIncludes(ConfigSectionNode node, string includePragma, string level)
+ {
+ const int CONST_MAX_INCLUDE_DEPTH = 7;
+ try
+ {
+ for (int count = 0; node.ProcessIncludePragmas(true, includePragma); count++)
+ if (count >= CONST_MAX_INCLUDE_DEPTH)
+ throw new ConfigException(StringConsts.CONFIGURATION_INCLUDE_PRAGMA_DEPTH_ERROR.Args(CONST_MAX_INCLUDE_DEPTH));
+ }
+ catch (Exception error)
+ {
+ throw new ConfigException(StringConsts.CONFIGURATION_INCLUDE_PRAGMA_ERROR.Args(level, error.ToMessageWithType()), error);
+ }
+ }
+
+
+ internal static void Unload()
+ {
+ if (!s_Loaded) return;
+ s_Loaded = false;
+ s_SystemApplicationType = AppModel.SystemApplicationType.Unspecified;
+ s_HostName = null;
+ s_DynamicHostNameSuffix = null;
+ s_ParentZoneGovernorPrimaryHostName = null;
+ try
+ {
+ if (s_Metabase!=null)
+ {
+ var mb = s_Metabase;
+ var fs = mb.FileSystem;
+ s_Metabase = null;
+
+ try
+ {
+ mb.Dispose();
+ }
+ finally
+ {
+ if (fs!=null)
+ {
+ fs.Dispose();
+ }
+ }//finally
+ }
+ }
+ catch
+ {
+ //nowhere to log anymore as all loggers have stopped
+ }
+ }
+
+ #region .pvt .impl
+
+ private static void determineHostName(ServiceBaseApplication bootApp)
+ {
+ var hNode = bootApp.ConfigRoot[CONFIG_AGNI_SECTION][CONFIG_HOST_SECTION];
+
+ s_HostName = hNode.AttrByName(CONFIG_HOST_NAME_ATTR).Value;
+
+ if (s_HostName.IsNullOrWhiteSpace())
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Warning, "Host name was not specified in config, trying to take from machine env var {0}".Args(ENV_VAR_HOST_NAME));
+ s_HostName = System.Environment.GetEnvironmentVariable(ENV_VAR_HOST_NAME);
+ }
+ if (s_HostName.IsNullOrWhiteSpace())
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Warning, "Host name was not specified in neither config nor env var, taking from local computer name");
+ s_HostName = "{0}/{1}".Args(DEFAULT_HOST_ZONE_PATH, System.Environment.MachineName);
+ }
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Host name: " + s_HostName);
+ }
+
+
+ private static void mountMetabank(ServiceBaseApplication bootApp)
+ {
+ var mNode = bootApp.ConfigRoot[CONFIG_AGNI_SECTION][CONFIG_METABASE_SECTION];
+
+ ensureMetabaseAppName(bootApp, mNode);
+
+
+ FileSystemSessionConnectParams fsSessionConnectParams;
+ var fs = getFileSystem(bootApp, mNode, out fsSessionConnectParams);
+
+ var fsRoot = mNode[CONFIG_FS_SECTION].AttrByName(CONFIG_ROOT_ATTR).Value;
+ if (fsRoot.IsNullOrWhiteSpace())
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Info,
+ "Metabase fs root is null in config, trying to take from machine env var {0}".Args(ENV_VAR_METABASE_FS_ROOT));
+ fsRoot = System.Environment.GetEnvironmentVariable(ENV_VAR_METABASE_FS_ROOT);
+ }
+
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Metabase FS root: " + fsRoot);
+
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Mounting metabank...");
+ try
+ {
+ s_Metabase = new Metabank(fs, fsSessionConnectParams, fsRoot );
+ }
+ catch(Exception error)
+ {
+ bootApp.writeLog(NFX.Log.MessageType.CatastrophicError, error.ToMessageWithType());
+ throw error;
+ }
+ bootApp.writeLog(NFX.Log.MessageType.Info, "...Metabank mounted");
+ }
+
+
+ private static Configuration getEffectiveAppConfigAndZoneGovernor(ServiceBaseApplication bootApp,
+ out Metabank.SectionHost zoneGovernorSection,
+ out bool isDynamicHost)
+ {
+ Configuration result = null;
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Getting effective app config for '{0}'...".Args(AgniSystem.MetabaseApplicationName));
+ try
+ {
+ var host = s_Metabase.CatalogReg.NavigateHost(s_HostName);
+
+ result = host.GetEffectiveAppConfig(AgniSystem.MetabaseApplicationName).Configuration;
+
+ zoneGovernorSection = host.ParentZoneGovernorPrimaryHost();//Looking in the same NOC only
+ isDynamicHost = host.Dynamic;
+ }
+ catch(Exception error)
+ {
+ bootApp.writeLog(NFX.Log.MessageType.CatastrophicError, error.ToMessageWithType());
+ throw error;
+ }
+ bootApp.writeLog(NFX.Log.MessageType.Info, "...config obtained");
+
+ return result;
+ }
+
+
+ private static void ensureMetabaseAppName(ServiceBaseApplication bootApp, IConfigSectionNode mNode)
+ {
+ if (AgniSystem.MetabaseApplicationName==null)
+ {
+ var appName = bootApp.CommandArgs[CMD_ARG_AGNI_SWITCH].AttrByName(CMD_ARG_APP_NAME).Value;
+ if (appName.IsNotNullOrWhiteSpace())
+ bootApp.writeLog(NFX.Log.MessageType.Info,
+ "Metabase application name was not defined in code, but is injected from cmd arg '-{0} {1}='".Args(CMD_ARG_AGNI_SWITCH, CMD_ARG_APP_NAME));
+ else
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Warning,
+ "Metabase application name was not defined in code or cmd switch, reading from '{0}/${1}'".Args(CONFIG_METABASE_SECTION, CONFIG_APPLICATION_NAME_ATTR));
+ appName = mNode.AttrByName(CONFIG_APPLICATION_NAME_ATTR).Value;
+ }
+
+ try
+ {
+ AgniSystem.MetabaseApplicationName = appName;
+ }
+ catch(Exception error)
+ {
+ bootApp.writeLog(NFX.Log.MessageType.CatastrophicError, error.ToMessageWithType());
+ throw error;
+ }
+ bootApp.writeLog(NFX.Log.MessageType.Info,
+ "Metabase application name from config set to: "+AgniSystem.MetabaseApplicationName);
+ }
+ else
+ {
+ bootApp.writeLog(NFX.Log.MessageType.Info,
+ "Metabase application name defined in code: "+AgniSystem.MetabaseApplicationName);
+
+ if (mNode.AttrByName(CONFIG_APPLICATION_NAME_ATTR).Exists)
+ bootApp.writeLog(NFX.Log.MessageType.Warning,
+ "Metabase application name defined in code but the boot config also defines the name which was ignored");
+ }
+ }
+
+
+ private static IFileSystem getFileSystem(ServiceBaseApplication bootApp,
+ IConfigSectionNode mNode,
+ out FileSystemSessionConnectParams cParams)
+ {
+ IFileSystem result = null;
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "Making metabase FS instance...");
+
+ var fsNode = mNode[CONFIG_FS_SECTION];
+
+ var fsFallbackTypeName = Environment.GetEnvironmentVariable(ENV_VAR_METABASE_FS_TYPE);
+ var fsFallbackType = typeof(NFX.IO.FileSystem.Local.LocalFileSystem);
+
+ if (fsFallbackTypeName.IsNotNullOrWhiteSpace())
+ fsFallbackType = Type.GetType(fsFallbackTypeName, true);
+
+ result = FactoryUtils.MakeAndConfigure(fsNode,
+ fsFallbackType,
+ args: new object[]{CONFIG_METABASE_SECTION, fsNode});
+
+ var paramsNode = fsNode[CONFIG_SESSION_CONNECT_PARAMS_SECTION];
+ if (paramsNode.Exists)
+ {
+ cParams = FileSystemSessionConnectParams.Make(paramsNode);
+ }
+ else
+ {
+ var fsFallbackCString = Environment.GetEnvironmentVariable(ENV_VAR_METABASE_FS_CSTRING);
+ if (fsFallbackCString.IsNotNullOrWhiteSpace())
+ cParams = FileSystemSessionConnectParams.Make(fsFallbackCString);
+ else
+ cParams = new FileSystemSessionConnectParams(){ User = User.Fake};
+ }
+
+ bootApp.writeLog(NFX.Log.MessageType.Info, "...Metabase FS FileSystemSessionConnectParams instance of '{0}' made".Args(cParams.GetType().FullName));
+ bootApp.writeLog(NFX.Log.MessageType.Info, "...Metabase FS instance of '{0}' made".Args(result.GetType().FullName));
+
+ return result;
+ }
+
+
+ #endregion
+
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/Cmdlets/Install.cs b/src/Agni/AppModel/HostGovernor/Cmdlets/Install.cs
new file mode 100644
index 0000000..4e0c233
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/Cmdlets/Install.cs
@@ -0,0 +1,60 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+using Agni.AppModel.Terminal;
+
+namespace Agni.AppModel.HostGovernor.Cmdlets
+{
+ public class Install : Cmdlet
+ {
+ public const string CONFIG_FORCE_ATTR = "force";
+
+ public Install(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var list = new List();
+ var force = m_Args.AttrByName(CONFIG_FORCE_ATTR).ValueAsBool(false);
+
+ if (force)
+ App.Log.Write( new NFX.Log.Message
+ {
+ Type = NFX.Log.MessageType.Warning,
+ Topic = SysConsts.LOG_TOPIC_APP_MANAGEMENT,
+ From = "{0}.Force".Args(GetType().FullName),
+ Text = "Installation with force=true initiated"
+ });
+
+ var anew = HostGovernorService.Instance.CheckAndPerformLocalSoftwareInstallation(list, force);
+
+ var progress = list.Aggregate(new StringBuilder(), (sb, s) => sb.AppendLine(s)).ToString();
+
+ App.Log.Write( new NFX.Log.Message
+ {
+ Type = NFX.Log.MessageType.Warning,
+ Topic = SysConsts.LOG_TOPIC_APP_MANAGEMENT,
+ From = "{0}.Force".Args(GetType().FullName),
+ Text = "Installation finished. Installed anew: " + anew,
+ Parameters = progress
+ });
+
+ return progress;
+ }
+
+ public override string GetHelp()
+ {
+ return
+@"Initiates check and installation of local software.
+ Parameters:
+ force=bool - force reinstall
+";
+ }
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/Cmdlets/Run.cs b/src/Agni/AppModel/HostGovernor/Cmdlets/Run.cs
new file mode 100644
index 0000000..6b0bdae
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/Cmdlets/Run.cs
@@ -0,0 +1,57 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+using Agni.AppModel.Terminal;
+
+namespace Agni.AppModel.HostGovernor.Cmdlets
+{
+ public class Run : Cmdlet
+ {
+ public const string CONFIG_CMD_ATTR = "cmd";
+ public const string CONFIG_ARGS_ATTR = "args";
+ public const string CONFIG_TIMEOUT_ATTR = "timeout";
+
+
+ public Run(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+
+ public override string Execute()
+ {
+ var cmd = m_Args.AttrByName(CONFIG_CMD_ATTR).Value;
+ if (cmd.IsNullOrWhiteSpace()) return "Missing 'cmd' - command to run";
+
+ var args = m_Args.AttrByName(CONFIG_ARGS_ATTR).ValueAsString(string.Empty);
+
+ var timeout = m_Args.AttrByName(CONFIG_TIMEOUT_ATTR).ValueAsInt(5000);
+
+ if (timeout<0) return "Timeout must be > 0 or blank";
+
+
+ bool timedOut;
+ var result = NFX.OS.ProcessRunner.Run(cmd, args, out timedOut, timeout);
+
+ if (timedOut)
+ result += "\n ....Process TIMED OUT....";
+
+ return result;
+ }
+
+ public override string GetHelp()
+ {
+ return
+@"Runs process blocking until it either exits or timeout expires.
+ Parameters:
+ cmd=string - command to run
+ args=int - arguments
+ timeout=int_ms - for how long to wait for process exit
+";
+ }
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/Exceptions.cs b/src/Agni/AppModel/HostGovernor/Exceptions.cs
new file mode 100644
index 0000000..128de6e
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/Exceptions.cs
@@ -0,0 +1,32 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Runtime.Serialization;
+
+namespace Agni.AppModel.HostGovernor
+{
+ ///
+ /// Thrown to indicate AHGOV related problems
+ ///
+ [Serializable]
+ public class AHGOVException : AgniException
+ {
+ public AHGOVException() : base() { }
+ public AHGOVException(string message) : base(message) { }
+ public AHGOVException(string message, Exception inner) : base(message, inner) { }
+ protected AHGOVException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+
+ ///
+ /// Thrown to indicate AHGOV ManagedApp-related problems
+ ///
+ [Serializable]
+ public class ManagedAppException : AHGOVException
+ {
+ public ManagedAppException() : base() { }
+ public ManagedAppException(string message) : base(message) { }
+ public ManagedAppException(string message, Exception inner) : base(message, inner) { }
+ protected ManagedAppException(SerializationInfo info, StreamingContext context) : base(info, context) { }
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/HostGovernorServer.cs b/src/Agni/AppModel/HostGovernor/HostGovernorServer.cs
new file mode 100644
index 0000000..454b896
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/HostGovernorServer.cs
@@ -0,0 +1,22 @@
+using System;
+
+namespace Agni.AppModel.HostGovernor
+{
+ ///
+ /// Implements contracts trampoline that uses a singleton instance of HostGovernorService
+ ///
+ public class HostGovernorServer
+ : Agni.Contracts.IHostGovernor,
+ Agni.Contracts.IPinger
+ {
+ public Contracts.HostInfo GetHostInfo()
+ {
+ return HostGovernorService.Instance.GetHostInfo();
+ }
+
+ public void Ping()
+ {
+ HostGovernorService.Instance.Ping();
+ }
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/HostGovernorService.cs b/src/Agni/AppModel/HostGovernor/HostGovernorService.cs
new file mode 100644
index 0000000..d08d686
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/HostGovernorService.cs
@@ -0,0 +1,513 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.IO;
+using System.Threading;
+
+using NFX;
+using NFX.Log;
+using NFX.Environment;
+using NFX.Collections;
+using NFX.ApplicationModel;
+using NFX.ServiceModel;
+using NFX.IO.FileSystem;
+using NFX.IO.FileSystem.Packaging;
+
+using Agni.Metabase;
+
+namespace Agni.AppModel.HostGovernor
+{
+
+ ///
+ /// Provides Host Governor Services - this is a singleton class
+ ///
+ public sealed class HostGovernorService : Service, Contracts.IPinger
+ {
+ #region CONSTS
+ public const string THREAD_NAME = "HostGovernorService";
+ public const int THREAD_GRANULARITY_MS = 3000;
+
+ public const string CONFIG_HOST_GOVERNOR_SECTION = "host-governor";
+
+ public const string CONFIG_DYNAMIC_HOST_ID_ATTR = "dynamic-host-id";
+ public const string CONFIG_STARTUP_INSTALL_CHECK_ATTR = "startup-install-check";
+ #endregion
+
+ #region Static
+ private static object s_InstanceLock = new object();
+ private static volatile HostGovernorService s_Instance;
+
+ ///
+ /// Returns true to indicate that this process has host governor instance
+ ///
+ public static bool IsHostGovernor
+ {
+ get { return s_Instance!=null; }
+ }
+
+ ///
+ /// Returns singleton instance or throws if service has not been allocated yet
+ ///
+ public static HostGovernorService Instance
+ {
+ get
+ {
+ var instance = s_Instance;
+ if (instance==null)
+ throw new AHGOVException(StringConsts.AHGOV_INSTANCE_NOT_ALLOCATED_ERROR);
+
+ return instance;
+ }
+ }
+ #endregion
+
+ #region .ctor/.dctor
+ ///
+ /// Creates a singleton instance or throws if instance is already created
+ ///
+ public HostGovernorService(bool launchedByARD, bool ardUpdateProblem) : base(null)
+ {
+ if (!AgniSystem.IsMetabase)
+ throw new AHGOVException(StringConsts.METABASE_NOT_AVAILABLE_ERROR.Args(GetType().FullName+".ctor()"));
+
+ lock(s_InstanceLock)
+ {
+ if (s_Instance!=null)
+ throw new AHGOVException(StringConsts.AHGOV_INSTANCE_ALREADY_ALLOCATED_ERROR);
+
+ m_LaunchedByARD = launchedByARD;
+ m_ARDUpdateProblem = ardUpdateProblem;
+
+ var exeName = System.Reflection.Assembly.GetEntryAssembly().Location;
+ m_RootPath = Directory.GetParent(Path.GetDirectoryName(exeName)).FullName;
+
+ s_Instance = this;
+ }
+ }
+
+ protected override void Destructor()
+ {
+ lock(s_InstanceLock)
+ {
+ base.Destructor();
+ s_Instance = null;
+ }
+ }
+ #endregion
+
+ #region Fields
+
+
+ private string m_RootPath;
+
+ private bool m_LaunchedByARD;
+ private bool m_ARDUpdateProblem;
+ private bool m_NeedsProcessRestart;
+ private bool m_StartupInstallCheck = true;
+ private Thread m_Thread;
+ private AutoResetEvent m_WaitEvent;
+
+ private List m_Apps = new List();
+
+ private int m_AppStartOrder;
+
+ private string m_DynamicHostID;
+ #endregion
+
+ #region Properties
+
+ public override string ComponentCommonName { get { return "hgov"; }}
+
+ ///
+ /// Returns true when this process was launched by Agni Root Daemon as opposed to being launched from console/script
+ ///
+ public bool LaunchedByARD {get{ return m_LaunchedByARD;}}
+
+ ///
+ /// Returns true when this process was launched by Agni Root Daemon that could not perform update properly -
+ /// most likely UPD and RUN folder slocked by some other process
+ ///
+ public bool ARDUpdateProblem {get{ return m_ARDUpdateProblem;}}
+
+
+ ///
+ /// Indicates whether install version check is done on service start (vs being invoked by command)
+ ///
+ public bool StartupInstallCheck {get{ return m_StartupInstallCheck;}}
+
+ ///
+ /// Returns the very root path under which "ard","/run-netf","/run-core","/upd" are
+ ///
+ public string RootPath { get{return m_RootPath;}}
+
+ ///
+ /// Returns the full path to "/run" directory out of which the current AHGOV process was launched
+ ///
+ public string RunPath
+ {
+ get
+ {
+ var isCore = NFX.PAL.PlatformAbstractionLayer.IsNetCore;
+ return Path.Combine(m_RootPath, isCore ? SysConsts.HGOV_RUN_CORE_DIR : SysConsts.HGOV_RUN_NETF_DIR);
+ }
+ }
+
+ ///
+ /// Returns the full path to "/upd" directory where AHGOV will place newer files
+ ///
+ public string UpdatePath { get{return Path.Combine(m_RootPath, SysConsts.HGOV_UPDATE_DIR);}}
+
+
+ ///
+ /// Returns a thread-safe copy of ManagedApps in the instance - applications managed by Agni Host Governor
+ ///
+ public IEnumerable ManagedApps
+ {
+ get
+ {
+ lock(m_Apps)
+ return m_Apps.ToList();
+ }
+ }
+
+ ///
+ /// Becomes true to indocate that the app;ication process should be restarted (by ARD)
+ ///
+ public bool NeedsProcessRestart
+ {
+ get { return m_NeedsProcessRestart;}
+ }
+
+ ///
+ /// Gets all unique Metabank.SectionApplication.AppPackage(s) for all applications on this host
+ ///
+ public IEnumerable AllPackages
+ {
+ get
+ {
+ var result = new List();
+ foreach(var application in m_Apps)
+ foreach(var package in application.Packages)
+ if (!result.Any(ap => ap.Name.EqualsIgnoreCase( package.Name )&&
+ ap.MatchedPackage.Equals(package.MatchedPackage) &&
+ ap.Path.EqualsIgnoreCase( package.Path ))) result.Add(package);
+ return result;
+ }
+ }
+
+ ///
+ /// Returns current application start order, the sequence # of the next app start
+ ///
+ public int AppStartOrder
+ {
+ get { return m_AppStartOrder;}
+ }
+
+ public Contracts.DynamicHostID? DynamicHostID
+ {
+ get
+ {
+ if (m_DynamicHostID.IsNullOrWhiteSpace()) return null;
+ return new Contracts.DynamicHostID(m_DynamicHostID, AgniSystem.HostMetabaseSection.ParentZone.RegionPath);
+ }
+ }
+
+ #endregion
+
+ #region Public
+
+ public Contracts.HostInfo GetHostInfo()
+ {
+ return Contracts.HostInfo.ForThisHost();
+ }
+
+ public void Ping()
+ {
+ //does nothing. just comes back
+ }
+
+ ///
+ /// Initiates check of locally installed packages and if they are different than install set, reinstalls in UpdatePath
+ ///
+ /// True if physical install was performed and AHGOV needs to restart so ARD may respawn it
+ public bool CheckAndPerformLocalSoftwareInstallation(IList progress, bool force = false)
+ {
+ IOMiscUtils.EnsureDirectoryDeleted(UpdatePath);
+
+ var anew = AgniSystem.Metabase.CatalogBin.CheckAndPerformLocalSoftwareInstallation(progress, force);
+ if (!anew) return false;
+ //Flag the end of successfull installation
+ File.WriteAllText(Path.Combine(UpdatePath, SysConsts.HGOV_UPDATE_FINISHED_FILE), SysConsts.HGOV_UPDATE_FINISHED_FILE_OK_CONTENT);
+
+ m_NeedsProcessRestart = true;
+
+ return true;
+ }
+
+
+
+ #endregion
+
+ #region Protected
+
+ protected override void DoConfigure(IConfigSectionNode node)
+ {
+ if (node==null)
+ node = App.ConfigRoot[CONFIG_HOST_GOVERNOR_SECTION];
+
+
+ m_StartupInstallCheck = node.AttrByName(CONFIG_STARTUP_INSTALL_CHECK_ATTR).ValueAsBool(true);
+ m_DynamicHostID = node.AttrByName(CONFIG_DYNAMIC_HOST_ID_ATTR).ValueAsString();
+
+ base.DoConfigure(node);
+ }
+
+ protected override void DoStart()
+ {
+ try
+ {
+ lock(m_Apps)
+ {
+ m_Apps.Clear();
+
+ //add managed apps as specified by host's role
+ foreach(var appInfo in AgniSystem.HostMetabaseSection.Role.Applications)
+ {
+ var app = new ManagedApp(this, appInfo);
+ m_Apps.Add(app);//the app is started later when it is ready
+ }
+ }
+
+
+ m_WaitEvent = new AutoResetEvent(false);
+
+ m_Thread = new Thread(threadSpin);
+ m_Thread.Name = THREAD_NAME;
+ m_Thread.Start();
+
+
+ }
+ catch
+ {
+ AbortStart();
+ throw;
+ }
+ }
+
+ protected override void DoSignalStop()
+ {
+ stopAllApps(false);
+ }
+
+ protected override void DoWaitForCompleteStop()
+ {
+ m_WaitEvent.Set();
+
+ m_Thread.Join();
+ m_Thread = null;
+
+ m_WaitEvent.Close();
+ m_WaitEvent = null;
+
+ base.DoWaitForCompleteStop();
+ }
+
+ #endregion
+
+
+ #region .pvt .impl
+ private void threadSpin()
+ {
+ const string FROM = "threadSpin()";
+ try
+ {
+ if (m_ARDUpdateProblem)
+ log(MessageType.CatastrophicError, FROM, StringConsts.AHGOV_ARD_UPDATE_PROBLEM_ERROR);
+ else
+ {
+ if (StartupInstallCheck)
+ if (checkInstall()) return;
+ }
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, FROM, "checkInstall() leaked: " + error.ToMessageWithType(), error);
+ }
+
+ try
+ {
+ autoStartApps();
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, FROM, "autoStartApps() leaked: " + error.ToMessageWithType(), error);
+ }
+
+ try
+ {
+ var first = true;
+ while (Running)
+ {
+ try
+ {
+ if (!first) m_WaitEvent.WaitOne(THREAD_GRANULARITY_MS);
+ else first = false;
+
+ var now = App.TimeSource.UTCNow;
+ //check for stopped processes that supposed to be auto-started
+
+ //Notify Zone governor about this host(Host Registry service)
+ registerWithZGov(now);
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, FROM, "while(Running){} leaked: " + error.ToMessageWithType(), error);
+ }
+ }
+ }
+ finally
+ {
+ stopAllApps(true);//block
+ }
+ }
+
+ private DateTime m_ScheduledZGovRegistration;
+ private int m_ConsecutiveZGovRegFailures;
+ private const int CONSECUTIVE_ZGOV_REG_FAIL_LONG_RETRY_THRESHOLD = 7;
+
+ private void registerWithZGov(DateTime now)
+ {
+ const string FROM = "registerWithZGov()";
+
+ if (now(zgov.RegionPath))
+ {
+ cl.RegisterSubordinateHost(Contracts.HostInfo.ForThisHost(), DynamicHostID);
+ ok = true;
+ break;
+ }
+ }
+ catch (Exception error)
+ {
+ log(MessageType.Error, FROM, "RegisterSubordinateHost('{0}') svc call threw: {1} ".Args(zgov.RegionPath, error.ToMessageWithType()), error, related: logid);
+ }
+ }
+
+ if (!ok && !thisHostHasZGov)
+ log(MessageType.Error, FROM, "Could not send this host registration to any of the ZGovs tried", related: logid);
+
+ //20151016 DKh added IF so if there is no ZGov it does not get bombarded
+ if (ok)
+ {
+ m_ScheduledZGovRegistration = now.AddMilliseconds(5000 + NFX.ExternalRandomGenerator.Instance.NextScaledRandomInteger(0, 25000));
+ m_ConsecutiveZGovRegFailures = 0;
+ }
+ else
+ {//20151016 DKh added IF so if there is no ZGov it does not get bombarded
+ if (any)
+ {
+ m_ConsecutiveZGovRegFailures++;
+ if (m_ConsecutiveZGovRegFailures();
+ list.GetReadOnlyEvent = (c)=>false;
+ list.ChangeEvent = delegate(EventedList cl, EventedList.ChangeType change, EventPhase phase, int idx, string item)
+ {
+ if (phase==EventPhase.After)
+ log(MessageType.Trace, "checkInstall()", " * " + item, related: id);
+ };
+ var anew = CheckAndPerformLocalSoftwareInstallation(list, false);
+ if (anew)
+ {
+ log(MessageType.Info, "checkInstall()", "CheckAndPerformLocalSoftwareInstallation() returned true. Will be restarting", related: id);
+ return true; //needs restart
+ }
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "checkInstall()", "CheckAndPerformLocalSoftwareInstallation() leaked: " + error.ToMessageWithType(), error, related: id);
+ }
+ return false;
+ }
+
+ private void autoStartApps()
+ {
+ var autoApps = m_Apps.Where(a => !a.AppInfo.Name.EqualsIgnoreCase(SysConsts.APP_NAME_HGOV) &&
+ a.AppInfo.AutoRun.HasValue);
+ foreach(var app in autoApps.OrderBy(a=>a.AppInfo.AutoRun.Value))
+ try
+ {
+ app.Start();
+ app.m_StartOrder = m_AppStartOrder;
+ m_AppStartOrder++;
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "autoStartApps()", "App '{0}' Leaked: {1}".Args(app.Name, error.ToMessageWithType()), error);
+ }
+ }
+
+ private void stopAllApps(bool block)
+ {
+ foreach(var app in m_Apps.OrderBy(p=>-p.StartOrder))//in reverse order of start
+ try
+ {
+ if (block)
+ app.WaitForCompleteStop();
+ else
+ app.SignalStop();
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "stopAllApps(block:{0})".Args(block), "Svc stop leaked: " + error.ToMessageWithType(), error);
+ }
+ }
+
+
+ internal void log(MessageType type, string from, string text, Exception error = null, Guid? related = null)
+ {
+ var msg = new NFX.Log.Message
+ {
+ Type = type,
+ Topic = SysConsts.LOG_TOPIC_APP_MANAGEMENT,
+ From = "{0}.{1}".Args(GetType().FullName, from),
+ Text = text,
+ Exception = error
+ };
+
+ if (related.HasValue) msg.RelatedTo = related.Value;
+
+ App.Log.Write( msg );
+ }
+ #endregion
+ }
+}
diff --git a/src/Agni/AppModel/HostGovernor/ManagedApp.cs b/src/Agni/AppModel/HostGovernor/ManagedApp.cs
new file mode 100644
index 0000000..185a254
--- /dev/null
+++ b/src/Agni/AppModel/HostGovernor/ManagedApp.cs
@@ -0,0 +1,249 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+using System.Threading;
+
+using NFX;
+using NFX.Log;
+using NFX.ServiceModel;
+
+using Agni.Metabase;
+
+namespace Agni.AppModel.HostGovernor
+{
+ ///
+ /// Represents an application managed by the HostGovernorService instance - the agni application that gets installed/updated/executed by
+ /// the Agni Governor Process. The standard service's Start/Stop commands launch the actual application process
+ ///
+ public sealed class ManagedApp : Service
+ {
+ #region CONSTS
+
+ public const int APP_PROCESS_LAUNCH_TIMEOUT_MS = 20000;
+
+ #endregion
+
+
+ #region .ctor
+ internal ManagedApp(HostGovernorService director, Metabank.SectionRole.AppInfo appInfo) : base(director)
+ {
+ Name = appInfo.ToString();
+ m_AppInfo = appInfo;
+ m_Packages = AgniSystem.HostMetabaseSection.GetAppPackages(appInfo.Name).ToList();
+ }
+ #endregion
+
+ #region Fields
+ private Metabank.SectionRole.AppInfo m_AppInfo;
+ internal int m_StartOrder;
+ private List m_Packages;
+
+ private Process m_Process;
+
+
+ #endregion
+
+ #region Properties
+
+ ///
+ /// Returns the AppInfo as feteched from the metabase
+ ///
+ public Metabank.SectionRole.AppInfo AppInfo { get{ return m_AppInfo;}}
+
+
+ ///
+ /// Returns packages that this application have
+ ///
+ public IEnumerable Packages { get{return m_Packages;}}
+
+ ///
+ /// Returns the start order of this app - when it was launched relative to others
+ ///
+ public int StartOrder { get{ return m_StartOrder;}}
+
+
+ ///
+ /// Returns executable launch command obtained from the role, or if it is blank from app itself
+ ///
+ public string ExeFile
+ {
+ get
+ {
+ var result = AppInfo.ExeFile;
+ if (result.IsNotNullOrWhiteSpace()) return result;
+ result = AgniSystem.Metabase.CatalogApp.Applications[AppInfo.Name].ExeFile;
+ return result;
+ }
+ }
+
+ ///
+ /// Returns executable launch command arguments obtained from the role, or if it is blank from app itself
+ ///
+ public string ExeArgs
+ {
+ get
+ {
+ var result = AppInfo.ExeArgs;
+ if (result.IsNotNullOrWhiteSpace()) return result;
+ result = AgniSystem.Metabase.CatalogApp.Applications[AppInfo.Name].ExeArgs;
+ return result;
+ }
+ }
+
+
+ #endregion
+
+ #region Public
+
+
+ #endregion
+
+ #region Protected
+
+ protected override void DoStart()
+ {
+ try
+ {
+ startProcess();
+ }
+ catch(Exception error)
+ {
+ AbortStart();
+ log(MessageType.CatastrophicError, "DoStart()", "Svc start leaked: " + error.ToMessageWithType(), error);
+ throw error;
+ }
+ }
+
+
+
+ protected override void DoSignalStop()
+ {
+ try
+ {
+ if (m_Process!=null)
+ {
+ if (!m_Process.HasExited)
+ {
+ log(MessageType.Info, "DoSignalStop()", "Sending application process a line to gracefully exit");
+ m_Process.StandardInput.WriteLine("");//Gracefully tells Application to exit
+ }
+ }
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "DoSignalStop()", "Svc signal stop leaked: " + error.ToMessageWithType(), error);
+ throw error;
+ }
+ }
+
+ protected override void DoWaitForCompleteStop()
+ {
+ try
+ {
+ closeProcess();
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "DoWaitForCompleteStop()", "Svc stop leaked: " + error.ToMessageWithType(), error);
+ throw error;
+ }
+ }
+
+ #endregion
+
+ #region .pvt .impl
+
+ private void processExited(object sender, EventArgs args)
+ {
+ if (Status!=ControlStatus.Active) return;//do not use Running here as it also checks for Starting
+
+ try
+ {
+ closeProcess();
+ }
+ catch(Exception error)
+ {
+ log(MessageType.CatastrophicError, "processExited()", "Process exited leaked: " + error.ToMessageWithType(), error);
+ throw error;
+ }
+ }
+
+ private void startProcess()
+ {
+ var rel = Guid.NewGuid();
+ var exe = System.IO.Path.Combine(ComponentDirector.RunPath, ExeFile);
+ var args = ExeArgs;
+
+ m_Process = new Process();
+ m_Process.StartInfo.FileName = exe;
+ m_Process.StartInfo.WorkingDirectory = ComponentDirector.RunPath;
+ m_Process.StartInfo.Arguments = args;
+ m_Process.StartInfo.UseShellExecute = false;
+ m_Process.StartInfo.CreateNoWindow = true;
+ m_Process.StartInfo.RedirectStandardInput = true;
+ m_Process.StartInfo.RedirectStandardOutput = true;
+ m_Process.StartInfo.WindowStyle = ProcessWindowStyle.Hidden;
+ m_Process.EnableRaisingEvents = true;//this must be true to get events
+ m_Process.Exited += processExited;
+
+
+ log(MessageType.Info, "startProcess()", "Starting '{0}'/'{1}'".Args(exe, args), null, rel);
+ m_Process.Start();
+ log(MessageType.Info, "startProcess()", "Process Started. Waiting for OK.", null, rel);
+
+ var watch = Stopwatch.StartNew();
+ while (!m_Process.HasExited &&
+ (m_Process.StandardOutput==null || m_Process.StandardOutput.EndOfStream) &&
+ watch.ElapsedMilliseconds < APP_PROCESS_LAUNCH_TIMEOUT_MS)
+ {
+ Thread.Sleep(500);
+ }
+
+ if (m_Process.HasExited)
+ throw new AHGOVException(StringConsts.AHGOV_APP_PROCESS_CRASHED_AT_STARTUP_ERROR.Args(Name, exe, args));
+
+ if (m_Process.StandardOutput==null)
+ throw new AHGOVException(StringConsts.AHGOV_APP_PROCESS_STD_OUT_NULL_ERROR.Args(Name, exe, args));
+
+ if (!m_Process.StandardOutput.EndOfStream)
+ {
+ if (m_Process.StandardOutput.Read()=='O' &&
+ m_Process.StandardOutput.Read()=='K' &&
+ m_Process.StandardOutput.Read()=='.')
+ {
+ log(MessageType.Info, "startProcess()", "Started and returned OK. '{0}'/'{1}'".Args(exe, args), null, rel);
+ return;//success
+ }
+ }
+
+ throw new AHGOVException(StringConsts.AHGOV_APP_PROCESS_NO_SUCCESS_AT_STARTUP_ERROR.Args(Name, exe, args));
+ }
+
+ private void closeProcess()
+ {
+ var rel = Guid.NewGuid();
+
+ if (m_Process!=null)
+ {
+ if (!m_Process.HasExited)
+ {
+ log(MessageType.Info, "closeProcess()", "Waiting for app process to exit...", null, rel);
+ // m_Process.StandardInput.WriteLine("");//Gracefully tells Application to exit
+ m_Process.WaitForExit();
+ log(MessageType.Info, "closeProcess()", "App process exited", null, rel);
+ }
+ m_Process.Close();
+ m_Process = null;
+ }
+ }
+
+
+ internal void log(MessageType type, string from, string text, Exception error = null, Guid? related = null)
+ {
+ ComponentDirector.log(type, "ManagedApp({0}).{1}".Args(Name, from), text, error, related);
+ }
+ #endregion
+ }
+}
diff --git a/src/Agni/AppModel/IAgniApplication.cs b/src/Agni/AppModel/IAgniApplication.cs
new file mode 100644
index 0000000..15031ac
--- /dev/null
+++ b/src/Agni/AppModel/IAgniApplication.cs
@@ -0,0 +1,78 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX.Environment;
+using NFX.ApplicationModel;
+using NFX.DataAccess;
+
+using Agni.Identification;
+
+namespace Agni.AppModel
+{
+
+ ///
+ /// Denotes system application/process types that this app container has, i.e.: HostGovernor, WebServer, etc.
+ ///
+ public enum SystemApplicationType
+ {
+ Unspecified = 0,
+ HostGovernor,
+ ZoneGovernor,
+ WebServer,
+ GDIDAuthority,
+ ServiceHost,
+ ProcessHost,
+ SecurityAuthority,
+ TestRig,
+ Tool
+ }
+
+
+ ///
+ /// Defines a contract for applications
+ ///
+ public interface IAgniApplication : IApplication
+ {
+ ///
+ /// Returns the name that uniquely identifies this application in the metabase. Every process/executable must provide its unique application name in metabase
+ ///
+ string MetabaseApplicationName { get; }
+
+ ///
+ /// References system-related functionality
+ ///
+ IAgniSystem TheSystem { get; }
+
+ ///
+ /// References application configuration root used to boot this application instance
+ ///
+ IConfigSectionNode BootConfigRoot { get; }
+
+ ///
+ /// Denotes system application/process type that this app container has, i.e.: HostGovernor, WebServer, etc.
+ ///
+ SystemApplicationType SystemApplicationType { get; }
+
+ ///
+ /// References distributed lock manager
+ ///
+ Locking.ILockManager LockManager { get; }
+
+ ///
+ /// References distributed GDID provider
+ ///
+ IGDIDProvider GDIDProvider { get; }
+
+ ///
+ /// References distributed process manager
+ ///
+ Workers.IProcessManager ProcessManager { get; }
+
+ ///
+ /// References dynamic host manager
+ ///
+ Dynamic.IHostManager DynamicHostManager { get; }
+ }
+}
diff --git a/src/Agni/AppModel/MasterIntfs.cs b/src/Agni/AppModel/MasterIntfs.cs
new file mode 100644
index 0000000..7267e36
--- /dev/null
+++ b/src/Agni/AppModel/MasterIntfs.cs
@@ -0,0 +1,51 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Time;
+
+using Agni.Identification;
+
+namespace Agni.AppModel
+{
+
+ ///
+ /// Defines rollef-up status per certain entity (i.e. Region, NOC, ZOne etc.)
+ ///
+ public interface IOperationalStatus
+ {
+ string Status { get; }
+
+ int HostsTotal { get; }
+ int ProcessorCoresUserUsedPct { get; }
+ int ProcessorCoresSysUsedPct { get; }
+ int ProcessorCoresTotal { get; }
+ int MemoryGBUsed { get; }
+ int MemoryGBTotal { get; }
+
+ long Errors { get;}
+ }
+
+ ///
+ /// Provides access to dynamic Agni status/operations.
+ /// This Entity is similar to metabase, however unlike the metabse which is read-only configuration, this is a portal for working with Agni dynamic (changing)
+ /// information, such as: get statistics, broadcast messages etc.
+ ///
+ public interface IAgniSystem : IApplicationComponent, IDisposable
+ {
+ bool Available { get; }
+
+
+ ///
+ /// Returns the status of the system
+ ///
+ IOperationalStatus Status { get; }
+
+ //todo In future add ability to get Zones,NOCS,Regions, their statitics etc...
+ }
+
+
+}
diff --git a/src/Agni/AppModel/NOPAgniSystem.cs b/src/Agni/AppModel/NOPAgniSystem.cs
new file mode 100644
index 0000000..eae5946
--- /dev/null
+++ b/src/Agni/AppModel/NOPAgniSystem.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX.ApplicationModel;
+
+namespace Agni.AppModel
+{
+ public class NOPAgniSystem : ApplicationComponent, IAgniSystem
+ {
+ private static NOPAgniSystem s_Instance = new NOPAgniSystem();
+
+ private NOPAgniSystem() { }
+
+ public static NOPAgniSystem Instance { get { return s_Instance; } }
+
+ public bool Available { get { return false; } }
+
+ public override string ComponentCommonName { get { return AgniSystemBase.AGNI_COMPONENT_NAME; } }
+
+ public IOperationalStatus Status
+ {
+ get
+ {
+ throw new NotImplementedException();
+ }
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni/AppModel/Terminal/AppRemoteTerminal.cs b/src/Agni/AppModel/Terminal/AppRemoteTerminal.cs
new file mode 100644
index 0000000..02cc39a
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/AppRemoteTerminal.cs
@@ -0,0 +1,246 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+using System.Runtime.Serialization;
+
+using NFX;
+using NFX.Security;
+using NFX.Environment;
+using NFX.ApplicationModel;
+
+using Agni.Contracts;
+using Agni.Security.Permissions.Admin;
+
+
+namespace Agni.AppModel.Terminal
+{
+
+ ///
+ /// Provides basic app-management capabilities
+ ///
+ [Serializable]
+ public class AppRemoteTerminal : ApplicationComponent, IRemoteTerminal, INamed, IDeserializationCallback, IConfigurable
+ {
+ public const string MARKUP_PRAGMA = "";
+
+ public const string CONFIG_APP_REMOTE_TERMINAL_SECTION = "remote-terminal";
+
+ public const string HELP_ARGS = "/?";
+
+
+ private static object s_IDLock = new Object();
+ private static int s_ID;
+ internal static Registry s_Registry = new Registry();
+
+
+ ///
+ /// Makes an instance of remote terminal which is configured under app/remote-terminal section.
+ /// If section is not defined then makes AppRemoteTerminal instance
+ ///
+ public static AppRemoteTerminal MakeNewTerminal()
+ {
+ return FactoryUtils.MakeAndConfigure(App.ConfigRoot[CONFIG_APP_REMOTE_TERMINAL_SECTION], typeof(AppRemoteTerminal));
+ }
+
+
+ public AppRemoteTerminal() : base()
+ {
+ lock (s_IDLock)
+ {
+ s_ID++;
+ m_ID = s_ID;
+ }
+ m_Name = new ELink((ulong)m_ID, null).Link;
+ m_Vars = new Vars();
+ m_ScriptRunner = new ScriptRunner();
+ }
+
+
+
+ protected override void Destructor()
+ {
+ s_Registry.Unregister(this);
+ base.Destructor();
+ }
+
+ public void OnDeserialization(object sender)
+ {
+ lock (s_IDLock)
+ {
+ if (this.m_ID > s_ID) s_ID = m_ID;
+ }
+ s_Registry.Register(this);
+ }
+
+ private int m_ID;
+ private string m_Name;
+ private string m_Who;
+ private DateTime m_WhenConnected;
+ private DateTime m_WhenInteracted;
+ private Vars m_Vars;
+ private ScriptRunner m_ScriptRunner;
+
+ public override string ComponentCommonName { get { return "term-" + m_Name; } }
+
+ ///
+ /// Returns unique terminal session ID
+ ///
+ public int ID { get { return m_ID; } }
+
+ ///
+ /// Returns unique terminal session ID as an alpha link
+ ///
+ public string Name { get { return m_Name; } }
+
+ ///
+ /// Returns description about who is connected
+ ///
+ public string Who { get { return m_Who; } }
+
+ ///
+ /// Returns UTC timestamp of connection initiation
+ ///
+ public DateTime WhenConnected { get { return m_WhenConnected; } }
+
+
+ ///
+ /// Returns UTC timestamp of last interaction
+ ///
+ public DateTime WhenInteracted { get { return m_WhenInteracted; } }
+
+
+ ///
+ /// Provides cmdlets
+ ///
+ public virtual IEnumerable Cmdlets { get { return CmdletFinder.Common; } }
+
+ ///
+ /// Provides variable resolver
+ ///
+ public virtual Vars Vars { get { return m_Vars; } }
+
+ public void Configure(IConfigSectionNode fromNode)
+ {
+ m_ScriptRunner.Configure(fromNode[ScriptRunner.CONFIG_SCRIPT_RUNNER_SECTION]);
+ ConfigAttribute.Apply(this, fromNode);
+ DoConfigure(fromNode);
+ }
+
+ public virtual RemoteTerminalInfo Connect(string who)
+ {
+ m_Who = who ?? SysConsts.UNKNOWN_ENTITY;
+ m_WhenConnected = App.TimeSource.UTCNow;
+ m_WhenInteracted = App.TimeSource.UTCNow;
+
+ s_Registry.Register(this);
+
+ return new RemoteTerminalInfo
+ {
+ TerminalName = Name,
+ WelcomeMsg = "Connected to '[{0}]{1}'@'{2}' on {3:G} {4:T} UTC. Session '{5}'".Args(AgniSystem.MetabaseApplicationName,
+ App.Name,
+ AgniSystem.HostName,
+ App.TimeSource.Now,
+ App.TimeSource.UTCNow,
+ Name),
+ Host = AgniSystem.HostName,
+ AppName = App.Name,
+ ServerLocalTime = App.TimeSource.Now,
+ ServerUTCTime = App.TimeSource.UTCNow
+ };
+ }
+
+ [AppRemoteTerminalPermission]
+ public virtual string Execute(string command)
+ {
+ m_WhenInteracted = App.TimeSource.UTCNow;
+
+ if (command == null) return string.Empty;
+
+ command = command.Trim();
+
+ if (command.IsNullOrWhiteSpace()) return string.Empty;
+
+ if (!command.EndsWith("}") && command.Contains(HELP_ARGS))
+ {
+ return getHelp(command.Replace(HELP_ARGS,"").Trim());
+ }
+
+ if (!command.EndsWith("}")) command += "{}";
+
+ var cmd = TerminalUtils.ParseCommand(command, m_Vars);
+ var result = new MemoryConfiguration();
+ result.EnvironmentVarResolver = cmd.EnvironmentVarResolver;
+ m_ScriptRunner.Execute(cmd, result);
+
+ return DoExecute(result.Root);
+ }
+
+ public string Execute(IConfigSectionNode command)
+ {
+ if(command==null || !command.Exists) return string.Empty;
+ return DoExecute(command);
+ }
+
+ public virtual string Disconnect()
+ {
+ return "Good bye!";
+ }
+
+ protected virtual void DoConfigure(IConfigSectionNode fromNode) {}
+
+ protected virtual string DoExecute(IConfigSectionNode command)
+ {
+ var cname = command.Name.ToLowerInvariant();
+
+ var tp = Cmdlets.FirstOrDefault(cmd => cmd.Name.EqualsIgnoreCase(cname));
+
+ if (tp == null)
+ return StringConsts.RT_CMDLET_DONTKNOW_ERROR.Args(cname);
+
+ //Check cmdlet security
+ NFX.Security.Permission.AuthorizeAndGuardAction(tp);
+ NFX.Security.Permission.AuthorizeAndGuardAction(tp.GetMethod(nameof(Execute)));
+
+ var cmdlet = Activator.CreateInstance(tp, this, command) as Cmdlet;
+ if (cmdlet == null)
+ throw new AgniException(StringConsts.RT_CMDLET_ACTIVATION_ERROR.Args(cname));
+
+ using (cmdlet)
+ {
+ return cmdlet.Execute();
+ }
+ }
+
+ private string getHelp(string cmd)
+ {
+ if (cmd.IsNullOrEmpty()) return string.Empty;
+
+ var target = Cmdlets.FirstOrDefault(c => c.Name.EqualsOrdIgnoreCase(cmd));
+ if (target == null) return string.Empty;
+
+ var result = new StringBuilder(1024);
+ result.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ result.AppendLine("");
+
+ string help;
+ using (var inst = Activator.CreateInstance(target, this, null) as Cmdlet)
+ {
+ try
+ {
+ help = inst.GetHelp();
+ }
+ catch (Exception error)
+ {
+ help = "Error getting help: " + error.ToMessageWithType();
+ }
+ result.AppendLine(" - {1}".Args(cmd, help));
+ }
+ result.AppendLine("");
+
+ return result.ToString();
+ }
+ }
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlet.cs b/src/Agni/AppModel/Terminal/Cmdlet.cs
new file mode 100644
index 0000000..5b6530b
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlet.cs
@@ -0,0 +1,30 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal
+{
+ ///
+ /// Provides generalizatin for commandlet - terminal command handler
+ ///
+ public abstract class Cmdlet : DisposableObject
+ {
+ protected Cmdlet(AppRemoteTerminal terminal, IConfigSectionNode args)
+ {
+ m_Terminal = terminal;
+ m_Args = args;
+ }
+
+ protected AppRemoteTerminal m_Terminal;
+ protected IConfigSectionNode m_Args;
+
+ public abstract string Execute();
+
+ public abstract string GetHelp();
+ }
+}
diff --git a/src/Agni/AppModel/Terminal/CmdletFinder.cs b/src/Agni/AppModel/Terminal/CmdletFinder.cs
new file mode 100644
index 0000000..dd1a1af
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/CmdletFinder.cs
@@ -0,0 +1,73 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+
+using NFX;
+
+namespace Agni.AppModel.Terminal
+{
+ ///
+ /// Provides enumerations of well-known cmdlets and facilities to find process-specific cmdlets
+ ///
+ public static class CmdletFinder
+ {
+ ///
+ /// Provides common cmdlets supported by all servers
+ ///
+ public static IEnumerable Common
+ {
+ get
+ {
+ return FindByNamespace(typeof(AppRemoteTerminal), "Agni.AppModel.Terminal.Cmdlets");
+ }
+ }
+
+ ///
+ /// Provides cmdlets supported by host governor
+ ///
+ public static IEnumerable HGov
+ {
+ get
+ {
+ return FindByNamespace(typeof(AppRemoteTerminal), "Agni.AppModel.HostGovernor.Cmdlets");
+ }
+ }
+
+ ///
+ /// Provides cmdlets supported by zone governor
+ ///
+ public static IEnumerable ZGov
+ {
+ get
+ {
+ return FindByNamespace(typeof(AppRemoteTerminal), "Agni.AppModel.ZoneGovernor.Cmdlets");
+ }
+ }
+
+
+ ///
+ /// Finds cmdlets in assembly that contains the specified type, optionally filtering the cmdlet types
+ ///
+ public static IEnumerable FindByNamespace(Type assemblyContainingType, string nsFilter = null)
+ {
+ return nsFilter.IsNullOrWhiteSpace() ? Find(assemblyContainingType)
+ : Find(assemblyContainingType, (t) => t.Namespace.EqualsSenseCase(nsFilter));
+ }
+
+ ///
+ /// Finds cmdlets in assembly that contains the specified type, optionally filtering the cmdlet types
+ ///
+ public static IEnumerable Find(Type assemblyContainingType, Func filter = null)
+ {
+ if (assemblyContainingType==null) assemblyContainingType = typeof(CmdletFinder);
+ var asm = Assembly.GetAssembly(assemblyContainingType);
+ var cmdlets = asm.GetTypes()
+ .Where(t => !t.IsAbstract && typeof(Cmdlet).IsAssignableFrom(t));
+
+ return filter!=null? cmdlets.Where( t => filter(t)) : cmdlets;
+ }
+
+ }
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Appl.cs b/src/Agni/AppModel/Terminal/Cmdlets/Appl.cs
new file mode 100644
index 0000000..fc135fb
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Appl.cs
@@ -0,0 +1,146 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+using NFX.ApplicationModel;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+ public class Appl : Cmdlet
+ {
+ private const string VAL = "|{0}\n" ;
+
+ public const string CONFIG_STOP_NOW_HR_ATTR = "stop-now-hour";
+
+
+ public Appl(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var app = AgniSystem.Application;
+
+ var stopNowHour = m_Args.AttrByName(CONFIG_STOP_NOW_HR_ATTR).ValueAsInt(-10);
+ if (stopNowHour == app.LocalizedTime.Hour)
+ {
+ var text = StringConsts.APPL_CMD_STOPPING_INFO.Args(m_Terminal.Name, m_Terminal.WhenConnected, m_Terminal.Who);
+ App.Log.Write( new NFX.Log.Message
+ {
+ Type = NFX.Log.MessageType.Warning,
+ Topic = SysConsts.LOG_TOPIC_APP_MANAGEMENT,
+ From = "{0}.StopNow".Args(GetType().FullName),
+ Text = text
+ });
+ App.Instance.Stop();
+ return text;//noone may see this as app may terminate faster than response delivered
+ }
+
+
+
+
+ var sb = new StringBuilder(1024);
+ sb.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ sb.AppendLine("");
+ sb.AppendLine("Application Container");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ sb.AppendFormat("Name "+VAL, app.Name );
+ sb.AppendFormat("Host "+VAL, AgniSystem.HostName );
+ sb.AppendFormat("Parent Zone Governor "+VAL, AgniSystem.ParentZoneGovernorPrimaryHostName ?? "[none, this host is top level]" );
+ sb.AppendFormat("Role "+VAL, AgniSystem.HostMetabaseSection.RoleName );
+ sb.AppendFormat("Role Apps "+VAL, AgniSystem.HostMetabaseSection.Role.AppNames.Aggregate("",(r,a)=>r+a+", ") );
+ sb.AppendFormat("Metabase App "+VAL, app.MetabaseApplicationName );
+ sb.AppendFormat("Instance ID "+VAL, app.InstanceID );
+ sb.AppendFormat("Start Time "+VAL, app.StartTime );
+ sb.AppendFormat("Running Time "+VAL, app.LocalizedTime - app.StartTime );
+ sb.AppendFormat("Type "+VAL, app.GetType().FullName );
+ sb.AppendFormat("Active "+VAL, app.Active);
+ sb.AppendFormat("Boot Conf Root "+VAL, app.BootConfigRoot );
+ sb.AppendFormat("Conf Root "+VAL, app.ConfigRoot );
+ sb.AppendFormat("Data Store "+VAL, app.DataStore.GetType().FullName );
+ sb.AppendFormat("Glue "+VAL, app.Glue.GetType().FullName );
+ sb.AppendFormat("Instrumentation. "+VAL, app.Instrumentation.GetType().FullName );
+ sb.AppendFormat("Localized Time "+VAL, app.LocalizedTime );
+ sb.AppendFormat("Time Location "+VAL, app.TimeLocation );
+ sb.AppendFormat("Log "+VAL, app.Log.GetType().FullName );
+ sb.AppendFormat("Object Store "+VAL, app.ObjectStore.GetType().FullName );
+ sb.AppendFormat("Security Manager "+VAL, app.SecurityManager.GetType().FullName );
+ sb.AppendFormat("Module Root "+VAL, app.ModuleRoot.GetType().FullName );
+ sb.AppendFormat("TimeSource "+VAL, app.TimeSource.GetType().FullName );
+
+ var lwarning = app.Log.LastWarning;
+ sb.AppendFormat("Last Warning "+VAL, lwarning!=null ? "{0} {1} {2} {3}".Args(lwarning.TimeStamp, lwarning.Guid, lwarning.From, lwarning.Text) : string.Empty );
+
+ var lerror = app.Log.LastError;
+ sb.AppendFormat("Last Error "+VAL, lerror!=null ? "{0} {1} {2} {3}".Args(lerror.TimeStamp, lerror.Guid, lerror.From, lerror.Text) : string.Empty );
+
+ var lcatastrophe = app.Log.LastCatastrophe;
+ sb.AppendFormat("Last Catastrophe "+VAL, lcatastrophe!=null ? "{0} {1} {2} {3}".Args(lcatastrophe.TimeStamp, lcatastrophe.Guid, lcatastrophe.From, lcatastrophe.Text) : string.Empty );
+
+
+ sb.AppendLine();
+ sb.AppendLine("Root Components");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ var all = ApplicationComponent.AllComponents;
+ foreach(var cmp in all.Where(cmp => !(cmp.ComponentDirector is IApplicationComponent)))
+ {
+ string name = null;
+ var named = cmp as INamed;
+ if (named!=null) name = named.Name;
+
+ sb.AppendLine("SID: {0,-4:D4} {1} {2} {3} {4}".Args(cmp.ComponentSID,
+ fdt(cmp.ComponentStartTime),
+ cmp.GetType().FullName,
+ cmp.ComponentCommonName, name));
+
+ var children = all.Where(c => object.ReferenceEquals(c.ComponentDirector, cmp));
+ foreach(var child in children)
+ {
+ string cname = null;
+ var cnamed = child as INamed;
+ if (cnamed!=null) cname = cnamed.Name;
+ sb.AppendLine(" -> {0,-6:D4} {1} {2} {3} {4}".Args(child.ComponentSID,
+ fdt(child.ComponentStartTime),
+ child.GetType().Name,
+ child.ComponentCommonName,
+ cname));
+ }
+
+ sb.AppendLine();
+ }
+
+ sb.AppendLine(" * Note: this command only outputs root components and 1 level of their immediate children");
+ sb.AppendLine("");
+
+ return sb.ToString();
+ }
+
+
+ public override string GetHelp()
+ {
+ return
+@"Displays the status of the Application Container:
+ Pass stop-now-hour=app_local_hour to stop the app.
+ This is needed so that an inadvertent stopping of the app container
+ is precluded. The parameter must match the current local app time
+";
+ }
+
+ internal static string fdt(DateTime date)
+ {
+ return "[{0:D2}/{1:D2} {2:D2}:{3:D2}:{4:D2}.{5:D3}]".Args(date.Month,
+ date.Day,
+ date.Hour,
+ date.Minute,
+ date.Second,
+ date.Millisecond);
+ }
+
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/CMan.cs b/src/Agni/AppModel/Terminal/Cmdlets/CMan.cs
new file mode 100644
index 0000000..d2539fc
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/CMan.cs
@@ -0,0 +1,222 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+using NFX.Glue;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ ///
+ /// Component Manager
+ ///
+ public class CMan : Cmdlet
+ {
+
+ public const string CONFIG_SID_ATTR = "sid";
+ public const string CONFIG_NAME_ATTR = "name";
+ public const string CONFIG_PARAM_ATTR = "param";
+ public const string CONFIG_VALUE_ATTR = "value";
+
+
+ public static ApplicationComponent GetApplicationComponentBySIDorName(IConfigSectionNode args)
+ {
+ if (args==null || !args.Exists) return null;
+
+ ApplicationComponent cmp = null;
+ var sid = args.AttrByName(CONFIG_SID_ATTR).ValueAsULong();
+ var cname = args.AttrByName(CONFIG_NAME_ATTR).Value;
+
+ if (sid>0) cmp = ApplicationComponent.GetAppComponentBySID(sid);
+
+ if (cmp==null && cname.IsNotNullOrWhiteSpace()) cmp = ApplicationComponent.GetAppComponentByCommonName(cname);
+ return cmp;
+ }
+
+
+ public CMan(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var sid = m_Args.AttrByName(CONFIG_SID_ATTR).ValueAsULong();
+ var cname = m_Args.AttrByName(CONFIG_NAME_ATTR).Value;
+ var param = m_Args.AttrByName(CONFIG_PARAM_ATTR).Value;
+ var value = m_Args.AttrByName(CONFIG_VALUE_ATTR).Value;
+
+ var sb = new StringBuilder(1024);
+ sb.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ sb.Append("");
+ sb.AppendLine("Component Manager");
+ sb.AppendLine("----------------------");
+
+ if (sid==0 && cname.IsNullOrWhiteSpace())
+ listAll(sb);
+ else
+ {
+ ApplicationComponent cmp = null;
+ if (sid>0) cmp = ApplicationComponent.GetAppComponentBySID(sid);
+ if (cmp==null && cname.IsNotNullOrWhiteSpace()) cmp = ApplicationComponent.GetAppComponentByCommonName(cname);
+ if (cmp!=null)
+ details(sb, cmp, param, value);
+ else
+ sb.AppendFormat("Component with the supplied SID and CommonName was not found\n");
+ }
+
+ sb.AppendLine("");
+
+ return sb.ToString();
+ }
+
+
+
+
+
+ public override string GetHelp()
+ {
+ return
+@"Prints component information and manages parameters.
+ Pass either sid or name for particular component.
+ Pass param and value to set the value in particular component.
+ Parameters:
+ sid = int - component instance SID
+ or
+ name=string - component common name
+
+ param=string - name of parameter to set
+ value=object - new parameter value to set
+
+";
+ }
+
+
+ private void listAll(StringBuilder sb)
+ {
+ var all = ApplicationComponent.AllComponents;
+ var root = all.Where(c => c.ComponentDirector==null);
+ sb.AppendLine("Root components w/o director:");
+ sb.AppendLine();
+ foreach(var cmp in root)
+ listOne(sb, all, cmp, 0);
+
+ sb.AppendLine();
+
+ var other = all.Where(c => c.ComponentDirector!=null && !(c.ComponentDirector is ApplicationComponent));
+ sb.AppendLine("Components with non-component director:");
+ sb.AppendLine();
+ foreach(var cmp in other)
+ listOne(sb, all, cmp, 0);
+ }
+
+ private void listOne(StringBuilder sb, IEnumerable all, ApplicationComponent cmp, int level)
+ {
+ if (level>7) return;//cyclical ref
+
+ var sp = level<=0?string.Empty : string.Empty.PadLeft(level*3);
+ var pfx0 = sp+"├▌";
+ var pfx = sp+"├─";
+ var pfxL = sp+"└─";
+
+
+ sb.AppendLine(pfx0+"SID: {1:D4} {2} {3} {4} "
+ .Args(
+ level==0 ? "white" : level>1 ? "darkcyan" : "cyan",
+ cmp.ComponentSID,
+ Appl.fdt(cmp.ComponentStartTime),
+ cmp.ComponentCommonName,
+ (cmp is INamed ? ((INamed)cmp).Name : "" )));
+
+ if (cmp.ComponentDirector!=null && !(cmp.ComponentDirector is ApplicationComponent))
+ sb.AppendLine(pfx+"Director: "+cmp.ComponentDirector.GetType().FullName);
+
+ sb.AppendLine(pfxL+"{0} {1}".Args(cmp.GetType().FullName, System.IO.Path.GetFileName( cmp.GetType().Assembly.Location )));
+
+
+
+ var children = all.Where(c => object.ReferenceEquals(c.ComponentDirector, cmp));
+ foreach(var child in children)
+ {
+ listOne(sb, all, child, level+1);
+ }
+
+ if (level==0) sb.AppendLine();
+ }
+
+
+
+ private void details(StringBuilder sb, ApplicationComponent cmp, string param, string value)
+ {
+ if (param.IsNotNullOrWhiteSpace())
+ {
+ sb.AppendLine("Trying to set parameter '{0}' to value '{1}'".Args(param, value ?? ""));
+ if (!NFX.ExternalParameterAttribute.SetParameter(cmp, param, value))
+ {
+ sb.AppendLine("Parameter '{0}' set did NOT SUCCEED".Args(param));
+ return;
+ }
+ sb.AppendLine("Parameter '{0}' set SUCCEEDED".Args(param));
+ sb.AppendLine();
+ }
+
+ dumpDetails(sb, cmp, 0);
+ }
+
+ private void dumpDetails(StringBuilder sb, ApplicationComponent cmp, int level)
+ {
+ if (level>7) return;//cyclical ref
+
+ var pfx = level<=0?string.Empty : string.Empty.PadLeft(level)+"->";
+
+ sb.AppendLine(pfx+"SID: "+cmp.ComponentSID);
+ sb.AppendLine(pfx+"CommonName: "+cmp.ComponentCommonName);
+ sb.AppendLine(pfx+"Start Time (local): "+cmp.ComponentStartTime);
+ sb.AppendLine(pfx+"Type: "+cmp.GetType().FullName);
+ sb.AppendLine(pfx+"Assembly: "+cmp.GetType().Assembly.FullName);
+ sb.AppendLine(pfx+"Service: "+(cmp is NFX.ServiceModel.Service ? "Yes" : "No") );
+ if (cmp is INamed)
+ sb.AppendLine(pfx+"Name: "+((INamed)cmp).Name);
+
+ sb.AppendLine(pfx+"Interfaces: "+cmp.GetType()
+ .GetInterfaces()
+ .OrderBy(it=>it.FullName)
+ .Aggregate("",(r,i)=>
+ r+(typeof(IExternallyParameterized).IsAssignableFrom(i) ?
+ "{0}".Args(i.Name) : i.Name)+", ") );
+
+ sb.AppendLine();
+ sb.AppendLine();
+ sb.AppendLine(pfx+"Parameters: ");
+ sb.AppendLine();
+
+ var pars = NFX.ExternalParameterAttribute.GetParametersWithAttrs(cmp.GetType());
+ foreach(var p in pars)
+ {
+ var nm = p.Item1;
+ object val;
+ if (!NFX.ExternalParameterAttribute.GetParameter(cmp, nm, out val)) val = "?";
+ var tp = p.Item2;
+ var atr = p.Item3;
+ sb.AppendLine(pfx+"{0,-35}: {1,-10} ({2}) {3}".Args(
+ nm,
+ val==null ? "" : val,
+ tp.DisplayNameWithExpandedGenericArgs().Replace('<','(').Replace('>',')')
+ , atr.Groups==null?"*":atr.Groups.Aggregate("",(r,a)=>r+a+", ")));
+ }
+
+
+ sb.AppendLine();
+ var dir = cmp.ComponentDirector;
+ sb.AppendLine(pfx+"Director: "+(dir==null? " -none- " : dir.GetType().FullName));
+ if (dir is ApplicationComponent)
+ dumpDetails(sb, dir as ApplicationComponent, level+1);
+ }
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Conf.cs b/src/Agni/AppModel/Terminal/Cmdlets/Conf.cs
new file mode 100644
index 0000000..d1bbcbb
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Conf.cs
@@ -0,0 +1,33 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public class Conf : Cmdlet
+ {
+ public Conf(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var conf = new LaconicConfiguration();
+ conf.CreateFromNode( App.ConfigRoot );
+ return conf.SaveToString();
+ }
+
+ public override string GetHelp()
+ {
+ return "Fetches current configuration tree";
+ }
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Echo.cs b/src/Agni/AppModel/Terminal/Cmdlets/Echo.cs
new file mode 100644
index 0000000..528af07
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Echo.cs
@@ -0,0 +1,31 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+using NFX.Glue;
+using Agni.Workers;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+ ///
+ /// Echo text
+ ///
+ public class Echo: Cmdlet
+ {
+ public Echo(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args) { }
+
+ public override string Execute()
+ {
+ return m_Args.ValueAsString();
+ }
+
+ public override string GetHelp()
+ {
+ return @"Echo text";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Exec.cs b/src/Agni/AppModel/Terminal/Cmdlets/Exec.cs
new file mode 100644
index 0000000..e67009c
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Exec.cs
@@ -0,0 +1,41 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.ApplicationModel;
+using NFX.Environment;
+using NFX.Glue;
+using Agni.Workers;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ ///
+ /// Execute commands
+ ///
+ public class Exec: Cmdlet
+ {
+ public const string CONFIG_TO_ATTR = "to";
+
+ public Exec(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args) { }
+
+ public override string Execute()
+ {
+ var to = m_Args.AttrByName(CONFIG_TO_ATTR).ValueAsString();
+ var sb = new StringBuilder();
+ foreach (var arg in m_Args.Children)
+ sb.Append(m_Terminal.Execute(arg));
+ if (to.IsNullOrWhiteSpace())
+ return sb.ToString();
+ m_Terminal.Vars[to] = sb.ToString();
+ return "OK";
+ }
+
+ public override string GetHelp()
+ {
+ return @"Execute commands";
+ }
+ }
+}
\ No newline at end of file
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Gc.cs b/src/Agni/AppModel/Terminal/Cmdlets/Gc.cs
new file mode 100644
index 0000000..e8f1146
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Gc.cs
@@ -0,0 +1,36 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+
+using NFX;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public class Gc : Cmdlet
+ {
+ public Gc(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var watch = Stopwatch.StartNew();
+ var before = GC.GetTotalMemory(false);
+ System.GC.Collect();
+ var after = GC.GetTotalMemory(false);
+ return "GC took {0} ms. and freed {1} bytes".Args(watch.ElapsedMilliseconds, before - after);
+ }
+
+ public override string GetHelp()
+ {
+ return "Invokes garbage collector";
+ }
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Glue.cs b/src/Agni/AppModel/Terminal/Cmdlets/Glue.cs
new file mode 100644
index 0000000..7c563db
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Glue.cs
@@ -0,0 +1,180 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+using NFX.Glue;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+ public class Glue : Cmdlet
+ {
+ public const string CONFIG_BINDING_ATTR = "binding";
+ private const string VAL = "|{0}\n" ;
+
+ public Glue(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var bname = m_Args.AttrByName(CONFIG_BINDING_ATTR).Value;
+
+ var glue = App.Glue;
+
+ var attr = m_Args.AttrByName("client-log-level");
+ if (attr.Exists) glue.ClientLogLevel = attr.ValueAsEnum(glue.ClientLogLevel);
+
+ attr = m_Args.AttrByName("server-log-level");
+ if (attr.Exists) glue.ServerLogLevel = attr.ValueAsEnum(glue.ServerLogLevel);
+
+ attr = m_Args.AttrByName("server-instance-lock-timeout-ms");
+ if (attr.Exists) glue.ServerInstanceLockTimeoutMs = attr.ValueAsInt(glue.ServerInstanceLockTimeoutMs);
+
+ attr = m_Args.AttrByName("default-timeout-ms");
+ if (attr.Exists) glue.DefaultTimeoutMs = attr.ValueAsInt(glue.DefaultTimeoutMs);
+
+ attr = m_Args.AttrByName("default-dispatch-timeout-ms");
+ if (attr.Exists) glue.DefaultDispatchTimeoutMs = attr.ValueAsInt(glue.DefaultDispatchTimeoutMs);
+
+
+
+
+
+
+ var sb = new StringBuilder(1024);
+ sb.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ sb.AppendLine("");
+ sb.AppendLine("Glue Status");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ sb.AppendFormat("Glue "+VAL, glue.GetType().FullName );
+ sb.AppendFormat("Bindings "+VAL, glue.Bindings.Count );
+ sb.AppendFormat("Client Log Level "+VAL, glue.ClientLogLevel );
+ sb.AppendFormat("Server Log Level "+VAL, glue.ServerLogLevel );
+ sb.AppendFormat("Client Msg Inspectors "+VAL, glue.ClientMsgInspectors.Count );
+ sb.AppendFormat("Server Msg Inspectors "+VAL, glue.ServerMsgInspectors.Count );
+ sb.AppendFormat("Dflt.Timeout ms. "+VAL, glue.DefaultTimeoutMs );
+ sb.AppendFormat("Dflt.Dispatch Timeout ms. "+VAL, glue.DefaultDispatchTimeoutMs );
+ sb.AppendFormat("Localized Time "+VAL, glue.LocalizedTime );
+ sb.AppendFormat("Time Location "+VAL, glue.TimeLocation );
+ sb.AppendFormat("Providers "+VAL, glue.Providers.Count );
+ sb.AppendFormat("Servers "+VAL, glue.Servers.Count );
+ sb.AppendFormat("Srv Inst Lock Timeout ms. "+VAL, glue.ServerInstanceLockTimeoutMs );
+
+
+ sb.AppendLine();
+ if (bname.IsNullOrWhiteSpace())
+ {
+ sb.AppendLine("Bindings");
+ sb.AppendLine("Name Type CI SI CTr STr CDump SDump");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ foreach(var binding in App.Glue.Bindings)
+ sb.AppendFormat("{0,-10} {1,-25} {2,1} {3,1} {4,4} {5,4} {6,8} {7,8} \n",
+ binding.Name,
+ binding.GetType().Name,
+ binding.InstrumentClientTransportStat ? ON : OFF,
+ binding.InstrumentServerTransportStat ? ON : OFF,
+ binding.ClientTransports.Count(),
+ binding.ServerTransports.Count(),
+ binding.ClientDump,
+ binding.ServerDump
+ );
+ }
+ else
+ {
+ var binding = glue.Bindings[bname];
+ if (binding==null)
+ sb.AppendFormat("Binding {0} not present in glue stack\n", bname);
+ else
+ {
+ sb.AppendLine("Binding Status");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ sb.AppendFormat("Name "+VAL, binding.Name );
+ sb.AppendFormat("Type "+VAL, binding.GetType().FullName );
+ sb.AppendFormat("Provider "+VAL, binding.Provider==null?"":binding.Provider.GetType().FullName );
+ sb.AppendFormat("Client Dump "+VAL, binding.ClientDump );
+ sb.AppendFormat("Server Dump "+VAL, binding.ServerDump );
+ sb.AppendFormat("Cl Tr Cnt Wait Threshold "+VAL, binding.ClientTransportCountWaitThreshold);
+ sb.AppendFormat("Cl Tr Ex Acq Timeout ms. "+VAL, binding.ClientTransportExistingAcquisitionTimeoutMs);
+ sb.AppendFormat("Cl Tr Idle Timeout ms. "+VAL, binding.ClientTransportIdleTimeoutMs);
+ sb.AppendFormat("Cl Tr Max Count "+VAL, binding.ClientTransportMaxCount);
+ sb.AppendFormat("Cl Tr Max Ex Acq Tmout ms."+VAL, binding.ClientTransportMaxExistingAcquisitionTimeoutMs);
+ sb.AppendFormat("Client Transport Count "+VAL, binding.ClientTransports.Count());
+ sb.AppendFormat("Server Transport Count "+VAL, binding.ServerTransports.Count());
+ sb.AppendFormat("Server Tr Idle Timeout ms."+VAL, binding.ServerTransportIdleTimeoutMs);
+ sb.AppendFormat("Localized Time "+VAL, binding.LocalizedTime );
+ sb.AppendFormat("Time Location "+VAL, binding.TimeLocation );
+
+ sb.AppendLine();
+ sb.AppendLine("Client Transports");
+ sb.AppendLine("Node IdlSec RBy SBy Err RMsg SMsg");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ foreach(var tran in binding.ClientTransports)
+ sb.AppendFormat("{0,-30} {1,6} {2,8} {3,8} {4,5} {5,5} {6,5}\n",
+ tran.Node,
+ tran.IdleAgeMs / 1000,
+ tran.StatBytesReceived,
+ tran.StatBytesSent,
+ tran.StatErrors,
+ tran.StatMsgReceived,
+ tran.StatMsgSent
+ );
+ sb.AppendLine();
+ sb.AppendLine("Server Transports");
+ sb.AppendLine("Node IdlSec RBy SBy Err RMsg SMsg");
+ sb.AppendLine("----------------------------------------------------------------------------");
+ foreach(var tran in binding.ServerTransports)
+ sb.AppendFormat("{0,-30} {1,6} {2,8} {3,8} {4,5} {5,5} {6,5}\n",
+ tran.Node,
+ tran.IdleAgeMs / 1000,
+ tran.StatBytesReceived,
+ tran.StatBytesSent,
+ tran.StatErrors,
+ tran.StatMsgReceived,
+ tran.StatMsgSent
+ );
+
+
+ }
+ }
+
+ sb.AppendLine();
+ sb.AppendLine("NOTE: For management use CMAN instead as it allows to set more parameters");
+ sb.AppendLine("");
+
+ return sb.ToString();
+ }
+
+
+ private const string ON = "X";
+ private const string OFF = "-";
+
+ public override string GetHelp()
+ {
+ return
+@"Dumps Glue status:
+ Pass binding=string to query the particular instance
+ Parameters:
+ binding=string - name of the binding of interest
+ client-log-level=MessageType - client side log level
+ server-log-level=MessageType - server side log level
+ server-instance-lock-timeout-ms=int - acquisition interval for
+ non-thread-safe servers
+ default-timeout-ms=int - default call timeout
+ default-dispatch-timeout-ms=int - default call dispatch timeout
+
+";
+ }
+
+
+ private IEnumerable bindings(string bname)
+ {
+ return App.Glue.Bindings.Where(b=>bname.IsNullOrWhiteSpace() || b.Name.EqualsIgnoreCase(bname));
+ }
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Help.cs b/src/Agni/AppModel/Terminal/Cmdlets/Help.cs
new file mode 100644
index 0000000..f310592
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Help.cs
@@ -0,0 +1,74 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Reflection;
+
+using NFX;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public class Help : Cmdlet
+ {
+ public const string CONFIG_CMD_ATTR = "cmd";
+
+ public Help(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var cmdlets = m_Terminal.Cmdlets;
+
+ var cmdName = m_Args.AttrByName(CONFIG_CMD_ATTR).Value;
+
+ if (cmdName.IsNotNullOrWhiteSpace())
+ cmdlets = cmdlets.Where( cmd => cmd.Name.EqualsIgnoreCase(cmdName) );
+
+ var result = new StringBuilder(1024);
+ result.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ result.AppendLine("");
+
+ result.AppendLine(" Remote Terminal Help ");
+ result.AppendLine();
+ result.AppendLine(" Commands get sent to server after ';' is typed at the end of line.");
+ result.AppendLine(" Use ' /?' for help.");
+ result.AppendLine(" Use 'exit;' to disconnect.");
+ result.AppendLine(" List of server cmdlets: ");
+ result.AppendLine();
+
+ foreach(var cmdlet in cmdlets.OrderBy(c=>c.Name))
+ {
+ var name = cmdlet.Name.ToLower();
+ var help = SysConsts.UNKNOWN_ENTITY;
+
+ using(var inst = Activator.CreateInstance(cmdlet, m_Terminal, m_Args) as Cmdlet)
+ {
+ try
+ {
+ help = inst.GetHelp();
+ }
+ catch(Exception error)
+ {
+ help = "Error getting help: " + error.ToMessageWithType();
+ }
+ }
+
+ result.AppendLine( " - {1}".Args( name, help) );
+ }
+
+ result.AppendLine("");
+ return result.ToString();
+ }
+
+ public override string GetHelp()
+ {
+ return "Provides help on commandlets; help{cmd=name}";
+ }
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Instr.cs b/src/Agni/AppModel/Terminal/Cmdlets/Instr.cs
new file mode 100644
index 0000000..0bc154f
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Instr.cs
@@ -0,0 +1,138 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Environment;
+using NFX.Glue;
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public enum InstrType { View, Glue, ClientGlue, ServerGlue, Metabase, Mb = Metabase }
+
+ public class Instr : Cmdlet
+ {
+
+ public const string CONFIG_TYPE_ATTR = "type";
+ public const string CONFIG_ON_ATTR = "on";
+ public const string CONFIG_BINDING_ATTR = "binding";
+
+ public Instr(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var type = m_Args.AttrByName(CONFIG_TYPE_ATTR).ValueAsEnum(InstrType.View);
+ var on = m_Args.AttrByName(CONFIG_ON_ATTR).ValueAsBool();
+ var bname = m_Args.AttrByName(CONFIG_BINDING_ATTR).Value;
+ switch(type)
+ {
+ case InstrType.ClientGlue:
+ {
+ foreach(var binding in bindings(bname))
+ binding.InstrumentClientTransportStat = on;
+
+ break;
+ }
+
+ case InstrType.ServerGlue:
+ {
+ foreach(var binding in bindings(bname))
+ binding.InstrumentServerTransportStat = on;
+
+ break;
+ }
+
+ case InstrType.Glue:
+ {
+ foreach(var binding in bindings(bname))
+ {
+ binding.InstrumentClientTransportStat = on;
+ binding.InstrumentServerTransportStat = on;
+ }
+ break;
+ }
+
+ case InstrType.Metabase:
+ {
+ if (AgniSystem.IsMetabase)
+ {
+ var mb = AgniSystem.Metabase;
+ mb.InstrumentationEnabled = on;
+ }
+ break;
+ }
+
+ default:
+ {
+ break;//dont change anything
+ }
+ }
+
+ var sb = new StringBuilder(1024);
+ sb.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ sb.AppendLine("");
+ sb.AppendLine("Instrumentation Status");
+ sb.AppendLine("----------------------");
+ sb.AppendFormat("Enabled: {0} Overflown: {1} RecordCount: {2} MaxRecordCount: {3}\n",
+ App.Instrumentation.Enabled,
+ App.Instrumentation.Overflown,
+ App.Instrumentation.RecordCount,
+ App.Instrumentation.MaxRecordCount);
+ sb.AppendFormat("Interval: {0}ms. OS Interval: {1}ms.\n",
+ App.Instrumentation.ProcessingIntervalMS,
+ App.Instrumentation.OSInstrumentationIntervalMS);
+ sb.AppendLine();
+ sb.AppendLine("Glue Bindings Instrumentation");
+ sb.AppendLine("-----------------------------");
+ foreach(var binding in App.Glue.Bindings)
+ sb.AppendFormat("{0,-10} {1,-25} Client: {2,6} Server: {3,6}\n",
+ binding.Name,
+ binding.GetType().Name,
+ binding.InstrumentClientTransportStat ? ON : OFF,
+ binding.InstrumentServerTransportStat ? ON : OFF);
+ sb.AppendLine();
+ var mon = AgniSystem.IsMetabase && AgniSystem.Metabase.InstrumentationEnabled;
+
+ sb.AppendLine();
+ sb.AppendLine("Metabase Instrumentation");
+ sb.AppendLine("-----------------------------");
+ sb.AppendFormat("Enabled: {0}\n", mon ? ON : OFF);
+
+ sb.AppendLine();
+ sb.AppendLine("NOTE: For management use CMAN instead as it allows to set more parameters");
+
+ sb.AppendLine("");
+
+ return sb.ToString();
+ }
+
+
+ private const string ON = "X";
+ private const string OFF = "-";
+
+ public override string GetHelp()
+ {
+ return
+@"Enables/disables instrumentation:
+ Pass type={View|Glue|ClientGlue|ServerGlue|
+ Metabase} to specify what to control.
+ Parameters:
+ on=bool - turn on/off
+ binding=string - optional name of binding to apply changes to
+";
+ }
+
+
+ private IEnumerable bindings(string bname)
+ {
+ return App.Glue.Bindings.Where(b=>bname.IsNullOrWhiteSpace() || b.Name.EqualsIgnoreCase(bname));
+ }
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Log.cs b/src/Agni/AppModel/Terminal/Cmdlets/Log.cs
new file mode 100644
index 0000000..c49e89f
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Log.cs
@@ -0,0 +1,65 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+
+using NFX;
+using NFX.Log;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+
+
+ public class Log : Cmdlet
+ {
+ public const string CONFIG_TYPE_ATTR = "type";
+ public const string CONFIG_ON_ATTR = "on";
+ public const string CONFIG_BINDING_ATTR = "binding";
+
+ public Log(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var type = m_Args.AttrByName(CONFIG_TYPE_ATTR).ValueAsEnum(InstrType.View);
+ var on = m_Args.AttrByName(CONFIG_ON_ATTR).ValueAsBool();
+ var bname = m_Args.AttrByName(CONFIG_BINDING_ATTR).Value;
+
+ var msg = new Message
+ {
+ From = m_Args.AttrByName("from").Value,
+ Source = m_Args.AttrByName("source").ValueAsInt(0),
+ Type = m_Args.AttrByName("type").ValueAsEnum(MessageType.Info),
+ Topic = m_Args.AttrByName("topic").ValueAsString("App Terminal"),
+ Text = m_Args.AttrByName("text").ValueAsString("-none-"),
+ Parameters = m_Args.AttrByName("parameters").Value
+ };
+ App.Log.Write( msg );
+
+ return msg.ToString();
+ }
+
+ public override string GetHelp()
+ {
+ return
+@"Writes to application log
+ Parameters:
+ from = string - name of code/component
+ source=int - int source
+ type=MessageType - standard log MessageType enum
+ topic=string - what msg relates to
+ text=string - msg text
+ parameters=string - msg parameters
+
+";
+ }
+
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Mbc.cs b/src/Agni/AppModel/Terminal/Cmdlets/Mbc.cs
new file mode 100644
index 0000000..07f9ed4
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Mbc.cs
@@ -0,0 +1,37 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+
+using NFX;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public class Mbc : Cmdlet
+ {
+ public Mbc(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ if (!AgniSystem.IsMetabase)
+ return "Metabase is not allocated";
+
+ var result = new StringBuilder();
+ AgniSystem.Metabase.DumpCacheStatus(result);
+
+ return result.ToString();
+ }
+
+ public override string GetHelp()
+ {
+ return "Dumps status of metabase cache";
+ }
+ }
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/NetSvc.cs b/src/Agni/AppModel/Terminal/Cmdlets/NetSvc.cs
new file mode 100644
index 0000000..8cfe6ad
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/NetSvc.cs
@@ -0,0 +1,79 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+using NFX;
+using NFX.Environment;
+
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+
+ public class NetSvc : Cmdlet
+ {
+ public const string CONFIG_HOST_ATTR = "host";
+ public const string CONFIG_NET_ATTR = "net";
+ public const string CONFIG_SVC_ATTR = "svc";
+ public const string CONFIG_BINDING_ATTR = "binding";
+ public const string CONFIG_FROM_ATTR = "from";
+
+ public NetSvc(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var watch = Stopwatch.StartNew();
+
+ var host = m_Args.AttrByName(CONFIG_HOST_ATTR).ValueAsString();
+ var net = m_Args.AttrByName(CONFIG_NET_ATTR).ValueAsString();
+ var svc = m_Args.AttrByName(CONFIG_SVC_ATTR).ValueAsString();
+ var binding = m_Args.AttrByName(CONFIG_BINDING_ATTR).ValueAsString(null);
+ var from = m_Args.AttrByName(CONFIG_FROM_ATTR).ValueAsString(null);
+
+ string node;
+ try
+ {
+ node = AgniSystem.Metabase.ResolveNetworkServiceToConnectString(host, net, svc, binding, from);
+ }
+ catch(Exception error)
+ {
+ return "ERROR: "+error.ToMessageWithType();
+ }
+
+ return
+@"Resolved
+Host: {0}
+Net: {1}
+Service: {2}
+Binding: {3}
+From: {4}
+
+into Glue node
+ {5}
+Elapsed: {6} ms.".Args(host, net, svc, binding, from, node, watch.ElapsedMilliseconds );
+ }
+
+ public override string GetHelp()
+ {
+ return
+@"Resolves network service call into Glue node.
+ Parameters:
+ host=path - metabase path of target host
+ net=name - network name
+ svc=name - service name
+ binding=name - optional, Glue binding
+ from=path - optional, metabase path to call-issuing host
+";
+ }
+
+
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Perf.cs b/src/Agni/AppModel/Terminal/Cmdlets/Perf.cs
new file mode 100644
index 0000000..a61d693
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Perf.cs
@@ -0,0 +1,88 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+
+using NFX;
+using NFX.OS;
+using NFX.Environment;
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+
+ public class Perf : Cmdlet
+ {
+ public const string CONFIG_DURATION_ATTR = "duration";
+ public const string CONFIG_SAMPLE_ATTR = "sample";
+
+
+ public Perf(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var duration = m_Args.AttrByName(CONFIG_DURATION_ATTR).ValueAsInt(8000);
+ var sample = m_Args.AttrByName(CONFIG_SAMPLE_ATTR).ValueAsInt(500);
+
+ if (duration<1000 || sample <100) return "Sampling rate must be > 100ms and duration >1000ms";
+
+
+ var watch = Stopwatch.StartNew();
+
+ var lst = new List>();
+
+ while(watch.ElapsedMillisecondsduration=int_ms - specifies measurement interval length.
+ The value must be >1000ms
+ sample=int_ms - specifies measurement sampling rate.
+ The value must be > 100ms
+";
+ }
+ }
+
+}
diff --git a/src/Agni/AppModel/Terminal/Cmdlets/Pilc.cs b/src/Agni/AppModel/Terminal/Cmdlets/Pilc.cs
new file mode 100644
index 0000000..04ef11f
--- /dev/null
+++ b/src/Agni/AppModel/Terminal/Cmdlets/Pilc.cs
@@ -0,0 +1,77 @@
+using System;
+using System.Collections.Generic;
+using System.Linq;
+using System.Text;
+using System.Diagnostics;
+using System.Threading.Tasks;
+
+using NFX;
+using NFX.Environment;
+using NFX.ApplicationModel;
+using NFX.ApplicationModel.Pile;
+
+
+
+namespace Agni.AppModel.Terminal.Cmdlets
+{
+ ///
+ /// Pile Cache
+ ///
+ public class Pilc : Cmdlet
+ {
+ public const string CONFIG_TABLE_ATTR = "table";
+ public const string CONFIG_PURGE_ATTR = "purge";
+
+ public Pilc(AppRemoteTerminal terminal, IConfigSectionNode args) : base(terminal, args)
+ {
+
+ }
+
+ public override string Execute()
+ {
+ var cache = CMan.GetApplicationComponentBySIDorName(m_Args) as ICache;
+
+ if (cache==null)
+ return "The specified component is not of ICache type";
+
+ var tName = m_Args.AttrByName(CONFIG_TABLE_ATTR).ValueAsString();
+ var purge = m_Args.AttrByName(CONFIG_PURGE_ATTR).ValueAsBool();
+
+ var sb = new StringBuilder(1024);
+ sb.AppendLine(AppRemoteTerminal.MARKUP_PRAGMA);
+ sb.Append("");
+ sb.AppendLine("Pile Cache");
+ sb.AppendLine("----------------------");
+
+ if (purge)
+ sb.AppendLine("Purging:");
+
+ foreach(var tbl in cache.Tables.Where(t => tName==null || t.Name.EqualsOrdIgnoreCase(tName)))
+ {
+ sb.AppendFormatLine("{0,-48}| {1,8:n0}({2,8:n0})| {3,4:n0}%", tbl.Name, tbl.Count, tbl.Capacity, tbl.LoadFactor*100d);
+ if (purge) tbl.Purge();
+ }
+
+ sb.AppendLine("");
+ return sb.ToString();
+ }
+
+ public override string GetHelp()
+ {
+ return
+@"Pile Cache Manager
+ Parameters:
+ sid=id - PileCache component SID
+ or
+