/**
* 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.deployer;
import com.amazonaws.util.StringInputStream;
import org.apache.commons.jxpath.JXPathContext;
import org.apache.commons.jxpath.JXPathNotFoundException;
import org.cloudml.codecs.JsonCodec;
import org.cloudml.connectors.Connector;
import org.cloudml.connectors.ConnectorFactory;
import org.cloudml.core.*;
import org.cloudml.core.actions.StandardLibrary;
import org.cloudml.core.collections.InternalComponentInstanceGroup;
import org.cloudml.core.collections.ProvidedExecutionPlatformGroup;
import org.cloudml.core.collections.RelationshipInstanceGroup;
import org.cloudml.mrt.Coordinator;
import java.io.ByteArrayOutputStream;
import java.io.InputStream;
import java.io.OutputStream;
import java.io.UnsupportedEncodingException;
import java.net.InetAddress;
import java.net.UnknownHostException;
import java.util.*;
import java.util.logging.Level;
import java.util.logging.Logger;
/**
* Created by ferrynico on 02/12/2014.
*/
public class Scaler {
private static final Logger journal = Logger.getLogger(Scaler.class.getName());
protected Deployment currentModel;
protected Coordinator coordinator;
StandardLibrary lib = new StandardLibrary();
VMInstance ci;
protected CloudAppDeployer dep;
public Scaler(Deployment currentModel, Coordinator coordinator, CloudAppDeployer dep){
this.currentModel=currentModel;
this.coordinator=coordinator;
this.dep=dep;
}
protected VM findVMGenerated(String fromName, String extension){
for(VM v: currentModel.getComponents().onlyVMs()){
if(v.getName().contains(fromName) && v.getName().contains(extension)){
return v;
}
}
return null;
}
private VM createNewInstanceOfVMFromImage(VMInstance vmi){
VM existingVM=vmi.asExternal().asVM().getType();
//VM v=currentModel.getComponents().onlyVMs().firstNamed(existingVM.getName()+"-fromImage");
VM v = findVMGenerated(existingVM.getName(),"fromImage");
if(v == null){//in case a type for the snapshot has already been created
String name=lib.createUniqueComponentInstanceName(currentModel,existingVM);
v=new VM(name+"-fromImage",existingVM.getProvider());
v.setGroupName(existingVM.getGroupName());
v.setRegion(existingVM.getRegion());
v.setImageId("tempID");
v.setLocation(existingVM.getLocation());
v.setMinRam(existingVM.getMinRam());
v.setMinCores(existingVM.getMinCores());
v.setMinStorage(existingVM.getMinStorage());
v.setSecurityGroup(existingVM.getSecurityGroup());
v.setSshKey(existingVM.getSshKey());
v.setProviderSpecificTypeName(existingVM.getProviderSpecificTypeName());
v.setPrivateKey(existingVM.getPrivateKey());
v.setProvider(existingVM.getProvider());
ProvidedExecutionPlatformGroup pepg=new ProvidedExecutionPlatformGroup();
for(ProvidedExecutionPlatform pep: existingVM.getProvidedExecutionPlatforms()){
ArrayList<Property> pg=new ArrayList<Property>();
for(Property property: pep.getOffers()){
Property prop=new Property(property.getName());
prop.setValue(property.getValue());
pg.add(prop);
}
ProvidedExecutionPlatform p =new ProvidedExecutionPlatform(name+"-"+pep.getName(),pg);
pepg.add(p);
}
v.setProvidedExecutionPlatforms(pepg.toList());
currentModel.getComponents().add(v);
}
ci=lib.provision(currentModel,v).asExternal().asVM();
return v;
}
private Map<InternalComponentInstance, InternalComponentInstance> duplicateHostedGraph(Deployment d, VMInstance vmiSource,VMInstance vmiDestination){
//InternalComponentInstanceGroup icig= currentModel.getComponentInstances().onlyInternals().hostedOn(vmiSource);
StandardLibrary lib=new StandardLibrary();
return lib.replicateSubGraph(d, vmiSource, vmiDestination);
}
protected void manageDuplicatedRelationships(RelationshipInstanceGroup rig, Set<ComponentInstance> listOfAllComponentImpacted){
if(rig != null){
dep.configureWithRelationships(rig);
for(RelationshipInstance ri: rig){
listOfAllComponentImpacted.add(ri.getClientComponent());
listOfAllComponentImpacted.add(ri.getServerComponent());
}
}
}
protected void configureBindingOfImpactedComponents(Set<ComponentInstance> listOfAllComponentImpacted, Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph){
for(InternalComponentInstance ici: duplicatedGraph.values()){
for(ProvidedPortInstance ppi: ici.getProvidedPorts()){
RelationshipInstanceGroup rig=currentModel.getRelationshipInstances().whereEitherEndIs(ppi);
manageDuplicatedRelationships(rig, listOfAllComponentImpacted);
}
for(RequiredPortInstance rpi: ici.getRequiredPorts()){
RelationshipInstanceGroup rig=currentModel.getRelationshipInstances().whereEitherEndIs(rpi);
manageDuplicatedRelationships(rig, listOfAllComponentImpacted);
}
}
}
protected void configureImpactedComponents(Set<ComponentInstance> listOfAllComponentImpacted, Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph){
for(ComponentInstance ici: listOfAllComponentImpacted){
coordinator.updateStatusInternalComponent(ici.getName(), InternalComponentInstance.State.INSTALLED.toString(), CloudAppDeployer.class.getName());
if(ici.isInternal()){
Provider p=ici.asInternal().externalHost().asVM().getType().getProvider();
Connector c2=ConnectorFactory.createIaaSConnector(p);
for(Resource r: ici.getType().getResources()){
dep.configure(c2, ci.getType(), ici.asInternal().externalHost().asVM(), r.getConfigureCommand(),false);
}
c2.closeConnection();
}
coordinator.updateStatusInternalComponent(ici.getName(), InternalComponentInstance.State.CONFIGURED.toString(), CloudAppDeployer.class.getName());
}
}
protected void startImpactedComponents(Set<ComponentInstance> listOfAllComponentImpacted, Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph){
for(ComponentInstance ici: listOfAllComponentImpacted){
if(ici.isInternal()){
Provider p=ici.asInternal().externalHost().asVM().getType().getProvider();
Connector c2=ConnectorFactory.createIaaSConnector(p);
for(Resource r: ici.getType().getResources()){
dep.start(c2,ci.getType(),ici.asInternal().externalHost().asVM(),r.getStartCommand());
}
c2.closeConnection();
}
coordinator.updateStatusInternalComponent(ici.getName(), InternalComponentInstance.State.RUNNING.toString(), CloudAppDeployer.class.getName());
}
for(InternalComponentInstance ici: duplicatedGraph.values()){
coordinator.updateStatusInternalComponent(ici.getName(), InternalComponentInstance.State.RUNNING.toString(), CloudAppDeployer.class.getName());
}
}
public void scaleOut(VMInstance vmi, int n){
ArrayList<VM> newbies=new ArrayList<VM>();
ArrayList<Map<InternalComponentInstance, InternalComponentInstance>> duplicatedGraphs=new ArrayList<Map<InternalComponentInstance, InternalComponentInstance>>();
ArrayList<Thread> ts=new ArrayList<Thread>();
ArrayList<VMInstance> cis=new ArrayList<VMInstance>();
for(int i=0;i<n;i++) {
VM temp = findVMGenerated(vmi.getType().getName(),"fromImage");
VM v=createNewInstanceOfVMFromImage(vmi);
newbies.add(v);
Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph = duplicateHostedGraph(currentModel, vmi, ci);
duplicatedGraphs.add(duplicatedGraph);
cis.add(ci);
if (temp == null) {
Connector c = ConnectorFactory.createIaaSConnector(vmi.getType().getProvider());
String ID = "";
if(!vmi.getType().getProvider().getName().toLowerCase().equals("flexiant"))
ID=c.createImage(vmi);
else {
ID=c.createImage(vmi);
restartHostedComponents(vmi);
}
c.closeConnection();
v.setImageId(ID);
} else {
v.setImageId(temp.getImageId());
}
}
for(int i=0;i<n;i++) {
final Map<InternalComponentInstance, InternalComponentInstance> d=duplicatedGraphs.get(i);
final VM vm=newbies.get(i);
final String name=vmi.getName();
final VMInstance ci=cis.get(i);
ts.add(new Thread(){
public void run() {
//once this is done we can work in parallel
Connector c2 = ConnectorFactory.createIaaSConnector(vm.getProvider());
HashMap<String, Object> result = c2.createInstance(ci);
c2.closeConnection();
coordinator.updateStatusInternalComponent(ci.getName(), result.get("status").toString(), CloudAppDeployer.class.getName());
coordinator.updateStatus(name, ComponentInstance.State.RUNNING, CloudAppDeployer.class.getName());
coordinator.updateIP(ci.getName(),result.get("publicAddress").toString(),CloudAppDeployer.class.getName());
setAllEnvVarComponent(ci,currentModel);
//4. configure the new VM
//execute the configuration bindings
Set<ComponentInstance> listOfAllComponentImpacted= new HashSet<ComponentInstance>();
configureBindingOfImpactedComponents(listOfAllComponentImpacted,d);
//execute configure commands on the components
configureImpactedComponents(listOfAllComponentImpacted,d);
//execute start commands on the components
startImpactedComponents(listOfAllComponentImpacted, d);
//restart components on the VM scaled
restartHostedComponents(ci);
}
});
ts.get(i).start();
}
for(Thread t: ts){
try {
t.join();
} catch (InterruptedException e) {
e.printStackTrace();
}
}
journal.log(Level.INFO, ">> Multiple scaling completed!");
}
private void setAllEnvVarComponent(VMInstance ci, Deployment d){
Map<String, String> env = System.getenv();
String ip="";
String port="";
if(env.containsKey("MODACLOUDS_MONITORING_MANAGER_ENDPOINT_IP")
&& env.containsKey("MODACLOUDS_MONITORING_MANAGER_ENDPOINT_PORT")) {
ip = env.get("MODACLOUDS_MONITORING_MANAGER_ENDPOINT_IP");
port = env.get("MODACLOUDS_MONITORING_MANAGER_ENDPOINT_PORT");
}else if(env.containsKey("MODACLOUDS_TOWER4CLOUDS_MANAGER_ENDPOINT_IP") &&
env.containsKey("MODACLOUDS_TOWER4CLOUDS_MANAGER_ENDPOINT_PORT")){
ip = env.get("MODACLOUDS_TOWER4CLOUDS_MANAGER_ENDPOINT_IP");
port = env.get("MODACLOUDS_TOWER4CLOUDS_MANAGER_ENDPOINT_PORT");
}else if(env.containsKey("MODACLOUDS_TOWER4CLOUDS_MANAGER_PUBLIC_ENDPOINT_IP") &&
env.containsKey("MODACLOUDS_TOWER4CLOUDS_MANAGER_PUBLIC_ENDPOINT_PORT")){
ip = env.get("MODACLOUDS_TOWER4CLOUDS_MANAGER_PUBLIC_ENDPOINT_IP");
port = env.get("MODACLOUDS_TOWER4CLOUDS_MANAGER_PUBLIC_ENDPOINT_PORT");
}else{
try {
ip= InetAddress.getLocalHost().getHostAddress();
port="8170";
} catch (UnknownHostException e) {
e.printStackTrace();
}
}
String cmd="";
cmd+=setEnvVarCommand(ci, "MODACLOUDS_TOWER4CLOUDS_MANAGER_IP", ip);
cmd+=setEnvVarCommand(ci, "MODACLOUDS_TOWER4CLOUDS_MANAGER_PORT", port);
for(InternalComponentInstance ici: ci.hostedComponents()){
for(Property p : ici.getProperties()){
if(p.getName().startsWith("env:")){
cmd+=prepareSetEnv(d, ici, p);
}
}
InternalComponent ic = ici.getType();
for(Property p : ic.getProperties()){
if(p.getName().contains("env:")){
cmd+=prepareSetEnv(d, ici, p);
}
}
}
setEnvVar(ci,cmd);
}
//TODO: All this code is replicated and should be refactored in the deployer
private String setEnvVarCommand(VMInstance vmi, String varName, String value){
if (!vmi.getType().getOs().toLowerCase().contains("windows")) {
//String command="echo export "+varName+"="+value+" >> ~/.bashrc";
//jc.execCommand(vmi.getId(), command, "ubuntu", vmi.getType().getPrivateKey());
return "sudo sh -c 'echo export "+varName+"="+value+" >> /etc/environment';";
} else {
//TODO: should we do something for Windows as well?
}
return "";
}
private void setEnvVar(VMInstance vmi, String cmd){
if (!vmi.getType().getOs().toLowerCase().contains("windows")) {
//String command="echo export "+varName+"="+value+" >> ~/.bashrc";
Connector jc = ConnectorFactory.createIaaSConnector(vmi.getType().getProvider());
jc.execCommand(vmi.getId(), cmd, "ubuntu", vmi.getType().getPrivateKey());
jc.closeConnection();
} else {
//TODO: should we do something for Windows as well?
}
}
private String prepareSetEnv(Deployment d, InternalComponentInstance c, Property p){
String value="";
if(p.getValue().startsWith("$")){
if(p.getValue().equals("${this.host.id}")){
value=c.externalHost().asVM().getId();
}
if(p.getValue().equals("${this.host.name}")){
value=c.externalHost().getName();
}
if(p.getValue().equals("${this.host.type.name}")){
value=c.externalHost().getType().getName();
}
if(p.getValue().equals("${this.provider.id}")){
value=c.externalHost().asVM().getType().getProvider().getName();
}
if(p.getValue().equals("${this.name}") || p.getValue().equals("${this.id}")){
value=c.getName();
}
if(p.getValue().equals("${this.type.name}")){
value=c.getType().getName();
}
}else{
try{
JXPathContext jxpc = JXPathContext.newContext(d);
Object o=jxpc.getValue(p.getValue());
value=o.toString();
}catch(NullPointerException e){
journal.log(Level.INFO, ">> Environment variable cannot be defined, xpath expression not valid");
}
catch(JXPathNotFoundException e){
journal.log(Level.INFO, ">> Environment variable cannot be defined, xpath expression not valid");
}
}
if(!value.equals("")){
return setEnvVarCommand(c.externalHost().asVM(), p.getName().split(":")[1], value);
}
return "";
}
/**
* Method to scale out a VM within the same provider
* Create a snapshot of the VM and then configure the bindings
*
* @param vmi an instance of VM
*/
public void scaleOut(VMInstance vmi) {
Deployment tmp=currentModel.clone();
VM temp = findVMGenerated(vmi.getType().getName(),"fromImage");
//1. instantiate the new VM using the newly created snapshot
VM v=createNewInstanceOfVMFromImage(vmi);
//2. update the deployment model by cloning the PaaS and SaaS hosted on the replicated VM
Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph=duplicateHostedGraph(currentModel,vmi, ci);
//3. For synchronization purpose we provision once the model has been fully updated
if(temp == null){
Connector c = ConnectorFactory.createIaaSConnector(vmi.getType().getProvider());
String ID="";
if(!vmi.getType().getProvider().getName().toLowerCase().equals("flexiant"))
ID=c.createImage(vmi);
else{
ID=c.createImage(vmi);
restartHostedComponents(vmi);
}
c.closeConnection();
v.setImageId(ID);
}else{
v.setImageId(temp.getImageId());
}
Connector c2=ConnectorFactory.createIaaSConnector(v.getProvider());
HashMap<String,Object> result=c2.createInstance(ci);
c2.closeConnection();
coordinator.updateStatusInternalComponent(ci.getName(), result.get("status").toString(), CloudAppDeployer.class.getName());
coordinator.updateStatus(vmi.getName(), ComponentInstance.State.RUNNING, CloudAppDeployer.class.getName());
coordinator.updateIP(ci.getName(),result.get("publicAddress").toString(),CloudAppDeployer.class.getName());
dep.setAllEnvVarComponent(currentModel);
//4. configure the new VM
//execute the configuration bindings
Set<ComponentInstance> listOfAllComponentImpacted= new HashSet<ComponentInstance>();
configureBindingOfImpactedComponents(listOfAllComponentImpacted,duplicatedGraph);
//execute configure commands on the components
configureImpactedComponents(listOfAllComponentImpacted,duplicatedGraph);
//execute start commands on the components
startImpactedComponents(listOfAllComponentImpacted, duplicatedGraph);
//restart components on the VM scaled
restartHostedComponents(ci);
journal.log(Level.INFO, ">> Scaling completed!");
}
private ArrayList<InternalComponentInstance> allHostedComponents(ComponentInstance ci){
ArrayList<InternalComponentInstance> list=new ArrayList<InternalComponentInstance>();
InternalComponentInstanceGroup icig=ci.hostedComponents();
if(icig !=null){
list.addAll(icig);
for(InternalComponentInstance ici: icig){
list.addAll(allHostedComponents(ici));
}
}
return list;
}
protected void restartHostedComponents(VMInstance ci){
journal.log(Level.INFO, ">> Restarting Hosted components of: "+ci.getName());
for(InternalComponentInstance ici: allHostedComponents(ci)){
Provider p=ci.getType().getProvider();
Connector c2=ConnectorFactory.createIaaSConnector(p);
for(Resource r: ici.getType().getResources()){
dep.start(c2,ci.getType(),ci,r.getStartCommand());
}
c2.closeConnection();
}
}
private List<VM> vmFromAProvider(Provider p){
ArrayList<VM> result=new ArrayList<VM>();
for(VM v : currentModel.getComponents().onlyVMs()){
if(v.getProvider().getName().equals(p.getName())){
result.add(v);
}
}
return result;
}
private VM findSimilarVMFromProvider(VM sampleVM, Provider p){
VM selected=null;
List<VM> availablesVM=vmFromAProvider(p);
if(availablesVM.size() > 0){
selected=availablesVM.get(0);
for(VM v: availablesVM){
if((v.getMinRam() >= sampleVM.getMinRam()) && (v.getMinRam() < selected.getMinRam())){
selected=v;
}
}
}
return selected;
}
public Deployment scaleOut(ExternalComponentInstance eci, Provider provider){
if(eci.isVM()){
scaleOut(eci.asVM(),provider);
return currentModel;
}else{
Deployment targetModel=cloneCurrentModel();
ExternalComponentInstance eci2=targetModel.getComponentInstances().onlyExternals().firstNamed(eci.getName());
ExternalComponent ec=eci2.getType().asExternal();
ec.setProvider(provider);
eci2.setStatus(ComponentInstance.State.STOPPED);
dep.deploy(targetModel);
return targetModel;
}
}
private Deployment cloneCurrentModel(){
//need to clone the model
JsonCodec jsonCodec=new JsonCodec();
ByteArrayOutputStream baos=new ByteArrayOutputStream();
jsonCodec.save(currentModel,baos);
Deployment targetModel=new Deployment();
try {
String aString = new String(baos.toByteArray(),"UTF-8");
InputStream is = new StringInputStream(aString);
targetModel = (Deployment) jsonCodec.load(is);
} catch (UnsupportedEncodingException e) {
e.printStackTrace();
}
return targetModel;
}
/**
* To scale our a VM on another provider (kind of bursting)
*
* @param vmi the vm instance to scale out
* @param provider the provider where we want to burst
*/
public void scaleOut(VMInstance vmi, Provider provider) {
Connector c = ConnectorFactory.createIaaSConnector(provider);
StandardLibrary lib = new StandardLibrary();
//need to clone the model
Deployment targetModel=cloneCurrentModel();
VM existingVM=findSimilarVMFromProvider(vmi.asExternal().asVM().getType(), provider);
if(existingVM == null){
journal.log(Level.INFO, ">> No VM available for this provider!");
return;
}
//VM existingVM=vmi.asExternal().asVM().getType();
//VM v=currentModel.getComponents().onlyVMs().firstNamed(existingVM.getName()+"-scaled");
VM v = findVMGenerated(existingVM.getName(), "scaled");
if(v == null){//in case a type for the snapshot has already been created
String name=lib.createUniqueComponentInstanceName(targetModel,existingVM);
v=new VM(name+"-scaled",provider);
v.setGroupName(existingVM.getGroupName());
v.setRegion(existingVM.getRegion());
v.setImageId(existingVM.getImageId());
v.setLocation(existingVM.getLocation());
v.setMinRam(existingVM.getMinRam());
v.setMinCores(existingVM.getMinCores());
v.setMinStorage(existingVM.getMinStorage());
v.setSecurityGroup(existingVM.getSecurityGroup());
v.setSshKey(existingVM.getSshKey());
v.setPrivateKey(existingVM.getPrivateKey());
v.setProvider(provider);
ProvidedExecutionPlatformGroup pepg=new ProvidedExecutionPlatformGroup();
for(ProvidedExecutionPlatform pep: existingVM.getProvidedExecutionPlatforms()){
ArrayList<Property> pg=new ArrayList<Property>();
for(Property property: pep.getOffers()){
Property prop=new Property(property.getName());
prop.setValue(property.getValue());
pg.add(prop);
}
ProvidedExecutionPlatform p =new ProvidedExecutionPlatform(name+"-"+pep.getName(),pg);
pepg.add(p);
}
v.setProvidedExecutionPlatforms(pepg.toList());
targetModel.getComponents().add(v);
}
ci=lib.provision(targetModel,v).asExternal().asVM();
//2. update the deployment model by cloning the PaaS and SaaS hosted on the replicated VM
Map<InternalComponentInstance, InternalComponentInstance> duplicatedGraph=duplicateHostedGraph(targetModel,vmi, ci);
dep.deploy(targetModel);
}
}