/*******************************************************************************
* 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.rmi.RemoteException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.HashSet;
import java.util.List;
import java.util.PriorityQueue;
import java.util.Set;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.apache.log4j.Logger;
import com.urbancode.terraform.tasks.common.EnvironmentTask;
import com.urbancode.terraform.tasks.common.TerraformContext;
import com.urbancode.terraform.tasks.vmware.events.TaskEventService;
import com.urbancode.terraform.tasks.vmware.util.GlobalIpAddressPool;
import com.urbancode.terraform.tasks.vmware.util.IpAddressPool;
import com.urbancode.terraform.tasks.vmware.util.Path;
import com.urbancode.terraform.tasks.vmware.util.VirtualHost;
import com.urbancode.x2o.tasks.MultiThreadTask;
import com.urbancode.x2o.tasks.RestorationException;
public class EnvironmentTaskVmware extends EnvironmentTask {
//**********************************************************************************************
// CLASS
//**********************************************************************************************
static private final Logger log = Logger.getLogger(EnvironmentTaskVmware.class);
static final private int MAX_THREADS = 30;
//**********************************************************************************************
// INSTANCE
//**********************************************************************************************
private VirtualHost host;
private String folderName;
private IpAddressPool ipPool;
private Path datacenterPath;
private Path destPath;
private Path hostPath;
private Path datastorePath;
private FolderTask folderTask;
private List<CloneTask> cloneTasks = new ArrayList<CloneTask>();
private List<NetworkTask> networkTasks = new ArrayList<NetworkTask>();
private List<SecurityGroupTask> securityGroups = new ArrayList<SecurityGroupTask>();
private TaskEventService eventService;
//----------------------------------------------------------------------------------------------
public EnvironmentTaskVmware(TerraformContext context) {
super(context);
eventService = new TaskEventService();
ipPool = GlobalIpAddressPool.getInstance().getIpAddressPool();
}
//----------------------------------------------------------------------------------------------
public TerraformContext fetchContext() {
return (TerraformContext) this.context;
}
//----------------------------------------------------------------------------------------------
public VirtualHost fetchVirtualHost() {
return host;
}
//----------------------------------------------------------------------------------------------
public Path getHostPath() {
return hostPath;
}
//----------------------------------------------------------------------------------------------
public Path getDatastorePath() {
return datastorePath;
}
//----------------------------------------------------------------------------------------------
public Path getDestPath() {
return destPath;
}
//----------------------------------------------------------------------------------------------
public String getFolderName() {
return folderName;
}
//----------------------------------------------------------------------------------------------
protected Path fetchDatacenterPath(){
return this.datacenterPath;
}
//----------------------------------------------------------------------------------------------
public FolderTask fetchFolderTask() {
return this.folderTask;
}
//----------------------------------------------------------------------------------------------
public List<CloneTask> getCloneTasks() {
return this.cloneTasks;
}
//----------------------------------------------------------------------------------------------
public List<NetworkTask> getNetworkTasks() {
return this.networkTasks;
}
//----------------------------------------------------------------------------------------------
public List<SecurityGroupTask> getSecurityGroupTasks() {
return this.securityGroups;
}
//----------------------------------------------------------------------------------------------
public TaskEventService fetchEventService() {
return this.eventService;
}
//----------------------------------------------------------------------------------------------
public IpAddressPool fetchIpPool() {
return this.ipPool;
}
//----------------------------------------------------------------------------------------------
public void setFolderName(String folderName) {
this.folderName = folderName;
}
//----------------------------------------------------------------------------------------------
/**
* Gives the folder a unique name based on the environment's UUID suffix.
*/
public void addUUIDToFolderName() {
folderName = folderName + "-" + fetchSuffix();
}
//----------------------------------------------------------------------------------------------
public void setDestPath(String destPathString) {
Path dest = new Path(destPathString);
this.destPath = dest;
}
//----------------------------------------------------------------------------------------------
public void setHostPath(String hostPathString) {
Path hostPth = new Path(hostPathString);
this.hostPath = hostPth;
}
//----------------------------------------------------------------------------------------------
public void setDatastorePath(String datastorePathString) {
Path datastore = new Path(datastorePathString);
this.datastorePath = datastore;
}
//----------------------------------------------------------------------------------------------
public void setVirtualHost(VirtualHost host) {
this.host = host;
}
//----------------------------------------------------------------------------------------------
public void setAllPaths(String datacenter, String hostName, String destination, String datastore) {
datacenterPath = new Path(datacenter);
hostPath = new Path(datacenterPath, hostName);
destPath = new Path(datacenterPath, destination);
datastorePath = new Path(datacenterPath, datastore);
}
//----------------------------------------------------------------------------------------------
public FolderTask createFolder() {
this.folderTask = new FolderTask();
this.folderTask.setDatacenterPath(datacenterPath);
this.folderTask.setDestPath(destPath);
this.folderTask.setVirtualHost(host);
return this.folderTask;
}
//----------------------------------------------------------------------------------------------
public CloneTask createClone() {
CloneTask result = new CloneTask(this);
this.cloneTasks.add(result);
return result;
}
//----------------------------------------------------------------------------------------------
public NetworkTask createNetwork() {
NetworkTask result = new NetworkTask();
result.setHostPath(hostPath);
result.setVirtualHost(host);
this.networkTasks.add(result);
return result;
}
//----------------------------------------------------------------------------------------------
public SecurityGroupTask createSecurityGroup() {
SecurityGroupTask result = new SecurityGroupTask();
this.securityGroups.add(result);
return result;
}
//----------------------------------------------------------------------------------------------
public NetworkTask restoreNetworkForName(String networkName) {
NetworkTask result = null;
for (NetworkTask network : networkTasks) {
if (network.getNetworkName().equals(networkName)) {
result = network;
break;
}
}
return result;
}
//----------------------------------------------------------------------------------------------
public SecurityGroupTask restoreSecurityGroupForName(String secGroupName) {
SecurityGroupTask result = null;
for (SecurityGroupTask sgt : securityGroups) {
if (sgt.getName().equals(secGroupName)) {
result = sgt;
break;
}
}
return result;
}
//----------------------------------------------------------------------------------------------
/**
* Creates a number of copies of CloneTasks equal to the "count" attribute minus 1
* For example, if count=3, then 3 VM clones are requested. We need to create 2
* more objects to represent those other clones.
*/
private void addNewClonesToTaskList() {
List<CloneTask> newCloneList = new ArrayList<CloneTask>();
for (CloneTask cloneTask : cloneTasks) {
newCloneList.add(cloneTask);
int count = cloneTask.fetchCount();
if (count > 1) {
for (int i=2; i<=count; i++) {
try {
CloneTask newCloneTask = (CloneTask) cloneTask.clone();
newCloneTask.setCount(1);
newCloneTask.setInstanceName(newCloneTask.getInstanceName() + "-" + i);
newCloneList.add(newCloneTask);
}
catch (CloneNotSupportedException e) {
log.warn("clone not supported exception", e);
}
}
}
}
cloneTasks = newCloneList;
}
//----------------------------------------------------------------------------------------------
/**
* Spins off a new thread for each clone in this "chunk" to be created in parallel.
* @param cloneTaskList
* @throws RemoteException
* @throws InterruptedException
* @throws Exception
*/
private void handleCloneCreation(List<CloneTask> cloneTaskList)
throws RemoteException, InterruptedException, Exception {
long pollInterval = 3000L;
long timeoutInterval = 25L * 60L * 1000L;
long start;
if (cloneTaskList != null && !cloneTaskList.isEmpty()) {
int threadPoolSize = cloneTaskList.size();
if (threadPoolSize > MAX_THREADS) {
threadPoolSize = MAX_THREADS;
}
// create instances - launch thread for each one
List<MultiThreadTask> threadList = new ArrayList<MultiThreadTask>();
ExecutorService service = Executors.newFixedThreadPool(threadPoolSize);
start = System.currentTimeMillis();
for (CloneTask instance : cloneTaskList) {
MultiThreadTask mThread = new MultiThreadTask(instance, true, context);
threadList.add(mThread);
service.execute(mThread);
}
service.shutdown(); // accept no more threads
while (!service.isTerminated()) {
if (System.currentTimeMillis() - start > timeoutInterval) {
throw new RemoteException(
"Timeout waiting for creation Instance threads to finish");
}
// wait until all threads are done
Thread.sleep(pollInterval);
}
// check for Exceptions caught in threads
for (MultiThreadTask task : threadList) {
if (task.getExceptions().size() != 0) {
for (Exception e : task.getExceptions()) {
log.error("Exception caught!", e);
throw e;
}
}
}
}
else {
log.error("List of instances to launch was null!");
}
}
//----------------------------------------------------------------------------------------------
/**
* Creates clones in the order specified in the xml by the "order" attribute.
* Clones with the same order number will be created in parallel.
* Each chunk of clones is passed off to handleCloneCreation.
* @throws Exception
*/
private void createClonesInOrder()
throws Exception {
PriorityQueue<CloneTask> taskQueue = new PriorityQueue<CloneTask>(cloneTasks);
List<CloneTask> concurrentClones = new ArrayList<CloneTask>();
int currentOrder = taskQueue.peek().getOrder();
//iterate until a different order number is found, then execute the cached clones
while (!taskQueue.isEmpty()) {
if (taskQueue.peek().getOrder() == currentOrder) {
concurrentClones.add(taskQueue.poll());
}
else {
log.info("about to launch " + concurrentClones.size() + " clones");
handleCloneCreation(concurrentClones);
log.info("finished creating " + concurrentClones.size() + " clones");
log.info("There are " + taskQueue.size() + " clones left in the queue");
concurrentClones.clear();
CloneTask clone = taskQueue.poll();
currentOrder = clone.getOrder();
concurrentClones.add(clone);
}
}
//create final group of clones
handleCloneCreation(concurrentClones);
}
//----------------------------------------------------------------------------------------------
/**
* Generates the status of the environment. The overall environment status will reflect whatever
* the status is of the most atypical VM.
* Here is the "priority" given to statuses - the environment status will be equal to the status
* with the highest priority. So if all clone statuses are "Running", the environment is
* "Running". If 3 clones are "Running" and one is "Starting", the environment is "Starting".
* Shut Down > Shutting Down > Starting > Powered Off Or Starting > Not Started Or Powered Off > Running
* @return A string representing the environment status.
*/
public String fetchEnvironmentStatus() {
String result = "";
String allStatuses = "";
Set<String> cloneStatuses = new HashSet<String>();
for (CloneTask cloneTask : cloneTasks) {
String status = cloneTask.fetchVmStatus();
cloneStatuses.add(status);
allStatuses = allStatuses + status + " ";
}
if (cloneStatuses.contains("Shut Down")) {
result = "Shut Down";
}
else if (cloneStatuses.contains("Shutting Down")) {
result = "Shutting Down";
}
else if (cloneStatuses.contains("Starting")) {
result = "Starting";
}
else if (cloneStatuses.contains("Powered Off Or Starting")) {
result = "Powered Off Or Starting";
}
else if (cloneStatuses.contains("Not Started or Powered Off")) {
result = "Not Started or Powered Off";
}
else if (cloneStatuses.contains("Running")) {
result = "Running";
}
log.debug("result status: " + result);
log.debug("all statuses: " + allStatuses);
if (result.equals("")) {
result = "Unknown";
}
return result;
}
//----------------------------------------------------------------------------------------------
/**
* Order of operations:
* Creates folder in vCenter.
* Creates private network (virtual switch and port group) if one was specified.
* Creates clones in order (see create method on CloneTask for order of that operation).
* Sleeps for some seconds to give vCenter time to update.
* Fetches the IP addresses of the VMs.
*/
@Override
public void create() {
if (host == null) {
throw new NullPointerException("Host is null!");
}
//folder task
createFolder();
addUUIDToFolderName();
folderTask.setFolderName(folderName);
folderTask.create();
//private network tasks
for (NetworkTask network : networkTasks) {
network.setVirtualHost(host);
network.setHostPath(hostPath);
network.create();
}
//vm clone tasks
addNewClonesToTaskList();
if (cloneTasks.size() > 0) {
try {
createClonesInOrder();
} catch (Exception e1) {
log.warn("exception while creating clones", e1);
}
try {
//wait for new IPs to get updated
log.info("Sleeping until new IPs are allocated.");
Thread.sleep(20000);
} catch (InterruptedException e1) {
log.warn("interrupted exception while waiting for new IPs", e1);
}
}
for (CloneTask cloneTask : cloneTasks) {
try {
cloneTask.setIpListFromVmInfo();
}
catch (Exception e) {
log.warn("exception when setting IP info", e);
}
}
}
//----------------------------------------------------------------------------------------------
/**
* Order of operations:
* Deletes each VM from disk.
* Deletes the folder.
* Deletes the virtual switch and port group.
*/
@Override
public void destroy() {
//vm clone tasks
try {
//restore folder task and vmware Folder object
createFolder();
folderTask.setFolderName(folderName);
folderTask.restore();
Collections.sort(cloneTasks);
Collections.reverse(cloneTasks);
for (CloneTask cloneTask : cloneTasks) {
cloneTask.destroy();
}
folderTask.destroy();
for (NetworkTask network : networkTasks) {
network.setVirtualHost(host);
network.setHostPath(hostPath);
network.destroy();
}
}
catch(RestorationException e) {
log.warn("Problem with restoring folder.", e);
}
}
//----------------------------------------------------------------------------------------------
/**
* Calls the restore() method on the folderTask, cloneTasks, and networkTasks in this environment.
*/
@Override
public void restore() {
//vm clone tasks
try {
//restore folder task and vmware Folder object
createFolder();
folderTask.setFolderName(folderName);
folderTask.restore();
for (CloneTask cloneTask : cloneTasks) {
cloneTask.restore();
}
for (NetworkTask network : networkTasks) {
network.setVirtualHost(host);
network.setHostPath(hostPath);
}
}
catch(RestorationException e) {
log.warn("Problem with restoring folder.", e);
}
}
}