/*
* Copyright 1999-2008 University of Chicago
*
* 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.nimbustools.ctxbroker.service;
import org.nimbustools.ctxbroker.Identity;
import org.nimbustools.ctxbroker.BrokerConstants;
import org.nimbustools.ctxbroker.ContextBrokerException;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Contextualization_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Provides_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Requires_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.IdentityProvides_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Requires_TypeIdentity;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Cloudcluster_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.Cloudworkspace_Type;
import org.nimbustools.ctxbroker.generated.gt4_0.description.AgentDescription_Type;
import org.nimbustools.ctxbroker.security.BootstrapFactory;
import org.nimbustools.ctxbroker.security.BootstrapInformation;
import org.globus.wsrf.impl.ResourceHomeImpl;
import org.globus.wsrf.impl.SimpleResourceKey;
import org.globus.wsrf.ResourceKey;
import org.globus.wsrf.ResourceException;
import org.globus.wsrf.Constants;
import org.globus.wsrf.config.ConfigException;
import org.globus.wsrf.utils.AddressingUtils;
import org.globus.wsrf.container.ServiceHost;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.apache.axis.components.uuid.UUIDGen;
import org.apache.axis.components.uuid.UUIDGenFactory;
import org.apache.axis.message.addressing.EndpointReferenceType;
import org.apache.axis.message.MessageElement;
import javax.naming.InitialContext;
import java.io.IOException;
import java.util.Calendar;
import java.util.Hashtable;
public class ContextBrokerHomeImpl extends ResourceHomeImpl
implements ContextBrokerHome {
// -------------------------------------------------------------------------
// STATIC VARIABLES
// -------------------------------------------------------------------------
private static final Log logger =
LogFactory.getLog(ContextBrokerHomeImpl.class.getName());
public static final String CONTEXTUALIZATION_BOOTSTRAP_FACTORY =
Constants.JNDI_SERVICES_BASE_NAME +
BrokerConstants.CTX_BROKER_PATH +
"/ctxBrokerBootstrapFactory";
// -------------------------------------------------------------------------
// INSTANCE VARIABLES
// -------------------------------------------------------------------------
private final String serviceAddress;
private boolean initialized;
private UUIDGen uuidGen;
private BootstrapFactory bootstrapFactory;
// String hostname --> Integer workspID
private final Hashtable<String, Integer> hostnameMap = new Hashtable<String, Integer>();
// String ip-address --> Integer workspID
private final Hashtable<String, Integer> ipMap = new Hashtable<String, Integer>();
// the lowest unused integer ID
private int nextID = 1;
// -------------------------------------------------------------------------
// CONSTRUCTOR
// -------------------------------------------------------------------------
public ContextBrokerHomeImpl() throws IOException {
super();
this.keyTypeName =
BrokerConstants.CONTEXTUALIZATION_RESOURCE_KEY_QNAME;
this.serviceAddress = ServiceHost.getBaseURL() +
BrokerConstants.CTX_BROKER_PATH;
}
// -------------------------------------------------------------------------
// SETUP
// -------------------------------------------------------------------------
public synchronized void initialize() throws Exception {
super.initialize();
if (this.initialized) {
logger.warn("already initialized: Nimbus Context Broker");
return;
}
logger.info("Setting up Nimbus Context Broker");
this.uuidGen = UUIDGenFactory.getUUIDGen();
this.bootstrapFactory = discoverBootstrapFactory();
this.initialized = true;
logger.info("Ready: Nimbus Context Broker");
}
public static BootstrapFactory discoverBootstrapFactory() throws Exception {
InitialContext ctx = null;
try {
ctx = new InitialContext();
final BootstrapFactory factory =
(BootstrapFactory) ctx.lookup(
CONTEXTUALIZATION_BOOTSTRAP_FACTORY);
if (factory == null) {
throw new Exception("null from JNDI for BootstrapFactory (?)");
}
return factory;
} finally {
if (ctx != null) {
ctx.close();
}
}
}
// -------------------------------------------------------------------------
// implements ContextBrokerHome
// -------------------------------------------------------------------------
public String getBrokerURL() throws ContextBrokerException {
return this.serviceAddress;
}
public String getContextSecret(EndpointReferenceType ref)
throws ContextBrokerException {
final ResourceKey ctxKey = this.getResourceKey(ref);
final ContextBrokerResource resource =
this.findResourceNoSecurity(ctxKey);
BootstrapInformation bootstrap = resource.getBootstrap();
// current secret string is a separated list:
/*
Separator
Position 0: Public certificate (pem) to contact ctx service
Separator
Position 1: Private key (pem) to contact ctx service
Separator
*/
StringBuffer buf = new StringBuffer();
buf.append(ContextBrokerHome.FIELD_SEPARATOR)
.append(bootstrap.getPublicX509String())
.append(ContextBrokerHome.FIELD_SEPARATOR)
.append(bootstrap.getPrivateString())
.append(ContextBrokerHome.FIELD_SEPARATOR);
return buf.toString();
}
/**
* @see ContextBrokerHome#addWorkspace
*/
public void addWorkspace(ContextBrokerResource resource,
Integer workspaceID,
Identity[] identities,
AgentDescription_Type ctxDocAndID)
throws ContextBrokerException {
if (identities == null) {
throw new IllegalArgumentException("identities may not be null");
}
if (ctxDocAndID == null) {
final String err = "A previously unknown workspace's context " +
"agent is invoking " +
"an operation before retrieve, we cannot register it.\n" +
"Identity dump: " + this.identitiesDump(identities);
throw new ContextBrokerException(err);
}
if (workspaceID == null) {
throw new IllegalArgumentException("workspaceID may not be null");
}
if (identities.length > 2 || identities.length == 0) {
throw new ContextBrokerException(
"these arbitrary identities are not supported at the moment," +
"you may only use 1 or 2 NICs");
}
Cloudcluster_Type cluster = ctxDocAndID.getCluster();
// checks that there is nothing illegal and that one and only
// one section will be marked active
int totalNodes = this.clusterSanityAndCount(cluster);
logger.debug("total nodes: " + totalNodes);
final Cloudworkspace_Type[] vms = cluster.getWorkspace();
Contextualization_Type ctxDoc = null;
for (Cloudworkspace_Type vm : vms) {
if (Boolean.TRUE.equals(vm.getActive())) {
ctxDoc = vm.getCtx(); // clusterSanityAndCount guarantees one and only one
break;
}
}
// clusterSanity checked this already, this is to satisfy automatic
// code analysis
if (ctxDoc == null) {
throw new NullPointerException();
}
this.basicCtxdocValidate(ctxDoc);
for (Identity identity : identities) {
final String hostname = identity.getHostname();
if (hostname != null) {
logger.debug("hostname: " + hostname);
this.newHostname(workspaceID, hostname, resource);
}
final String ip = identity.getIp();
if (ip != null) {
logger.debug("ip: " + ip);
this.newIP(workspaceID, ip, resource);
}
}
resource.addWorkspace(workspaceID,
identities,
ctxDoc.getRequires(),
ctxDoc.getProvides(),
totalNodes);
}
/**
* @see ContextBrokerHome#getResourceKey
*/
public ResourceKey getResourceKey(EndpointReferenceType epr)
throws ContextBrokerException {
return new SimpleResourceKey(this.getKeyTypeName(),
this.getID(epr));
}
/**
* @see ContextBrokerHome#createNewResource
*/
public EndpointReferenceType createNewResource(String callerDN,
boolean allowInjections)
throws ContextBrokerException {
if (!this.initialized) {
throw new ContextBrokerException(
ContextBrokerHomeImpl.class.getName() +
" not initialized.");
}
final ContextBrokerResource resource;
try {
resource = (ContextBrokerResource)createNewInstance();
} catch (Exception e) {
throw new ContextBrokerException("", e);
}
final String uuid = this.uuidGen.nextUUID();
resource.setID(uuid);
resource.setAllowInjections(allowInjections);
// TODO: make this limit configurable and/or possibly make a dynamic
// decision. For example, if the scheduler is NOT best effort
// then it is probably OK to just make this expire in a day or
// even an hour. Also, if re-contextualization is implemented,
// presumbaly the VMs (for now) would contact with same cred
// so expiration will affect things in that situation
final Calendar expires = Calendar.getInstance();
expires.add(Calendar.MONTH, 1);
final BootstrapInformation bootstrap =
this.bootstrapFactory.newBootstrap(uuid,
this.serviceAddress,
expires);
resource.setBootstrap(bootstrap);
// in the future, policy should come from elsewhere
try {
resource.initSecureResource(callerDN, bootstrap.getBootstrapDN());
} catch (ConfigException e) {
throw new ContextBrokerException("", e);
}
logger.info("WS-CTX created new contextualization " +
"resource: '" + uuid + "' for DN = '" + callerDN + "'");
ResourceKey key = this.getResourceKey(uuid);
this.add(key, resource);
return getEPR(key);
}
// -------------------------------------------------------------------------
// OTHER/IMPL
// -------------------------------------------------------------------------
private void newHostname(Integer vmid, String hostname,
ContextBrokerResource resource) {
if (vmid == null) {
throw new IllegalArgumentException("vmid may not be null");
}
if (hostname == null) {
throw new IllegalArgumentException("hostname may not be null");
}
final String rsrc = (String) resource.getID();
if (rsrc == null) {
throw new IllegalArgumentException("resource ID may not be null");
}
this.noPersistenceHostnameAddition(rsrc, vmid, hostname);
}
private void newIP(Integer vmid, String ip,
ContextBrokerResource resource) {
if (vmid == null) {
throw new IllegalArgumentException("vmid may not be null");
}
if (ip == null) {
throw new IllegalArgumentException("ip may not be null");
}
final String rsrc = (String) resource.getID();
if (rsrc == null) {
throw new IllegalArgumentException("resource ID may not be null");
}
this.noPersistenceIPAddition(rsrc, vmid, ip);
}
public synchronized Integer getID(ContextBrokerResource resource,
IdentityProvides_Type[] identities,
AgentDescription_Type sent) throws ContextBrokerException {
if (resource == null) {
throw new IllegalArgumentException("resource may not be null");
}
if (identities == null || identities.length == 0) {
throw new IllegalArgumentException("no identites");
}
final String rsrc = (String) resource.getID();
if (rsrc == null) {
throw new IllegalArgumentException("resource ID may not be null");
}
for (IdentityProvides_Type identity : identities) {
if (identity == null) {
logger.error("Null identity in request, ID dump: " +
this.identitiesDump(identities));
return null;
}
String ip = identity.getIp();
if (ip != null) {
final Integer result = noPersistenceIPQuery(rsrc, ip);
if (result != null) {
return result;
}
} else {
logger.error("Null IP in request, ID dump: " +
this.identitiesDump(identities));
return null;
}
}
for (IdentityProvides_Type identity : identities) {
String hostname = identity.getHostname();
if (hostname != null) {
final Integer result = noPersistenceHostnameQuery(rsrc,
hostname);
if (result != null) {
return result;
}
} else {
logger.error("Null hostname in request, ID dump: " +
this.identitiesDump(identities));
return null;
}
}
for (IdentityProvides_Type identity : identities) {
String iface = identity.get_interface();
if (iface == null) {
logger.error("Null interface in request, ID dump: " +
this.identitiesDump(identities));
return null;
}
}
// create an integer ID for this member of the context that we have not
// seen before
synchronized (this) {
// nothing is null or missing except possibly pubkey
final StringBuffer buf = new StringBuffer();
buf.append("Broker hearing from a VM for first time, IP(s)=");
Identity[] ids = new Identity[identities.length];
for (int i = 0; i < identities.length; i++) {
ids[i] = new Identity();
ids[i].setIface(identities[i].get_interface());
ids[i].setIp(identities[i].getIp());
buf.append(" '").append(identities[i].getIp()).append("'");
ids[i].setHostname(identities[i].getHostname());
ids[i].setPubkey(identities[i].getPubkey());
}
final Integer thisID = this.nextID;
this.nextID = this.nextID + 1;
buf.append(" and internal ID # = ").append(thisID.toString());
logger.info(buf.toString());
this.addWorkspace(resource,
thisID,
ids,
sent);
}
return null;
}
// for errors
private String identitiesDump(IdentityProvides_Type[] identities) {
final StringBuffer buf = new StringBuffer();
for (int i = 0; i < identities.length; i++) {
buf.append("IdentityProvides_Type #")
.append(i);
IdentityProvides_Type id = identities[i];
if (id == null) {
buf.append(" is null.\n");
} else {
buf.append(":\n")
.append(" - interface: '")
.append(id.get_interface())
.append("'\n");
buf.append(" - ip: '")
.append(id.getIp())
.append("'\n");
buf.append(" - hostname: '")
.append(id.getHostname())
.append("'\n");
buf.append(" - ssh pubkey: '")
.append(id.getPubkey())
.append("'\n");
}
}
return buf.toString();
}
private String identitiesDump(Identity[] identities) {
final StringBuffer buf = new StringBuffer();
for (int i = 0; i < identities.length; i++) {
buf.append("IdentityProvides_Type #")
.append(i);
Identity id = identities[i];
if (id == null) {
buf.append(" is null.\n");
} else {
buf.append(":\n")
.append(" - interface: '")
.append(id.getIface())
.append("'\n");
buf.append(" - ip: '")
.append(id.getIp())
.append("'\n");
buf.append(" - hostname: '")
.append(id.getHostname())
.append("'\n");
buf.append(" - ssh pubkey: '")
.append(id.getPubkey())
.append("'\n");
}
}
return buf.toString();
}
private Integer noPersistenceIPQuery(String rsrc,
String ip) {
return this.ipMap.get(rsrc+ip);
}
private void noPersistenceIPAddition(String rsrc,
Integer id,
String ip) {
this.ipMap.put(rsrc+ip, id);
}
private Integer noPersistenceHostnameQuery(String rsrc,
String hostname) {
return this.hostnameMap.get(rsrc+hostname);
}
private void noPersistenceHostnameAddition(String rsrc,
Integer id,
String hostname) {
this.hostnameMap.put(rsrc+hostname, id);
}
// -------------------------------------------------------------------------
// WS stuff
// -------------------------------------------------------------------------
private ContextBrokerResource findResourceNoSecurity(ResourceKey ctxKey)
throws ContextBrokerException {
try {
return (ContextBrokerResource) this.find(ctxKey);
} catch (ResourceException e) {
String msg = "Cannot find contextualization resource: '" +
getID(ctxKey) + "'";
throw new ContextBrokerException(msg, e);
}
}
private ContextBrokerResource findResource(ResourceKey ctxKey)
throws ContextBrokerException {
ContextBrokerResource resource;
try {
resource = (ContextBrokerResource) this.find(ctxKey);
} catch (ResourceException e) {
String msg = "Cannot find contextualization resource: '" +
getID(ctxKey) + "'";
throw new ContextBrokerException(msg, e);
}
return resource;
}
private int clusterSanityAndCount(Cloudcluster_Type cluster)
throws ContextBrokerException {
if (cluster == null) {
throw new ContextBrokerException("Invalid cluster description " +
"section, <cluster> is missing.");
}
Cloudworkspace_Type[] vms = cluster.getWorkspace();
if (vms == null || vms.length == 0) {
throw new ContextBrokerException("Invalid cluster description " +
"section, missing workspace elements.");
}
int totalNodeCount = 0;
int numActive = 0;
for (Cloudworkspace_Type vm : vms) {
if (vm == null) {
throw new ContextBrokerException(
"Invalid cluster description section, missing a " +
"workspace element in array.");
}
if (vm.getCtx() == null) {
throw new ContextBrokerException(
"Invalid cluster description section, missing a " +
"ctx element in the workspace list.");
}
if (vm.getQuantity() <= 0) {
throw new ContextBrokerException(
"Invalid cluster description section, missing " +
"quantity.");
}
totalNodeCount += vm.getQuantity();
// active can be null which equals False
final Boolean active = vm.getActive();
if (Boolean.TRUE.equals(active)) {
numActive += 1;
}
}
if (numActive < 1) {
throw new ContextBrokerException(
"Invalid cluster description, there is no " +
"section marked with <active>");
}
if (numActive > 1) {
throw new ContextBrokerException(
"Invalid cluster description, more than one " +
"section is marked with <active> (there are " +
numActive + " marked active)");
}
return totalNodeCount;
}
private void basicCtxdocValidate(Contextualization_Type ctx)
throws ContextBrokerException {
if (ctx == null) {
throw new IllegalArgumentException("contextualization " +
"document given to validate is null");
}
Provides_Type provides = ctx.getProvides();
Requires_Type requires = ctx.getRequires();
if (provides == null && requires == null) {
throw new ContextBrokerException("Both provides and " +
"requires are missing. Will not contextualize this. " +
"If there is nothing to do, do not include " +
"contextualization document.");
}
if (provides != null) {
IdentityProvides_Type[] givenIDs = provides.getIdentity();
if (givenIDs == null || givenIDs.length == 0) {
throw new ContextBrokerException("Provides section is " +
"present but has no identity elements. Will not " +
"contextualize.");
}
}
if (requires == null) {
return;
}
Requires_TypeIdentity[] givenID = requires.getIdentity();
if (givenID == null || givenID.length == 0) {
return;
}
// next two exceptions are for forwards compatibility where it
// may be possible to specify specific identities required
// (without going through role finding which will always place
// identities in the filled requires document for a role,
// regardless if all identities are required or not).
if (givenID.length > 1) {
throw new ContextBrokerException("Given requires " +
"section has multiple identity elements? Currently " +
"only supporting zero or one empty identity element " +
"in requires section (which signals all identities " +
"are desired). Will not contextualize.");
}
if (givenID[0].getHostname() != null ||
givenID[0].getIp() != null ||
givenID[0].getPubkey() != null) {
throw new ContextBrokerException("Given requires " +
"section has an identity element with information " +
"in it? Currently only supporting zero or one " +
"*empty* identity element in requires section " +
"(which signals all identities are desired). Will " +
"not contextualize.");
}
}
public ResourceKey getResourceKey(String id) {
return new SimpleResourceKey(this.getKeyTypeName(), id);
}
public EndpointReferenceType getEPR(ResourceKey key)
throws ContextBrokerException {
final EndpointReferenceType epr;
try {
epr = AddressingUtils.createEndpointReference(
this.serviceAddress, key);
} catch (Exception exp) {
throw new ContextBrokerException("Error creating " +
"contextualization endpoint reference", exp);
}
return epr;
}
public String getID(EndpointReferenceType epr)
throws ContextBrokerException {
if (epr == null) {
throw new ContextBrokerException("epr is null");
}
if (epr.getProperties() == null) {
throw new ContextBrokerException("epr properties are null");
}
MessageElement key = epr.getProperties().get(this.getKeyTypeName());
if (key == null) {
throw new ContextBrokerException("contextualization " +
"resource key not present in EPR");
}
return key.getValue();
}
public static String getID(ResourceKey key)
throws ContextBrokerException {
if (key == null) {
throw new ContextBrokerException("key is null");
}
return (String)key.getValue();
}
}