/* * Copyright (c) 2015 EMC Corporation * All Rights Reserved */ package com.emc.storageos.imageservercontroller.impl; import static com.emc.storageos.imageservercontroller.ImageServerConstants.HTTP_FAILURE_DIR; import static com.emc.storageos.imageservercontroller.ImageServerConstants.HTTP_FIRSTBOOT_DIR; import static com.emc.storageos.imageservercontroller.ImageServerConstants.HTTP_KICKSTART_DIR; import static com.emc.storageos.imageservercontroller.ImageServerConstants.HTTP_SUCCESS_DIR; import static com.emc.storageos.imageservercontroller.ImageServerConstants.PXELINUX_CFG_DIR; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.emc.storageos.db.client.model.ComputeImage; import com.emc.storageos.db.client.model.ComputeImageJob; import com.emc.storageos.db.client.model.ComputeImageServer; import com.emc.storageos.imageservercontroller.exceptions.ImageServerControllerException; public class PxeIntegrationService { private static final Logger log = LoggerFactory.getLogger(PxeIntegrationService.class); private static final String ESXI5X_UUID_TEMPLATE = "imageserver/esxi5x_uuid.template"; private static final String ESXI5X_UNATTENDED_TEMPLATE = "imageserver/esxi5x_unattended.template"; private static final String RHEL_FIRSTBOOT_SH = "imageserver/rhel-firstboot.sh"; private static final String ESXI_FIRSTBOOT_SH = "imageserver/esxi-firstboot.sh"; public void createSession(ImageServerDialog d, ComputeImageJob job, ComputeImage ci, ComputeImageServer imageServer) { if (ci._isEsxi5x()||ci._isEsxi6x()) { createEsxiSession(d, job, ci, imageServer); } else if (ci._isRedhat() || ci._isCentos() || ci._isOracle()) { throw ImageServerControllerException.exceptions.unknownOperatingSystem(); } else { throw ImageServerControllerException.exceptions.unknownOperatingSystem(); } } /** * Create PXE UUID config and PXE uuid boot.cfg files for ESXi 5.x and 6.x * and put them under tftpboot/pxelinux.cfg/. * * @param session * @param os */ private void createEsxiSession(ImageServerDialog d, ComputeImageJob job, ComputeImage ci, ComputeImageServer imageServer) { // create uuid file String s = ImageServerUtils.getResourceAsString(ESXI5X_UUID_TEMPLATE); StringBuilder sb = new StringBuilder(s); ImageServerUtils.replaceAll(sb, "${os_full_name}", ci.getImageName()); ImageServerUtils.replaceAll(sb, "${os_path}", ci.getPathToDirectory()); ImageServerUtils.replaceAll(sb, "${pxe_identifier}", job.getPxeBootIdentifier()); String content = sb.toString(); log.trace(content); d.writeFile(imageServer.getTftpBootDir() + PXELINUX_CFG_DIR + job.getPxeBootIdentifier(), content); // create uuid.boot.cfg - only for esxi 5 s = d.readFile(imageServer.getTftpBootDir() + ci.getPathToDirectory() + "/boot.cfg"); sb = new StringBuilder(s.trim()); ImageServerUtils.replaceAll(sb, "/", "/" + ci.getPathToDirectory()); ImageServerUtils.replaceAll(sb, "runweasel", "runweasel vmkopts=debugLogToSerial:1 ks=http://" + imageServer.getImageServerSecondIp() + ":" + imageServer.getImageServerHttpPort() + "/ks/" + job.getPxeBootIdentifier() + " kssendmac"); content = sb.toString(); log.trace(content); d.writeFile(imageServer.getTftpBootDir() + PXELINUX_CFG_DIR + job.getPxeBootIdentifier() + ".boot.cfg", content); // create kick-start content = generateKickstart(job, ci, imageServer); d.writeFile(imageServer.getTftpBootDir() + HTTP_KICKSTART_DIR + job.getPxeBootIdentifier(), content); // create first boot content = generateFirstboot(job, ci); d.writeFile(imageServer.getTftpBootDir() + HTTP_FIRSTBOOT_DIR + job.getPxeBootIdentifier(), content); // remove these in case there was previous installation that succeeded or failed after we timed out d.rm(imageServer.getTftpBootDir() + HTTP_SUCCESS_DIR + job.getPxeBootIdentifier()); d.rm(imageServer.getTftpBootDir() + HTTP_FAILURE_DIR + job.getPxeBootIdentifier()); } private String generateKickstart(ComputeImageJob job, ComputeImage ci, ComputeImageServer imageServer) { log.info("generateKickstart for sess: " + job.getId()); return generateKickstartEsxEsxi(job, ci, imageServer); } /** * Generates a kick-start script that runs on the server after the PXE boot base OS is loaded * from LAN. It usually formats a (boot) disk on the SAN for installation of the OS and * creates instructions for the first boot script to run. For details, see the esxi5x_unattended.template * file for details. * * @param job * compute image job * @param ci * compute image info * @param imageServer * compute image server * @return the kick-start script contents with all variables replaced. */ private String generateKickstartEsxEsxi(ComputeImageJob job, ComputeImage ci, ComputeImageServer imageServer) { log.info("generateKickstartEsxEsxi"); String clearDevice = null; String installDevice = null; String bootDeviceUuid = null; if (job.getBootDevice() != null) { if (ImageServerUtils.isUuid(job.getBootDevice())) { bootDeviceUuid = ImageServerUtils.uuidFromString(job.getBootDevice()).toString().replaceAll("-", ""); } else { bootDeviceUuid = job.getBootDevice(); } clearDevice = "--drives=naa." + bootDeviceUuid; installDevice = "--disk=naa." + bootDeviceUuid; } else { // If we can't find the boot device, it may be an issue with retrying the operation. Nevertheless, // don't try to do anything else at this point. throw ImageServerControllerException.exceptions.deviceNotKnown(); } String str = null; StringBuilder sb = null; if (ci._isEsxi5x()||ci._isEsxi6x()) { sb = new StringBuilder(ImageServerUtils.getResourceAsString(ESXI5X_UNATTENDED_TEMPLATE)); ImageServerUtils.replaceAll(sb, "${DATASTORE_SYM_LINK}", "LINECOUNT=`localcli --format-param=show-header=false storage vmfs extent list | grep \"naa." + bootDeviceUuid + "\" | wc -l` \n" + "if [ $LINECOUNT = 1 ] ; then \n" + "LOCALDS=`localcli --format-param=show-header=false storage vmfs extent list | cut -d \" \" -f 1` \n" + "ln -s /vmfs/volumes/`readlink /vmfs/volumes/$LOCALDS` /vmfs/volumes/datastore1 \n" + "fi"); } else { throw ImageServerControllerException.exceptions.unknownOperatingSystem(); } ImageServerUtils.replaceAll(sb, "${os_path}", ci.getLabel()); // does not apply for ESXi 5 assertKickStartParam(clearDevice, "clearpart device"); assertKickStartParam(installDevice, "install device"); assertKickStartParam(imageServer.getImageServerSecondIp(), "image server second IP"); assertKickStartParam(imageServer.getImageServerHttpPort(), "image server HTTP port"); assertKickStartParam(job.getPxeBootIdentifier(), "PXE boot identifier"); assertKickStartParam(job.getPasswordHash(), "Password hash"); // common parameters for all versions ImageServerUtils.replaceAll(sb, "${raw.uuid}", bootDeviceUuid); ImageServerUtils.replaceAll(sb, "${clear.device}", clearDevice); ImageServerUtils.replaceAll(sb, "${install.device}", installDevice); ImageServerUtils.replaceAll(sb, "${http.ip}", imageServer.getImageServerSecondIp()); ImageServerUtils.replaceAll(sb, "${http.port}", imageServer.getImageServerHttpPort()); ImageServerUtils.replaceAll(sb, "${session.id}", job.getPxeBootIdentifier()); str = sb.toString(); log.info(str); ImageServerUtils.replaceAll(sb, "${root.password}", job.getPasswordHash()); return sb.toString(); } /** * Parameter assertion to ensure valid values are getting set in the scripts. * * @param param * parameter variable * @param paramName * parameter variable's name */ private void assertKickStartParam(String param, String paramName) { if (param == null || param.isEmpty()) { throw ImageServerControllerException.exceptions.missingKickstartParameter(paramName); } } /** * Generates a first-boot script that runs after the host is kick-started. Sets critical * IP parameters so the host will be reachable on the network and can be added to clusters, * etc. * * @param job * compute image job * @param ci * compute image info * @return the firstboot script contents with all variables replaced. */ private String generateFirstboot(ComputeImageJob job, ComputeImage ci) { StringBuilder sb = null; if (ci._isLinux()) { sb = new StringBuilder(ImageServerUtils.getResourceAsString(RHEL_FIRSTBOOT_SH)); } else { sb = new StringBuilder(ImageServerUtils.getResourceAsString(ESXI_FIRSTBOOT_SH)); // applies to ESXi only // VBDU TODO: COP-28460, Check for explicit ESX type ImageServerUtils.replaceAll(sb, "__DEFAULT_VLAN_MARKER__", nonNullValue(job.getManagementNetwork())); } ImageServerUtils.replaceAll(sb, "__HOSTNAME_MARKER__", nonNullValue(job.getHostName())); ImageServerUtils.replaceAll(sb, "__HOSTIP_MARKER__", nonNullValue(job.getHostIp())); ImageServerUtils.replaceAll(sb, "__NETMASK_MARKER__", nonNullValue(job.getNetmask())); ImageServerUtils.replaceAll(sb, "__GATEWAY_MARKER__", nonNullValue(job.getGateway())); ImageServerUtils.replaceAll(sb, "__NTP_SERVER_MARKER__", nonNullValue(job.getNtpServer())); String[] dnsServers = nonNullValue(job.getDnsServers()).split(","); ImageServerUtils.replaceAll(sb, "__DNS_PRIMARY_IP_MARKER__", dnsServers[0].trim()); ImageServerUtils.replaceAll(sb, "__DNS_SECONDARY_IP_MARKER__", dnsServers.length == 1 ? "" : dnsServers[1].trim()); String str = sb.toString(); log.info(str); return str; } /** * Convenience method to replace any null String with an empty string. * Useful for parameter replacement in script generation where the parameter is * allowed to be empty. * * @param val * string object * @return empty string if val is null, otherwise val */ private String nonNullValue(String val) { if (val == null) { return ""; } return val; } }