/** * Abiquo community edition * cloud management application for hybrid clouds * Copyright (C) 2008-2010 - Abiquo Holdings S.L. * * This application is free software; you can redistribute it and/or * modify it under the terms of the GNU LESSER GENERAL PUBLIC * LICENSE as published by the Free Software Foundation under * version 3 of the License * * This software is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU * LESSER GENERAL PUBLIC LICENSE v.3 for more details. * * You should have received a copy of the GNU Lesser General Public * License along with this library; if not, write to the * Free Software Foundation, Inc., 59 Temple Place - Suite 330, * Boston, MA 02111-1307, USA. */ package com.abiquo.api.resources.cloud; import java.util.List; import java.util.Map; import java.util.Map.Entry; import javax.ws.rs.Consumes; import javax.ws.rs.DELETE; import javax.ws.rs.GET; import javax.ws.rs.POST; import javax.ws.rs.PUT; import javax.ws.rs.Path; import javax.ws.rs.PathParam; import javax.ws.rs.Produces; import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; import javax.ws.rs.core.UriInfo; import org.apache.commons.lang.StringUtils; import org.apache.wink.common.annotations.Parent; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Controller; import com.abiquo.api.resources.AbstractResource; import com.abiquo.api.resources.TaskResourceUtils; import com.abiquo.api.services.NetworkService; import com.abiquo.api.services.cloud.VirtualApplianceService; import com.abiquo.api.services.cloud.VirtualMachineLock; import com.abiquo.api.util.IRESTBuilder; import com.abiquo.model.rest.RESTLink; import com.abiquo.model.transport.AcceptedRequestDto; import com.abiquo.model.util.ModelTransformer; import com.abiquo.scheduler.SchedulerLock; import com.abiquo.server.core.appslibrary.VirtualMachineTemplateDto; import com.abiquo.server.core.cloud.VirtualAppliance; import com.abiquo.server.core.cloud.VirtualApplianceDto; import com.abiquo.server.core.cloud.VirtualApplianceState; import com.abiquo.server.core.cloud.VirtualApplianceStateDto; import com.abiquo.server.core.cloud.VirtualMachineState; import com.abiquo.server.core.cloud.VirtualMachineTaskDto; import com.abiquo.server.core.infrastructure.network.IpPoolManagement; import com.abiquo.server.core.infrastructure.network.IpsPoolManagementDto; import com.abiquo.server.core.task.Task; import com.abiquo.server.core.task.TasksDto; @Parent(VirtualAppliancesResource.class) @Path(VirtualApplianceResource.VIRTUAL_APPLIANCE_PARAM) @Controller public class VirtualApplianceResource extends AbstractResource { public static final String VIRTUAL_APPLIANCE = "virtualappliance"; public static final String VIRTUAL_APPLIANCE_PARAM = "{" + VIRTUAL_APPLIANCE + "}"; public static final String VIRTUAL_APPLIANCE_GET_IPS_PATH = "action/ips"; public static final String VIRTUAL_APPLIANCE_DEPLOY_PATH = "action/deploy"; public static final String VIRTUAL_APPLIANCE_DEPLOY_REL = "deploy"; public static final String VIRTUAL_APPLIANCE_UNDEPLOY_PATH = "action/undeploy"; public static final String VIRTUAL_APPLIANCE_UNDEPLOY_REL = "undeploy"; public static final String VIRTUAL_APPLIANCE_PRICE_PATH = "action/price"; public static final String VIRTUAL_APPLIANCE_PRICE_REL = "price"; public static final String VIRTUAL_APPLIANCE_STATE_REL = "state"; public static final String VIRTUAL_APPLIANCE_FORCE_DELETE_PARAM = "force"; public static final String VIRTUAL_APPLIANCE_ACTION_PRICE = "/action/price"; @Autowired private VirtualApplianceService service; @Autowired private NetworkService netService; @Autowired private VirtualMachineLock vmLock; /** * Return the virtual appliance if exists. And also the expanded nodes. <br> * <ul> * <li>Last Task: returns the last {@link Task} of every node * </ul> * * @title Retrieve a virtual appliance * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param restBuilder to build the links * @return the {@link VirtualApplianceDto} transfer object for the virtual appliance. * @throws Exception */ @GET @Produces(VirtualApplianceDto.MEDIA_TYPE) public VirtualApplianceDto getVirtualAppliance( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, @QueryParam(value = "expand") final String expand, @Context final IRESTBuilder restBuilder, @Context final UriInfo uriInfo) throws Exception { VirtualAppliance vapp = service.getVirtualAppliance(vdcId, vappId); VirtualApplianceDto dto = createTransferObject(vapp, restBuilder); expandNodes(vdcId, vappId, expand, uriInfo, dto); return dto; } private void expandNodes(final Integer vdcId, final Integer vappId, final String expand, final UriInfo uriInfo, final VirtualApplianceDto dto) { String[] expands = StringUtils.split(expand, ","); if (expands != null) { for (String e : expands) { if ("last_task".equalsIgnoreCase(e)) { List<Task> lastTasks = service.getAllNodesLastTask(vdcId, vappId); if (lastTasks != null && !lastTasks.isEmpty()) { TasksDto t = TaskResourceUtils.transform(lastTasks, uriInfo); dto.setLastTasks(t); } } } } } /** * Modifies a virtual appliance * * @title Modify a virtual appliance * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param dto virtual appliance to modify * @param restBuilder a Context-injected object to create the links of the Dto * @return a {VirtualApplianceDto} with the modified virtual appliance * @throws Exception */ @PUT @Consumes(VirtualApplianceDto.MEDIA_TYPE) @Produces(VirtualApplianceDto.MEDIA_TYPE) public VirtualApplianceDto updateVirtualAppliance( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, final VirtualApplianceDto dto, @Context final IRESTBuilder restBuilder) throws Exception { VirtualAppliance vapp = service.updateVirtualAppliance(vdcId, vappId, dto); return createTransferObject(vapp, restBuilder); } /** * Returns all IPs from a vrtual appliance * * @title Retrive all IPs * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param restBuilder a Context-injected object to create the links of the Dto * @return a {IpsPollManagementDto} with all IPs of the virtual appliance * @throws Exception */ @GET @Path(VirtualApplianceResource.VIRTUAL_APPLIANCE_GET_IPS_PATH) @Produces(IpsPoolManagementDto.MEDIA_TYPE) public IpsPoolManagementDto getIPsByVirtualAppliance( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, @Context final IRESTBuilder restBuilder) throws Exception { VirtualAppliance vapp = service.getVirtualAppliance(vdcId, vappId); // Get the list of ipPoolManagements objects List<IpPoolManagement> all = netService.getListIpPoolManagementByVirtualApp(vapp); IpsPoolManagementDto ips = new IpsPoolManagementDto(); for (IpPoolManagement ip : all) { ips.add(IpAddressesResource.createTransferObject(ip, restBuilder)); } return ips; } /** * Return the {@link VirtualApplianceDto} object from the POJO {@link VirtualAppliance} * * @param vapp object to convert * @param builder context rest builder * @return the result Dto object * @throws Exception */ public static VirtualApplianceDto createTransferObject(final VirtualAppliance vapp, final IRESTBuilder builder) throws Exception { VirtualApplianceDto dto = ModelTransformer.transportFromPersistence(VirtualApplianceDto.class, vapp); dto = addLinks(builder, dto, vapp.getVirtualDatacenter().getId(), vapp.getEnterprise() .getId()); return dto; } private static VirtualApplianceDto addLinks(final IRESTBuilder builder, final VirtualApplianceDto dto, final Integer vdcId, final Integer enterpriseId) { dto.setLinks(builder.buildVirtualApplianceLinks(dto, vdcId, enterpriseId)); return dto; } // @PUT // @Path(VIRTUAL_APPLIANCE_ACTION_ADD_IMAGE) /***********************************/ /***********************************/ /* EXPERIMENTAL, NOT AVAILABLE YET */ /***********************************/ /***********************************/ public void addImage( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, final VirtualMachineTemplateDto vmtemplate) { /** * TODO */ } /** * Returns the state of the virtual appliance * * @title Retrieve the state of the virtual appliance * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param restBuilder a Context-injected object to create the links of the Dto * @return a {VirtualAplianceStateDto} object with the state of the virtual appliance * @throws Exception */ @GET @Path(VIRTUAL_APPLIANCE_STATE_REL) @Produces(VirtualApplianceStateDto.MEDIA_TYPE) public VirtualApplianceStateDto getChangeState( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, @Context final IRESTBuilder restBuilder) throws Exception { VirtualApplianceState state = service.getVirtualApplianceState(vdcId, vappId); VirtualApplianceStateDto dto = virtualApplianceStateToDto(vdcId, vappId, restBuilder, state); return dto; } /** * Start a deploy task for all virtual machines of a virtual appliances * * @title Deploy a virtual appliance * @wiki Deploys all the virtual machines in the virtual appliance with the given options. This * call returns a 202 HTTP code (accepted) and a list of URIs where you can track each * deploy (one for each virtual machine in the virtual appliance). The options are yet to * be finalized. For the purposes of this example we will be using: * -forceEnterpriseSoftLimits = OFF * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param taskOptions options of the task * @param restBuilder a Context-injected object to create the links of the Dto * @param uriInfo * @return a {AcceptedRequestDto} object * @throws Exception */ @POST @Path(VIRTUAL_APPLIANCE_DEPLOY_PATH) @Consumes(VirtualMachineTaskDto.MEDIA_TYPE) @Produces(AcceptedRequestDto.MEDIA_TYPE) public AcceptedRequestDto<String> deploy( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, final VirtualMachineTaskDto taskOptions, @Context final IRESTBuilder restBuilder, @Context final UriInfo uriInfo) throws Exception { AcceptedRequestDto<String> dto = new AcceptedRequestDto<String>(); // by default force limits final boolean forceLimits = taskOptions != null ? taskOptions.isForceEnterpriseSoftLimits() : true; // Lock all virtual machines in the vapp Map<Integer, VirtualMachineState> originalStates = vmLock.lockVirtualApplianceBeforeDeploying(vdcId, vappId); final String lockMsg = "Allocate vapp " + vappId; try { SchedulerLock.acquire(lockMsg); Map<Integer, String> links = service.deployVirtualAppliance(vdcId, vappId, forceLimits); addStatusLinks(links, dto, uriInfo); } catch (Exception ex) { // Make sure all virtual machines are unlocked if deploy fails vmLock.unlockVirtualMachines(originalStates); throw ex; } finally { SchedulerLock.release(lockMsg); } return dto; } /** * Start an undeploy task for all virtual machines of a virtual appliances * * @title Uneploy a virtual appliance * @wiki Perform an undeploy of all the machines that are DEPLOYED (meaning that they exist in * the hypervisor). This means that after the call, all of the virtual machines in that * virtual appliance in Abiquo will be NOT_ALLOCATED. If an undeploy is successful, all * the virtual machines in that virtual appliance will be deleted from the hypervisor. If * any of the virtual machines are in the ON state, Abiquo will perform a power off before * deconfiguring them. You can also set the force undeploy parameter in the virtual * machine task entity. If this is set to true, it will also delete the imported virtual * machines. This call returns a 202 HTTP code (accepted) and a list of URIs where you can * track each undeploy (one for each virtual machine in the virtual appliance). * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param taskOptions options of the task * @param restBuilder a Context-injected object to create the links of the Dto * @param uriInfo * @return a {AcceptedRequestDto} * @throws Exception */ @POST @Path(VIRTUAL_APPLIANCE_UNDEPLOY_PATH) @Consumes(VirtualMachineTaskDto.MEDIA_TYPE) @Produces(AcceptedRequestDto.MEDIA_TYPE) public AcceptedRequestDto<String> undeploy( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, final VirtualMachineTaskDto taskOptions, @Context final IRESTBuilder restBuilder, @Context final UriInfo uriInfo) throws Exception { Boolean forceUndeploy; if (taskOptions.getForceUndeploy() == null) { forceUndeploy = Boolean.FALSE; } else { forceUndeploy = taskOptions.getForceUndeploy(); } // Lock all virtual machines in the vapp Map<Integer, VirtualMachineState> originalStates = vmLock.lockVirtualApplianceBeforeUndeploying(vdcId, vappId); try { Map<Integer, String> links = service.undeployVirtualAppliance(vdcId, vappId, forceUndeploy, originalStates); AcceptedRequestDto<String> dto = new AcceptedRequestDto<String>(); addStatusLinks(links, dto, uriInfo); return dto; } catch (Exception ex) { // Make sure all virtual machines are unlocked if deploy fails vmLock.unlockVirtualMachines(originalStates); throw ex; } } private VirtualApplianceStateDto virtualApplianceStateToDto(final Integer vdcId, final Integer vappId, final IRESTBuilder restBuilder, final VirtualApplianceState state) { VirtualApplianceStateDto dto = new VirtualApplianceStateDto(); dto.setPower(state); dto.addLinks(restBuilder.buildVirtualApplianceStateLinks(dto, vappId, vdcId)); return dto; } private void addStatusLinks(final Map<Integer, String> links, final AcceptedRequestDto<String> dto, final UriInfo uriInfo) { for (Entry<Integer, String> e : links.entrySet()) { Integer vmId = e.getKey(); String url = e.getValue(); RESTLink link = buildTaskLink(vmId, url, uriInfo); dto.addLink(link); } } /** * Delete the virtual appliance if exists. * * @title Delete a virtual appliance * @wiki This deletes a Virtual Appliance * @param vdcId identifier of the virtual datacenter. * @param vappId identifier of the virtual appliance. * @param restBuilder to build the links * @throws Exception */ @DELETE public void deleteVirtualAppliance( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId) throws Exception { // Lock all virtual machines in the vapp Map<Integer, VirtualMachineState> originalStates = vmLock.lockVirtualApplianceBeforeDeleting(vdcId, vappId); try { service.deleteVirtualAppliance(vdcId, vappId, originalStates); // TODO: Should return a task list if the vapp is going to be undeployed? } catch (Exception ex) { // Make sure all virtual machines are unlocked if deploy fails vmLock.unlockVirtualMachines(originalStates); throw ex; } } /** * Returns a message (String) with the price info of the virtual appliance * * @title Retrieve the price message * @param vdcId identifier of the virtual datacenter * @param vappId identifier of the virtual appliance * @param restBuilder a Context-injected object to create the links of the Dto * @return a {String} with the price message of the virtual appliance * @throws Exception */ @GET @Path(VIRTUAL_APPLIANCE_PRICE_PATH) @Produces(MediaType.TEXT_PLAIN) public String getPriceVirtualAppliance( @PathParam(VirtualDatacenterResource.VIRTUAL_DATACENTER) final Integer vdcId, @PathParam(VirtualApplianceResource.VIRTUAL_APPLIANCE) final Integer vappId, @Context final IRESTBuilder restBuilder) throws Exception { // VirtualAppliancePriceDto virtualAppliancePriceDto = // service.getPriceVirtualAppliance(vdcId, vappId); // return virtualAppliancePriceDto; String virtualAppliancePrice = service.getPriceVirtualApplianceText(vdcId, vappId); return virtualAppliancePrice; } protected RESTLink buildTaskLink(final Integer vmId, final String taskId, final UriInfo uriInfo) { // Build task link String link = uriInfo.getRequestUri().toString(); link = link.replaceAll("action.*", ""); link = link.replaceAll("(/)*$", ""); link = link.concat("/").concat(VirtualMachinesResource.VIRTUAL_MACHINES_PATH).concat("/") .concat(String.valueOf(vmId)).concat(TaskResourceUtils.TASKS_PATH).concat("/") .concat(taskId); return new RESTLink("status", link); } }