/*
* Copyright 2008 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.impl.associations.strategy;
import net.jini.core.entry.Entry;
import net.jini.core.lookup.ServiceItem;
import net.jini.id.ReferentUuid;
import net.jini.id.Uuid;
import net.jini.id.UuidFactory;
import org.rioproject.associations.Association;
import org.rioproject.associations.AssociationDescriptor;
import org.rioproject.deploy.DeployedService;
import org.rioproject.deploy.DeploymentMap;
import org.rioproject.deploy.ServiceBeanInstance;
import org.rioproject.entry.OperationalStringEntry;
import org.rioproject.opstring.ClassBundle;
import org.rioproject.impl.opstring.OpStringManagerProxy;
import org.rioproject.opstring.OperationalStringManager;
import org.rioproject.opstring.ServiceElement;
import org.rioproject.sla.SLA;
import org.rioproject.system.ComputeResourceUtilization;
import org.rioproject.system.MeasuredResource;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.concurrent.Executors;
import java.util.concurrent.ScheduledExecutorService;
import java.util.concurrent.TimeUnit;
/**
* A round-robin selector that selects services running on compute resources
* whose system resources are not depleted. System resource depletion is
* determined by {@link org.rioproject.system.MeasuredResource} provided as part of
* the {@link org.rioproject.system.ResourceCapability} object returned as part
* of the deployment map.
*
* @author Dennis Reedy
*/
public class Utilization<T> extends AbstractServiceSelectionStrategy<T> {
private SLA sla;
private final List<ServiceCapability<T>> services = Collections.synchronizedList(new ArrayList<ServiceCapability<T>>());
private OperationalStringManager opMgr;
/** Scheduler for Cybernode utilization gathering */
private ScheduledExecutorService scheduler;
private static Logger logger = LoggerFactory.getLogger(Utilization.class.getName());
@Override
public void setAssociation(final Association<T> association) {
this.association = association;
initialize(association.getOperationalStringName());
}
@SuppressWarnings("unused")
public void setSLA(final SLA sla) {
this.sla = sla;
}
public T getService() {
T service = null;
ServiceCapability<T> selected = null;
/* Round-Robin */
for(ServiceCapability<T> sc : getServices()) {
if(sc==null)
continue;
if(sc.isInvokable()) {
services.remove(sc);
services.add(sc);
selected = sc;
service = sc.getService();
break;
}
}
if(logger.isTraceEnabled()) {
String name = association==null?"<unknown>":association.getName();
if(selected!=null) {
String address = selected.cru==null?"<unknown>":selected.cru.getAddress();
String util = selected.cru==null?"<unknown>":selected.cru.getUtilization().toString();
logger.trace("Using associated service [{}] at Host address={}, Utilization={}, values={}",
name, address, util, selected.getMeasuredResourcesAsList().toString());
} else {
logger.trace("All services are either breached, or none are available for associated service [{}]", name);
}
}
return service;
}
@Override
public void serviceAdded(final T service) {
logger.trace("Adding service for {}, {}", association.getName(), service);
ServiceItem item = association.getServiceItem(service);
logger.trace("Found ServiceItem?, {}", item);
if(item!=null) {
addService(item);
} else {
logger.warn("Unable to obtain ServiceItem for {}, force refresh all service instances", service.toString());
services.clear();
for(ServiceItem serviceItem : association.getServiceItems())
addService(serviceItem);
}
}
@Override
public void serviceRemoved(final T service) {
if(removeService(service)){
logger.debug("Service removed, service collection size={}", services.size());
}
}
@Override
public void terminate() {
if(scheduler!=null) {
scheduler.shutdownNow();
}
if(opMgr!=null) {
try {
((OpStringManagerProxy.OpStringManager)opMgr).terminate();
} catch(IllegalStateException e) {
logger.warn("Terminating the Utilization strategy for associated service {}", association.getName(), e);
}
}
}
private void initialize(final String opStringName) {
String opStringNameToUse = opStringName;
if(opStringNameToUse==null) {
ServiceItem item = association.getServiceItem();
if(item==null)
return;
for(Entry e : item.attributeSets) {
if(e instanceof OperationalStringEntry) {
opStringNameToUse = ((OperationalStringEntry)e).name;
break;
}
}
}
if(opStringNameToUse!=null && opMgr==null && association.getServiceItem()!=null) {
try {
opMgr = OpStringManagerProxy.getProxy(opStringNameToUse, null);
ComputeResourceUtilizationFetcher cruf = new ComputeResourceUtilizationFetcher(opMgr,
opStringNameToUse);
setServiceList(association.getServiceItems());
long initialDelay = 0;
long period = 1000*5;
scheduler = Executors.newSingleThreadScheduledExecutor();
scheduler.scheduleAtFixedRate(cruf, initialDelay, period, TimeUnit.MILLISECONDS);
logger.trace("Initialized");
} catch (RemoteException e) {
logger.warn("Getting ServiceElement for [{}]", association.getName(), e);
} catch (Exception e) {
logger.warn("Unable to create OpStringManagerProxy for associated service [{}]", association.getName(), e);
}
} else {
logger.warn("Unable to initialize successfully. opStringNameToUse: {}, opMgr: {}, association.getServiceItem(): {}",
opStringNameToUse, opMgr, association.getServiceItem());
}
}
private void setServiceList(final ServiceItem[] items) {
services.clear();
for(ServiceItem item : items) {
addService(item);
}
}
@SuppressWarnings("unchecked")
private synchronized void addService(final ServiceItem item) {
if(opMgr==null) {
String opStringName = null;
for(Entry e : item.attributeSets) {
if(e instanceof OperationalStringEntry) {
opStringName = ((OperationalStringEntry)e).name;
break;
}
}
initialize(opStringName);
}
Uuid uuid;
if(item.service instanceof ReferentUuid)
uuid = ((ReferentUuid)item.service).getReferentUuid();
else {
uuid = UuidFactory.create(item.serviceID.getMostSignificantBits(),
item.serviceID.getLeastSignificantBits());
}
boolean alreadyHaveIt = false;
for(ServiceCapability<T> sc : services) {
if(sc.uuid.equals(uuid)) {
alreadyHaveIt = true;
break;
}
}
if(!alreadyHaveIt) {
services.add(new ServiceCapability(item.service, uuid));
logger.trace("Adding new ServiceCapability, service count now {}", services.size());
} else {
logger.trace("Already have {}, service count now {}", item, services.size());
}
}
private boolean removeService(final T service) {
boolean removed = false;
for(ServiceCapability sc : getServices()) {
if(sc.getService().equals(service)) {
removed = services.remove(sc);
}
}
return removed;
}
@SuppressWarnings("unchecked")
private ServiceCapability<T>[] getServices() {
return services.toArray(new ServiceCapability[services.size()]);
}
class ComputeResourceUtilizationFetcher implements Runnable {
final OperationalStringManager opMgr;
final String opStringName;
final List<DeployedService> list = new ArrayList<DeployedService>();
final List<ServiceElement> serviceElements = new ArrayList<ServiceElement>();
ComputeResourceUtilizationFetcher(final OperationalStringManager opMgr,
final String opStringName) {
this.opMgr = opMgr;
this.opStringName = opStringName;
}
public void run() {
list.clear();
try {
logger.trace("ComputeResourceUtilizationFetcher, obtaining DeploymentMap for [{}]", opStringName);
DeploymentMap dMap = opMgr.getDeploymentMap();
if(serviceElements.isEmpty()) {
serviceElements.addAll(getMatchingServiceElements(dMap));
if(serviceElements.isEmpty())
logger.warn("Unable to obtain matching ServiceElement(s) for associated service [{}]",
association.getName());
}
if(dMap!=null) {
for(ServiceElement elem : serviceElements)
list.addAll(dMap.getDeployedServices(elem));
}
} catch (RemoteException e) {
logger.warn("Getting utilization for service [{}], terminating", association.getAssociationDescriptor(), e);
terminate();
}
for(DeployedService deployed : list) {
ServiceBeanInstance sbi = deployed.getServiceBeanInstance();
ComputeResourceUtilization cru =
deployed.getComputeResourceUtilization();
for(ServiceCapability sc : services) {
if(sc.uuid.equals(sbi.getServiceBeanID())) {
logger.trace("Obtained ComputeResourceUtilization for [{}]", association.getName());
sc.setComputeResourceUtilization(cru);
break;
}
}
}
}
private List<ServiceElement> getMatchingServiceElements(final DeploymentMap dMap) {
List<ServiceElement> matching = new ArrayList<ServiceElement>();
AssociationDescriptor ad = association.getAssociationDescriptor();
String[] adInterfaces = ad.getInterfaceNames();
Arrays.sort(adInterfaces);
for(ServiceElement elem : dMap.getServiceElements()) {
List<String> list = new ArrayList<String>();
for(ClassBundle cb : elem.getExportBundles()) {
list.add(cb.getClassName());
}
String[] cbInterfaces = list.toArray(new String[list.size()]);
if(Arrays.equals(adInterfaces, cbInterfaces)) {
if(ad.matchOnName()) {
if(ad.getName().equals(elem.getName())) {
matching.add(elem);
}
} else {
matching.add(elem);
}
}
}
return matching;
}
}
class ServiceCapability<T> {
final T service;
final Uuid uuid;
ComputeResourceUtilization cru;
boolean wasBreached = false;
final Object updateLock = new Object();
ServiceCapability(final T service, final Uuid uuid) {
this.service = service;
this.uuid = uuid;
}
T getService() {
return service;
}
void setComputeResourceUtilization(final ComputeResourceUtilization cru) {
synchronized(updateLock) {
this.cru = cru;
}
}
List<MeasuredResource> getMeasuredResourcesAsList() {
List<MeasuredResource> list = new ArrayList<MeasuredResource>();
if(cru!=null) {
synchronized(updateLock) {
list.addAll(cru.getMeasuredResources());
}
}
return list;
}
boolean isInvokable() {
boolean isInvokable;
synchronized(updateLock) {
isInvokable = (cru == null || cru.measuredResourcesWithinRange());
if(cru !=null) {
if(isInvokable) {
for(MeasuredResource mRes : cru.getMeasuredResources()) {
if(sla!=null &&
sla.getIdentifier().equals(mRes.getIdentifier())) {
isInvokable = mRes.evaluate(sla);
break;
}
}
} else {
if(logger.isDebugEnabled()) {
List<MeasuredResource> breached = new ArrayList<MeasuredResource>();
for(MeasuredResource mRes : cru.getMeasuredResources()) {
if(mRes.thresholdCrossed()) {
breached.add(mRes);
}
}
logger.debug("Associated service at Host address={}, Utilization={} has breached resources: {}",
cru.getAddress(), cru.getUtilization(), breached.toString());
}
}
if(isInvokable && wasBreached) {
logger.debug("Associated service at Host address={}, Utilization={} was breached, now invokable",
cru.getAddress(), cru.getUtilization());
}
}
}
wasBreached = !isInvokable;
return isInvokable;
}
}
}