/** * This file is part of CloudML [ http://cloudml.org ] * * Copyright (C) 2012 - SINTEF ICT * Contact: Franck Chauvel <franck.chauvel@sintef.no> * * Module: root * * CloudML is free software: you can redistribute it and/or modify * it under the terms of the GNU Lesser General Public License as * published by the Free Software Foundation, either version 3 of * the License, or (at your option) any later version. * * CloudML is distributed in the hope that it will be useful, but * WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU Lesser General Public License for more details. * * You should have received a copy of the GNU Lesser General * Public License along with CloudML. If not, see * <http://www.gnu.org/licenses/>. */ package org.cloudml.connectors; import com.google.common.collect.ImmutableSet; import com.google.inject.Module; import org.apache.commons.io.FileUtils; import java.io.File; import java.io.IOException; import java.util.ArrayList; import java.util.HashMap; import java.util.Map; import java.util.Set; import java.util.logging.Level; import java.util.logging.Logger; import org.cloudml.core.ComponentInstance; import org.cloudml.core.Property; import org.cloudml.core.VM; import org.cloudml.core.VMInstance; import org.jclouds.ContextBuilder; import org.jclouds.compute.ComputeService; import org.jclouds.compute.ComputeServiceContext; import org.jclouds.compute.RunNodesException; import org.jclouds.compute.RunScriptOnNodesException; import org.jclouds.compute.domain.*; import org.jclouds.domain.LoginCredentials; import org.jclouds.io.Payloads; import org.jclouds.logging.config.NullLoggingModule; import org.jclouds.openstack.nova.v2_0.NovaApi; import org.jclouds.openstack.nova.v2_0.compute.options.NovaTemplateOptions; import org.jclouds.openstack.nova.v2_0.features.ImageApi; import org.jclouds.openstack.nova.v2_0.features.ServerApi; import org.jclouds.ssh.SshClient; import org.jclouds.sshj.config.SshjSshClientModule; import static org.jclouds.compute.options.RunScriptOptions.Builder.blockOnComplete; import static org.jclouds.compute.options.RunScriptOptions.Builder.overrideLoginCredentials; import static org.jclouds.compute.predicates.NodePredicates.runningInGroup; import static org.jclouds.scriptbuilder.domain.Statements.exec; /** * Created by Nicolas Ferry on 08.05.14. */ public class OpenStackConnector implements Connector{ private static final Logger journal = Logger.getLogger(JCloudsConnector.class.getName()); private ComputeServiceContext computeContext; private ComputeService novaComputeService; private final String endpoint; private NovaApi serverApi; private HashMap<String,Object> runtimeInformation=new HashMap<String, Object>(); public OpenStackConnector(String endPoint,String provider,String login,String secretKey){ this.endpoint=endPoint; journal.log(Level.INFO, ">> Connecting to "+provider+" ..."); Iterable<Module> modules = ImmutableSet.<Module> of( new SshjSshClientModule(), new NullLoggingModule()); ContextBuilder builder = ContextBuilder.newBuilder(provider) .endpoint(endPoint) .credentials(login, secretKey) .modules(modules); journal.log(Level.INFO, ">> Authenticating ..."); computeContext=builder.buildView(ComputeServiceContext.class); novaComputeService= computeContext.getComputeService(); serverApi=builder.buildApi(NovaApi.class); } /** * Retrieve information about a VM * @param name name of a VM * @return data about a VM */ public ComputeMetadata getVMByName(String name){ for(ComputeMetadata n : novaComputeService.listNodes()){ if(n.getName() != null && n.getName().equals(name)) return n; } return null; } /** * retrieve the list of VMs * @return a list of information about each VM */ public Set<? extends ComputeMetadata> listOfVMs(){ return novaComputeService.listNodes(); } /** * Retrieve data about a VM * @param id id of a VM * @return Information about a VM */ public NodeMetadata getVMById(String id){ return novaComputeService.getNodeMetadata(id); } /** * Close the connection */ public void closeConnection(){ novaComputeService.getContext().close(); journal.log(Level.INFO, ">> Closing connection ..."); } /** * Create an image of the VM * @param vmi a VMInstance * @return id of the image */ public String createImage(VMInstance vmi){ String id=""; Image i = checkIfImageExist(vmi.getName()+"-image"); if(i == null){ NodeMetadata nm=getVMById(vmi.getId()); ServerApi serverApi1=serverApi.getServerApiForZone(vmi.getType().getRegion()); journal.log(Level.INFO, ">> Creating an image of VM: "+vmi.getName()+" "+nm.getId()+" :: "+vmi.getType().getRegion()); id=serverApi1.createImageFromServer(vmi.getName()+"-image",nm.getId().split("/")[1]); String status=""; while (!status.toLowerCase().equals("available")){ Image im=novaComputeService.getImage(vmi.getType().getRegion()+"/"+id); status=im.getStatus().name(); try { Thread.sleep(3000); } catch (InterruptedException e) { journal.log(Level.SEVERE, e.getMessage()); } } journal.log(Level.INFO, ">> Image created with ID: "+id); }else{ id=i.getId().split("/")[1]; } return id; } @Override public void startVM(VMInstance a) { journal.log(Level.INFO, ">> Starting VM: "+a.getName()); try{ novaComputeService.resumeNode(a.getId()); }catch(Exception exception){ journal.log(Level.INFO, ">> Check VM status!"); } } @Override public void stopVM(VMInstance a) { journal.log(Level.INFO, ">> Stopping VM: "+a.getName()); try{ novaComputeService.suspendNode(a.getId()); }catch(Exception exception){ journal.log(Level.INFO, ">> Check VM status!"); } } /** * Create a snapshot of the volume attached to the VM * @param vmi * @return id of the snapshot */ public String createSnapshot(VMInstance vmi){ return createImage(vmi); } /** * Check if an image exist * @param imageID the id of the image * @return */ public Boolean imageIsAvailable(String imageID){ if(novaComputeService.getImage(imageID) != null) return true; return false; } /** * Prepare the credential builder * @param login * @param key * @return */ private org.jclouds.domain.LoginCredentials.Builder initCredentials(String login, String key){ String contentKey; org.jclouds.domain.LoginCredentials.Builder b= LoginCredentials.builder(); File f = new File(key); if(f.exists() && !f.isDirectory()) { try { contentKey = FileUtils.readFileToString(new File(key)); b.user(login); b.noPassword(); b.privateKey(contentKey); } catch (IOException e) { journal.log(Level.SEVERE, e.getMessage()); } }else{ b.user(login); b.noPassword(); b.privateKey(key); } return b; } /** * Upload a file on a selected VM * @param sourcePath path to the file to be uploaded * @param destinationPath path to the file to be created * @param VMId Id of a VM * @param login user login * @param key key to connect */ public void uploadFile(String sourcePath, String destinationPath, String VMId, String login, String key){ /*org.jclouds.domain.LoginCredentials.Builder b=initCredentials(login, key); SshClient ssh = novaComputeService.getContext().utils().sshForNode().apply(NodeMetadataBuilder.fromNodeMetadata(getVMById(VMId)).credentials(b.build()).build()); try { ssh.connect(); ssh.put(destinationPath, Payloads.newPayload(new File(sourcePath))); } finally { if (ssh != null) ssh.disconnect(); journal.log(Level.INFO, ">> File uploaded!"); }*/ NodeMetadata n = novaComputeService.getNodeMetadata(VMId); if(n != null) { SSHConnector sc = new SSHConnector(key, login, n.getPublicAddresses().iterator().next()); sc.upload(sourcePath, destinationPath); } } /** * Execute a command on a group of VMs * @param group name of the group * @param command the command to be executed * @param login username * @param key sshkey * @throws RunScriptOnNodesException */ public void execCommandInGroup(String group, String command, String login, String key) throws RunScriptOnNodesException { journal.log(Level.INFO, ">> executing command..."); journal.log(Level.INFO, ">> "+ command); org.jclouds.domain.LoginCredentials.Builder b=initCredentials(login, key); Map<? extends NodeMetadata, ExecResponse> responses = novaComputeService.runScriptOnNodesMatching( runningInGroup(group), exec(command), overrideLoginCredentials(b.build()) .runAsRoot(false) .wrapInInitScript(false));// run command directly for(Map.Entry<? extends NodeMetadata, ExecResponse> r : responses.entrySet()) journal.log(Level.INFO, ">> "+r.getValue()); } /** * Execute a command on a specified node * @param id id of the node * @param command the command to be executed * @param login username * @param key sshkey for connection */ public void execCommand(String id, String command, String login, String key){ journal.log(Level.INFO, ">> executing command..."); journal.log(Level.INFO, ">> "+ command); /*org.jclouds.domain.LoginCredentials.Builder b=initCredentials(login, key); ExecResponse response = novaComputeService.runScriptOnNode( id, exec(command), overrideLoginCredentials(b.build()) .runAsRoot(false) .wrapInInitScript(false));// run command directly journal.log(Level.INFO, ">> "+response.getOutput()); */ NodeMetadata n = novaComputeService.getNodeMetadata(id); if(n != null) { SSHConnector sc = new SSHConnector(key, login, n.getPublicAddresses().iterator().next()); sc.execCommandSsh(command); } } /** * Update the runtime metadata of a VM if already deployed * @param a description of a VM */ public void updateVMMetadata(VMInstance a){ ComputeMetadata cm= getVMByName(a.getName()); if(cm != null){ if(((NodeMetadata)cm).getPublicAddresses().size() > 0) a.setPublicAddress(((NodeMetadata)cm).getPublicAddresses().iterator().next()); a.setId(cm.getId()); a.setStatus(ComponentInstance.State.RUNNING); } } /** * retrieve the list of images avaialbels * @return the list of available images */ public Set<? extends Image> listOfImages(){ return novaComputeService.listImages(); } /** * Search for an image with that name * @param name name if the image * @return an Image */ public Image checkIfImageExist(String name){ for(Image i : listOfImages()){ if(i.getName().equals(name)) return i; } return null; } /** * Provision a VM * @param a description of the VM to be created * @return */ public HashMap<String,Object> createInstance(VMInstance a){ ComponentInstance.State state = ComponentInstance.State.UNRECOGNIZED; VM vm = a.getType(); ComputeMetadata cm= getVMByName(a.getName()); /* UPDATE THE MODEL */ if(cm != null){ updateVMMetadata(a); state=ComponentInstance.State.RUNNING; }else{ Template template=null; NodeMetadata nodeInstance = null; String groupName="cloudml-instance"; if(!vm.getGroupName().equals("")) groupName= vm.getGroupName(); TemplateBuilder templateBuilder = novaComputeService.templateBuilder(); if(vm.getRegion() == null) journal.log(Level.INFO, ">> No Region " ); if(!vm.getImageId().equals("")){ String fullId=""; if((vm.getRegion() != null) && (!vm.getRegion().equals("")) && (!vm.getImageId().contains("/"))){ journal.log(Level.INFO, ">> Region: " +vm.getRegion()); fullId=vm.getRegion()+"/"+vm.getImageId(); }else{ fullId=vm.getImageId(); } if(imageIsAvailable(fullId)){ templateBuilder.imageId(fullId); }else{ journal.log(Level.INFO, ">> There is no image with the following ID: " +fullId); } } journal.log(Level.INFO, ">> Provisioning a VM ..."); if (vm.getMinRam() > 0) templateBuilder.minRam(vm.getMinRam()); if (vm.getMinCores() > 0) templateBuilder.minCores(vm.getMinCores()); String region=""; if(vm.getRegion() != null){ if(!vm.getRegion().equals("")){ region=vm.getRegion()+"/"; } } if (!vm.getLocation().equals("")) templateBuilder.locationId(region+vm.getLocation()); if (!vm.getOs().equals("")) templateBuilder.imageDescriptionMatches(vm.getOs()); else templateBuilder.osFamily(OsFamily.UBUNTU); templateBuilder.os64Bit(vm.getIs64os()); template = templateBuilder.build(); journal.log(Level.INFO, ">> VM type: " + template.getHardware().getId() + " on location: " + template.getLocation().getId()); a.getProperties().add(new Property("ProviderInstanceType", template.getHardware().getId())); a.getProperties().add(new Property("location", template.getLocation().getId())); template.getOptions().as(NovaTemplateOptions.class).keyPairName(vm.getSshKey()); template.getOptions().as(NovaTemplateOptions.class).securityGroups(vm.getSecurityGroup()); template.getOptions().as(NovaTemplateOptions.class).overrideLoginUser(a.getName()); ArrayList<String> names=new ArrayList<String>(); names.add(a.getName()); template.getOptions().as(NovaTemplateOptions.class).nodeNames(names); ArrayList<String> sgNames=new ArrayList<String>(); sgNames.add(vm.getSecurityGroup()); template.getOptions().as(NovaTemplateOptions.class).securityGroupNames(sgNames); template.getOptions().blockUntilRunning(true); try { Set<? extends NodeMetadata> nodes = novaComputeService.createNodesInGroup(groupName, 1, template); nodeInstance = nodes.iterator().next(); journal.log(Level.INFO, ">> Running VM: "+nodeInstance.getName()+" Id: "+ nodeInstance.getId() +" with public address: "+nodeInstance.getPublicAddresses() + " on OS:"+nodeInstance.getOperatingSystem()+ " " + nodeInstance.getCredentials().identity+":"+nodeInstance.getCredentials().getUser()+":"+nodeInstance.getCredentials().getPrivateKey()); } catch (RunNodesException e) { journal.log(Level.SEVERE, e.getMessage()); //a.setStatusAsError(); state = ComponentInstance.State.ERROR; } if(nodeInstance.getPublicAddresses().iterator().hasNext()){ //a.setPublicAddress(nodeInstance.getPublicAddresses().iterator().next()); runtimeInformation.put("publicAddress",nodeInstance.getPublicAddresses().iterator().next()); }else{ //a.setPublicAddress(nodeInstance.getPrivateAddresses().iterator().next()); runtimeInformation.put("publicAddress",nodeInstance.getPrivateAddresses().iterator().next()); } if(nodeInstance.getHardware().getProcessors().iterator().hasNext()) a.setCore((int)nodeInstance.getHardware().getProcessors().iterator().next().getCores()); a.setId(nodeInstance.getId()); a.getProperties().add(new Property("ProviderSpecificType", nodeInstance.getHardware().getId())); a.setProviderSpecificType(template.getHardware().getId()); state = ComponentInstance.State.RUNNING; } runtimeInformation.put("status",state); return runtimeInformation; } /** * Terminate a specified VM * @param id id of the VM */ public void destroyVM(String id){ novaComputeService.destroyNode(id); } }