/*******************************************************************************
* Copyright 2012 Urbancode, Inc
*
* 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 com.urbancode.terraform.tasks.vmware.events;
import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.io.InputStream;
import java.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import org.apache.commons.io.FileUtils;
import org.apache.log4j.Logger;
import com.urbancode.terraform.tasks.common.util.IOUtil;
import com.urbancode.terraform.tasks.vmware.CloneTask;
import com.urbancode.terraform.tasks.vmware.EnvironmentTaskVmware;
import com.urbancode.terraform.tasks.vmware.PortRangeTask;
import com.urbancode.terraform.tasks.vmware.PostCreateTask;
import com.urbancode.terraform.tasks.vmware.RouterConfigPostCreateTask;
import com.urbancode.terraform.tasks.vmware.SecurityGroupRefTask;
import com.urbancode.terraform.tasks.vmware.SecurityGroupTask;
import com.urbancode.terraform.tasks.vmware.util.GlobalIpAddressPool;
import com.urbancode.terraform.tasks.vmware.util.Ip4;
import com.urbancode.terraform.tasks.vmware.util.VirtualHost;
import com.urbancode.x2o.tasks.ExtensionTask;
import com.urbancode.x2o.tasks.SubTask;
import com.vmware.vim25.mo.VirtualMachine;
public class CloneVmCreatedEventListener extends ExtensionTask implements TaskEventListener {
//**********************************************************************************************
// CLASS
//**********************************************************************************************
static private final Logger log = Logger.getLogger(CloneVmCreatedEventListener.class);
//**********************************************************************************************
// INSTANCE
//**********************************************************************************************
private EnvironmentTaskVmware environment;
private CloneTask routerTask;
private VirtualMachine router;
private String routerUser = null;
private String routerPassword = null;
private String gateway = null;
private CloneTask instanceTask;
private VirtualMachine instance;
private List<SecurityGroupTask> securityGroups = new ArrayList<SecurityGroupTask>();
private String hostNetworkIp;
private String privateIp;
int virtualInterfaceNum = 0;
private int portOffset = 10000;
String tempConfDir;
//----------------------------------------------------------------------------------------------
public CloneVmCreatedEventListener() {
}
//----------------------------------------------------------------------------------------------
public CloneVmCreatedEventListener(CloneTask routerTask) {
this.environment = routerTask.fetchEnvironment();
this.routerTask = routerTask;
this.tempConfDir = System.getenv("TERRAFORM_HOME") +
File.separator + "temp" + "-" + environment.fetchSuffix() + File.separator;
}
//----------------------------------------------------------------------------------------------
public String getGateway() {
return gateway;
}
//----------------------------------------------------------------------------------------------
public void setGateway(String gateway) {
this.gateway = gateway;
}
//----------------------------------------------------------------------------------------------
@Override
public void setValues(CloneTask cloneTask) {
this.environment = cloneTask.fetchEnvironment();
this.routerTask = cloneTask;
this.routerUser = routerTask.getUser();
this.routerPassword = routerTask.fetchPassword();
this.tempConfDir = System.getenv("TERRAFORM_HOME") +
File.separator + "temp" + "-" + environment.fetchSuffix() + File.separator;
}
//----------------------------------------------------------------------------------------------
@Override
public void handleEvent(TaskEvent event) {
this.tempConfDir = System.getenv("TERRAFORM_HOME") +
File.separator + "temp" + "-" + environment.fetchSuffix() + File.separator;
SubTask subTask = event.getSubTask();
CloneTask cloneTask = null;
//for configuring non-router clones only
if (subTask instanceof CloneTask) {
cloneTask = (CloneTask) subTask;
if (!subTask.equals(routerTask)) {
routerUser = routerTask.getUser();
routerPassword = routerTask.fetchPassword();
try {
cloneTask.powerOnVm();
}
catch (Exception e) {
log.warn("Exception while powering on VM " + cloneTask.getInstanceName(), e);
}
if (cloneTask.getAssignHostIp() || cloneTask.getAssignPrivateIpOnly()) {
try {
//update VMs and security group
this.instanceTask = cloneTask;
for (SecurityGroupRefTask sgr : instanceTask.getSecurityGroupRefs()) {
this.securityGroups.add(sgr.fetchSecurityGroup());
}
this.router = routerTask.fetchVm();
this.instance = instanceTask.fetchVm();
//get private ip
VirtualHost host = environment.fetchVirtualHost();
host.waitForIp(instance);
privateIp = instance.getGuest().getIpAddress();
log.info("configuring private networking for VM " + cloneTask.getInstanceName());
if (cloneTask.getAssignPrivateIpOnly()) {
try {
//update the iptables in the router
addInstanceToIpTables(null, privateIp);
runCommand(routerUser, routerPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "stop");
runCommand(routerUser, routerPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "start");
cloneTask.setPrivateIp(privateIp);
}
catch (IOException e) {
log.warn("IOException while configuring networking (probably invalid file path)", e);
}
catch (InterruptedException e) {
log.warn("InterruptedException while configuring networking", e);
}
}
else {
//get host ip
Ip4 ip = GlobalIpAddressPool.getInstance().allocateIp();
this.hostNetworkIp = ip.toString();
configureNetworking();
}
}
catch (RemoteException e) {
log.warn("RemoteException while waiting for IP address", e);
}
catch (InterruptedException e) {
log.warn("InterruptedException while waiting for IP address", e);
}
}
}
}
}
//----------------------------------------------------------------------------------------------
public void configureNetworking() {
try {
//update interfaces, then bring interface down and up
addNewEntryToInterfaces("" + virtualInterfaceNum);
//update the iptables in the router
addInstanceToIpTables(hostNetworkIp, privateIp);
virtualInterfaceNum++;
instanceTask.setPublicIp(hostNetworkIp);
instanceTask.setPrivateIp(privateIp);
}
catch (IOException e) {
log.warn("IOException while configuring networking (probably invalid file path)", e);
}
catch (InterruptedException e) {
log.warn("InterruptedException while configuring networking", e);
}
}
//----------------------------------------------------------------------------------------------
private void addInstanceToIpTables(String networkIp, String privateIp)
throws IOException, InterruptedException {
String result = null;
String hostIpTablesPath = this.tempConfDir + "iptables.conf";
String guestIpTablesPath = "/etc/iptables.conf";
copyFileFromGuestToHost(guestIpTablesPath, hostIpTablesPath);
String iptables = FileUtils.readFileToString(new File(hostIpTablesPath));
String commentString = "#Instance Prerouting Tables";
//no public IP will be assigned
if(networkIp == null) {
String ipTablesString = commentString;
for (SecurityGroupTask securityGroup : this.securityGroups) {
for (PortRangeTask prt : securityGroup.getPortRanges()) {
ipTablesString = ipTablesString + "\n" +
createIpTablesStringForVm(privateIp, prt.getFirstPort(), prt.getLastPort());
}
}
result = iptables.replace(commentString, ipTablesString);
}
else {
if (this.securityGroups.isEmpty()) {
String ipTablesString = commentString + "\n" + createDefaultIpTablesStringForVm(networkIp, privateIp);
result = iptables.replace(commentString, ipTablesString);
}
else {
String ipTablesString = commentString;
for (SecurityGroupTask securityGroup : this.securityGroups) {
for (PortRangeTask prt : securityGroup.getPortRanges()) {
ipTablesString = ipTablesString + "\n" +
createIpTablesStringForVm(networkIp, privateIp, prt.getFirstPort(), prt.getLastPort());
}
}
result = iptables.replace(commentString, ipTablesString);
}
}
//trailing newline is necessary for iptables (commons-io removes it when file is read)
iptables = iptables + "\n";
if (result== null || result.equalsIgnoreCase(iptables)) {
throw new IOException();
}
FileWriter out = new FileWriter(hostIpTablesPath, false);
out.write(result);
out.close();
copyFileFromHostToGuest(hostIpTablesPath, guestIpTablesPath);
runCommand(routerUser, routerPassword, "runProgramInGuest", "/bin/sh", "-c",
"\"/sbin/iptables-restore </etc/iptables.conf\"");
}
//----------------------------------------------------------------------------------------------
private void addNewEntryToInterfaces(String ifaceName)
throws IOException, InterruptedException {
String result = null;
String hostInterfacesPath = this.tempConfDir + "interfaces";
String guestInterfacesPath = "/etc/network/interfaces";
log.debug("host interfaces path: " + hostInterfacesPath);
copyFileFromGuestToHost(guestInterfacesPath, hostInterfacesPath);
String interfaces = FileUtils.readFileToString(new File(hostInterfacesPath));
String commentString = "#Insert New Interfaces";
String interfacesString = commentString + "\n" + createInterfaceString(hostNetworkIp, ifaceName);
result = interfaces.replace(commentString, interfacesString);
if (result== null || result.equalsIgnoreCase(interfaces)) {
throw new IOException();
}
FileWriter out = new FileWriter(hostInterfacesPath, false);
out.write(result);
out.close();
copyFileFromHostToGuest(hostInterfacesPath, guestInterfacesPath);
//restart the network
runCommand(routerUser, routerPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "stop");
runCommand(routerUser, routerPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "start");
}
//----------------------------------------------------------------------------------------------
private String createIpTablesStringForVm(String privateIp, int beginPort, int endPort) {
String routerIp = fetchRouterIp();
//example string
//-A PREROUTING -d 10.15.50.1/32 -p tcp -m tcp --dport 10022 -j DNAT --to-destination 192.168.0.2:22
String result = "";
//dport command has colon delimiter while to-destination command has hyphen delimiter
String dPorts;
String ports;
int beginWithOffset = beginPort + portOffset;
int endWithOffset = endPort + portOffset;
if (beginPort == endPort) {
dPorts = "" + beginWithOffset;
ports = "" + beginPort;
}
else {
dPorts = "" + beginWithOffset + ":" + endWithOffset;
ports = "" + beginPort + "-" + endPort;
}
result = result + "-A PREROUTING -d " + routerIp +
"/32 -p tcp -m tcp --dport " + dPorts + " -j DNAT --to-destination " + privateIp + ":" + ports;
return result;
}
//----------------------------------------------------------------------------------------------
private String createIpTablesStringForVm(String networkIp, String privateIp, int beginPort, int endPort) {
//example string
//-A PREROUTING -d 10.15.50.2/32 -p tcp -m tcp --dport 22 -j DNAT --to-destination 192.168.0.2:22
String result = "";
//dport command has colon delimiter while to-destination command has hyphen delimiter
String dPorts;
String ports;
if (beginPort == endPort) {
dPorts = "" + beginPort;
ports = "" + beginPort;
}
else {
dPorts = "" + beginPort + ":" + endPort;
ports = "" + beginPort + "-" + endPort;
}
result = result + "-A PREROUTING -d " + networkIp +
"/32 -p tcp -m tcp --dport " + dPorts + " -j DNAT --to-destination " + privateIp + ":" + ports;
return result;
}
//----------------------------------------------------------------------------------------------
private String createDefaultIpTablesStringForVm (String networkIp, String privateIp) {
//example string
//-A PREROUTING -d 10.15.50.2/32 -p tcp -m tcp --dport 22 -j DNAT --to-destination 192.168.0.2:22
String result = "";
result = result + "-A PREROUTING -d " + networkIp +
"/32 -p tcp -m tcp --dport 22 -j DNAT --to-destination " + privateIp + ":22" + "\n";
result = result + "-A PREROUTING -d " + networkIp +
"/32 -p tcp -m tcp --dport 80 -j DNAT --to-destination " + privateIp + ":80" + "\n";
result = result + "-A PREROUTING -d " + networkIp +
"/32 -p tcp -m tcp --dport 3306 -j DNAT --to-destination " + privateIp + ":3306";
return result;
}
//----------------------------------------------------------------------------------------------
private String createInterfaceString(String ip, String ifaceName) {
String result;
if(gateway == null | "".equals(gateway)) {
log.warn("Gateway is null or emptyand was not set properly.");
}
result = "auto eth0:" + ifaceName + "\n";
result = result + "iface eth0:" + ifaceName + " inet static" + "\n";
result = result + "address " + ip + "\n";
result = result + "gateway " + gateway + "\n";
result = result + "netmask 255.255.0.0" + "\n\n";
return result;
}
//----------------------------------------------------------------------------------------------
private String fetchRouterIp() {
String result = null;
List<PostCreateTask> pcTasks = routerTask.getPostCreateTaskList();
for(PostCreateTask pcTask : pcTasks) {
if (pcTask instanceof RouterConfigPostCreateTask) {
result = ((RouterConfigPostCreateTask) pcTask).fetchRouterIp();
break;
}
}
return result;
}
//----------------------------------------------------------------------------------------------
public void runCommand(String user, String password, String vmRunCommand, String... args)
throws IOException, InterruptedException {
runCommand(user, password, vmRunCommand, Arrays.asList(args));
}
//----------------------------------------------------------------------------------------------
public void runCommand(String vmUser, String vmPassword, String vmRunCommand, List<String> args)
throws IOException, InterruptedException {
if(vmUser == null || vmPassword == null) {
log.error("Either VM user or password were null. " +
"They need to be specified in the template under the clone element.");
throw new NullPointerException();
}
VirtualHost host = environment.fetchVirtualHost();
host.waitForVmtools(router);
String vmx = host.getVmxPath(router);
String url = host.getUrl();
String virtualHostUser = host.getUser();
String virtualHostPassword = host.getPassword();
List<String> commandLine = new ArrayList<String>();
commandLine.add("vmrun");
commandLine.add("-T");
commandLine.add("server");
commandLine.add("-h");
commandLine.add(url);
commandLine.add("-u");
commandLine.add(virtualHostUser);
commandLine.add("-p");
commandLine.add(virtualHostPassword);
commandLine.add("-gu");
commandLine.add(vmUser);
commandLine.add("-gp");
commandLine.add(vmPassword);
commandLine.add(vmRunCommand);
commandLine.add(vmx);
commandLine.addAll(args);
ProcessBuilder builder = new ProcessBuilder(commandLine);
builder.redirectErrorStream(true);
Process process = builder.start();
InputStream procIn = process.getInputStream();
IOUtil.getInstance().discardStream(procIn);
int exitCode = process.waitFor();
if (exitCode != 0) {
throw new IOException("Command failed with code " + exitCode);
}
log.info("ran command " + vmRunCommand + " " + args.get(0));
}
//----------------------------------------------------------------------------------------------
public void copyFileFromGuestToHost(String origin, String destination)
throws IOException, InterruptedException {
List<String> args = new ArrayList<String>();
//destination path is relative
File temp = new File(destination);
String absDestination = temp.getAbsolutePath();
args.add(origin);
args.add(absDestination);
runCommand(routerUser, routerPassword, "copyFileFromGuestToHost", args);
}
//----------------------------------------------------------------------------------------------
public void copyFileFromHostToGuest(String origin, String destination)
throws IOException, InterruptedException {
List<String> args = new ArrayList<String>();
//destination path is relative
File temp = new File(origin);
String absOrigin = temp.getAbsolutePath();
args.add(absOrigin);
args.add(destination);
runCommand(routerUser, routerPassword, "copyFileFromHostToGuest", args);
}
//----------------------------------------------------------------------------------------------
@Override
public void create() throws Exception {
}
//----------------------------------------------------------------------------------------------
@Override
public void destroy() throws Exception {
}
}