// ********************************************************************** // // Copyright (c) 2003-2016 ZeroC, Inc. All rights reserved. // // This copy of Ice is licensed to you under the terms described in the // ICE_LICENSE file included in this distribution. // // ********************************************************************** using System; using System.Collections; using System.Collections.Generic; using System.Threading; using System.Diagnostics; namespace IceBox { // // NOTE: the class isn't final on purpose to allow users to eventually // extend it. // class ServiceManagerI : ServiceManagerDisp_ { public ServiceManagerI(Ice.Communicator communicator, string[] args) { _communicator = communicator; _logger = _communicator.getLogger(); Ice.Properties props = _communicator.getProperties(); if(props.getProperty("Ice.Admin.Enabled").Length == 0) { _adminEnabled = props.getProperty("Ice.Admin.Endpoints").Length > 0; } else { _adminEnabled = props.getPropertyAsInt("Ice.Admin.Enabled") > 0; } if(_adminEnabled) { string[] facetFilter = props.getPropertyAsList("Ice.Admin.Facets"); if(facetFilter.Length > 0) { _adminFacetFilter = new HashSet(facetFilter); } else { _adminFacetFilter = new HashSet(); } } _argv = args; _traceServiceObserver = _communicator.getProperties().getPropertyAsInt("IceBox.Trace.ServiceObserver"); } public override Dictionary getSliceChecksums(Ice.Current current) { return Ice.SliceChecksums.checksums; } public override void startService(string name, Ice.Current current) { ServiceInfo info = new ServiceInfo(); lock(this) { // // Search would be more efficient if services were contained in // a map, but order is required for shutdown. // int i; for(i = 0; i < _services.Count; ++i) { info = _services[i]; if(info.name.Equals(name)) { if(_services[i].status != ServiceStatus.Stopped) { throw new AlreadyStartedException(); } info.status = ServiceStatus.Starting; _services[i] = info; break; } } if(i == _services.Count) { throw new NoSuchServiceException(); } _pendingStatusChanges = true; } bool started = false; try { info.service.start(info.name, info.communicator == null ? _sharedCommunicator : info.communicator, info.args); started = true; } catch(Exception e) { _logger.warning("ServiceManager: exception while starting service " + info.name + ":\n" + e.ToString()); } lock(this) { int i; for(i = 0; i < _services.Count; ++i) { info = _services[i]; if(info.name.Equals(name)) { if(started) { info.status = ServiceStatus.Started; List services = new List(); services.Add(name); servicesStarted(services, _observers.Keys); } else { info.status = ServiceStatus.Stopped; } _services[i] = info; break; } } _pendingStatusChanges = false; System.Threading.Monitor.PulseAll(this); } } public override void stopService(string name, Ice.Current current) { ServiceInfo info = new ServiceInfo(); lock(this) { // // Search would be more efficient if services were contained in // a map, but order is required for shutdown. // int i; for(i = 0; i < _services.Count; ++i) { info = _services[i]; if(info.name.Equals(name)) { if(info.status != ServiceStatus.Started) { throw new AlreadyStoppedException(); } info.status = ServiceStatus.Stopping; _services[i] = info; break; } } if(i == _services.Count) { throw new NoSuchServiceException(); } _pendingStatusChanges = true; } bool stopped = false; try { info.service.stop(); stopped = true; } catch(Exception e) { _logger.warning("ServiceManager: exception while stopping service " + info.name + "\n" + e.ToString()); } lock(this) { int i; for(i = 0; i < _services.Count; ++i) { info = _services[i]; if(info.name.Equals(name)) { if(stopped) { info.status = ServiceStatus.Stopped; List services = new List(); services.Add(name); servicesStopped(services, _observers.Keys); } else { info.status = ServiceStatus.Started; } _services[i] = info; break; } } _pendingStatusChanges = false; System.Threading.Monitor.PulseAll(this); } } public override void addObserver(ServiceObserverPrx observer, Ice.Current current) { List activeServices = new List(); // // Null observers and duplicate registrations are ignored // lock(this) { if(observer != null) { try { _observers.Add(observer, true); } catch(ArgumentException) { return; } if(_traceServiceObserver >= 1) { _logger.trace("IceBox.ServiceObserver", "Added service observer " + _communicator.proxyToString(observer)); } foreach(ServiceInfo info in _services) { if(info.status == ServiceStatus.Started) { activeServices.Add(info.name); } } } } if(activeServices.Count > 0) { observer.begin_servicesStarted(activeServices.ToArray(), this.observerCompleted, null); } } public override void shutdown(Ice.Current current) { _communicator.shutdown(); } public int run() { try { Ice.Properties properties = _communicator.getProperties(); // // Create an object adapter. Services probably should NOT share // this object adapter, as the endpoint(s) for this object adapter // will most likely need to be firewalled for security reasons. // Ice.ObjectAdapter adapter = null; if(properties.getProperty("IceBox.ServiceManager.Endpoints").Length != 0) { adapter = _communicator.createObjectAdapter("IceBox.ServiceManager"); Ice.Identity identity = new Ice.Identity(); identity.category = properties.getPropertyWithDefault("IceBox.InstanceName", "IceBox"); identity.name = "ServiceManager"; adapter.add(this, identity); } // // Parse the property set with the prefix "IceBox.Service.". These // properties should have the following format: // // IceBox.Service.Foo=:Package.Foo [args] // // We parse the service properties specified in IceBox.LoadOrder // first, then the ones from remaining services. // string prefix = "IceBox.Service."; Dictionary services = properties.getPropertiesForPrefix(prefix); string[] loadOrder = properties.getPropertyAsList("IceBox.LoadOrder"); List servicesInfo = new List(); for(int i = 0; i < loadOrder.Length; ++i) { if(loadOrder[i].Length > 0) { string key = prefix + loadOrder[i]; string value = services[key]; if(value == null) { FailureException ex = new FailureException(); ex.reason = "ServiceManager: no service definition for `" + loadOrder[i] + "'"; throw ex; } servicesInfo.Add(new StartServiceInfo(loadOrder[i], value, _argv)); services.Remove(key); } } foreach(KeyValuePair entry in services) { string name = entry.Key.Substring(prefix.Length); string value = entry.Value; servicesInfo.Add(new StartServiceInfo(name, value, _argv)); } // // Check if some services are using the shared communicator in which // case we create the shared communicator now with a property set that // is the union of all the service properties (from services that use // the shared communicator). // if(properties.getPropertiesForPrefix("IceBox.UseSharedCommunicator.").Count > 0) { Ice.InitializationData initData = new Ice.InitializationData(); initData.properties = createServiceProperties("SharedCommunicator"); foreach(StartServiceInfo service in servicesInfo) { if(properties.getPropertyAsInt("IceBox.UseSharedCommunicator." + service.name) <= 0) { continue; } // // Load the service properties using the shared communicator properties as // the default properties. // Ice.Properties svcProperties = Ice.Util.createProperties(ref service.args, initData.properties); // // Remove properties from the shared property set that a service explicitly clears. // Dictionary allProps = initData.properties.getPropertiesForPrefix(""); foreach(string key in allProps.Keys) { if(svcProperties.getProperty(key).Length == 0) { initData.properties.setProperty(key, ""); } } // // Add the service properties to the shared communicator properties. // foreach(KeyValuePair entry in svcProperties.getPropertiesForPrefix("")) { initData.properties.setProperty(entry.Key, entry.Value); } // // Parse .* command line options (the Ice command line options // were parsed by the call to createProperties above). // service.args = initData.properties.parseCommandLineOptions(service.name, service.args); } string facetNamePrefix = "IceBox.SharedCommunicator."; bool addFacets = configureAdmin(initData.properties, facetNamePrefix); _sharedCommunicator = Ice.Util.initialize(initData); if(addFacets) { // Add all facets created on shared communicator to the IceBox communicator // but renamed ., except for the Process facet which is // never added. foreach(KeyValuePair p in _sharedCommunicator.findAllAdminFacets()) { if(!p.Key.Equals("Process")) { _communicator.addAdminFacet(p.Value, facetNamePrefix + p.Key); } } } } foreach(StartServiceInfo s in servicesInfo) { startService(s.name, s.entryPoint, s.args); } // // We may want to notify external scripts that the services // have started. This is done by defining the property: // // PrintServicesReady=bundleName // // Where bundleName is whatever you choose to call this set of // services. It will be echoed back as "bundleName ready". // // This must be done after start() has been invoked on the // services. // string bundleName = properties.getProperty("IceBox.PrintServicesReady"); if(bundleName.Length > 0) { Console.Out.WriteLine(bundleName + " ready"); } // // Don't move after the adapter activation. This allows // applications to wait for the service manager to be // reachable before sending a signal to shutdown the // // Ice.Application.shutdownOnInterrupt(); // // Register "this" as a facet to the Admin object and create Admin object // try { _communicator.addAdminFacet(this, "IceBox.ServiceManager"); _communicator.getAdmin(); } catch(Ice.ObjectAdapterDeactivatedException) { // // Expected if the communicator has been shutdown. // } // // Start request dispatching after we've started the services. // if(adapter != null) { try { adapter.activate(); } catch(Ice.ObjectAdapterDeactivatedException) { // // Expected if the communicator has been shutdown. // } } _communicator.waitForShutdown(); } catch(FailureException ex) { _logger.error(ex.ToString()); return 1; } catch(Exception ex) { _logger.error("ServiceManager: caught exception:\n" + ex.ToString()); return 1; } finally { // // Invoke stop() on the services. // stopAll(); } return 0; } private void startService(string service, string entryPoint, string[] args) { lock(this) { // // Extract the assembly name and the class name. // string err = "ServiceManager: unable to load service '" + entryPoint + "': "; int sepPos = entryPoint.IndexOf(':'); if(sepPos != -1 && IceInternal.AssemblyUtil.platform_ == IceInternal.AssemblyUtil.Platform.Windows) { const string driveLetters = "abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ"; if(entryPoint.Length > 3 && sepPos == 1 && driveLetters.IndexOf(entryPoint[0]) != -1 && (entryPoint[2] == '\\' || entryPoint[2] == '/')) { sepPos = entryPoint.IndexOf(':', 3); } } if(sepPos == -1) { FailureException e = new FailureException(); e.reason = err + "invalid entry point format"; throw e; } System.Reflection.Assembly serviceAssembly = null; string assemblyName = entryPoint.Substring(0, sepPos); string className = entryPoint.Substring(sepPos + 1); try { // // First try to load the assembly using Assembly.Load, which will succeed // if a fully-qualified name is provided or if a partial name has been qualified // in configuration. If that fails, try Assembly.LoadFrom(), which will succeed // if a file name is configured or a partial name is configured and DEVPATH is used. // try { serviceAssembly = System.Reflection.Assembly.Load(assemblyName); } catch(System.IO.IOException ex) { try { serviceAssembly = System.Reflection.Assembly.LoadFrom(assemblyName); } catch(System.IO.IOException) { throw ex; } } } catch(System.Exception ex) { FailureException e = new FailureException(ex); e.reason = err + "unable to load assembly: " + assemblyName; throw e; } // // Instantiate the class. // System.Type c = null; try { c = serviceAssembly.GetType(className, true); } catch(System.Exception ex) { FailureException e = new FailureException(ex); e.reason = err + "GetType failed for '" + className + "'"; throw e; } ServiceInfo info = new ServiceInfo(); info.name = service; info.status = ServiceStatus.Stopped; info.args = args; // // If IceBox.UseSharedCommunicator. is defined, create a // communicator for the service. The communicator inherits // from the shared communicator properties. If it's not // defined, add the service properties to the shared // commnunicator property set. // Ice.Communicator communicator; if(_communicator.getProperties().getPropertyAsInt("IceBox.UseSharedCommunicator." + service) > 0) { Debug.Assert(_sharedCommunicator != null); communicator = _sharedCommunicator; } else { // // Create the service properties. We use the communicator properties as the default // properties if IceBox.InheritProperties is set. // Ice.InitializationData initData = new Ice.InitializationData(); initData.properties = createServiceProperties(service); if(info.args.Length > 0) { // // Create the service properties with the given service arguments. This should // read the service config file if it's specified with --Ice.Config. // initData.properties = Ice.Util.createProperties(ref info.args, initData.properties); // // Next, parse the service ".*" command line options (the Ice command // line options were parsed by the createProperties above) // info.args = initData.properties.parseCommandLineOptions(service, info.args); } // // Clone the logger to assign a new prefix. If one of the built-in loggers is configured // don't set any logger. // if(initData.properties.getProperty("Ice.LogFile").Length == 0 && (initData.properties.getPropertyAsInt("Ice.UseSyslog") <= 0 || IceInternal.AssemblyUtil.platform_ == IceInternal.AssemblyUtil.Platform.Windows)) { initData.logger = _logger.cloneWithPrefix(initData.properties.getProperty("Ice.ProgramName")); } // // If Admin is enabled on the IceBox communicator, for each service that does not set // Ice.Admin.Enabled, we set Ice.Admin.Enabled=1 to have this service create facets; then // we add these facets to the IceBox Admin object as IceBox.Service... // string serviceFacetNamePrefix = "IceBox.Service." + service + "."; bool addFacets = configureAdmin(initData.properties, serviceFacetNamePrefix); // // Remaining command line options are passed to the communicator. This is // necessary for Ice plug-in properties (e.g.: IceSSL). // info.communicator = Ice.Util.initialize(ref info.args, initData); communicator = info.communicator; if(addFacets) { // Add all facets created on the service communicator to the IceBox communicator // but renamed IceBox.Service.., except for the Process facet // which is never added foreach(KeyValuePair p in communicator.findAllAdminFacets()) { if(!p.Key.Equals("Process")) { _communicator.addAdminFacet(p.Value, serviceFacetNamePrefix + p.Key); } } } } try { // // Instantiate the service. // try { // // If the service class provides a constructor that accepts an Ice.Communicator argument, // use that in preference to the default constructor. // Type[] parameterTypes = new Type[1]; parameterTypes[0] = typeof(Ice.Communicator); System.Reflection.ConstructorInfo ci = c.GetConstructor(parameterTypes); if(ci != null) { try { Object[] parameters = new Object[1]; parameters[0] = _communicator; info.service = (Service)ci.Invoke(parameters); } catch(System.MethodAccessException ex) { FailureException e = new FailureException(ex); e.reason = err + "unable to access service constructor " + className + "(Ice.Communicator)"; throw e; } } else { // // Fall back to the default constructor. // try { info.service = (Service)IceInternal.AssemblyUtil.createInstance(c); if(info.service == null) { FailureException e = new FailureException(); e.reason = err + "no default constructor for '" + className + "'"; throw e; } } catch(System.UnauthorizedAccessException ex) { FailureException e = new FailureException(ex); e.reason = err + "unauthorized access to default service constructor for " + className; throw e; } } } catch(FailureException) { throw; } catch(System.InvalidCastException ex) { FailureException e = new FailureException(ex); e.reason = err + "service does not implement IceBox.Service"; throw e; } catch(System.Reflection.TargetInvocationException ex) { if(ex.InnerException is IceBox.FailureException) { throw ex.InnerException; } else { FailureException e = new FailureException(ex.InnerException); e.reason = err + "exception in service constructor for " + className; throw e; } } catch(System.Exception ex) { FailureException e = new FailureException(ex); e.reason = err + "exception in service constructor " + className; throw e; } try { info.service.start(service, communicator, info.args); } catch(FailureException) { throw; } catch(System.Exception ex) { FailureException e = new FailureException(ex); e.reason = "exception while starting service " + service; throw e; } info.status = ServiceStatus.Started; _services.Add(info); } catch(System.Exception ex) { if(info.communicator != null) { destroyServiceCommunicator(service, info.communicator); } throw ex; } } } private void stopAll() { lock(this) { // // First wait for any active startService/stopService calls to complete. // while(_pendingStatusChanges) { System.Threading.Monitor.Wait(this); } // // For each service, we call stop on the service and flush its database environment to // the disk. Services are stopped in the reverse order of the order they were started. // _services.Reverse(); List stoppedServices = new List(); foreach(ServiceInfo info in _services) { if(info.status == ServiceStatus.Started) { try { info.service.stop(); stoppedServices.Add(info.name); } catch(System.Exception e) { _logger.warning("IceBox.ServiceManager: exception while stopping service " + info.name + ":\n" + e.ToString()); } } if(info.communicator != null) { destroyServiceCommunicator(info.name, info.communicator); } } if(_sharedCommunicator != null) { removeAdminFacets("IceBox.SharedCommunicator."); try { _sharedCommunicator.destroy(); } catch(System.Exception e) { _logger.warning("ServiceManager: exception while destroying shared communicator:\n" + e.ToString()); } _sharedCommunicator = null; } _services.Clear(); servicesStopped(stoppedServices, _observers.Keys); } } private void servicesStarted(List services, Dictionary.KeyCollection observers) { // // Must be called with 'this' unlocked // if(services.Count > 0) { string[] servicesArray = services.ToArray(); foreach(ServiceObserverPrx observer in observers) { observer.begin_servicesStarted(servicesArray, this.observerCompleted, null); } } } private void servicesStopped(List services, Dictionary.KeyCollection observers) { // // Must be called with 'this' unlocked // if(services.Count > 0) { string[] servicesArray = services.ToArray(); foreach(ServiceObserverPrx observer in observers) { observer.begin_servicesStopped(servicesArray, this.observerCompleted, null); } } } private void observerCompleted(Ice.AsyncResult result) { try { result.throwLocalException(); } catch(Ice.LocalException ex) { lock(this) { ServiceObserverPrx observer = ServiceObserverPrxHelper.uncheckedCast(result.getProxy()); if(_observers.Remove(observer)) { observerRemoved(observer, ex); } } } } private void observerRemoved(ServiceObserverPrx observer, System.Exception ex) { if(_traceServiceObserver >= 1) { // // CommunicatorDestroyedException may occur during shutdown. The observer notification has // been sent, but the communicator was destroyed before the reply was received. We do not // log a message for this exception. // if(!(ex is Ice.CommunicatorDestroyedException)) { _logger.trace("IceBox.ServiceObserver", "Removed service observer " + _communicator.proxyToString(observer) + "\nafter catching " + ex.ToString()); } } } private enum ServiceStatus { Stopping, Stopped, Starting, Started } struct ServiceInfo { public string name; public Service service; public Ice.Communicator communicator; public ServiceStatus status; public string[] args; } class StartServiceInfo { public StartServiceInfo(string service, string value, string[] serverArgs) { // // Separate the entry point from the arguments. // name = service; try { args = IceUtilInternal.Options.split(value); } catch(IceUtilInternal.Options.BadQuote ex) { FailureException e = new FailureException(); e.reason = "ServiceManager: invalid arguments for service `" + name + "':\n" + ex.Message; throw e; } Debug.Assert(args.Length > 0); entryPoint = args[0]; // // Shift the arguments. // string[] tmp = new string[args.Length - 1]; Array.Copy(args, 1, tmp, 0, args.Length - 1); args = tmp; if(serverArgs.Length > 0) { ArrayList l = new ArrayList(); for(int j = 0; j < args.Length; j++) { l.Add(args[j]); } for(int j = 0; j < serverArgs.Length; j++) { if(serverArgs[j].StartsWith("--" + service + ".", StringComparison.Ordinal)) { l.Add(serverArgs[j]); } } args = (string[])l.ToArray(typeof(string)); } } public string name; public string entryPoint; public string[] args; } private Ice.Properties createServiceProperties(String service) { Ice.Properties properties; Ice.Properties communicatorProperties = _communicator.getProperties(); if(communicatorProperties.getPropertyAsInt("IceBox.InheritProperties") > 0) { properties = communicatorProperties.ice_clone_(); // Inherit all except Ice.Admin.xxx properties foreach(string p in properties.getPropertiesForPrefix("Ice.Admin.").Keys) { properties.setProperty(p, ""); } } else { properties = Ice.Util.createProperties(); } String programName = communicatorProperties.getProperty("Ice.ProgramName"); if(programName.Length == 0) { properties.setProperty("Ice.ProgramName", service); } else { properties.setProperty("Ice.ProgramName", programName + "-" + service); } return properties; } private void destroyServiceCommunicator(string service, Ice.Communicator communicator) { if(communicator != null) { try { communicator.shutdown(); communicator.waitForShutdown(); } catch(Ice.CommunicatorDestroyedException) { // // Ignore, the service might have already destroyed // the communicator for its own reasons. // } catch(System.Exception e) { _logger.warning("ServiceManager: exception while shutting down communicator for service " + service + "\n" + e.ToString()); } removeAdminFacets("IceBox.Service." + service + "."); try { communicator.destroy(); } catch(System.Exception e) { _logger.warning("ServiceManager: exception while destroying communicator for service " + service + "\n" + e.ToString()); } } } private bool configureAdmin(Ice.Properties properties, string prefix) { if(_adminEnabled && properties.getProperty("Ice.Admin.Enabled").Length == 0) { List facetNames = new List(); foreach(string p in _adminFacetFilter) { if(p.StartsWith(prefix)) { facetNames.Add(p.Substring(prefix.Length)); } } if(_adminFacetFilter.Count == 0 || facetNames.Count > 0) { properties.setProperty("Ice.Admin.Enabled", "1"); if(facetNames.Count > 0) { // TODO: need String.Join with escape! properties.setProperty("Ice.Admin.Facets", String.Join(" ", facetNames.ToArray())); } return true; } } return false; } private void removeAdminFacets(string prefix) { try { foreach(string p in _communicator.findAllAdminFacets().Keys) { if(p.StartsWith(prefix)) { _communicator.removeAdminFacet(p); } } } catch(Ice.CommunicatorDestroyedException) { // Ignored } catch(Ice.ObjectAdapterDeactivatedException) { // Ignored } } private Ice.Communicator _communicator; private bool _adminEnabled = false; private HashSet _adminFacetFilter = null; private Ice.Communicator _sharedCommunicator = null; private Ice.Logger _logger; private string[] _argv; // Filtered server argument vector private List _services = new List(); private bool _pendingStatusChanges = false; private Dictionary _observers = new Dictionary(); private int _traceServiceObserver = 0; } }