/* * 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.selectors; import com.sun.jini.landlord.LeasedResource; import net.jini.id.Uuid; import org.rioproject.monitor.service.AssociationMatcher; import org.rioproject.monitor.service.InstantiatorResource; import org.rioproject.monitor.service.ProvisionException; import org.rioproject.monitor.service.ProvisionRequest; import org.rioproject.opstring.ServiceElement; import org.rioproject.impl.service.LandlordLessor; import org.rioproject.impl.service.LeaseListener; import org.rioproject.impl.service.ServiceResource; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import java.util.*; /** * This abstract class defines the semantics for selecting a * <code>ServiceResource</code> based on requirements contained within a * <code>ServiceElement</code>. Additionally, this interface defines * semantics for the removal and snapshot retrieval of * <code>ServiceResource</code> objects under its control. * <p> * Objects which implement this interface will need to create a * <code>Collection</code> to manage the <code>ServiceResource</code> * elements it is notified of. The managed <code>Collection</code> will back * the <code>Collection</code> of <code>ServiceResource</code> elements * which are leased and managed by the <code>LandlordLessor</code> * <p> * The <code>LandlordLessor</code> will send notifications of the following: * <ul> * <li>Notifies the manager of a lease expiration * <li>Notifies the manager of a new lease being created * <li>Notifies the manager of a lease being renewed * </ul> * <p> * It is the responsibility of the entity to register with the * <code>LandlordLessor</code>. The object that implements this interface * should define the actions to take upon receipt of such notifications * <p> * Concrete implementations of this class must provide a zero-argument * constructor in order to be instantiated by the ProvisionMonitor * * @author Dennis Reedy */ public abstract class ServiceResourceSelector implements LeaseListener { /** The collection of ServiceResource objects to manage */ protected Collection<LeasedResource> collection; /* Semaphore for access to modifying the collection */ protected final Object collectionLock = new Object(); static final Logger logger = LoggerFactory.getLogger(ServiceResourceSelector.class); /** * The LandlordLessor which will be registered to, and will provide Lease * notification events */ protected LandlordLessor landlord; /** * Set the <code>LandlordLessor</code> the * <code>ServiceResourceSelector</code> will register to. * * @param landlord The LandlordLessor */ public void setLandlordLessor(final LandlordLessor landlord) { this.landlord = landlord; landlord.addLeaseListener(this); } /** * This method will attempt to identify an available * <code>ServiceResource</code> based on the operational criteria * specified by a ServiceBean * * @param provisionRequest The ProvisionRequest * @return If a <code>ServiceResource</code> object can * be identified, return the <code>ServiceResource</code>, otherwise * return <code>null</code> * * @throws Exception If there are errors getting a ServiceResource */ public ServiceResource getServiceResource(final ProvisionRequest provisionRequest) throws Exception { ServiceResource[] svcResources = getServiceResources(); if(svcResources.length==0) { provisionRequest.addFailureReason(String.format("There are no registered Cybernodes, unable to provision %s", provisionRequest.getServiceElement().getName())); return null; } return (selectServiceResource(provisionRequest, svcResources)); } /** * This method will attempt to identify an available * <code>ServiceResource</code> based on the operational criteria * specified by a ServiceBean * * @param provisionRequest The ProvisionRequest * @param uuid The Uuid of the InstantiatorResource to either include or * not include * @param inclusive Either include or exclude the uuid from the selection. * If true, include the uuid otherwise exclude the uuid * @return If a <code>ServiceResource</code> object can * be identified, otherwise return <code>null</code> * * @throws ProvisionException if any errors occur selecting a resource */ public ServiceResource getServiceResource(final ProvisionRequest provisionRequest, final Uuid uuid, final boolean inclusive) throws ProvisionException { return (selectServiceResource(provisionRequest, getServiceResources(uuid, inclusive))); } /** * Select a ServiceResource for dynamic ServiceBean provisioning based on * the operational criteria of a ServiceBean * * @param provisionRequest The ProvisionRequest * @param svcResources Array ServiceResource candidates * @return If a <code>ServiceResource</code> object can * be identified, otherwise return <code>null</code> * * @throws org.rioproject.monitor.service.ProvisionException If there are unrecoverable errors * provisioning the service */ protected ServiceResource selectServiceResource(final ProvisionRequest provisionRequest, final ServiceResource[] svcResources) throws ProvisionException { if(svcResources.length==0) { provisionRequest.addFailureReason(String.format("There are no available Cybernodes, unable to provision %s", provisionRequest.getServiceElement().getName())); return null; } /* Filter out isolated associations and max per machine levels set * at the physical level */ ServiceResource[] filteredResources = filterMachineBoundaries(provisionRequest, svcResources); if(filteredResources.length>0) { filteredResources = filterIsolated(provisionRequest, filteredResources); } else { provisionRequest.addFailureReason(String.format("There are no available Cybernodes, unable to provision %s", provisionRequest.getServiceElement().getName())); return null; } for (ServiceResource svcResource : filteredResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); /* * Make sure the InstantiatorResource has not reached it's * serviceLimit */ ServiceElement sElem = provisionRequest.getServiceElement(); int serviceLimit = ir.getServiceLimit(); int total = ir.getServiceElementCount() + ir.getInProcessCounter(); if (total >= serviceLimit) { String reason = String.format("%s reached service limit of [%d] (service element count:%d, in-process:%d), " + "cannot be used to instantiate [%s/%s]", ir.getName(), serviceLimit, ir.getServiceElementCount(), ir.getInProcessCounter(), sElem.getOperationalStringName(), sElem.getName()); provisionRequest.addFailureReason(reason); logger.debug(reason); continue; } /* * Check if the InstantiatorResource doesn't already have the * maximum amount of services allocated. this is different then * MaxPerNode */ int planned = sElem.getPlanned(); int actual = ir.getServiceElementCount(sElem); logger.trace("{} has [{}] instance(s), planned [{}] of [{}/{}]", ir.getName(), actual, planned, sElem.getOperationalStringName(), sElem.getName()); if (actual >= planned) { String message = String.format("%s has already allocated [%s] of planned [%s] instances of [%s/%s]", ir.getName(), actual, planned, sElem.getOperationalStringName(), sElem.getName()); provisionRequest.addFailureReason(message); continue; } if (ir.getDynamicEnabled()) { try { if (ir.canProvision(provisionRequest)) { serviceResourceSelected(svcResource); logger.trace("[{}, service count: {}] has been selected for service [{}/{}]", ir.getName(), ir.getServiceCount(), sElem.getOperationalStringName(), sElem.getName()); return (svcResource); } } catch (Exception e) { logger.warn("[{}] during canProvision check for [{}]", ir.getName(), sElem.getOperationalStringName(), sElem.getName(), e); if(e instanceof ProvisionException) throw (ProvisionException)e; } } else { logger.trace("{}, dynamic enabled: {}", ir.getName(), ir.getDynamicEnabled()); } } return (null); } /** * Filter ServiceResource instances for isolated requirements * * @param provisionRequest The ProvisionRequest to verify * @param candidates The candidate ServiceResource instances * * @return An array of suitable ServiceResource instances */ public ServiceResource[] filterIsolated(final ProvisionRequest provisionRequest, final ServiceResource... candidates) { /* For the set of candidate instantiator resources, remove the * candidate instantiator resources that have the same host name */ InstantiatorResource[] known = getInstantiatorResources(provisionRequest.getServiceElement(), true); List<ServiceResource> candidateList = new ArrayList<ServiceResource>(); candidateList.addAll(Arrays.asList(candidates)); for (ServiceResource candidate1 : candidates) { InstantiatorResource candidate = (InstantiatorResource) candidate1.getResource(); if (!AssociationMatcher.meetsIsolatedRequirements(provisionRequest.getServiceElement(), candidate, known)) { candidateList.remove(candidate1); } } if(logger.isDebugEnabled() && candidateList.isEmpty()) { logger.debug("Service [{}/{}] has a virtual machine boundary constraint and an instance of the service has " + "been found on all Cybernodes executing on each machine. There are no available Cybernodes to " + "allocate service instances", provisionRequest.getServiceElement().getOperationalStringName(), provisionRequest.getServiceElement().getName()); } return(candidateList.toArray(new ServiceResource[candidateList.size()])); } /* * Filter on machine boundaries * * @param provisionRequest * @param candidates * @return */ public ServiceResource[] filterMachineBoundaries(final ProvisionRequest provisionRequest, final ServiceResource... candidates) { ServiceElement elem = provisionRequest.getServiceElement(); int maxPerMachine = elem.getMaxPerMachine(); if(!(maxPerMachine!=-1 && elem.getMachineBoundary()==ServiceElement.MachineBoundary.PHYSICAL)) { return(candidates); } /* * 1. Create a table composed of keys that are the host address and * values a list of Cybernodes * 2. Count the number services each cybernode has on each host * 3. Remove table entries that exceed the count per host */ Map<String, List<ServiceResource>> table = new HashMap<String, List<ServiceResource>>(); for (ServiceResource candidate : candidates) { InstantiatorResource ir = (InstantiatorResource)candidate.getResource(); List<ServiceResource> list = table.get(ir.getHostAddress()); if(list==null) list = new ArrayList<ServiceResource>(); list.add(candidate); table.put(ir.getHostAddress(), list); } List<String> remove = new ArrayList<String>(); for(Map.Entry<String, List<ServiceResource>> entry : table.entrySet()) { List<ServiceResource> list = entry.getValue(); int serviceCount = 0; for(ServiceResource sr : list) { InstantiatorResource ir = (InstantiatorResource)sr.getResource(); serviceCount += ir.getInProcessCounter(elem) + ir.getServiceElementCount(elem); if(serviceCount>=maxPerMachine) { remove.add(ir.getHostAddress()); break; } } } for(String s : remove) { table.remove(s); } List<ServiceResource> candidateList = new ArrayList<ServiceResource>(); for(Map.Entry<String, List<ServiceResource>> entry : table.entrySet()) { List<ServiceResource> list = entry.getValue(); for(int i=0; i<list.size(); i++) { if(i<maxPerMachine) { candidateList.add(list.get(i)); } else { break; } } } if(logger.isDebugEnabled() && candidateList.isEmpty() && elem.getProvisionType().equals(ServiceElement.ProvisionType.DYNAMIC)) { logger.debug("Service [{}/{}] has a physical machine boundary constraint and an " + "instance of the service has been found on all known machines.", elem.getOperationalStringName(), elem.getName()); } return(candidateList.toArray(new ServiceResource[candidateList.size()])); } /** * This method allows concrete implementations of this class to order the * Collection of ServiceResource instances based on a ServiceResource being * selected * * @param resource A ServiceResource instance selected for provisioning a * ServiceBean */ public abstract void serviceResourceSelected(ServiceResource resource); /** * Get all available <code>ServiceResource</code> instances that support * the <code>ServiceElement</code> object's contained requirement * specifications * * @param provisionRequest The ProvisionRequest * @return Array of ServiceResource instances that * support the requirements * * @throws ProvisionException If there are unrecoverable errors * provisioning the service */ public ServiceResource[] getServiceResources(final ProvisionRequest provisionRequest) throws ProvisionException { ServiceResource[] svcResources = getServiceResources(); ArrayList<ServiceResource> list = new ArrayList<ServiceResource>(); for (ServiceResource svcResource : svcResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); try { if (ir.canProvision(provisionRequest)) { list.add(svcResource); } } catch (Exception e) { logger.warn("[{}] during canProvision check for [{}/{}]", ir.getName(), provisionRequest.getServiceElement().getOperationalStringName(), provisionRequest.getServiceElement().getName(), e); if(e instanceof ProvisionException) throw (ProvisionException)e; } } return list.toArray(new ServiceResource[list.size()]); } /** * Get all available <code>ServiceResource</code> instances that match the * Uuid provided * * @param uuid A uuid address to match * @param inclusive Either include or exclude the resource. If true, * include the resource if it matches the uuid, otherwise exclude the * resource * @return Array of ServiceResource instances that match * the host address */ ServiceResource[] getServiceResources(final Uuid uuid, final boolean inclusive) { ServiceResource[] svcResources = getServiceResources(); ArrayList<ServiceResource> list = new ArrayList<ServiceResource>(); for (ServiceResource svcResource : svcResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); if (ir.getInstantiatorUuid().equals(uuid)) { if (inclusive) list.add(svcResource); } else { if (!inclusive) list.add(svcResource); } } return list.toArray(new ServiceResource[list.size()]); } /** * Get all available <code>ServiceResource</code> instances that match the * host address provided * * @param hostAddress A hostAddress address to match * @param inclusive Either include or exclude the resource. If true, * include the resource if host addresses match, otherwise exclude the * resource * * @return Array of ServiceResource instances that match the host address */ public ServiceResource[] getServiceResources(final String hostAddress, final boolean inclusive) { return(getServiceResources(getServiceResources(), hostAddress, inclusive)); } /** * Get all available <code>ServiceResource</code> instances that match the * host address provided * * @param svcResources Array of ServiceResource instances * @param hostAddress A hostAddress address to match * @param inclusive Either include or exclude the resource. If true, * include the resource if host addresses match, otherwise exclude the * resource * * @return Array of ServiceResource instances that match the host address */ ServiceResource[] getServiceResources(final ServiceResource[] svcResources, final String hostAddress, final boolean inclusive) { ArrayList<ServiceResource> list = new ArrayList<ServiceResource>(); for (ServiceResource svcResource : svcResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); if (ir.getHostAddress().equals(hostAddress)) { if (inclusive) list.add(svcResource); } else { if (!inclusive) list.add(svcResource); } } return list.toArray(new ServiceResource[list.size()]); } /** * Drop a <code>ServiceResource</code> from the managed * <code>Collection</code>. This method will also remove the * <code>ServiceResource</code> from the <code>LandlordLessor</code> as * well * * @param resource The ServiceResource */ public void dropServiceResource(final ServiceResource resource) { remove(resource); try { landlord.cancel(resource.getCookie()); } catch(Exception ignore) { logger.warn("Landlord Lease cancellation failure, Force removal from collection of InstantiatorResource"); remove(resource); } } /** * This method will return a snapshot of all <code>ServiceResource</code> * elements contained in its managed <code>Collection</code> * * @return An array of ServiceResource instances */ public ServiceResource[] getServiceResources() { LeasedResource[] resources; synchronized(collectionLock) { resources = collection.toArray(new LeasedResource[collection.size()]); } ServiceResource[] svcResources = new ServiceResource[resources.length]; for(int i=0; i<resources.length; i++) svcResources[i] = (ServiceResource)resources[i]; if(logger.isTraceEnabled()) logger.trace("Landlord returned {} instances, ServiceResource count: {}", resources.length, svcResources.length); return svcResources; } /** * Notifies the manager of a lease expiration <br> * * @param resource The resource associated with the expiration */ public void expired(final LeasedResource resource) { if(resource != null) remove(resource); } /** * Notifies the manager of a lease removal <br> * * @param resource The resource associated with the removal */ public void removed(final LeasedResource resource) { if(resource != null) remove(resource); } /** * Notifies the manager of a new lease being created. * * @param resource The resource associated with the new Lease. */ public void register(final LeasedResource resource) { add(resource); } /** * Notifies the manager of a lease being renewed. * * @param resource The resource associated with the new Lease. */ public void renewed(final LeasedResource resource) { update(resource); } /** * If the <code>Collection</code> backed by the concrete class requires * processing other then that defined by <code>Collection.add</code> * override this method to provide the appropriate semantics * * @param resource The LeasedResource to add */ protected void add(final LeasedResource resource) { try { synchronized(collectionLock) { collection.add(resource); } } catch(UnsupportedOperationException e) { logger.warn("Adding LeasedResource Failed", e); } } /** * If the <code>Collection</code> backed by the concrete class requires * processing other then that defined by <code>Collection.remove</code> * override this method to provide the appropriate semantics * * @param resource The LeasedResource to remove */ protected void remove(final LeasedResource resource) { try { synchronized(collectionLock) { if(resource != null) { collection.remove(resource); } } } catch(UnsupportedOperationException e) { logger.warn("Removing LeasedResource Failed", e); } } /** * If the <code>Collection</code> backed by the concrete class requires * processing other then that defined by <code>Collection.add</code> * override this method to provide the appropriate semantics * * @param resource The LeasedResource */ protected void update(final LeasedResource resource) { try { synchronized(collectionLock) { collection.add(resource); } } catch(UnsupportedOperationException e) { logger.warn("Updating LeasedResource Failed", e); } } /** * Get all <code>InstantiatorResource</code> instances that have * instantiated instances of the ServiceElement * * @param sElem The ServiceElement * @return Array of InstantiatorResource instances that have instantiated * the ServiceElement */ public InstantiatorResource[] getInstantiatorResources(final ServiceElement sElem) { return(getInstantiatorResources(sElem, false)); } /** * Get all <code>InstantiatorResource</code> instances that have * instantiated instances of the ServiceElement * * @param sElem The ServiceElement * @param includeInProcess Whether to include in process elements * @return Array of InstantiatorResource instances that have instantiated * the ServiceElement */ InstantiatorResource[] getInstantiatorResources(final ServiceElement sElem, final boolean includeInProcess) { ServiceResource[] svcResources = getServiceResources(); ArrayList<InstantiatorResource> list = new ArrayList<InstantiatorResource>(); for (ServiceResource svcResource : svcResources) { InstantiatorResource ir = (InstantiatorResource) svcResource.getResource(); if (includeInProcess) { if (ir.getServiceElementCount(sElem) > 0 || ir.getInProcessCounter(sElem) > 0) list.add(ir); } else { if (ir.getServiceElementCount(sElem) > 0) list.add(ir); } } return list.toArray(new InstantiatorResource[list.size()]); } }