org.apache.uima.ducc.sm.ServiceHandler Maven / Gradle / Ivy
The newest version!
/*
* Licensed to the Apache Software Foundation (ASF) under one
* or more contributor license agreements. See the NOTICE file
* distributed with this work for additional information
* regarding copyright ownership. The ASF licenses this file
* to you 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.apache.uima.ducc.sm;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Set;
import org.apache.uima.ducc.cli.DuccServiceApi;
import org.apache.uima.ducc.cli.IUiOptions.UiOption;
import org.apache.uima.ducc.common.NodeIdentity;
import org.apache.uima.ducc.common.persistence.services.IStateServices;
import org.apache.uima.ducc.common.persistence.services.IStateServices.AccessMode;
import org.apache.uima.ducc.common.utils.DuccLogger;
import org.apache.uima.ducc.common.utils.DuccProperties;
import org.apache.uima.ducc.common.utils.id.DuccId;
import org.apache.uima.ducc.transport.event.AServiceRequest;
import org.apache.uima.ducc.transport.event.ServiceDisableEvent;
import org.apache.uima.ducc.transport.event.ServiceEnableEvent;
import org.apache.uima.ducc.transport.event.ServiceIgnoreEvent;
import org.apache.uima.ducc.transport.event.ServiceModifyEvent;
import org.apache.uima.ducc.transport.event.ServiceObserveEvent;
import org.apache.uima.ducc.transport.event.ServiceQueryEvent;
import org.apache.uima.ducc.transport.event.ServiceQueryReplyEvent;
import org.apache.uima.ducc.transport.event.ServiceReplyEvent;
import org.apache.uima.ducc.transport.event.ServiceStartEvent;
import org.apache.uima.ducc.transport.event.ServiceStopEvent;
import org.apache.uima.ducc.transport.event.ServiceUnregisterEvent;
import org.apache.uima.ducc.transport.event.common.DuccWorkJob;
import org.apache.uima.ducc.transport.event.common.IDuccProcessMap;
import org.apache.uima.ducc.transport.event.common.IDuccWork;
import org.apache.uima.ducc.transport.event.common.IDuccWorkMap;
import org.apache.uima.ducc.transport.event.common.IDuccWorkService;
import org.apache.uima.ducc.transport.event.sm.IService.ServiceState;
import org.apache.uima.ducc.transport.event.sm.IServiceDescription;
import org.apache.uima.ducc.transport.event.sm.ServiceDependency;
import org.apache.uima.ducc.transport.event.sm.ServiceMap;
public class ServiceHandler
implements SmConstants,
Runnable
{
/**
*
*/
private DuccLogger logger = DuccLogger.getLogger(ServiceHandler.class.getName(), COMPONENT_NAME);
private DuccId jobid = null;
private IServiceManager serviceManager;
private ServiceStateHandler serviceStateHandler = new ServiceStateHandler();
private ServiceMap serviceMap = new ServiceMap(); // note this is the sync object for publish
private IStateServices stateHandler;
private Map newJobs = new HashMap();
private Map newServices = new HashMap();
private Map deletedJobs = new HashMap();
private Map deletedServices = new HashMap();
private Map modifiedJobs = new HashMap();
private Map modifiedServices = new HashMap();
private List pendingRequests = new LinkedList();
private Object stateUpdateLock = new Object();
private Map optionMap; // for modify()
public ServiceHandler(IServiceManager serviceManager)
{
this.serviceManager = serviceManager;
Runtime.getRuntime().addShutdownHook(new ServiceShutdown());
DuccServiceApi dsi = new DuccServiceApi(null); // instantiate this to access the modify options
UiOption[] options = dsi.getModifyOptions();
optionMap = new HashMap();
for ( UiOption o : options ) {
optionMap.put(o.pname(), o);
}
}
/*
* initialize data structures (in preparation for resume)
*/
public void init() {
String methodName = "init";
logger.debug(methodName, jobid, "");
serviceStateHandler = new ServiceStateHandler();
serviceMap.clear();
}
/*
* resume: this head node has become master
*/
public void resume(IDuccWorkMap dwm) {
String methodName = "resume";
logger.debug(methodName, jobid, "");
// clear state
newJobs.clear();
newServices.clear();
deletedJobs.clear();
deletedServices.clear();
modifiedJobs.clear();
modifiedServices.clear();
pendingRequests.clear();
// Add implementors from OR publication to SM state
Set serviceKeys = dwm.getServiceKeySet();
if(serviceKeys != null) {
for(DuccId duccId : serviceKeys) {
IDuccWork dw = dwm.findDuccWork(duccId);
if(dw != null) {
IDuccWorkService service = (IDuccWorkService) dw;
long swId = duccId.getFriendly();
String endpoint = service.getServiceEndpoint();
if(endpoint != null) {
ServiceSet ss = serviceStateHandler.getServiceByUrl(endpoint);
if(ss != null) {
ServiceInstance si = new ServiceInstance(ss);
ss.implementors.put(swId, si);
addInstance(ss, si);
logger.info(methodName, ss.getId(), swId, ss.endpoint);
}
else {
logger.warn(methodName, duccId, "ss == null");
}
}
else {
logger.warn(methodName, duccId, "endpoint == null");
}
}
else {
logger.warn(methodName, duccId, "dw == null");
}
}
}
else {
logger.debug(methodName, jobid, "serviceKeys == null");
}
}
/*
* quiesce: this head node has become backup
*/
public void quiesce() {
String methodName = "quiesce";
List list = serviceStateHandler.getServices();
logger.debug(methodName, jobid, list.size());
// stop all pingers
for(ServiceSet ss : list) {
DuccId duccId = ss.getId();
ss.setState(ServiceState.Dispossessed);
logger.info(methodName, duccId, ss.getState(), ss.getImplementors().length, ss.getKey());
ss.stopPingThread();
}
}
public void setAccessMode(AccessMode accessMode) {
stateHandler.setAccessMode(accessMode);
}
void setStateHandler(IStateServices handler)
{
this.stateHandler = handler;
}
public synchronized void run()
{
String methodName = "run";
while ( true ) {
try {
wait();
} catch (InterruptedException e) {
logger.error(methodName, null, e);
}
try {
runCommands(); // enqueued orders that came in while I was away
processUpdates();
} catch (Throwable t) {
logger.error(methodName, null, t);
}
}
}
/**
* At boot only ... pass in the set of all known active services to each service so it can update
* internal state with current published state.
*/
void bootImplementors(Map incoming)
{
String methodName = "bootImplementors";
for ( DuccId id : incoming.keySet() ) {
DuccWorkJob j = incoming.get(id);
String ep = j.getServiceEndpoint();
ServiceSet sset = serviceStateHandler.getServiceByUrl(ep);
if ( sset == null ) {
// must cancel this service, no idea what it is
} else {
sset.bootImplementor(id, j.getJobState()); // boot by id, job, not known so more stuff
// has to be built up
}
}
List services = serviceStateHandler.getServices();
for ( ServiceSet sset : services ) {
try {
sset.bootComplete();
} catch ( Exception e ) {
logger.warn(methodName, sset.getId(), "Error updating meta properties:", e);
}
if ( sset.countImplementors() > 0 ) { // if something was running, let's make sure all the starts are done
sset.start();
}
}
}
void processUpdates()
{
String methodName = "processUpdates";
logger.info(methodName, null, "Processing updates.");
Map deletedJobsMap = new HashMap();
Map modifiedJobsMap = new HashMap();
Map newJobsMap = new HashMap();
Map deletedServicesMap = new HashMap();
Map modifiedServicesMap = new HashMap();
Map newServicesMap = new HashMap();
synchronized(stateUpdateLock) {
deletedJobsMap.putAll(deletedJobs);
deletedJobs.clear();
logger.info(methodName, jobid, "deletedJobsMap", deletedJobsMap.size());
modifiedJobsMap.putAll(modifiedJobs);
modifiedJobs.clear();
logger.info(methodName, jobid, "modifiedJobsMap", modifiedJobsMap.size());
deletedServicesMap.putAll(deletedServices);
deletedServices.clear();
logger.info(methodName, jobid, "deletedServicessMap", deletedServicesMap.size());
modifiedServicesMap.putAll(modifiedServices);
modifiedServices.clear();
logger.info(methodName, jobid, "modifiedServicesMap", modifiedServicesMap.size());
newServicesMap.putAll(newServices);
newServices.clear();
logger.info(methodName, jobid, "newServicesMap", newServicesMap.size());
newJobsMap.putAll(newJobs);
newJobs.clear();
logger.info(methodName, jobid, "newJobsMap", newJobsMap.size());
}
// We could potentially have several updates where a service or arrives, is modified, and then deleted, while
// we are busy. Need to handle them in the right order.
//
// Jobs are dependent on services but not the other way around - I think we need to handle services first,
// to avoid the case where something is dependent on something that will exist soon but doesn't currently.
handleNewServices (newServicesMap );
handleModifiedServices(modifiedServicesMap);
handleDeletedServices (deletedServicesMap );
handleNewJobs (newJobsMap );
handleModifiedJobs (modifiedJobsMap );
handleDeletedJobs (deletedJobsMap );
List regsvcs = serviceStateHandler.getServices();
for ( ServiceSet sset : regsvcs ) {
sset.enforceAutostart();
}
serviceManager.publish(serviceMap);
}
void signalUpdates( // This is the incoming or map, with work split into categories.
// The incoming maps are volatile - must save contents before returning.
HashMap newJobs,
HashMap newServices,
HashMap deletedJobs,
HashMap deletedServices,
HashMap modifiedJobs,
HashMap modifiedServices
)
{
synchronized(stateUpdateLock) {
this.newJobs.putAll(newJobs);
this.newServices.putAll(newServices);
this.deletedJobs.putAll(deletedJobs);
this.deletedServices.putAll(deletedServices);
this.modifiedJobs.putAll(modifiedJobs);
this.modifiedServices.putAll(modifiedServices);
}
synchronized(this) {
notify();
}
}
void runCommands()
{
String methodName = "runCommands";
LinkedList tmp = new LinkedList();
synchronized(pendingRequests) {
tmp.addAll(pendingRequests);
pendingRequests.clear();
}
logger.info(methodName, null, "Running", tmp.size(), "API Tasks.");
synchronized(this) {
for ( ApiHandler apih : tmp ) {
apih.run();
}
}
}
void addApiTask(ApiHandler apih)
{
synchronized(pendingRequests) {
pendingRequests.add(apih);
}
}
/**
* This is called when an endpoint is referenced as a dependent service from a job or a service.
* It is called only when a new job or service is first discovred in the OR map.
*/
protected Map resolveDependencies(DuccWorkJob w, ServiceDependency s)
{
//String methodName = "resolveDependencies";
DuccId id = w.getDuccId();
String[] deps = w.getServiceDependencies();
// New services, if any are discovered
// Put them into the global map of known services if needed and up the ref count
boolean fatal = false;
Map jobServices = new HashMap();
for ( String dep : deps ) {
ServiceSet sset = serviceStateHandler.getServiceByUrl(dep);
if ( sset == null ) {
s.addMessage(dep, "Service is unknown.");
s.setState(ServiceState.NotAvailable);
fatal = true;
continue;
}
jobServices.put(dep, sset);
}
if ( fatal ) {
jobServices.clear();
} else {
for ( ServiceSet sset : jobServices.values() ) {
// If service is unregistered and then rerigistered while the job is running it may have lost
// its connections, which we insure we always have here.
serviceStateHandler.putServiceForJob(w.getDuccId(), sset);
sset.reference(id); // might start it if it's not running
}
}
return jobServices;
}
/**
* Resolves state for the job in id based on the what it is dependent upon - the independent services
*
* Enter this code ONLY if it is determined that the 'independent' work, 'id', does in fact have
* declared dependencies.
*
* @param id This is the ID of a job or service we want to work out the service state for
* @param dep This is the thing we send to OR telling it about the state of 'id'
*/
protected void resolveState(DuccId id, ServiceDependency dep)
{
Map services = serviceStateHandler.getServicesForJob(id);
if ( services == null ) {
dep.setState(ServiceState.NotAvailable); // says that nothing i need is available
return;
}
ServiceState state = ServiceState.Available;
//
// Start with the most permissive state and reduce it as we walk the list
// Running > Initializing > Waiting > NotAvailable
//
// This sets the state to the min(all dependent service states)
//
for ( ServiceSet sset : services.values() ) {
if ( sset.getState().ordinality() < state.ordinality() ) state = sset.getState();
dep.setIndividualState(sset.getKey(), sset.getState());
if ( sset.excessiveFailures() ) {
dep.addMessage(sset.getKey(), sset.getErrorString());
}
// logger.debug(methodName, id, "Set individual state", sset.getState());
}
if ( state.ordinality() < 5 ) { // UIMA-4223, if we got this far, the services all exist but at least one of them
// is not usable. We use this slightly artificial state to insure the OR keeps
// the work WaitingForServices.
state = ServiceState.Pending;
}
dep.setState(state);
}
/**
* A job or service has ended. Here's common code to clean up the dependent services.
* @param id - the id of the job or service that stopped
* @param deps - the services that 'id' was dependent upon
*/
protected void stopDependentServices(DuccId id)
{
String methodName = "stopDependentServices";
Map deps = serviceStateHandler.getServicesForJob(id);
if ( deps == null ) {
logger.info(methodName, id, "No dependent services to stop, returning.");
return; // service already deleted, timing issue
}
//
// Bop through all the things job 'id' is dependent upon, and update their refcounts. If
// the refs go to 0 we stop the pinger and sometimes the independent service itself.
//
for ( Long depid : deps.keySet() ) {
logger.debug(methodName, id, "Looking up service", depid);
ServiceSet sset = deps.get(depid);
if ( sset == null ) {
logger.error(methodName, id, "Internal error: Null service for " + depid); // sanity check, should never happen
continue;
}
sset.dereference(id); // also maybe stops the pinger
}
// last, indicate that job 'id' has nothing it's dependent upon any more
serviceStateHandler.removeServicesForJob(id);
}
protected void handleNewJobs(Map work)
{
String methodName = "handleNewJobs";
// Map of updates to send to OR
HashMap updates = new HashMap();
for ( DuccId id : work.keySet() ) {
DuccWorkJob w = (DuccWorkJob) work.get(id);
if ( !w.isActive() ) {
logger.info(methodName, id, "Bypassing inactive job, state =", w.getStateObject());
continue;
}
ServiceDependency s = new ServiceDependency(); // for the OR
updates.put(id, s);
String[] deps = w.getServiceDependencies();
if ( deps == null ) { // no deps, just mark it running and move on
s.setState(ServiceState.Available);
logger.info(methodName, id, "Added to map, no service dependencies.");
continue;
}
Map jobServices = resolveDependencies(w, s);
for ( ServiceSet sset : jobServices.values() ) {
logger.info(methodName, id, "Job is dependent on", sset.getKey());
}
resolveState(id, s);
logger.info(methodName, id, "Added job to map, with service dependency state.", s.getState());
logger.info(methodName, id, s.getMessages());
}
serviceMap.putAll(updates);
}
protected void handleModifiedJobs(Map work)
{
String methodName = "handleModifiedobs";
//
// Only look at active jobs. The others will be going away soon and we use
// that time as a grace period to keep the management machinery running in
// case more work comes in in the next few minutes.
//
// Everything is already in the service map so we just update the state.
//
for ( DuccId id : work.keySet() ) {
DuccWorkJob j = (DuccWorkJob) work.get(id);
String[] deps = j.getServiceDependencies();
if ( deps == null ) { // no deps, just mark it running and move on
logger.info(methodName, id, "No service dependencies, no updates made.");
continue;
}
ServiceDependency s = serviceMap.get(id);
if ( j.isFinished() ) {
stopDependentServices(id);
s.setState(ServiceState.NotAvailable);
s.clearMessages();
} else if ( j.isActive() ) {
resolveDependencies(j, s);
resolveState(id, s);
}
}
}
protected void handleDeletedJobs(Map work)
{
String methodName = "handleDeletedobs";
for ( DuccId id : work.keySet() ) {
DuccWorkJob w = (DuccWorkJob) work.get(id);
String[] deps = w.getServiceDependencies();
if ( deps == null ) { // no deps, just mark it running and move on
logger.info(methodName, id, "No service dependencies, no updates made.");
continue;
}
stopDependentServices(id);
logger.info(methodName, id, "Deleted job from map");
}
serviceMap.removeAll(work.keySet());
}
protected void handleNewServices(Map work)
{
String methodName = "handleNewServices";
Map updates = new HashMap(); // to be added to the service map sent to OR
for ( DuccId id : work.keySet() ) {
DuccWorkJob w = (DuccWorkJob) work.get(id);
//
// On restart we sometimes get stale stuff that we just ignore.
//
if ( !w.isActive() ) {
logger.info(methodName, id, "Bypassing inactive service, state=", w.getStateObject());
continue;
}
ServiceDependency s = new ServiceDependency();
updates.put(id, s);
String endpoint = w.getServiceEndpoint();
if ( endpoint == null ) { // the job is damaged if this happens
String msg = "No service endpoint. Service cannot be validated.";
logger.warn(methodName, id, msg);
s.addMessage("null", msg); // this is a fatal state always
s.setState(ServiceState.NotAvailable);
continue;
}
String[] deps = w.getServiceDependencies(); // other services this svc depends on
ServiceSet sset = serviceStateHandler.getServiceByImplementor(id.getFriendly());
if ( sset == null ) {
s.addMessage(endpoint, "No registered service for " + endpoint);
s.setState(ServiceState.NotAvailable);
continue;
}
//
// No deps. Put it in the map and move on.
//
if ( deps == null ) {
logger.info(methodName, id, "Added service to map, no service dependencies. ");
s.setState(ServiceState.Available); // good to go in the OR (the state of things i'm dependent upon)
sset.signalUpdate(w);
continue;
}
resolveDependencies(w, s); // check what I depend on and maybe kick 'em
resolveState(id, s); // get cumulative state based on my deps
sset.signalUpdate(w); // kick my own instance
logger.info(methodName, id, "Added to map, with service dependencies,", s.getState());
}
serviceMap.putAll(updates); // for return to OR
}
/**
* The assumption here is that we already had the service instance in our map, and OR is
* delivering an update. That means the instance was known to us in the past, it is not new.
*/
protected void handleModifiedServices(Map work)
{
String methodName = "handleModifiedServices";
//
// This is a specific service process, but not necessarily the whole service.
//
for ( DuccId id : work.keySet() ) {
DuccWorkJob w = (DuccWorkJob) work.get(id);
IDuccProcessMap pm = w.getProcessMap();
String node = "";
Long share_id = -1L;
if ( pm.size() > 1 ) {
logger.warn(methodName, id, "Process map is too large, should be size 1. Size:", pm.size(), "Cannot determine node or share_id for service.");
} else if ( pm.size() < 1 ) {
logger.warn(methodName, id, "Process map is empty but we are expecting exactly one entry. Cannot determine node or share id for service.");
} else {
for ( DuccId pid : pm.keySet() ) {
NodeIdentity ni = pm.get(pid).getNodeIdentity();
node = ni.getCanonicalName();
share_id = pid.getFriendly();
}
}
// Use the service id in case the url has been removed while unregistering
String ssid = w.getServiceId();
if (ssid == null ) { // probably impossible but lets not chance NPE
logger.warn(methodName, id, "Missing service id, ignoring.");
continue;
}
ServiceSet sset = serviceStateHandler.getServiceByImplementor(id.getFriendly());
if (sset == null) {
// If already removed try via the service id (the url may have been removed while unregistering)
long sid = Long.valueOf(ssid);
sset = serviceStateHandler.getServiceById(sid);
if ( sset == null ) {
// leftover junk publication maybe? can't tell
logger.info(methodName, id, "Active service instance update for", w.getServiceId(),
"but have no registration for it. Job state:", w.getJobState());
continue;
}
logger.info(methodName, id, "Update for possibly unregistered service. Job State:", w.getJobState());
}
if ( !sset.containsImplementor(id) ) {
if ( !sset.canDeleteInstance(w) ) {
// the instance isn't dead, this is a possible problem
logger.warn(methodName, id, "sset for", sset.getId(), "does not contain instance");
}
continue; // we don't care any more, he's gone
}
if ( share_id != -1 ) {
sset.updateInstance(id.getFriendly(), share_id, node);
}
ServiceDependency s = serviceMap.get(id);
if ( w.isFinished() ) { // nothing more, just dereference and maybe stop stuff I'm dependent upon
// state Completing or Completed
stopDependentServices(id);
s.setState(ServiceState.NotAvailable); // tell orchestrator
} else if ( w.getServiceDependencies() != null ) { // update state from things I'm dependent upon
resolveDependencies(w, s);
resolveState(id, s);
}
sset.signalUpdate(w);
}
}
protected void handleDeletedServices(Map work)
{
String methodName = "handleDeletedServices";
for ( DuccId id : work.keySet() ) {
DuccWorkJob w = (DuccWorkJob) work.get(id);
String url = w.getServiceEndpoint();
logger.info(methodName, id, "Instance deleted for", url);
if (url == null ) { // probably impossible but lets not chance NPE
logger.warn(methodName, id, "Missing service endpoint, ignoring.");
continue;
}
//
// Dereference and maybe stop the services I'm dependent upon
//
if ( w.getServiceDependencies() == null ) {
logger.info(methodName, id, "No service dependencies to update on removal.");
} else {
stopDependentServices(id); // update references, remove implicit services if any
}
ServiceSet sset = serviceStateHandler.getServiceByImplementor(id.getFriendly());
if ( sset != null ) { // can happen on unregister
sset.signalUpdate(w);
}
}
serviceMap.removeAll(work.keySet()); // and finally the deleted services
}
/**
* Add in the service dependencies to the query.
*/
void updateServiceQuery(IServiceDescription sd, ServiceSet sset)
{
//
// The thing may not be running yet / at-all. Pull out the deps from the registration and
// query them individually.
//
String[] deps = sset.getIndependentServices();
if ( deps != null ) {
for ( String dep : deps ) {
ServiceSet independent = serviceStateHandler.getServiceByUrl(dep);
if ( independent != null ) {
sd.addDependency(dep, independent.getState().decode());
} else {
sd.addDependency(dep, ServiceState.Stopped.decode());
}
}
}
}
synchronized ServiceReplyEvent query(ServiceQueryEvent ev) // UIMA-4336 Redeclare return type
{
//String methodName = "query";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceQueryReplyEvent reply = new ServiceQueryReplyEvent();
if (( id == -1) && ( url == null )) {
for ( ServiceSet sset : serviceStateHandler.getServices()) {
IServiceDescription sd = sset.query();
updateServiceQuery(sd, sset);
reply.addService(sd);
reply.setReturnCode(true);
}
} else {
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
reply.setEndpoint(url);
reply.setId(id);
if ( sset == null ) {
reply.setMessage("Unknown service");
reply.setEndpoint(url);
reply.setReturnCode(false);
} else {
IServiceDescription sd = sset.query();
updateServiceQuery(sd, sset);
reply.addService(sd);
reply.setReturnCode(true);
}
}
return reply;
}
boolean authorized(String operation, ServiceSet sset, AServiceRequest req)
{
String methodName = "authorized";
String userin = req.getUser();
String userout = sset.getUser();
if ( userin.equals(userout) ) { // owner is always authorized
logger.info(methodName, sset.getId(), operation, "request from", userin, "allowed.");
return true;
}
if ( serviceManager.isAdministrator(req) ) { // global admin is always authorized
logger.info(methodName, sset.getId(), operation, "request from", userin, "allowed as DUCC administrator. Service owner:", userout);
return true;
}
if ( sset.isAuthorized(userin) ) { // registered co-owner is always authorized
logger.info(methodName, sset.getId(), operation, "request from", userin, "alloed as co-ownder. Service owner:", userout);
return true;
}
logger.info(methodName, sset.getId(), operation, "request from", userin, "not authorized. Service owner:", userout);
return false;
}
synchronized ServiceReplyEvent start(ServiceStartEvent ev)
{
// String methodName = "start";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("start", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
int running = sset.countImplementors();
int instances = ev.getInstances();
if ( (instances == -1) && !sset.enabled() ) { // no args always enables
sset.enable();
} else if ( ! sset.enabled() ) {
return ServiceManagerComponent.makeResponse(false, "Service is disabled, cannot start (" + sset.getDisableReason() + ")", url, sset.getId().getFriendly());
}
if ( sset.isDebug() ) {
if ( sset.countImplementors() > 0 ) {
return ServiceManagerComponent.makeResponse(true,
"Already has instances[" + running + "] and service has process_debug set - no additional instances started",
sset.getKey(),
sset.getId().getFriendly());
}
}
int registered = sset.getNInstancesRegistered();
int wanted = 0;
if ( instances == -1 ) {
wanted = Math.max(0, registered - running);
} else {
wanted = instances;
}
if ( wanted == 0 ) {
return ServiceManagerComponent.makeResponse(true,
"Already has instances[" + running + "] - no additional instances started",
sset.getKey(),
sset.getId().getFriendly());
}
pendingRequests.add(new ApiHandler(ev, this));
if ( sset.isDebug() && (wanted > 1) ) {
return ServiceManagerComponent.makeResponse(true,
"Instances adjusted to [1] because process_debug is set",
sset.getKey(),
sset.getId().getFriendly());
} else {
return ServiceManagerComponent.makeResponse(true,
"New instances[" + wanted + "]",
sset.getKey(),
sset.getId().getFriendly());
}
}
//
// Everything to do this must be vetted before it is called
//
// Start with no instance says: start enough new processes to get up the registered amount
// Start with some instances says: start exactly this many
// If the --save option is included, also update the registration
//
void doStart(ServiceStartEvent ev)
{
String methodName = "doStart";
long friendly = ev.getFriendly();
String epname = ev.getEndpoint();
int instances = ev.getInstances();
ServiceSet sset = serviceStateHandler.getServiceForApi(friendly, epname);
int running = sset.countImplementors();
int registered = sset.getNInstancesRegistered();
int wanted = 0;
if ( sset.isDebug() ) {
if ( sset.countImplementors() > 0 ) {
logger.warn(methodName, sset.getId(), "Not starting additional instances because process_debug is set.");
return;
}
if ( instances > 1 ) {
logger.warn(methodName, sset.getId(), "Adjusting instances to [1] because process_debug is set.");
instances = 1;
}
}
if ( instances == -1 ) {
wanted = Math.max(0, registered - running);
} else {
wanted = instances;
}
sset.resetRuntimeErrors();
sset.setStarted(); // manual start overrides, if there's still a problem
sset.updateInstances(running + wanted); // pass in target instances
}
synchronized ServiceReplyEvent stop(ServiceStopEvent ev)
{
String methodName = "stop";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("stop", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
if ( sset.isStopped() ) {
return ServiceManagerComponent.makeResponse(false, "Already stopped", sset.getKey(), sset.getId().getFriendly());
}
int running = sset.countImplementors();
int instances = ev.getInstances();
int tolose;
String msg;
// CLI/API prevents instances < -1
if ( instances == -1 ) { // figure out n to lose
tolose = running;
msg = "Stopping all deployments.";
} else {
tolose = Math.min(instances, running);
msg = "Stopping " + tolose + " deployments.";
}
logger.info(methodName, sset.getId(), msg);
pendingRequests.add(new ApiHandler(ev, this));
return ServiceManagerComponent.makeResponse(true, msg, sset.getKey(), sset.getId().getFriendly());
}
//
// Everything to do this must be vetted before it is called
//
// If instances == 0 set stop the whole service
// Otherwise we just stop the number asked for
// If --save is insicated we update the registry
//
void doStop(ServiceStopEvent event)
//long id, String url, int instances)
{
//String methodName = "doStop";
int instances = event.getInstances();
long id = event.getFriendly();
String url = event.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
int running = sset.countImplementors();
int tolose;
// CLI/API prevents instances < -1
if ( instances == -1 ) { // figure out n to lose
sset.disableAndStop("Disabled by stop from id " + event.getUser());
} else {
tolose = Math.min(instances, running);
sset.updateInstances(Math.max(0, running - tolose)); // pass in target intances running
}
}
synchronized ServiceReplyEvent disable(ServiceDisableEvent ev)
{
String methodName = "disable";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("disable", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
if ( !sset.enabled() ) {
return ServiceManagerComponent.makeResponse(true, "Service is already disabled", sset.getKey(), sset.getId().getFriendly());
}
sset.disable("Disabled by owner or administrator " + ev.getUser());
try {
sset.updateMetaProperties();
} catch ( Exception e ) {
logger.warn(methodName, sset.getId(), "Error updating meta properties:", e);
}
return ServiceManagerComponent.makeResponse(true, "Disabled", sset.getKey(), sset.getId().getFriendly());
}
synchronized ServiceReplyEvent enable(ServiceEnableEvent ev)
{
String methodName = "enable";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("enable", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
if ( sset.enabled() ) {
return ServiceManagerComponent.makeResponse(true, "Service is already enabled", sset.getKey(), sset.getId().getFriendly());
}
sset.enable();
try {
sset.updateMetaProperties();
} catch ( Exception e ) {
logger.warn(methodName, sset.getId(), "Error updating meta properties:", e);
}
return ServiceManagerComponent.makeResponse(true, "Enabled.", sset.getKey(), sset.getId().getFriendly());
}
synchronized ServiceReplyEvent ignore(ServiceIgnoreEvent ev)
{
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("ignore", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
if ( sset.isAutostart() ) {
return ServiceManagerComponent.makeResponse(false, "Service is autostarted, ignore-references not applied.", sset.getKey(), sset.getId().getFriendly());
}
if ( !sset.isReferencedStart() ) {
return ServiceManagerComponent.makeResponse(true, "Service is already ignoring references", sset.getKey(), sset.getId().getFriendly());
}
if ( sset.countImplementors() == 0 ) {
return ServiceManagerComponent.makeResponse(false, "Cannot ignore references, service is not running.", sset.getKey(), sset.getId().getFriendly());
}
sset.ignoreReferences();
return ServiceManagerComponent.makeResponse(true, "References now being ignored.", sset.getKey(), sset.getId().getFriendly());
}
synchronized ServiceReplyEvent observe(ServiceObserveEvent ev)
{
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("observe", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
if ( sset.isAutostart() ) {
return ServiceManagerComponent.makeResponse(false, "Must set autostart off before enabling reference-starts.", sset.getKey(), sset.getId().getFriendly());
}
if ( sset.countImplementors() == 0 ) {
return ServiceManagerComponent.makeResponse(false, "Cannot observe references, service is not running.", sset.getKey(), sset.getId().getFriendly());
}
sset.observeReferences();
return ServiceManagerComponent.makeResponse(true, "Observing references.", sset.getKey(), sset.getId().getFriendly());
}
synchronized ServiceReplyEvent register(DuccId id, DuccProperties props, DuccProperties meta, boolean isRecovered)
{
String methodName = "register";
String error = null;
boolean must_deregister = false;
String url = meta.getProperty("endpoint");
ServiceSet sset = serviceStateHandler.getServiceByUrl(url);
if (sset != null ) {
error = "Duplicate registered by " + sset.getUser();
return ServiceManagerComponent.makeResponse(false, error, url, sset.getId().getFriendly());
}
try {
sset = new ServiceSet(this, this.stateHandler, id, props, meta);
} catch (Throwable t) {
// throws because endpoint is not parsable
error = t.getMessage();
return ServiceManagerComponent.makeResponse(false, error, url, id.getFriendly());
}
try {
// if it's a "fresh" reservation it must go into the db. otherwise it is already
// in the db and doesn't need to be inserted
sset.storeProperties(isRecovered);
} catch ( Exception e ) {
error = ("Internal error; unable to store service descriptor. " + url);
logger.error(methodName, id, e);
}
// must check for cycles or we can deadlock
if ( ! must_deregister ) {
// TODO R2, revive the cycle checker
// CycleChecker cc = new CycleChecker(sset);
// if ( cc.hasCycle() ) {
// error = ("Service dependencies contain a cycle with " + cc.getCycles());
// logger.error(methodName, id, error);
// must_deregister = true;
// }
}
if ( error == null ) {
serviceStateHandler.registerService(id.getFriendly(), url, sset);
return ServiceManagerComponent.makeResponse(true, "Registered", url, id.getFriendly());
} else {
return ServiceManagerComponent.makeResponse(false, error, url, id.getFriendly());
}
}
synchronized ServiceReplyEvent modify(ServiceModifyEvent ev)
{
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("modify", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
pendingRequests.add(new ApiHandler(ev, this));
return ServiceManagerComponent.makeResponse(true, "Modify accepted:", sset.getKey(), sset.getId().getFriendly());
}
boolean restart_pinger = false;
boolean restart_service = false;
void modifyRegistration(ServiceSet sset, UiOption option, String value)
{
int intval = 0;
boolean boolval = false;
// TODO: this case covers ALL service options, but note that only those in the modify list
// in the CLI are actually used. Eventually we will cover them all.
switch ( option ) {
case Instances:
intval = Integer.parseInt(value);
sset.updateRegisteredInstances(intval);
break;
case Autostart:
boolval = Boolean.parseBoolean(value);
sset.setAutostart(boolval);
break;
case Administrators:
sset.setJobProperty(option.pname(), value);
sset.parseAdministrators(value);
break;
// For the moment, these all update the registration but don't change internal
// operation.
case Description:
case LogDirectory:
case Jvm:
case ProcessJvmArgs:
case Classpath:
case SchedulingClass:
case Environment:
case ProcessMemorySize:
case ProcessExecutable:
case ProcessExecutableArgs:
case ServiceDependency:
case ProcessInitializationTimeMax:
case WorkingDirectory:
sset.setJobProperty(option.pname(), value);
break;
case InstanceInitFailureLimit:
sset.updateInitFailureLimit(value);
sset.setJobProperty(option.pname(), value);
break;
case ServiceLinger:
sset.updateLinger(value);
sset.setJobProperty(option.pname(), value);
break;
case ProcessDebug:
// Note this guy updates the props differently based on the value
sset.updateDebug(value); // value may be numeric, or "off"
break;
case ServicePingArguments:
case ServicePingClasspath:
case ServicePingJvmArgs:
case ServicePingTimeout:
case ServicePingDoLog:
case ServicePingClass:
case InstanceFailureWindow:
case InstanceFailureLimit:
if ( value.equals("default") ) {
sset.deleteJobProperty(option.pname());
} else {
sset.setJobProperty(option.pname(), value);
}
restart_pinger = true;
break;
default:
// In case a deprecated option such as classpath_order slips through
break;
}
}
//void doModify(long id, String url, int instances, Trinary autostart, boolean activate)
void doModify(ServiceModifyEvent sme)
{
String methodName = "doModify";
long id = sme.getFriendly();
String url = sme.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
DuccProperties mods = sme.getProperties();
restart_pinger = false;
restart_service = false;
boolean updateMeta = false;
Set keys = mods.stringPropertyNames();
for (String kk : keys ) {
UiOption k = optionMap.get(kk);
if ( k == null ) {
logger.debug(methodName, sset.getId(), "Bypass property", kk);
continue;
}
switch ( k ) {
case Help:
case Debug:
case Modify:
// used by CLI only, won't even be passed in
continue;
case Autostart:
updateMeta = true; // UIMA-4928 (Should move it to the svc props)
default:
}
String v = (String) mods.get(kk);
try {
modifyRegistration(sset, k, v);
} catch ( Throwable t ) {
logger.error(methodName, sset.getId(), "Modify", kk, "to", v, "Failed:", t);
continue;
}
logger.info(methodName, sset.getId(), "Modify", kk, "to", v, "restart_service[" + restart_service + "]", "restart_pinger[" + restart_pinger + "]");
}
sset.resetRuntimeErrors();
try {
sset.updateSvcProperties();
if (updateMeta) {
sset.updateMetaProperties();
}
} catch (Exception e) {
logger.error(methodName, sset.getId(), "Cannot store properties:", e);
}
if ( restart_pinger ) {
sset.restartPinger();
restart_pinger = false;
}
// restart_service - not yet
}
synchronized ServiceReplyEvent unregister(ServiceUnregisterEvent ev)
{
String methodName = "unregister";
long id = ev.getFriendly();
String url = ev.getEndpoint();
ServiceSet sset = serviceStateHandler.getServiceForApi(id, url);
if ( sset == null ) {
logger.info(methodName, null, "Unknown service", id, url);
return ServiceManagerComponent.makeResponse(false, "Unknown service", url, id);
}
if ( ! authorized("unregister", sset, ev) ) {
return ServiceManagerComponent.makeResponse(false, "Owned by " + sset.getUser(), url, sset.getId().getFriendly());
}
// Ensure that the event has the id as the name will be removed from the name->id map
ev.setFriendly(sset.getId().getFriendly());
serviceStateHandler.unregister(sset);
pendingRequests.add(new ApiHandler(ev, this));
logger.info(methodName, null, "Unregistering service", id, url);
return ServiceManagerComponent.makeResponse(true, "Shutting down implementors", sset.getKey(), sset.getId().getFriendly());
}
//
// Everything to do this must be vetted before it is called. Run in a new thread to not hold up the API.
//
void doUnregister(ServiceUnregisterEvent ev)
{
String methodName = "doUnregister";
long friendly = ev.getFriendly();
// Can only get by id when unregistering as the name has been removed from the name=>id map
ServiceSet sset = serviceStateHandler.getServiceById(friendly);
if ( sset == null ) { // Should never happen
logger.error(methodName, null, "Service", friendly, "is not a known, service. No action taken.");
return;
}
String url = sset.getKey();
sset.disableAndStop("Disabled by unregister from id " + ev.getUser());
if ( sset.isPingOnly() ) {
logger.info(methodName, sset.getId(), "Unregister ping-only setvice:", friendly, url);
serviceStateHandler.removeService(sset);
try {
sset.deleteProperties();
} catch (Exception e) {
logger.error(methodName, sset.getId(), "Cannot delete service from DB:", e);
}
} else if ( sset.countImplementors() > 0 ) {
logger.debug(methodName, sset.getId(), "Stopping implementors:", friendly, url);
} else {
logger.debug(methodName, sset.getId(), "Removing from map:", friendly, url);
sset.clearQueue(); // will call removeServices if everything looks ok
}
}
void addInstance(ServiceSet sset, ServiceInstance inst)
{
serviceStateHandler.addImplementorFor(sset, inst);
}
void removeImplementor(ServiceSet sset, ServiceInstance inst)
{
serviceStateHandler.removeImplementorFor(sset, inst);
}
void removeService(ServiceSet sset)
{
serviceStateHandler.removeService(sset);
}
/**
* From: http://en.wikipedia.org/wiki/Topological_sorting
*
* L Empty list that will contain the sorted elements
* S Set of all nodes with no incoming edges
* while S is non-empty do
* remove a node n from S
* insert n into L
* for each node m with an edge e from n to m do
* remove edge e from the graph
* if m has no other incoming edges then
* insert m into S
* if graph has edges then
* return error (graph has at least one cycle)
* else
* return L (a topologically sorted order)
*/
// class CycleChecker
// {
// ServiceSet sset;
// int edges = 0;
// List cycles = null;
// CycleChecker(ServiceSet sset)
// {
// this.sset = sset;
// }
// boolean hasCycle()
// {
// // Start by building the dependency graph
// // TODO: Maybe consider saving this. Not clear there's much of a
// // gain doing the extra bookeeping beause the graphs will always
// // be small and will only need checking on registration or arrival
// // of a submitted service. So this cycle checking is always
// // fast anyway.
// //
// // Bookeeping could be a bit ugly because a submitted service could
// // bop in and change some dependency graph. We really only care
// // for checking cycles, so we'll check the cycles as things change
// // and then forget about it.
// //
// String[] deps = sset.getIndependentServices();
// if ( deps == null ) return false; // man, that was fast!
// Map visited = new HashMap(); // all the nodes in the graph
// clearEdges(sset, visited);
// List nodes = new ArrayList();
// nodes.addAll(visited.values());
// buildGraph(nodes);
// List sorted = new ArrayList(); // topo-sorted list of nodes
// List current = new ArrayList(); // nodes with no incoming edges
// // Constant: current has all nodes with no incoming edges
// for ( ServiceSet node : nodes ) {
// if ( ! node.hasPredecessor() ) current.add(node);
// }
// while ( current.size() > 0 ) {
// ServiceSet next = current.remove(0); // remove a node n from S
// sorted.add(next); // insert n int L
// List successors = next.getSuccessors();
// for ( ServiceSet succ : successors ) { // for each node m(pred) with an edge e from n to m do
// next.removeSuccessor(succ); // remove edge from graph
// succ.removePredecessor(next); // ...
// edges--;
// if ( !succ.hasPredecessor() ) current.add(succ); // if m(pred) has no incoming edges insert m into S
// }
// }
// if ( edges == 0 ) return false; // if graph has no edges, no cycles
// cycles = new ArrayList(); // oops, and here they are
// for ( ServiceSet node : nodes ) {
// if ( node.hasSuccessor() ) {
// for ( ServiceSet succ : node.getSuccessors() ) {
// cycles.add(node.getKey() + " -> " + succ.getKey());
// }
// }
// }
// return true;
// }
// String getCycles()
// {
// return cycles.toString();
// }
// //
// // Traveerse the graph and make sure all the nodes are "clean"
// //
// void clearEdges(ServiceSet node, Map visited)
// {
// String key = node.getKey();
// node.clearEdges();
// if ( visited.containsKey(key) ) return;
// visited.put(node.getKey(), node);
// String[] deps = node.getIndependentServices();
// if ( deps == null ) return;
// for ( String dep : deps ) {
// ServiceSet sset = serviceStateHandler.getServiceByName(dep);
// if ( sset != null ) {
// clearEdges(sset, visited);
// }
// }
// }
// void buildGraph(List nodes)
// {
// for ( ServiceSet node : nodes ) {
// String[] deps = node.getIndependentServices(); // never null if we get this far
// if ( deps != null ) {
// for ( String d : deps ) {
// ServiceSet outgoing = serviceStateHandler.getServiceByName(d);
// if ( outgoing == null ) continue;
// outgoing.setIncoming(node);
// node.setOutgoing(outgoing);
// edges++;
// }
// }
// }
// }
// }
/**
* This is the shutdown hook that stops all the pingers.
*/
class ServiceShutdown
extends Thread
{
ServiceShutdown()
{
System.out.println("Setting shutdown hook");
}
public void run()
{
System.out.println("Running shutdown hook");
List allServices = serviceStateHandler.getServices();
for (ServiceSet sset : allServices) {
sset.stopMonitor();
}
try {
stateHandler.shutdown();
} catch ( Exception e ) {
logger.warn("ServicShutdown.run", null, "Error closing database: ", e);
}
}
}
class ServiceStateHandler
{
// Map of active service descriptors by endpoint. For UIMA services, key is the endpoint.
// Map from name->id and from id->service so can quickly unregister by removing from the 1st map UIMA-5372
private Map registeredServiceIdsByUrl = new HashMap();
private Map registeredServicesById = new HashMap();
// Map from instance-id -> service (each instance has a unique ID)
private Map servicesByImplementor = new HashMap();
// // For each job, the collection of services it is dependent upon
// // DUccId is a Job Id (or id for serice that has dependencies)
private Map> servicesByJob = new HashMap>();
/*
* Simply remove the name from the name->id map so the name can be re-used quickly.
* The now-orphaned id will be used for the remainder of the shutdown steps. UIMA-5372
*/
synchronized void unregister(ServiceSet sset)
{
String methodName = "ServiceStateHandler.unregister";
String key = sset.getKey();
logger.info(methodName, sset.getId(), "Removing", key, "from name->id map");
registeredServiceIdsByUrl.remove(key);
sset.deregister(); // just sets a flag so we know how to handle it when it starts to die
}
synchronized boolean hasService(DuccId id)
{
String methodName = "ServiceStateHandler.hasService";
logger.info(methodName, null, "containsKey", id, registeredServicesById.containsKey(id.getFriendly()));
return registeredServicesById.containsKey(id.getFriendly());
}
synchronized void registerService(Long id, String ep, ServiceSet sset)
{
String methodName = "ServiceStateHandler.registerService";
logger.info(methodName, sset.getId(), "adding", ep, id);
registeredServiceIdsByUrl.put(ep, id);
registeredServicesById.put(id, sset);
}
// Must map url->id then id->service
synchronized ServiceSet getServiceByUrl(String n)
{
Long id = registeredServiceIdsByUrl.get(n);
return id == null ? null : registeredServicesById.get(id);
}
synchronized ServiceSet getServiceById(long id)
{
return registeredServicesById.get(id);
}
// Note: must exclude services being unregistered
synchronized List getServices()
{
ArrayList answer = new ArrayList();
for ( ServiceSet sset : registeredServicesById.values() ) {
if (!sset.isDeregistered()) {
answer.add(sset);
}
}
return answer;
}
synchronized void addImplementorFor(ServiceSet sset, ServiceInstance inst)
{
servicesByImplementor.put(inst.getId(), sset);
}
synchronized ServiceSet getServiceByImplementor(long instId)
{
return servicesByImplementor.get(instId);
}
synchronized void removeImplementorFor(ServiceSet sset, ServiceInstance inst)
{
servicesByImplementor.remove(inst.getId());
}
// API passes in either an id or an endpoint but not both.
synchronized ServiceSet getServiceForApi(long id, String n)
{
return n == null ? getServiceById(id) : getServiceByUrl(n);
}
synchronized void removeService(ServiceSet sset)
{
long id = sset.getId().getFriendly();
registeredServicesById.remove(id); // The name has already been removed
// The registeredServices need to have been removed during unregister which is the only way
// to get rid of a service.
Long[] implids = sset.getImplementors();
for ( long l : implids ) {
servicesByImplementor.remove(l);
}
DuccId[] refids = sset.getReferences();
for ( DuccId rid : refids) {
servicesByJob.remove(rid);
}
}
// synchronized void removeService(long id)
// {
// ServiceSet sset = servicesByFriendly.remove(id);
// if ( sset != null ) {
// String key = sset.getKey();
// servicesByName.remove(key);
// }
// }
// synchronized void removeService(String n, long id)
// {
// if ( n == null ) removeService(id);
// else removeService(n);
// }
synchronized Map getServicesForJob(DuccId id)
{
return servicesByJob.get(id);
}
synchronized void putServiceForJob(DuccId id, ServiceSet s)
{
Map services = servicesByJob.get(id);
if ( services == null ) {
services = new HashMap();
servicesByJob.put(id, services);
}
services.put(s.getId().getFriendly(), s);
}
synchronized void removeServicesForJob(DuccId id)
{
servicesByJob.remove(id);
}
// synchronized void recordNewServices(Map services)
// {
// servicesByName.putAll(services);
// }
}
// tester for the topo sorter
public static void main(String[] args)
{
}
}