/** * Copyright (c) 2016 Inria * * All rights reserved. This program and the accompanying materials * are made available under the terms of the Eclipse Public License v1.0 * which accompanies this distribution, and is available at * http://www.eclipse.org/legal/epl-v10.html * * Contributors: * - Christophe Gourdin <christophe.gourdin@inria.fr> * */ package org.occiware.clouddesigner.occi.infrastructure.connector.vmware.utils; import java.io.File; import java.lang.reflect.InvocationTargetException; import java.rmi.RemoteException; import java.util.LinkedList; import java.util.List; import org.eclipse.core.runtime.IProgressMonitor; import org.eclipse.jface.operation.IRunnableWithProgress; import org.occiware.clouddesigner.occi.infrastructure.connector.vmware.addons.exceptions.UserDataException; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.vmware.vim25.GuestProgramSpec; import com.vmware.vim25.NamePasswordAuthentication; import com.vmware.vim25.TaskInfo; import com.vmware.vim25.TaskInfoState; import com.vmware.vim25.mo.Folder; import com.vmware.vim25.mo.GuestOperationsManager; import com.vmware.vim25.mo.GuestProcessManager; import com.vmware.vim25.mo.Task; import com.vmware.vim25.mo.VirtualMachine; // import org.occiware.driver.ssh.*; /** * VMWare user data management. How user data works : This runnable will test if * the instance is definitively started and check if GuestTools are running or * not. - With GuestTools --> Create a .properties file with datas (the origin * string must have separator ; for each key/value) in home directory of the * current user. If no GuestTools installed, await 30 seconds (if instance is * power on) and then: - Without GuestTools --> The ssh public certificate must * be use for connection to the virtual machine, Via ssh --> Create a * .properties file with datas (the origin string must have separator ; for each * key/value) in home directory of the current user. * * @author Christophe Gourdin - Inria. * */ public class UserDataHelper implements Runnable, IRunnableWithProgress { private static Logger LOGGER = LoggerFactory.getLogger(UserDataHelper.class); private String userDatas = null; // private String ipAddress; private String username; private String password; /** * Unique id of this vm on vcenter. Used only for guest tools access on this * instance. */ private String morId = null; /** * If no morId setted, get the instance from his name and path. */ private String name = null; private Folder instanceFolder = null; private String userDataFile = null; private String ipAddress = null; private int port; private VClientImpl vClient = new VClientImpl(); /** * For guest tools or ssh connection with username and password. * * @param instanceId * @param instanceName * @param userDatas * @param username * @param password */ public UserDataHelper(String instanceId, String instanceName, String userDatas, String username, String password, String userDataFile) { super(); this.userDatas = userDatas; // this.ipAddress = ipAddress; this.username = username; this.password = password; this.morId = instanceId; this.name = instanceName; this.userDataFile = userDataFile; // this.instanceFolder = instanceFolder; } public UserDataHelper(String instanceId, String instanceName, String userDatas, String username, String password, String ipAddress, int port, String userDataFile) { super(); this.userDatas = userDatas; this.username = username; this.password = password; this.ipAddress = ipAddress; this.port = port; this.morId = instanceId; this.name = instanceName; this.userDataFile = userDataFile; } /** * Execute the create / update of the user data properties on the qualified * instance. */ @Override public void run() { // Connect to the client. if (vClient == null) { vClient = new VClientImpl(); } try { vClient.init(); boolean connected = vClient.checkConnection(); if (connected) { if (instanceFolder == null) { // Get the root Folder. LOGGER.info("instance folder not specified for user data set, assuming root folder."); instanceFolder = vClient.getServiceInstance().getRootFolder(); } // Client is connected to vcenter. // Now check if the instance has vmware tools installed, if not // we use ipAddress provided and ssh. if (instanceFolder == null) { throw new Exception("Instance folder is unknown."); } Thread.sleep(10000); // Find the the instance. VirtualMachine vm = null; // if (morId != null) { // try { // LOGGER.info("Managed object reference id : " + morId); // vm = VMHelper.findVMForMorId(instanceFolder, morId); // } catch (Exception ex) { // LOGGER.error("Exception thrown : " + ex.getMessage() + " // class: " + ex.getClass().getName()); // } // } // If no vm, find by instance name. if (vm == null) { LOGGER.info("Searching vm in inventory with the instance name : " + name); try { vm = VMHelper.findVMForName(instanceFolder, name); } catch (Exception ex) { LOGGER.error("Exception thrown : " + ex.getMessage() + " class: " + ex.getClass().getName()); ex.printStackTrace(); throw ex; } } if (vm == null) { LOGGER.error("VM not found for name : " + name); throw new Exception("No vm found on vcenter for setting user datas."); } String vmState; try { vmState = VMHelper.getPowerState(vm); } catch (Exception ex) { LOGGER.error("Exception thrown while getting vm state: " + ex.getMessage() + " class: " + ex.getClass().getName()); ex.printStackTrace(); throw ex; } int count = 0; LOGGER.info("Virtual machine state : " + vmState); TaskInfo taskInf = null; if (vmState.equals(VMHelper.POWER_OFF)) { // Check if a clone task is used. Task[] tasks = vm.getRecentTasks(); Task taskClone = null; if (tasks != null && tasks.length > 0) { LOGGER.info("Recent Tasks are detected: "); for (Task task : tasks) { TaskInfo info = task.getTaskInfo(); String taskName = info.getName(); LOGGER.info("Task name : " + taskName); LOGGER.info("Task entity name : " + info.getEntityName()); LOGGER.info("Task description id : " + info.getDescriptionId()); LOGGER.info("Task key : " + info.getKey()); LOGGER.info("Mor id : " + info.getEntity().getVal()); if (taskName != null && taskName.contains("lone")) { taskInf = info; taskClone = task; break; } } } if (taskInf != null && taskClone != null) { LOGGER.info("Task : " + taskInf.getName() + " is detected."); // Waiting for task to finish... if (!taskInf.isCancelled() && (taskInf.getState().equals(TaskInfoState.running) || taskInf.getState().equals(TaskInfoState.queued))) { TaskInfoState state; do { try { Thread.sleep(1000); } catch (InterruptedException ex) { LOGGER.warn("Thread userdata is interrupted : " + ex.getMessage()); } state = taskClone.getTaskInfo().getState(); Integer progress = taskClone.getTaskInfo().getProgress(); if (state == TaskInfoState.success) { progress = 100; } else if (progress == null) { progress = 0; } LOGGER.info("State=" + state + "(" + progress + "%)"); } while (state != TaskInfoState.error && state != TaskInfoState.success); } } } LOGGER.info("Checking if instance is started..."); while (vmState.equals(VMHelper.POWER_OFF)) { try { Thread.sleep(5000); LOGGER.info("Trying to apply user data count : " + count); count++; } catch (InterruptedException ex) { LOGGER.warn("Thread userdata is interrupted : " + ex.getMessage()); } vmState = VMHelper.getPowerState(vm); if (vmState.equals(VMHelper.POWER_OFF)) { // Start the virtual machine before processing. try { VMHelper.powerOn(vm); } catch (RemoteException | InterruptedException ex) { LOGGER.warn( "Trying to power on the instance " + vm.getName() + " failed : " + ex.getMessage()); } } if (count >= 24) { throw new Exception("The user data couldn't apply cause from timeout."); } } LOGGER.info("VM : " + vm.getName() + " is powered on."); if (VMHelper.isToolsInstalled(vm)) { LOGGER.info("Calling apply userdata via vmware guest tools."); applyUserDataViaGuestTools(vm); } else { // VMware tools not installed and the vm is not found on // this vcenter, try to apply user data via ssh directly // (without vClient so). LOGGER.info("Call apply userdata via ssh."); if (port == 0) { port = 22; } applyUserDataViaSSH(); } } } catch (Exception ex) { // Fail silently. LOGGER.error(ex.getMessage()); } finally { vClient.disconnect(); } } /** * Apply user data via guest tools (guestoperation). * * @param vm */ private void applyUserDataViaGuestTools(VirtualMachine vm) throws UserDataException { if (vm == null) { LOGGER.warn("No virtual machine supplied for applying user datas."); return; } if (userDatas == null) { throw new UserDataException("No user datas specified to apply."); } else { userDatas.replaceAll("\r\n", "\n"); } if (username == null || password == null) { throw new UserDataException("No credentials given to apply instance connection."); } int count = 0; LOGGER.info("checking vmware tools state..."); if (vm.getGuest() != null) { String guestState = VMHelper.getGuestState(vm); if (guestState == null) { LOGGER.warn("The guest tools state is unknown..."); } else { LOGGER.info("VMware tools check, is not running but installed. Wait for running state."); while (!guestState.equals("running")) { LOGGER.info("Waiting for running guest state... count: " + count); try { if (count >= 50) { LOGGER.warn("Cannot wait more times to set user data, please retry later."); throw new UserDataException("Cannot wait more times to set user data, please retry later."); } Thread.sleep(5000); count++; guestState = VMHelper.getGuestState(vm); } catch (InterruptedException ex) { LOGGER.error("Wait to guest running state interrupted"); break; } } } } try { Thread.sleep(2000); } catch(InterruptedException ex) { LOGGER.warn("Thread userdata is interrupted, trying to continue..."); } try { // example of user datas: userDatas = "touch /tmp/userdata.txt | // 'toto:1,titi: hello world' > /tmp/userdata.txt" LOGGER.info("Applying user datas please standby..."); GuestOperationsManager gom = vClient.getServiceInstance().getGuestOperationsManager(); NamePasswordAuthentication npa = new NamePasswordAuthentication(); npa.username = username; npa.password = password; npa.interactiveSession = false; GuestProgramSpec gps = new GuestProgramSpec(); gps.programPath = "/bin/echo"; if (userDataFile == null) { userDataFile = "/tmp/roboconf.properties"; } gps.arguments = "$\'" + this.userDatas + "\' > " + userDataFile; // spec.arguments = "$\'" + this.userData + "\' > /tmp/roboconf.properties"; // before: gps.programPath = "/bin/bash"; // gps.arguments = userDatas; GuestProcessManager gpm = gom.getProcessManager(vm); long result = gpm.startProgramInGuest(npa, gps); LOGGER.info("Process: " + result); LOGGER.info("User data has applied : " + userDatas); } catch (RemoteException ex) { throw new UserDataException("Unknown remote error : " + ex.getMessage()); } } private void applyUserDataViaSSH() { /* TODO : Add the dependencies on another project (like docker has done it..) String knownHosts = System.getProperty("user.home") + File.separator + ".ssh" + File.separator + "known_hosts"; SshClient sshClient = new SshClient(username, password, null, knownHosts, ipAddress, port); sshClient.setTimeout(5000); try { sshClient.connect(); sshClient.execute("echo " + userDatas); // System.out.println("result : " + result); sshClient.disconnect(); } catch (SshException ex) { System.err.println("Exception thrown when executing one command : " + ex.getMessage()); } */ } @Override public void run(IProgressMonitor monitor) throws InvocationTargetException, InterruptedException { this.run(); } }