/******************************************************************************* * 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; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.util.ArrayList; import java.util.List; import org.apache.commons.io.FileUtils; import org.apache.log4j.Logger; import com.urbancode.terraform.tasks.vmware.util.GlobalIpAddressPool; import com.urbancode.terraform.tasks.vmware.util.VirtualHost; import com.vmware.vim25.Description; import com.vmware.vim25.VirtualDeviceConfigSpec; import com.vmware.vim25.VirtualDeviceConfigSpecOperation; import com.vmware.vim25.VirtualDeviceConnectInfo; import com.vmware.vim25.VirtualE1000; import com.vmware.vim25.VirtualE1000e; import com.vmware.vim25.VirtualEthernetCard; import com.vmware.vim25.VirtualEthernetCardNetworkBackingInfo; import com.vmware.vim25.VirtualMachineConfigSpec; import com.vmware.vim25.VirtualPCNet32; import com.vmware.vim25.VirtualVmxnet; import com.vmware.vim25.mo.Task; import com.vmware.vim25.mo.VirtualMachine; public class RouterConfigPostCreateTask extends PostCreateTask { //********************************************************************************************** // CLASS //********************************************************************************************** static private final Logger log = Logger.getLogger(RouterConfigPostCreateTask.class); //********************************************************************************************** // INSTANCE //********************************************************************************************** private String gateway; private String dns; private String routerIp = null; //---------------------------------------------------------------------------------------------- public RouterConfigPostCreateTask() { super(); } //---------------------------------------------------------------------------------------------- public RouterConfigPostCreateTask(CloneTask cloneTask) { super(cloneTask); } //---------------------------------------------------------------------------------------------- public String getGateway() { return gateway; } //---------------------------------------------------------------------------------------------- public String getDns() { return dns; } //---------------------------------------------------------------------------------------------- public String fetchRouterIp() { return routerIp; } //---------------------------------------------------------------------------------------------- public void setGateway(String gateway) { this.gateway = gateway; } //---------------------------------------------------------------------------------------------- public void setDns(String dns) { this.dns = dns; } //---------------------------------------------------------------------------------------------- /** * Configures a Debian-based router after it has been created. * Copies over the isc-dhcp-server, iptables, dhcpd, and interfaces configuration files. * The host machine running Terraform must have VMRun installed. * This method ends when the router successfully broadcasts its IP address. * If it does not broadcast an IP address this method will time out after 10 minutes. */ @Override public void create() { //set VM now that the VM has been created this.vmToConfig = this.cloneTask.fetchVm(); this.tempConfDirNoSeparator = System.getenv("TERRAFORM_HOME") + File.separator + "temp" + "-" + environment.fetchSuffix(); this.tempConfDir = tempConfDirNoSeparator + File.separator; try { log.info(this.tempConfDirNoSeparator); File configDir = new File(this.tempConfDirNoSeparator); configDir.mkdirs(); copyTempFiles(); addFirstInterface(this.tempConfDir + "interfaces.temp", this.tempConfDir + "interfaces"); handleNetworkRefs(); //power on vm cloneTask.powerOnVm(); //bring down networking runCommand(vmUser, vmPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "stop"); //copy networking files to router copyFileFromHostToGuest(this.tempConfDir + "isc-dhcp-server", "/etc/default/isc-dhcp-server"); copyFileFromHostToGuest(this.tempConfDir + "iptables.conf", "/etc/iptables.conf"); copyFileFromHostToGuest(this.tempConfDir + "dhcpd.conf", "/etc/dhcp/dhcpd.conf"); copyFileFromHostToGuest(this.tempConfDir + "interfaces", "/etc/network/interfaces"); //start networking and dhcp service runCommand(vmUser, vmPassword, "runProgramInGuest", "/usr/sbin/service", "networking", "start"); runCommand(vmUser, vmPassword, "runProgramInGuest", "/sbin/insserv", "isc-dhcp-server"); runCommand(vmUser, vmPassword, "runProgramInGuest", "/usr/sbin/service", "isc-dhcp-server", "start"); VirtualHost host = environment.fetchVirtualHost(); host.waitForIp(vmToConfig); } catch (IOException e) { log.warn("Failed to load file while configuring router", e); } catch (InterruptedException e) { log.warn("InterruptedException while configuring router", e); } catch (Exception e) { log.warn("Unknown exception while configuring router", e); } } //---------------------------------------------------------------------------------------------- @Override public void destroy() { this.tempConfDirNoSeparator = System.getenv("TERRAFORM_HOME") + File.separator + "temp" + "-" + environment.fetchSuffix(); this.tempConfDir = tempConfDirNoSeparator + File.separator; File configDir = new File(this.tempConfDirNoSeparator); try { log.info("deleting environment-specific conf directory: " + this.tempConfDirNoSeparator); FileUtils.deleteDirectory(configDir); } catch (IOException e) { log.warn("Unable to delete conf directory", e); } } //---------------------------------------------------------------------------------------------- private void copyTempFiles() throws IOException { copyTempFile("iptables.conf.temp"); copyTempFile("dhcpd.conf.temp"); copyTempFile("interfaces.temp"); copyTempFile("isc-dhcp-server.temp"); } //---------------------------------------------------------------------------------------------- private void copyTempFile(String fileName) throws IOException { ClassLoader loader = Thread.currentThread().getContextClassLoader(); String cpDir = "org/urbancode/terraform/conf" + File.separator; InputStream inputStream = loader.getResourceAsStream(cpDir + fileName); try { writeInputStreamToFile(inputStream, this.tempConfDir + fileName); } catch(IOException e) { inputStream.close(); } } //---------------------------------------------------------------------------------------------- private void writeInputStreamToFile(InputStream inStream, String filePath) throws IOException { File outFile = new File(filePath); OutputStream out=new FileOutputStream(outFile); byte buf[]=new byte[1024]; int len; try { while ((len=inStream.read(buf))>0) { out.write(buf,0,len); } } catch(IOException e) { log.warn("IOException while copying to file " + filePath, e); } finally { out.close(); inStream.close(); } } //---------------------------------------------------------------------------------------------- public void addFirstInterface(String inFileName, String outFileName) throws IOException { GlobalIpAddressPool ipPool = GlobalIpAddressPool.getInstance(); routerIp = ipPool.allocateIp().toString(); String ifaces = FileUtils.readFileToString(new File(inFileName)); ifaces = ifaces + "\n\nauto eth0\n" + "allow-hotplug eth0\n" + "iface eth0 inet static\n" + " address " + routerIp + "\n" + " gateway " + gateway + "\n" + " netmask 255.255.0.0\n" + "#Insert New Interfaces\n"; writeToFile(outFileName, ifaces, false); } //---------------------------------------------------------------------------------------------- public void handleNetworkRefs() throws NetworkConfigurationException, InterruptedException, IOException { //create network cards (VM must be powered off) List<Integer> nicIndexes = new ArrayList<Integer>(); boolean first = true; int subnetNum = 0; List<NetworkRefTask> netRefs = cloneTask.getNetworkRefs(); for (NetworkRefTask netRef : netRefs) { nicIndexes.add(netRef.getNicIndex()); } for (NetworkRefTask netRef : netRefs) { String netName = netRef.fetchSwitch().getSwitchPath().getName(); int nicIndex = netRef.getNicIndex(); int netAdapterNum = nicIndex + 1; String nicName = "Network adapter " + netAdapterNum; addNewNetworkCard(vmToConfig, netName, nicName, netRef.getNicType()); netRef.attachNic(); //add new interface/subnet/network to iptables, interfaces, and dhcpd String iptablesIn; String interfacesIn; String dhcpdIn; if (first) { iptablesIn = this.tempConfDir + "iptables.conf.temp"; interfacesIn = this.tempConfDir + "interfaces"; dhcpdIn = this.tempConfDir + "dhcpd.conf.temp"; first = false; } else { iptablesIn = this.tempConfDir + "iptables.conf"; interfacesIn = this.tempConfDir + "interfaces"; dhcpdIn = this.tempConfDir + "dhcpd.conf"; } String iptablesOut = this.tempConfDir + "iptables.conf"; String interfacesOut = this.tempConfDir + "interfaces"; String dhcpdOut = this.tempConfDir + "dhcpd.conf"; nicIndexes.remove(new Integer(nicIndex)); addIfaceToIptables(nicIndex, nicIndexes, iptablesIn, iptablesOut); addInterface(nicIndex, subnetNum, interfacesIn, interfacesOut); addSubnetToDhcpd(subnetNum, dhcpdIn, dhcpdOut); nicIndexes.add(nicIndex); subnetNum++; } //edit default dhcp interfaces file String ifacesString = createDhcpInterfacesString(nicIndexes); String inFileName = this.tempConfDir + "isc-dhcp-server.temp"; String outFileName = this.tempConfDir + "isc-dhcp-server"; createDhcpInterfacesFile(ifacesString, inFileName, outFileName); } //---------------------------------------------------------------------------------------------- public void addNewNetworkCard(VirtualMachine vm, String netName, String nicName, String nicType) throws NetworkConfigurationException { try { VirtualMachineConfigSpec vmSpec = new VirtualMachineConfigSpec(); VirtualDeviceConfigSpec nicSpec = createNicSpec(netName, nicName, nicType); vmSpec.setDeviceChange(new VirtualDeviceConfigSpec[] {nicSpec}); Task task = vm.reconfigVM_Task(vmSpec); @SuppressWarnings("unused") String result = task.waitForTask(); } catch(Exception e) { throw new NetworkConfigurationException("Exception while adding network card to VM: " + e.getClass().getCanonicalName(), e); } } //---------------------------------------------------------------------------------------------- public VirtualDeviceConfigSpec createNicSpec(String netName, String nicName, String nicType) { //create the specs for the new virtual ethernet card VirtualDeviceConfigSpec nicSpec = new VirtualDeviceConfigSpec(); nicSpec.setOperation(VirtualDeviceConfigSpecOperation.add); VirtualEthernetCard nic = null; if(nicType.equalsIgnoreCase("E1000")) { nic = new VirtualE1000(); } else if (nicType.equalsIgnoreCase("E1000e")) { nic = new VirtualE1000e(); } else if (nicType.equalsIgnoreCase("vmxnet")) { nic = new VirtualVmxnet(); } else if (nicType.equalsIgnoreCase("pcnet32") || nicType.equalsIgnoreCase("vlance")) { nic = new VirtualPCNet32(); } VirtualEthernetCardNetworkBackingInfo nicBacking = new VirtualEthernetCardNetworkBackingInfo(); nicBacking.setDeviceName(netName); VirtualDeviceConnectInfo connectInfo = new VirtualDeviceConnectInfo(); connectInfo.setConnected(true); connectInfo.setStartConnected(true); nic.setConnectable(connectInfo); Description info = new Description(); info.setLabel(nicName); info.setSummary(netName); nic.setDeviceInfo(info); // allowable types: "generated", "manual", "assigned" nic.setAddressType("generated"); nic.setBacking(nicBacking); //according to vsphere api, keys should be unique, but this does not appear to be enforced nic.setKey(0); nicSpec.setDevice(nic); return nicSpec; } //---------------------------------------------------------------------------------------------- public void addIfaceToIptables( int nicIndex, List<Integer> excludedIndexes, String inFileName, String outFileName) throws IOException { //add rules above last line of iptables.conf file String eth = "eth" + nicIndex; String inboundRule = "-A FORWARD -i eth0 -o " + eth + " -m state --state RELATED,ESTABLISHED -j ACCEPT"; String outboundRule = "-A FORWARD -i " + eth + " -o eth0 -j ACCEPT"; String iptables = FileUtils.readFileToString(new File(inFileName)); String[] split = iptables.split("\n"); String lastLine = split[split.length - 1]; lastLine = inboundRule + "\n" + outboundRule + "\n" + lastLine; for (Integer i : excludedIndexes) { String exEth = "eth" + i.toString(); String inboundReject = "-A FORWARD -i " + exEth + " -o " + eth + " -j REJECT"; String outboundReject = "-A FORWARD -i " + eth + " -o " + exEth + " -j REJECT"; lastLine = inboundReject + "\n" + outboundReject + "\n" + lastLine; } split[split.length - 1] = lastLine; iptables = join(split, "\n"); //trailing newline is necessary for iptables (commons-io removes it when file is read) iptables = iptables + "\n"; writeToFile(outFileName, iptables, false); } //---------------------------------------------------------------------------------------------- public String createDhcpInterfacesString(List<Integer> nicIndexes) { //constructs content for isc-dhcp-server file //example: INTERFACES="eth1 eth2" (with quotes) String result = "INTERFACES=\""; boolean first = true; for (Integer i : nicIndexes) { if (first) { result = result + "eth" + i.toString(); first = false; } else { result = result + " eth" + i.toString(); } } result = result + "\""; return result; } //---------------------------------------------------------------------------------------------- public void createDhcpInterfacesFile(String ifacesString, String oldFileName, String newFileName) throws IOException { //create isc-dhcp-server file string String ifacesFileAsString = FileUtils.readFileToString(new File(oldFileName)); String result = ifacesFileAsString.replace("INTERFACES=\"\"", ifacesString); result = result + "\n"; writeToFile(newFileName, result, false); } //---------------------------------------------------------------------------------------------- public void addInterface(int nicIndex, int subnetNum, String inFileName, String outFileName) throws IOException { //add new interface to /etc/network/interfaces file String eth = "eth" + nicIndex; String ifaces = FileUtils.readFileToString(new File(inFileName)); ifaces = ifaces + "\nauto " + eth + "\n" + "allow-hotplug " + eth + "\n" + "iface " + eth + " inet static\n" + " address 192.168." + subnetNum + ".1\n" + " netmask 255.255.255.0\n"; writeToFile(outFileName, ifaces, false); } //---------------------------------------------------------------------------------------------- public void addSubnetToDhcpd(int subnetNum, String inFileName, String outFileName) throws IOException { //add new subnet to dhcpd.conf file String dhcpd = FileUtils.readFileToString(new File(inFileName)); dhcpd = dhcpd + "\nsubnet 192.168." + subnetNum + ".0 netmask 255.255.255.0 {\n" + "use-host-decl-names on;\n" + "option routers 192.168." + subnetNum + ".1;\n" + "option domain-name-servers " + dns + ";\n" + "pool {\n" + "range 192.168." + subnetNum + ".2 192.168." + subnetNum + ".250;\n" + "}\n" + "}\n"; writeToFile(outFileName, dhcpd, false); } }