/* * Copyright to the original author or authors. * * Licensed under the Apache License, Version 2.0 (the "License"); * you may not use this file except in compliance with the License. * You may obtain a copy of the License at * * http://www.apache.org/licenses/LICENSE-2.0 * * Unless required by applicable law or agreed to in writing, software * distributed under the License is distributed on an "AS IS" BASIS, * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. * See the License for the specific language governing permissions and * limitations under the License. */ package org.rioproject.monitor.service; import com.sun.jini.config.Config; import com.sun.jini.landlord.FixedLeasePeriodPolicy; import com.sun.jini.landlord.LeasePeriodPolicy; import com.sun.jini.landlord.LeasedResource; import net.jini.config.Configuration; import net.jini.core.constraint.RemoteMethodControl; import net.jini.core.event.EventRegistration; import net.jini.core.lease.Lease; import net.jini.core.lease.LeaseDeniedException; import net.jini.core.lease.UnknownLeaseException; import net.jini.id.Uuid; import net.jini.security.BasicProxyPreparer; import net.jini.security.ProxyPreparer; import org.rioproject.deploy.DeployedService; import org.rioproject.deploy.ServiceBeanInstantiator; import org.rioproject.deploy.ServiceProvisionEvent; import org.rioproject.event.EventHandler; import org.rioproject.monitor.ProvisionFailureEvent; import org.rioproject.monitor.service.managers.FixedServiceManager; import org.rioproject.monitor.service.managers.PendingManager; import org.rioproject.monitor.service.selectors.RoundRobinSelector; import org.rioproject.monitor.service.selectors.Selector; import org.rioproject.monitor.service.selectors.ServiceResourceSelector; import org.rioproject.monitor.service.tasks.ProvisionFailureEventTask; import org.rioproject.monitor.service.tasks.ProvisionTask; import org.rioproject.monitor.service.util.FailureReasonFormatter; import org.rioproject.monitor.service.util.LoggingUtil; import org.rioproject.opstring.ServiceElement; import org.rioproject.impl.service.LandlordLessor; import org.rioproject.impl.service.LeaseListenerAdapter; import org.rioproject.impl.service.ServiceResource; import org.rioproject.system.ResourceCapability; import org.rioproject.impl.util.TimeConstants; import org.rioproject.impl.watch.GaugeWatch; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.io.IOException; import java.rmi.MarshalledObject; import java.rmi.NoSuchObjectException; import java.rmi.RemoteException; import java.util.ArrayList; import java.util.Collections; import java.util.List; import java.util.concurrent.Executors; import java.util.concurrent.ThreadPoolExecutor; import java.util.concurrent.atomic.AtomicInteger; /** * The ServiceProvisioner is responsible for the managing the leases of * <code>ServiceBeanInstantiator</code> objects and performing the actual * provisioning of <code>ServiceElement</code> objects to available * <code>ServiceBeanInstantiator</code> instances based on the capability to * fulfill the Quality of Service specification of the * <code>ServiceElement</code>. * * @author Dennis Reedy */ public class ServiceProvisioner implements ServiceProvisionDispatcher { /** Indicates a Provision failure */ public static final int PROVISION_FAILURE = 1; /** Indicates a JSB cannot be provisioned */ public static final int UNINSTANTIABLE_JSB = 1 << 1; /** Indicates a JSB cannot be provisioned */ public static final int BAD_CYBERNODE = 1 << 2; /** The Landlord that will manage leases for ServiceInstantiation resources */ private LandlordLessor landlord; /** ProvisionEvent sequence number */ private AtomicInteger serviceProvisionEventSequenceNumber = new AtomicInteger(0); /** EventRegistration sequence number */ private AtomicInteger eventRegistrationSequenceNumber = new AtomicInteger(0); /** Event source */ private Object eventSource; /** EventHandler to fire ProvisionFailureEvent notifications */ private EventHandler failureHandler; /** Executor for provision processing */ private ThreadPoolExecutor provisioningPool; /** Executor for provision failure event processing */ private ThreadPoolExecutor provisionFailurePool; /** Collection of in-process provision attempts */ private final List<ServiceElement> inProcess = Collections.synchronizedList(new ArrayList<ServiceElement>()); /** A Watch to measure provision time */ private GaugeWatch watch; /** Manages pending provision dispatch requests for provision types of auto */ private final PendingManager pendingMgr; /** Manages provision dispatch requests for provision types of station */ private final FixedServiceManager fixedServiceManager; /** Manages the selection of ServiceResource objects for provisioning requests */ private final ServiceResourceSelector selector; /** ProxyPreparer for ServiceInstantiator proxies */ private ProxyPreparer instantiatorPreparer; private static final String CONFIG_COMPONENT = "org.rioproject.monitor"; /** Logger instance */ private static final Logger logger = LoggerFactory.getLogger(ServiceProvisioner.class); private boolean terminating = false; private boolean terminated = false; /** * Create a ServiceProvisioner * * @param config Configuration object used to set operational parameters * @param eventSource The originator of events * @param failureHandler Event handler for processing failure event notifications * @param watch For timing how long it takes to provision a service * @throws Exception if errors are encountered using the Configuration * object or creating LandlordLessor */ ServiceProvisioner(final Configuration config, final Object eventSource, final EventHandler failureHandler, final GaugeWatch watch) throws Exception { if(config==null) throw new IllegalArgumentException("config is null"); if(failureHandler==null) throw new IllegalArgumentException("failureHandler is null"); /* 5 minute default Lease time */ long DEFAULT_LEASE_TIME = TimeConstants.FIVE_MINUTES; /* 1 day max lease time */ long DEFAULT_MAX_LEASE_TIME = TimeConstants.ONE_DAY; /* Get the Lease policy */ LeasePeriodPolicy provisionerLeasePolicy = (LeasePeriodPolicy)Config.getNonNullEntry(config, CONFIG_COMPONENT, "provisionerLeasePeriodPolicy", LeasePeriodPolicy.class, new FixedLeasePeriodPolicy(DEFAULT_MAX_LEASE_TIME, DEFAULT_LEASE_TIME)); /* Get the ProxyPreparer for ServiceInstantiator instances */ instantiatorPreparer = (ProxyPreparer)config.getEntry(CONFIG_COMPONENT, "instantiatorPreparer", ProxyPreparer.class, new BasicProxyPreparer()); /* Create a ThreadPool for provisioning notification */ //provisioningPool = (ThreadPoolExecutor) Executors.newFixedThreadPool(provisioningPoolMaxThreads); provisioningPool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); /* Create a ThreadPool for provision failure notification */ //provisionFailurePool = (ThreadPoolExecutor) Executors.newFixedThreadPool(provisioningPoolMaxThreads); provisionFailurePool = (ThreadPoolExecutor) Executors.newCachedThreadPool(); /* Create the LandlordLessor */ landlord = new LandlordLessor(config, provisionerLeasePolicy); landlord.addLeaseListener(new LeaseMonitor()); /* Get the ServiceResourceSelector */ selector = (ServiceResourceSelector)config.getEntry(CONFIG_COMPONENT, "serviceResourceSelector", ServiceResourceSelector.class, new RoundRobinSelector()); selector.setLandlordLessor(landlord); logger.trace("ServiceResourceSelector : {}", selector.getClass().getName()); this.eventSource = eventSource; this.watch = watch; this.failureHandler = failureHandler; pendingMgr = new PendingManager(getServiceProvisionContext(null, null)); fixedServiceManager = new FixedServiceManager(getServiceProvisionContext(null, null)); } private ServiceProvisionContext getServiceProvisionContext(final ProvisionRequest request, final ServiceResource serviceResource) { ServiceProvisionContext context = new ServiceProvisionContext(selector, provisioningPool, inProcess, eventSource, watch, this, provisionFailurePool, failureHandler, serviceProvisionEventSequenceNumber); context.setProvisionRequest(request); context.setServiceResource(serviceResource); return context; } /** * Clean up all resources */ void terminate() { terminating = true; landlord.stop(true); provisioningPool.shutdownNow(); provisionFailurePool.shutdownNow(); terminated = true; } /** * @return the ServiceResourceSelector */ ServiceResourceSelector getServiceResourceSelector() { return selector; } /** * @return The PendingManager */ PendingManager getPendingManager() { return pendingMgr; } /** * @return The FixedServiceManager */ FixedServiceManager getFixedServiceManager() { return(fixedServiceManager); } /** * Registers a RemoteEventListener that will be notified in a round robin * fashion to instantiate a ServiceBean as defined by the ServiceElement object * that will be contained within the ServiceProvisionEvent<br> * * @param sbi The ServiceBeanInstantiator wrapped in a MarshalledObject * @param handback A handback object * @param resourceCapability The capabilities of the ServiceInstantiator * @param deployedServices List of deployed services * @param serviceLimit The maximum number of services the * ServiceBeanInstantiator has been configured to instantiate * @param duration Requested lease duration * * @return EventRegistration * * @throws LeaseDeniedException If the Lease is denied for any reason * @throws RemoteException for comm errors */ EventRegistration register(final MarshalledObject<ServiceBeanInstantiator> sbi, final MarshalledObject handback, final ResourceCapability resourceCapability, final List<DeployedService> deployedServices, final int serviceLimit, final long duration) throws LeaseDeniedException, RemoteException { ServiceBeanInstantiator instantiator; try { instantiator = sbi.get(); } catch (IOException e) { throw new LeaseDeniedException("Error calling MarshalledObject.get(), "+e.getLocalizedMessage()); } catch (ClassNotFoundException e) { throw new LeaseDeniedException("Could not load ServiceBeanInstantiator, "+e.getLocalizedMessage()); } if(instantiator instanceof RemoteMethodControl) instantiator = (ServiceBeanInstantiator)instantiatorPreparer.prepareProxy(instantiator); String name; try { name = instantiator.getName(); } catch(Throwable t) { name = "Cybernode"; } Uuid uuid = instantiator.getInstantiatorUuid(); InstantiatorResource resource = new InstantiatorResource(sbi, instantiator, name, uuid, handback, resourceCapability, serviceLimit); for (LeasedResource lr : landlord.getLeasedResources()) { InstantiatorResource ir = (InstantiatorResource)((ServiceResource)lr).getResource(); if(ir.equals(resource)) { throw new LeaseDeniedException("Already registered"); } } try { resource.setDeployedServices(deployedServices); } catch (Throwable t) { logger.warn("Registering a ServiceBeanInstantiator", t); throw new LeaseDeniedException("Getting ServiceRecords"); } ServiceResource serviceResource = new ServiceResource(resource); /* Add ServiceResource to landlord */ Lease lease = landlord.newLease(serviceResource, duration); EventRegistration registration = new EventRegistration(ServiceProvisionEvent.ID, eventSource, lease, eventRegistrationSequenceNumber.incrementAndGet()); logger.debug("Registered new {}, count [{}]", name, landlord.total()); /* Process all provision types of Fixed first */ fixedServiceManager.process(serviceResource); /* See if any dynamic provision types are pending */ pendingMgr.process(); return registration; } /** * Get the corresponding InstantiatorResource by iterating through all * known ServiceResource instances. If not found or the lease is not valid * throw an UnknownLeaseException * * @param resource The ServiceBeanInstantiator * @param updatedCapabilities Updated ResourceCapability * @param deployedServices List of deployed services * @param serviceLimit The maximum number of services the * ServiceBeanInstantiator has been configured to instantiate * * @throws UnknownLeaseException If the Lease is unknown * @throws RemoteException if the ServiceBeanInstantiator proxy fails * preparation */ void handleFeedback(final ServiceBeanInstantiator resource, final ResourceCapability updatedCapabilities, final List<DeployedService> deployedServices, final int serviceLimit) throws UnknownLeaseException, RemoteException { ServiceBeanInstantiator preparedResource = resource; if(resource instanceof RemoteMethodControl) preparedResource = (ServiceBeanInstantiator)instantiatorPreparer.prepareProxy(resource); if(logger.isTraceEnabled()) logger.trace("Calling {}", selector.getClass().getName()); ServiceResource[] svcResources = selector.getServiceResources(); if(svcResources.length == 0) { logger.warn("{} is updating resource information, but we don't have any registered Cybernodes. " + "Force removal of all Leases", resource.getName()); landlord.removeAll(); throw new UnknownLeaseException("Update failed, there are no known leases."); } boolean updated = false; ServiceResource couldNotEnsureLease = null; for(ServiceResource svcResource : svcResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); logger.trace("Checking for InstantiatorResource match"); if(ir.getInstantiator().equals(preparedResource)) { logger.trace("Update from {}, current serviceCount {}, serviceLimit {}", ir.getName(), deployedServices.size(), serviceLimit); logger.trace("Matched InstantiatorResource"); if(!landlord.ensure(svcResource)) { couldNotEnsureLease = svcResource; break; } updated = true; logger.trace("Set updated resource capabilities"); ir.setResourceCapability(updatedCapabilities); logger.trace("Set serviceLimit to {}", serviceLimit); ir.setServiceLimit(serviceLimit); try { logger.trace("Set deployedServices, was: {}, updated count is now: {}", ir.getServiceCount(), deployedServices.size()); ir.setDeployedServices(deployedServices); } catch (Throwable t) { logger.warn("Getting ServiceRecords from {}", ir.getName(), t); } /* Process all provision types of Fixed first */ fixedServiceManager.process(svcResource); /* See if any dynamic provision types are pending */ pendingMgr.process(); break; } else { logger.trace("Did not match InstantiatorResource"); } } if(couldNotEnsureLease!=null) { selector.dropServiceResource(couldNotEnsureLease); throw new UnknownLeaseException("Could not ensure lease. Lease expiration: "+couldNotEnsureLease.getExpiration()+", " + "current time: "+System.currentTimeMillis()); } if(!updated) { logger.warn("Update failed, no matching registration found for {}", resource.getName()); throw new UnknownLeaseException("Update failed, no matching registration found"); } } /** * Dispatch a provision request. This method is used to provision ServiceElement * object that has a provision type of DYNAMIC * * @param request The ProvisionRequest */ public void dispatch(final ProvisionRequest request) { ServiceResource resource = Selector.acquireServiceResource(request, selector); dispatch(request, resource, 0); } /** * Provision a pending ServiceElement with a provision type of DYNAMIC with an * index into the Collection of pending ServiceElement instances managed by the * PendingManager. If a ServiceResource cannot be found, put the ServiceElement * under the management of the PendingManager and fire a ProvisionFailureEvent * * @param request The ProvisionRequest * @param resource A ServiceResource that contains an InstantiatorResource * which meets the operational requirements of the ServiceElement * @param index Index of the ServiceElement in the pending collection */ public void dispatch(final ProvisionRequest request, final ServiceResource resource, final long index) { if(terminating || terminated) { logger.info("Request to dispatch {} ignored, utility has terminated", LoggingUtil.getLoggingName(request)); return; } try { if(resource != null) { inProcess.add(request.getServiceElement()); provisioningPool.execute(new ProvisionTask(getServiceProvisionContext(request, resource), pendingMgr, index)); } else { logger.warn(FailureReasonFormatter.format(request, selector)); /* If we have a ServiceProvisionListener, notify the * listener */ if(request.getServiceProvisionListener()!=null) { try { request.getServiceProvisionListener().failed(request.getServiceElement(), true); } catch(NoSuchObjectException e) { logger.warn("ServiceBeanInstantiatorListener failure notification did not succeed, "+ "[java.rmi.NoSuchObjectException: {}], remove ServiceBeanInstantiatorListener [{}]", e.getLocalizedMessage(), request.getServiceProvisionListener()); request.setServiceProvisionListener(null); } catch(Exception e) { logger.warn("ServiceBeanInstantiatorListener notification", e); } } /* If this is not the result of a relocation request, add to the pending testManager */ if(!request.getType().equals(ProvisionRequest.Type.RELOCATE)) { pendingMgr.addProvisionRequest(request, index); logger.debug("Wrote [{}] to {}", LoggingUtil.getLoggingName(request), pendingMgr.getType()); pendingMgr.dumpCollection(); } processProvisionFailure(new ProvisionFailureEvent(eventSource, request.getServiceElement(), request.getFailureReasons(), null)); } } catch(Throwable t) { logger.warn("Dispatching ProvisionRequest", t); processProvisionFailure(new ProvisionFailureEvent(eventSource, request.getServiceElement(), t.getClass().getName()+":"+t.getLocalizedMessage(), t)); } } /* * Helper method to dispatch a ProvisionFailureEventTask to send a ProvisionFailureEvent */ private void processProvisionFailure(final ProvisionFailureEvent event) { provisionFailurePool.execute(new ProvisionFailureEventTask(event, failureHandler)); } /** * Monitors ServiceBeanInstantiator leases being removed. */ class LeaseMonitor extends LeaseListenerAdapter { @Override public void expired(final LeasedResource resource) { remove(resource); } @Override public void removed(final LeasedResource resource) { remove(resource); } private void remove(final LeasedResource resource) { InstantiatorResource ir = (InstantiatorResource)((ServiceResource)resource).getResource(); int instantiatorCount = landlord.total(); logger.info("{} @ {} removed, count now [{}]", ir.getName(), ir.getResourceCapability().getAddress(), instantiatorCount); } } }