package se.kth.karamel.backend.launcher.occi; import cz.cesnet.cloud.occi.Model; import cz.cesnet.cloud.occi.api.Client; import cz.cesnet.cloud.occi.api.EntityBuilder; import cz.cesnet.cloud.occi.api.exception.CommunicationException; import cz.cesnet.cloud.occi.api.exception.EntityBuildingException; import cz.cesnet.cloud.occi.api.http.HTTPClient; import cz.cesnet.cloud.occi.api.http.auth.HTTPAuthentication; import cz.cesnet.cloud.occi.api.http.auth.VOMSAuthentication; import cz.cesnet.cloud.occi.core.Entity; import cz.cesnet.cloud.occi.core.Link; import cz.cesnet.cloud.occi.core.Resource; import cz.cesnet.cloud.occi.exception.AmbiguousIdentifierException; import cz.cesnet.cloud.occi.exception.InvalidAttributeValueException; import cz.cesnet.cloud.occi.infrastructure.Compute; import cz.cesnet.cloud.occi.infrastructure.IPNetworkInterface; import cz.cesnet.cloud.occi.infrastructure.NetworkInterface; import java.io.File; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.ArrayList; import se.kth.karamel.backend.launcher.Launcher; import se.kth.karamel.backend.running.model.ClusterRuntime; import se.kth.karamel.common.clusterdef.json.JsonCluster; import se.kth.karamel.common.exception.InvalidOcciCredentialsException; import se.kth.karamel.common.exception.KaramelException; import se.kth.karamel.common.util.Confs; import se.kth.karamel.common.util.OcciCredentials; import se.kth.karamel.common.util.SshKeyPair; import java.util.List; import java.util.Set; import org.apache.log4j.Logger; import se.kth.karamel.backend.converter.UserClusterDataExtractor; import se.kth.karamel.backend.running.model.GroupRuntime; import se.kth.karamel.backend.running.model.MachineRuntime; import se.kth.karamel.common.clusterdef.Occi; import se.kth.karamel.common.clusterdef.Provider; import se.kth.karamel.common.clusterdef.json.JsonGroup; import se.kth.karamel.common.util.Settings; /** * Created by Mamut on 2016-1-19. * Class required to launch machines in OCCI compatible clouds */ public final class OcciLauncher extends Launcher{ private static final int DEFAULT_SSH_PORT = 22; private static final Logger logger = Logger.getLogger(OcciLauncher.class); public final OcciContext context; private static boolean TESTING = true; private final SshKeyPair sshKeyPair; /** * * @param occiContext * @param sshKeyPair */ public OcciLauncher(OcciContext occiContext, SshKeyPair sshKeyPair) { this.context = occiContext; this.sshKeyPair = sshKeyPair; } /** * Dummy validate function * @param occiCredentials * @return * @throws InvalidOcciCredentialsException */ public static OcciContext validateCredentials(OcciCredentials occiCredentials) throws InvalidOcciCredentialsException { OcciContext context = new OcciContext(occiCredentials); //basic path and file existence check File f = new File(context.getOcciCredentials().getUserCertificatePath()); if((!f.exists() || f.isDirectory())) { logger.info(String.format("Certificate does not exist in provided path " + context.getOcciCredentials().getUserCertificatePath())); throw new InvalidOcciCredentialsException("Certificate does not exist in provided path " + context.getOcciCredentials().getUserCertificatePath()); } f = new File(context.getOcciCredentials().getSystemCertDir()); if ((!f.exists() || !f.isDirectory())) { logger.info(String.format("Certificate folder does not exist in provided path " + context.getOcciCredentials().getSystemCertDir())); throw new InvalidOcciCredentialsException("Certificate folder does not exist in provided path " + context.getOcciCredentials().getSystemCertDir()); } return context; } /** * Function loads settings stored on local store from previous usage * @param confs * @return */ public static OcciCredentials readCredentials(Confs confs) { String userCertificatePath = confs.getProperty("occi.user.certificate.path"); String systemCertDir = confs.getProperty("occi.certificate.dir"); if (userCertificatePath == null || userCertificatePath.isEmpty()) { userCertificatePath = Settings.OCCI_USER_CERTIFICATE_PATH; } if (systemCertDir == null || systemCertDir.isEmpty()) { systemCertDir = Settings.OCCI_CERTIFICATE_DIR; } OcciCredentials occiCredentials = null; if (userCertificatePath != null && !userCertificatePath.isEmpty() && systemCertDir != null && !systemCertDir.isEmpty()) { occiCredentials = new OcciCredentials(); occiCredentials.setUserCertificatePath(userCertificatePath); occiCredentials.setSystemCertDir(systemCertDir); } return occiCredentials; } @Override public void cleanup(JsonCluster definition, ClusterRuntime runtime) throws KaramelException { List<GroupRuntime> groups = runtime.getGroups(); for (GroupRuntime group : groups) { group.getCluster().resolveFailures(); Provider provider = UserClusterDataExtractor.getGroupProvider(definition, group.getName()); Occi occi = (Occi) provider; if (provider instanceof Occi) { //List all VM ids - compute resource URIs List<String> allOcciVmsIds = new ArrayList<>();; for (MachineRuntime machine : group.getMachines()) { if (machine.getName() != null) { //compute resource location URI (vms to delete) allOcciVmsIds.add(machine.getVmId()); } } try { //Cleanup Vms logger.info(String.format("Killing following machines with ids: " + "%s", allOcciVmsIds)); HTTPAuthentication authentication = new VOMSAuthentication(context.getOcciCredentials() .getUserCertificatePath()); //set custom certificates if needed authentication.setCAPath(context.getOcciCredentials().getSystemCertDir()); Client client = new HTTPClient(URI.create(occi.getOcciEndpoint()), authentication); //Destroy Vms in Occi for (String VmId : allOcciVmsIds) { logger.info(String.format("Deleting compute : " + VmId)); boolean done = client.delete(URI.create(VmId)); if (done) { logger.info(String.format("Deleted.")); } else { logger.info(String.format("Not deleted.")); } } } catch(Throwable ex) { logger.info(String.format(ex.toString())); throw new KaramelException("Occi Cleanup fail", ex); } } } } /** * Function tries to create VM on Occi cloud defined by client and occiImage * @param client * @param vmName * @param occiImage * @param occiImageSize * @param SSHPublicKey * @return * @throws InvalidAttributeValueException * @throws EntityBuildingException * @throws AmbiguousIdentifierException * @throws se.kth.karamel.common.exception.KaramelException */ public URI createCompute(Client client, String vmName, String occiImage, String occiImageSize, String SSHPublicKey ) throws AmbiguousIdentifierException, KaramelException, InvalidAttributeValueException, EntityBuildingException { Model model; EntityBuilder eb; URI location = null; //GET MODEL AND ENTITYBUILDER model = client.getModel(); //logger.info("Model Occi: " + String.format(model.toString())); eb = new EntityBuilder(model); Compute compute = eb.getCompute(); compute.setTitle(vmName); compute.setHostname(vmName); try{ logger.info(String.format("occi Image:" + occiImage)); //compute.addMixin(model.findMixin(occiImage)); compute.addMixin(model.findMixin(URI.create(occiImage))); } catch (Throwable ex) { logger.error(String.format("Occi image mixin " + occiImage + "not found (did you chose existing one?")); throw new KaramelException("occi image mixin not found (did you chose existing one?)", ex); } try{ logger.info(String.format("occi ImageSize:" + occiImageSize)); //compute.addMixin(model.findMixin(occiImageSize)); compute.addMixin(model.findMixin(URI.create(occiImageSize))); } catch (Throwable ex) { logger.error(String.format("Occi image mixin " + occiImageSize + "not found (did you chose existing one?")); throw new KaramelException("occi image size mixin not found (did you chose existing one?)", ex); } //Pub key addition to VM for root ssh compute.addMixin(model.findMixin("public_key")); compute.addAttribute("org.openstack.credentials.publickey.name", "my_key"); compute.addAttribute("org.openstack.credentials.publickey.data", this.sshKeyPair.getPublicKey()); logger.info(String.format("Creating VM :" + vmName)); try{ location = client.create(compute); } catch (CommunicationException ex) { logger.info(String.format("OCCI create compute failed")); throw new KaramelException("OCCI create compute failed", ex); } return location; } /** * Get compute resource public IPs * @param client * @param location * @return Returns public IP addresses from compute resource (VM) specified by URI location * @throws CommunicationException * @throws UnknownHostException */ public ArrayList<String> getPublicIPs (Client client, URI location) throws CommunicationException, UnknownHostException{ //Get private and public IP addresses ArrayList<String> publicIps = new ArrayList(); List<Entity> entities = client.describe(location); for (Entity entity : entities) { Resource resource = (Resource) entity; Set<Link> links = resource.getLinks(NetworkInterface.TERM_DEFAULT); for (Link link : links) { String address = link.getValue(IPNetworkInterface.ADDRESS_ATTRIBUTE_NAME); if (!InetAddress.getByName(address).isSiteLocalAddress()) { publicIps.add(address); } } } return publicIps; } @Override public String forkGroup(JsonCluster definition, ClusterRuntime runtime, String groupName) throws KaramelException { return groupName; } @Override public List<MachineRuntime> forkMachines(JsonCluster definition, ClusterRuntime runtime, String groupName) throws KaramelException { Occi occi = (Occi) UserClusterDataExtractor.getGroupProvider(definition, groupName); JsonGroup definedGroup = UserClusterDataExtractor.findGroup(definition, groupName); GroupRuntime group = UserClusterDataExtractor.findGroup(runtime, groupName); //log details logger.info(String.format("Occi ForkMachines ...")); logger.info(String.format("Provider of groupName %s is occi.", groupName)); logger.info(String.format("Cert path '%s' ...", context.getOcciCredentials().getUserCertificatePath())); logger.info(String.format("Cert Dir path '%s' ...", context.getOcciCredentials().getSystemCertDir())); logger.info(String.format("Occi Image "+ occi.getOcciImage())); logger.info(String.format("Occi ImageSize "+ occi.getOcciImageSize())); logger.info(String.format("Occi Endpoint "+ occi.getOcciEndpoint())); logger.info(String.format("Occi ForkMachines Cert path: " + context.getOcciCredentials().getUserCertificatePath())); logger.info(String.format("Occi ForkMachines Cert Dir path: " + context.getOcciCredentials().getSystemCertDir())); logger.info(String.format("Occi ForkMachines Machine Num in Group: " + definedGroup.getSize())); try { HTTPAuthentication authentication = new VOMSAuthentication(context.getOcciCredentials() .getUserCertificatePath()); //set custom certificates authentication.setCAPath(context.getOcciCredentials().getSystemCertDir()); logger.info(String.format("Connecting to endpoint " + occi.getOcciEndpoint())); Client client = new HTTPClient(URI.create(occi.getOcciEndpoint()), authentication); //connect client client.connect(); //FORK THE MACHINES IN GROUP int numForked = 0; final int numMachines = definedGroup.getSize(); List<MachineRuntime> machines = new ArrayList<>(); URI location; while (numForked < numMachines) { //CREATE COMPUTES and store their URI location String vmName = groupName + numForked; location = this.createCompute(client, vmName, occi.getOcciImage(), occi.getOcciImageSize(), this.sshKeyPair.getPublicKey()); //Construct machine runtime list MachineRuntime machine = new MachineRuntime(group); machine.setMachineType("occi/" + occi.getOcciEndpoint() + "/" + occi.getOcciImage()); ArrayList<String> publicIps = this.getPublicIPs(client, location); machine.setPublicIp(publicIps.isEmpty() ? "" : publicIps.get(0)); machine.setPrivateIp(""); logger.info(String.format("Public IP of " + vmName + " is " + (publicIps.isEmpty() ? "" : publicIps.get(0)))); machine.setVmId(location.toString()); machine.setName(vmName); machine.setSshPort(DEFAULT_SSH_PORT); machine.setSshUser(occi.getUsername()); logger.info(String.format("VM username: " + occi.getUsername())); machines.add(machine); numForked++; } return machines; } catch(Throwable ex) { logger.info(String.format(ex.toString())); throw new KaramelException("Occi ForkMachines ", ex); } } }