/* * 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.tools.cli; import com.sun.jini.lookup.entry.LookupAttributes; import net.jini.admin.Administrable; import net.jini.admin.JoinAdmin; import net.jini.config.Configuration; import net.jini.config.ConfigurationException; import net.jini.core.discovery.LookupLocator; import net.jini.core.entry.Entry; import net.jini.core.lookup.ServiceItem; import net.jini.core.lookup.ServiceRegistrar; import net.jini.core.lookup.ServiceTemplate; import net.jini.discovery.DiscoveryGroupManagement; import net.jini.discovery.DiscoveryManagement; import net.jini.lease.LeaseRenewalManager; import net.jini.lookup.*; import net.jini.lookup.entry.Host; import net.jini.lookup.entry.Name; import net.jini.security.BasicProxyPreparer; import net.jini.security.ProxyPreparer; import org.rioproject.cybernode.Cybernode; import org.rioproject.entry.ComputeResourceInfo; import org.rioproject.monitor.ProvisionMonitor; import org.rioproject.impl.client.DiscoveryManagementPool; import org.rioproject.impl.discovery.RecordingDiscoveryListener; import org.rioproject.impl.discovery.ReggieStat; import java.io.IOException; import java.lang.reflect.Field; import java.rmi.RemoteException; import java.util.*; import java.util.concurrent.*; /** * Find a service * * @author Dennis Reedy */ public class ServiceFinder { private ServiceDiscoveryManager sdm; private LookupCache allServicesCache; private LookupCache cybernodeCache; private LookupCache monitorCache; private ServiceTemplate allServices; private ServiceTemplate cybernodeServices; private ServiceTemplate monitorServices; /** Collection of service info */ private final List<ServiceInfo> serviceInfo = new ArrayList<ServiceInfo>(); /** ThreadPool for fetching service information */ private Executor serviceInfoFetchPool; /** Table of ServiceInfo and InfoFetchStats */ private final Map<ServiceInfo, InfoFetchStat> serviceInfoFetchMap = new HashMap<ServiceInfo, InfoFetchStat>(); private RecordingDiscoveryListener recordingDiscoveryListener; private ProxyPreparer proxyPreparer; private ProxyPreparer adminProxyPreparer; /** * Create a ServiceFinder * * @param groups The groups to discover * @param locators The LookupLocators to use * @param config The Configuration to set * * @throws IOException If there are problems initializing discovery * @throws ConfigurationException If problems are encountered accessing * the configuration */ public ServiceFinder(final String[] groups, final LookupLocator[] locators, final Configuration config) throws IOException, ConfigurationException { proxyPreparer = (ProxyPreparer)config.getEntry(CLI.CONFIG_COMPONENT, "proxyPreparer", ProxyPreparer.class, new BasicProxyPreparer()); adminProxyPreparer = (ProxyPreparer)config.getEntry(CLI.CONFIG_COMPONENT, "adminProxyPreparer", ProxyPreparer.class, new BasicProxyPreparer()); int serviceInfoPoolSize = (Integer) config.getEntry(CLI.CONFIG_COMPONENT, "serviceInfoPoolSize", int.class, 5); serviceInfoFetchPool = Executors.newFixedThreadPool(serviceInfoPoolSize); DiscoveryManagementPool discoPool = DiscoveryManagementPool.getInstance(); DiscoveryManagement discoMgr = discoPool.getDiscoveryManager("cli", groups, locators, null, config); //LookupDiscoveryManager discoMgr = // new LookupDiscoveryManager(groups, locators, null, lookupConfig); recordingDiscoveryListener = new RecordingDiscoveryListener(discoMgr); discoMgr.addDiscoveryListener(recordingDiscoveryListener); sdm = new ServiceDiscoveryManager(discoMgr, new LeaseRenewalManager()); allServices = new ServiceTemplate(null, null, null); cybernodeServices = new ServiceTemplate(null, new Class[] {Cybernode.class}, null); monitorServices = new ServiceTemplate(null, new Class[] {ProvisionMonitor.class}, null); if(!CLI.getInstance().commandLine) { allServicesCache = sdm.createLookupCache(allServices, null, new ServiceListener()); cybernodeCache = sdm.createLookupCache(cybernodeServices, null, null); monitorCache = sdm.createLookupCache(monitorServices, null, null); } } ReggieStat[] getReggieStats(final int type) { return(recordingDiscoveryListener.getReggieStats(type)); } /** * Terminate the utility */ public void terminate() { sdm.terminate(); } /** * Find all services * * @param machines String array of machine names to filter * @param attrs An array of attributes to filter on * * @return Array of ServiceItem instances corresponding to the number of * discovered service instances. If no services have been discovered, a * zero-lengh array is returned */ public ServiceItem[] find(final String[] machines, final Entry[] attrs) { ServiceItem[] results = new ServiceItem[0]; try { ServiceItemFilter filter = null; if((machines!=null && machines.length>0) || attrs!=null && attrs.length>0) filter = new ServiceFilter(machines, attrs); if(!CLI.getInstance().commandLine) { results = allServicesCache.lookup(filter, Integer.MAX_VALUE); if(results.length==0) System.out.println("num lookups "+ getDiscoveryManagement().getRegistrars().length+ ", total services "+results.length); else System.out.println("total services "+results.length); } else { long timeOut = (Long) CLI.getInstance().settings.get(CLI.DISCOVERY_TIMEOUT); long t0 = System.currentTimeMillis(); results = sdm.lookup(allServices, 1, Integer.MAX_VALUE, filter, timeOut); long t1 = System.currentTimeMillis(); System.out.println("total "+results.length); System.out.println("discovery time "+(t1-t0)+" millis, "+ ((t1-t0)/1000)+" seconds, "+ "timeout used "+timeOut); } } catch (Exception e) { e.printStackTrace(); } return(results); } /** * Find all Cybernode services * * @param machines String array of machine names to filter * @param attrs An array of attributes to filter on * * @return Array of ServiceItem instances corresponding to the number of * discovered Cybernode service instances. If no Cybernode services have * been discovered, a zero-lengh array is returned */ public ServiceItem[] findCybernodes(final String[] machines, final Entry[] attrs) { ServiceItem[] results = new ServiceItem[0]; try { ServiceItemFilter filter = null; if((machines!=null && machines.length>0) || attrs!=null && attrs.length>0) filter = new ServiceFilter(machines, attrs); if(!CLI.getInstance().commandLine) { results = cybernodeCache.lookup(filter, Integer.MAX_VALUE); System.out.println("total "+results.length); } else { long timeOut = (Long) CLI.getInstance().settings.get(CLI.DISCOVERY_TIMEOUT); long t0 = System.currentTimeMillis(); results = sdm.lookup(cybernodeServices, 1, Integer.MAX_VALUE, filter, timeOut); long t1 = System.currentTimeMillis(); System.out.println("total "+results.length); System.out.println("discovery time "+(t1-t0)+" millis, "+ "timeout used "+timeOut); } } catch (Exception e) { e.printStackTrace(); } return(results); } /** * Find all Monitor services * * @param machines String array of machine names to filter * @param attrs An array of attributes to filter on * * @return Array of ServiceItem instances corresponding to the number of * discovered Monitor service instances. If no Monitor services have been * discovered, a zero-lengh array is returned */ public ServiceItem[] findMonitors(final String[] machines, final Entry[] attrs) { return(findMonitors(machines, attrs, true)); } /** * Find all Monitor services * * @param machines String array of machine names to filter * @param attrs An array of attributes to filter on * @param verbose For more output set to true * * @return Array of ServiceItem instances corresponding to the number of * discovered Monitor service instances. If no Monitor services have been * discovered, a zero-lengh array is returned */ public ServiceItem[] findMonitors(final String[] machines, final Entry[] attrs, final boolean verbose) { ServiceItem[] results = new ServiceItem[0]; try { ServiceItemFilter filter = null; if((machines!=null && machines.length>0) || attrs!=null && attrs.length>0) filter = new ServiceFilter(machines, attrs); if(!CLI.getInstance().commandLine) { results = monitorCache.lookup(filter, Integer.MAX_VALUE); if(verbose) { System.out.println("total "+results.length); } } else { long timeOut = (Long) CLI.getInstance().settings.get(CLI.DISCOVERY_TIMEOUT); long t0 = System.currentTimeMillis(); results = sdm.lookup(monitorServices, 1, Integer.MAX_VALUE, filter, timeOut); if(verbose) { long t1 = System.currentTimeMillis(); System.out.println("total "+results.length); System.out.println("discovery time "+(t1-t0)+" millis, "+ "timeout used "+timeOut); } } } catch (Exception e) { e.printStackTrace(); } return(results); } /** * Get DiscoveryManagement * * @return The DiscoveryManagement instance being used */ public DiscoveryManagement getDiscoveryManagement() { return(sdm.getDiscoveryManager()); } /** * Resolve the information for a service in a poolable thread * * @param sInfo The ServiceInfo to resolve * * @return a Future */ Future<ServiceInfo> resolveServiceInfo(final ServiceInfo sInfo) { InfoFetchStat i = new InfoFetchStat(sInfo.getServiceName()); i.starTime = System.currentTimeMillis(); synchronized(serviceInfoFetchMap) { serviceInfoFetchMap.put(sInfo, i); } FutureTask<ServiceInfo> future = new FutureTask<ServiceInfo>(new InfoFetcher(sInfo)); serviceInfoFetchPool.execute(future); return future; } /** * Get the table ServiceInfo instances * * @return A Map of ServiceInfo to creation time instances. A new Map * is created each time. If there are no pending ServiceInfo instances * an empty Map is returned */ Map<ServiceInfo, InfoFetchStat> getServiceInfoFetchMap() { Map<ServiceInfo, InfoFetchStat> map; synchronized(serviceInfoFetchMap) { map = new HashMap<ServiceInfo, InfoFetchStat>(serviceInfoFetchMap); } return map; } /** * A ServiceItemFilter that filters on host names and attributes */ public class ServiceFilter implements ServiceItemFilter { private String[] hostNames; private Entry[] attrs; /** * Create an ServiceFilter * * @param hostNames The names of hosts to match * @param attrs Additional attributes to filter */ public ServiceFilter(final String[] hostNames, final Entry[] attrs) { if(hostNames==null) this.hostNames = new String[0]; else this.hostNames = hostNames; if(attrs==null) { this.attrs = new Entry[0]; } else { this.attrs = new Entry[attrs.length]; System.arraycopy(attrs, 0, this.attrs, 0, attrs.length); } } /** * If the input ServiceItem has an * {@link org.rioproject.entry.ComputeResourceInfo} and the hostname or address * matches an array element in the <code>hostNames</code> array, return * <code>true</code> * * @see net.jini.lookup.ServiceItemFilter#check */ public boolean check(final ServiceItem item) { boolean matched = false; if(hostNames.length==0) { matched = true; } else { if(item.service instanceof ServiceRegistrar) { try { String host = ((ServiceRegistrar)item.service).getLocator().getHost(); for (String hostName : hostNames) { if (hostName.equalsIgnoreCase(host)) { matched = true; break; } } } catch(RemoteException e) { e.printStackTrace(); } } else { matched = hostNameMatches(item.attributeSets); } } if(matched) matched = checkAttributes(item.attributeSets); return(matched); } /** * Determine if the attribute collecton contains an ComputeResourceInfo which * matches an element in the hostNames array * * @param attrs Array of Entry objects * * @return If an ComputeResourceInfo is found in the collection and and the * hostname or address matches an array element in the * <code>hostNames</code> array, return <code>true</code>, otherwise, * return <code>false</code> */ public boolean hostNameMatches(final Entry[] attrs) { Host host = getHost(attrs); if(host!=null) return(checkHosts(host)); ComputeResourceInfo computeResourceInfo = getComputeResourceInfo(attrs); if(computeResourceInfo!=null) return(checkHosts(computeResourceInfo)); return(false); } /** * Check the hostNames list * * @param aInfo The ComputeResourceInfo entry to check * * @return If the hostname in the ComputeResourceInfo is in the known * hostnames, return true */ boolean checkHosts(final ComputeResourceInfo aInfo) { boolean found = false; for (String hostName : hostNames) { if (hostName.equalsIgnoreCase(aInfo.hostAddress) || hostName.equalsIgnoreCase(aInfo.hostName)) { found = true; } } return(found); } /** * Check the hostNames list * * @param host The host to check * * @return If found return true */ boolean checkHosts(final Host host) { boolean found = false; for (String hostName : hostNames) { if (hostName.equalsIgnoreCase(host.hostName)) { found = true; } } return (found); } /** * Check attributes list for match * * @param attributes The array of attributes to check * * @return True if the attributes match */ boolean checkAttributes(final Entry[] attributes) { if(attrs.length==0) return(true); boolean matched = false; int numMatched = 0; for (Entry attr : attrs) { for (Entry attribute : attributes) { if (LookupAttributes.matches(attr, attribute)) { matched = true; numMatched++; break; } } if (!matched) break; } return(numMatched==attrs.length); } } /** * A ServiceDiscoveryListener */ public class ServiceListener implements ServiceDiscoveryListener { public void serviceAdded(final ServiceDiscoveryEvent sdEvent) { ServiceItem item = sdEvent.getPostEventServiceItem(); try { if(item.service instanceof ProvisionMonitor) item.service = proxyPreparer.prepareProxy(item.service); ServiceInfo sb = new ServiceInfo(item); boolean added = false; synchronized(serviceInfo) { if(!serviceInfo.contains(sb)) { added = serviceInfo.add(sb); } } if(added) { resolveServiceInfo(sb); } } catch (Throwable t) { Throwable cause = t; if(t.getCause()!=null) cause = t.getCause(); System.out.println("Unable to administer service, exception " + "preparing proxy ["+ cause.getClass().getName()+": "+ cause.getMessage()+ "], " + "check log for details"); t.printStackTrace(); } } public void serviceRemoved(final ServiceDiscoveryEvent sdEvent) { ServiceItem item = sdEvent.getPreEventServiceItem(); ServiceInfo[] info = getServiceInfo(); for (ServiceInfo anInfo : info) { if (anInfo.item.serviceID.equals(item.serviceID)) { synchronized (serviceInfo) { serviceInfo.remove(anInfo); } break; } } } public void serviceChanged(final ServiceDiscoveryEvent sdEvent) { ServiceItem item = sdEvent.getPostEventServiceItem(); ServiceInfo[] info = getServiceInfo(); for (ServiceInfo anInfo : info) { if (anInfo.item.serviceID.equals(item.serviceID)) { anInfo.setServiceItem(item); break; } } } } /** * Get all ServiceInfo * * @return An array of ServiceInfo objects */ public ServiceInfo[] getServiceInfo() { ServiceInfo[] info; synchronized(serviceInfo) { info = serviceInfo.toArray(new ServiceInfo[serviceInfo.size()]); } return(info); } /** * Class to hold a ServiceItem and the groups the service is a member of. * This class is created to pre-fetch group information since it is an * expensive operation, as it uses remote invocation to acquire a * service's JoinAdmin */ public static class ServiceInfo { static final String UNKNOWN="<?>"; private String name = UNKNOWN; private String[] groups = new String[]{UNKNOWN}; private ServiceItem item; private String host = UNKNOWN; ServiceInfo(final ServiceItem item) { setServiceItem(item); } private void setServiceItem(final ServiceItem item) { this.item = item; name = getName(item.attributeSets); if(name == null) name = item.service.getClass().getName(); } /* * Get the service name */ String getServiceName() { return(name); } /* * Set the hostname */ void setHost(final String h) { host = h; } /* * Get the hostname */ String getHost() { return(host); } /* * Set the groups */ void setGroups(final String[] g) { groups = new String[g.length]; System.arraycopy(g, 0, groups, 0, g.length); } /* * Get the groups */ String[] getGroups() { return(groups); } /* * Get the ServiceItem * * @return The ServiceItem */ ServiceItem getServiceItem() { return(item); } @Override public int hashCode() { return(item.serviceID.hashCode()); } @Override public boolean equals(final Object obj) { if(this == obj) return(true); if(!(obj instanceof ServiceInfo)) return(false); ServiceInfo that = (ServiceInfo)obj; return(this.item.serviceID.equals(that.item.serviceID)); } } /** * Used to fetch information about services */ public class InfoFetcher implements Callable<ServiceInfo> { ServiceInfo sInfo; InfoFetcher(final ServiceInfo sInfo) { this.sInfo = sInfo; } void updateHost() { InfoFetchStat ifs; synchronized(serviceInfoFetchMap) { ifs = serviceInfoFetchMap.get(sInfo); ifs.host = sInfo.host; serviceInfoFetchMap.put(sInfo, ifs); } } @Override public ServiceInfo call() throws Exception { try { ServiceItem item = sInfo.getServiceItem(); if(item.service instanceof ServiceRegistrar) { try { LookupLocator loc = ((ServiceRegistrar)item.service).getLocator(); sInfo.setHost(loc.getHost()+":"+loc.getPort()); updateHost(); } catch(RemoteException e) { e.printStackTrace(); } } else { Host host = getHost(item.attributeSets); if(host!=null) { sInfo.setHost(host.hostName); updateHost(); } else { ComputeResourceInfo computeResourceInfo = getComputeResourceInfo(item.attributeSets); if(computeResourceInfo != null) { /*if(computeResourceInfo.hostName.equals(computeResourceInfo.hostAddress)) sInfo.setHost(computeResourceInfo.hostName); else*/ sInfo.setHost(computeResourceInfo.hostName+"@"+computeResourceInfo.hostAddress); updateHost(); } } } if(item.service instanceof Administrable) { try { String [] groups = new String[0]; Object admin = ((Administrable)item.service).getAdmin(); if(admin instanceof JoinAdmin) { groups = ((JoinAdmin)admin).getLookupGroups(); } /* If no groups were returned and the Administrable object * additionally implements DiscoveryAdmin */ if(groups!=null && groups.length == 0 && (admin instanceof DiscoveryAdmin)) groups = ((DiscoveryAdmin)admin).getMemberGroups(); if(groups == DiscoveryGroupManagement.ALL_GROUPS) groups = new String[]{"ALL_GROUPS"}; sInfo.setGroups(groups); } catch(RemoteException e) { System.err.println("EXCEPTION GETTING GROUPS FOR ["+sInfo.getServiceName()+"]"); e.printStackTrace(); } } } catch (Throwable t) { System.err.println("EXCEPTION GETTING ServiceInfo FOR ["+sInfo.getServiceName()+"]"); t.printStackTrace(); } finally { long t1 = System.currentTimeMillis(); InfoFetchStat ifs; int size; synchronized(serviceInfoFetchMap) { ifs = serviceInfoFetchMap.remove(sInfo); size = serviceInfoFetchMap.size(); } ifs.stopTime = t1; if(!CLI.getInstance().commandLine) System.err.println("XXXX ServiceInfo resolve time for " + "["+sInfo.getServiceName()+"] = "+ (t1-ifs.starTime)+", pool size = "+size); } return sInfo; } } /** * Simple data structure holding the service name, the time the * fetch request began, and the time it ended */ public static class InfoFetchStat { String name; String host=ServiceInfo.UNKNOWN; long starTime; long stopTime; InfoFetchStat(final String name) { this.name = name; } } /** * Get the name of the service from either the Name attribute or the ServiceInfo * attribute * * @param attrs Array of Entry attributes * * @return The name of the service from either the Name attribute or the * ServiceInfo attribute */ public static String getName(final Entry[] attrs) { String name = null; for (Entry attr : attrs) { if (attr instanceof Name) { name = ((Name) attr).name; break; } } if(name==null) { for (Entry attr : attrs) { if (attr instanceof net.jini.lookup.entry.ServiceInfo) { name = ((net.jini.lookup.entry.ServiceInfo)attr).name; break; } } } return(name); } /** * Get the Host attribute * * @param attrs Array of attributes * * @return A Host object */ public static Host getHost(final Entry[] attrs) { Host host = null; for (Entry attr : attrs) { if (attr.getClass().getName().equals( Host.class.getName())) { if (attr instanceof Host) { host = (Host) attr; break; } else { /* * This addresses the issue where the discovered service * has a Host but there is a class loading * problem, which results in the classes being loaded by * sibling class loaders, and assignability doesnt work. */ host = new Host(); try { Field hn = attr.getClass().getDeclaredField("hostName"); host.hostName = (String) hn.get(attr); break; } catch (Exception e) { e.printStackTrace(); } } } } return(host); } /** * Get the ComputeResourceInfo attribute * * @param attrs An array of attributes to search * * @return If found, the ComputeResourceInfo entry */ public static ComputeResourceInfo getComputeResourceInfo(final Entry[] attrs) { ComputeResourceInfo computeResourceInfo = null; for (Entry attr : attrs) { if (attr.getClass().getName().equals(ComputeResourceInfo.class.getName())) { if (attr instanceof ComputeResourceInfo) { computeResourceInfo = (ComputeResourceInfo) attr; break; } else { /* * This addresses the issue where the discovered service * has an ComputeResourceInfo but there is a class loading * problem, which results in the classes being loaded by sibling * class loaders, and assignability doesnt work. */ computeResourceInfo = new ComputeResourceInfo(); try { Field ha = attr.getClass().getDeclaredField("hostAddress"); Field hn = attr.getClass().getDeclaredField("hostName"); computeResourceInfo.hostAddress = (String) ha.get(attr); computeResourceInfo.hostName = (String) hn.get(attr); break; } catch (Exception e) { e.printStackTrace(); } } } } return (computeResourceInfo); } /** * Get the Adminstrable proxy for a service, and prepare the returned proxy * with the adminProxyPreparer * * @param proxy The service proxy * @return A prepared Administrable proxy * * @throws RemoteException If errors occur */ public Object getPreparedAdmin(final Object proxy) throws RemoteException { Object admin = ((Administrable)proxy).getAdmin(); return(adminProxyPreparer.prepareProxy(admin)); } }