/** * 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.services; import static com.abiquo.api.resources.RemoteServiceResource.createTransferObject; import static com.abiquo.server.core.infrastructure.RemoteService.STATUS_ERROR; import static com.abiquo.server.core.infrastructure.RemoteService.STATUS_SUCCESS; import java.net.URI; import java.net.URISyntaxException; import java.util.List; import javax.persistence.EntityManager; import javax.ws.rs.WebApplicationException; import org.apache.wink.client.ClientConfig; import org.apache.wink.client.ClientResponse; import org.apache.wink.client.Resource; import org.apache.wink.client.RestClient; import org.apache.wink.common.internal.utils.UriHelper; import org.springframework.beans.factory.annotation.Autowired; import org.springframework.stereotype.Service; import org.springframework.transaction.annotation.Propagation; import org.springframework.transaction.annotation.Transactional; import org.springframework.util.StringUtils; import com.abiquo.api.exceptions.APIError; import com.abiquo.appliancemanager.client.AMClient; import com.abiquo.appliancemanager.client.AMClientException; import com.abiquo.model.enumerator.RemoteServiceType; import com.abiquo.model.transport.SingleResourceTransportDto; import com.abiquo.model.transport.error.ErrorDto; import com.abiquo.model.transport.error.ErrorsDto; import com.abiquo.server.core.infrastructure.Datacenter; import com.abiquo.server.core.infrastructure.InfrastructureRep; import com.abiquo.server.core.infrastructure.RemoteService; import com.abiquo.server.core.infrastructure.RemoteServiceDto; import com.abiquo.server.core.infrastructure.Repository; import com.abiquo.tracer.ComponentType; import com.abiquo.tracer.EventType; import com.abiquo.tracer.SeverityType; @Service("remoteServiceService") @Transactional(readOnly = true) public class RemoteServiceService extends DefaultApiService { public static final String CHECK_RESOURCE = "check"; @Autowired private InfrastructureRep infrastructureRepo; public RemoteServiceService() { } public RemoteServiceService(final EntityManager em) { infrastructureRepo = new InfrastructureRep(em); } public List<RemoteService> getRemoteServices() { return infrastructureRepo.findAllRemoteServices(); } public List<RemoteService> getRemoteServicesByDatacenter(final Integer datacenterId) { Datacenter datacenter = infrastructureRepo.findById(datacenterId); if (datacenter == null) { addNotFoundErrors(APIError.NON_EXISTENT_DATACENTER); flushErrors(); } return infrastructureRepo.findRemoteServicesByDatacenter(datacenter); } /** * Add a new remoteService * * @param rs remoteServoce * @param datacenter datacenter where add it * @return TransferObject: RemoteServiceDto if OK, ErrorsDto else */ @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public SingleResourceTransportDto addRemoteService(final RemoteService rs, final Datacenter datacenter) { return addRemoteService(rs, datacenter, false); } @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public RemoteServiceDto addRemoteService(final RemoteService rs, final Integer datacenterId) { Datacenter datacenter = infrastructureRepo.findById(datacenterId); return (RemoteServiceDto) addRemoteService(rs, datacenter, true); } @Transactional(propagation = Propagation.REQUIRED, readOnly = false) public SingleResourceTransportDto addRemoteService(final RemoteService rs, final Datacenter datacenter, final boolean flushErrors) { if (rs.getType() == null) { addValidationErrors(APIError.WRONG_REMOTE_SERVICE_TYPE); flushErrors(); } if (datacenter == null) { addNotFoundErrors(APIError.NON_EXISTENT_DATACENTER); flushErrors(); } RemoteServiceDto responseDto = new RemoteServiceDto(); ErrorsDto errorsDto = checkUniqueness(datacenter, rs, flushErrors); if (!flushErrors && (errorsDto.getCollection() == null || errorsDto.getCollection().size() > 0)) { return errorsDto; } else { RemoteService remoteService = datacenter.createRemoteService(rs.getType(), rs.getUri(), 0); ErrorsDto configurationErrors = validateRemoteService(datacenter, rs, remoteService, responseDto); infrastructureRepo.insertRemoteService(remoteService); responseDto = createTransferObject(remoteService); if (!configurationErrors.isEmpty()) { responseDto.setConfigurationErrors(configurationErrors); // can't add an AM with errors as the Datacenter repository won't be created if (rs.getType() == RemoteServiceType.APPLIANCE_MANAGER) { infrastructureRepo.deleteRemoteService(remoteService); tracer.log(SeverityType.WARNING, ComponentType.DATACENTER, EventType.REMOTE_SERVICES_CREATE, "remoteServices.am.error", responseDto.getUri(), datacenter.getName(), configurationErrors.toString()); return responseDto; } } } tracer.log(SeverityType.INFO, ComponentType.DATACENTER, EventType.REMOTE_SERVICES_CREATE, "remoteServices.created", responseDto.getType().getName(), responseDto.getUri(), datacenter.getName()); return responseDto; } /** * Configure the Datacenter repository based on the ''repositoryLocation'' consulted from AM. */ private ErrorsDto createApplianceManager(final Datacenter datacenter, final RemoteService remoteService) { int previousStatus = remoteService.getStatus(); ErrorsDto configurationErrors = new ErrorsDto(); if (infrastructureRepo.isRepositoryBeingUsed(datacenter)) { remoteService.setStatus(STATUS_ERROR); APIError error = APIError.APPLIANCE_MANAGER_REPOSITORY_IN_USE; configurationErrors.add(new ErrorDto(error.getCode(), error.getMessage())); return configurationErrors; } if (previousStatus == STATUS_SUCCESS) { String repositoryLocation = null; try { try { repositoryLocation = getAMConfiguredRepositoryLocation(remoteService.getUri()); } catch (AMClientException amEx) { remoteService.setStatus(STATUS_ERROR); APIError error = APIError.REMOTE_SERVICE_CONNECTION_FAILED; configurationErrors.add(new ErrorDto(error.getCode(), remoteService.getType() .getName() + ", " + amEx.getMessage())); return configurationErrors; } if (infrastructureRepo.existRepositoryInOtherDatacenter(datacenter, repositoryLocation)) { remoteService.setStatus(STATUS_ERROR); APIError error = APIError.APPLIANCE_MANAGER_REPOSITORY_ALREADY_DEFINED; configurationErrors.add(new ErrorDto(error.getCode(), error.getMessage())); return configurationErrors; } if (!infrastructureRepo.existRepositoryInSameDatacenter(datacenter, repositoryLocation)) { infrastructureRepo.createRepository(datacenter, repositoryLocation); } } catch (WebApplicationException e) { remoteService.setStatus(STATUS_ERROR); APIError error = APIError.REMOTE_SERVICE_CONNECTION_FAILED; configurationErrors.add(new ErrorDto(error.getCode(), remoteService.getType() .getName() + ", " + error.getMessage())); return configurationErrors; } } // we don't want to serialize the errors if they are empty return configurationErrors; } private String getAMConfiguredRepositoryLocation(final String serviceUri) throws AMClientException { return new AMClient().initialize(serviceUri, false).getRepositoryConfiguration() .getLocation(); } public RemoteService getRemoteService(final Integer id) { return infrastructureRepo.findRemoteServiceById(id); } public RemoteService getVSMRemoteService(final Datacenter datacenter) { return getRemoteService(datacenter.getId(), RemoteServiceType.VIRTUAL_SYSTEM_MONITOR); } public RemoteService getAMRemoteService(final Datacenter datacenter) { return getRemoteService(datacenter.getId(), RemoteServiceType.APPLIANCE_MANAGER); } public RemoteService getRemoteService(final Integer datacenterId, final RemoteServiceType type) { Datacenter datacenter = infrastructureRepo.findById(datacenterId); if (datacenter == null) { addNotFoundErrors(APIError.NON_EXISTENT_DATACENTER); flushErrors(); } List<RemoteService> services = infrastructureRepo.findRemoteServiceWithTypeInDatacenter(datacenter, type); RemoteService remoteService = null; if (!services.isEmpty()) { // Only one remote service of each type by datacenter. remoteService = services.get(0); } // DHCP is not required else if (type != RemoteServiceType.DHCP_SERVICE) { addNotFoundErrors(APIError.NON_EXISTENT_REMOTE_SERVICE_TYPE); flushErrors(); } return remoteService; } @Transactional(propagation = Propagation.REQUIRED) public RemoteServiceDto modifyRemoteService(final Integer id, final RemoteServiceDto dto) throws URISyntaxException { RemoteService old = getRemoteService(id); // check new uri if (org.apache.commons.lang.StringUtils.isBlank(dto.getUri())) { addValidationErrors(APIError.REMOTE_SERVICE_MALFORMED_URL); flushErrors(); } // if it's the same uri, we must check the rs to update the state // (it can change [stop, redis,rabbit,...]) if (old.getUri().equals(dto.getUri())) { final ErrorsDto checkError = checkRemoteServiceStatus(old.getDatacenter(), dto.getType(), dto.getUri()); if (checkError.isEmpty()) { old.setStatus(STATUS_SUCCESS); dto.setStatus(STATUS_SUCCESS); } else { old.setStatus(STATUS_ERROR); dto.setStatus(STATUS_ERROR); } infrastructureRepo.updateRemoteService(old); tracer .log(SeverityType.INFO, ComponentType.DATACENTER, EventType.REMOTE_SERVICES_UPDATE, "remoteServices.updated", dto.getType().getName()); return dto; } try { URI uriChecked = new URI(dto.getUri()); if (uriChecked.getPort() < 0) { addConflictErrors(APIError.REMOTE_SERVICE_UNDEFINED_PORT); flushErrors(); } else { if (dto.getType().checkUniqueness()) { if (infrastructureRepo.existAnyRemoteServiceWithUri(dto.getUri())) { addConflictErrors(APIError.REMOTE_SERVICE_URL_ALREADY_EXISTS); flushErrors(); } } } } catch (URISyntaxException e) { addConflictErrors(APIError.REMOTE_SERVICE_MALFORMED_URL); flushErrors(); } final ErrorsDto checkError = checkRemoteServiceStatus(old.getDatacenter(), dto.getType(), dto.getUri()); if (!checkError.isEmpty()) { addConflictErrors(APIError.REMOTE_SERVICE_CANNOT_BE_CHECKED); flushErrors(); } old.setUri(dto.getUri()); old.setType(dto.getType()); old.setStatus(STATUS_SUCCESS); dto.setStatus(STATUS_SUCCESS); if (dto.getType() == RemoteServiceType.APPLIANCE_MANAGER) { checkModifyApplianceManager(old, dto); } flushErrors(); infrastructureRepo.updateRemoteService(old); RemoteServiceDto responseDto = createTransferObject(old); tracer.log(SeverityType.INFO, ComponentType.DATACENTER, EventType.REMOTE_SERVICES_UPDATE, "remoteServices.updated", dto.getType().getName()); return responseDto; } /** * If the current datacenter have a repository being used then the new appliance manager MUST * use the same repository uri. Also updates the repository location (if the old isn't being * used). */ private void checkModifyApplianceManager(final RemoteService old, final RemoteServiceDto dto) { if (infrastructureRepo.isRepositoryBeingUsed(old.getDatacenter())) { if (dto.getStatus() == STATUS_SUCCESS) { try { String newRepositoryLocation = getAMConfiguredRepositoryLocation(dto.getUri()); Repository oldRepository = infrastructureRepo.findRepositoryByDatacenter(old.getDatacenter()); String oldRepositoryLocation = oldRepository.getUrl(); if (!oldRepositoryLocation.equalsIgnoreCase(newRepositoryLocation)) { addConflictErrors(APIError.APPLIANCE_MANAGER_REPOSITORY_IN_USE); } } catch (WebApplicationException e) { addConflictErrors(APIError.APPLIANCE_MANAGER_REPOSITORY_IN_USE); } catch (AMClientException e) { addConflictErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); } } else // STATUES_ERROR { addConflictErrors(APIError.APPLIANCE_MANAGER_REPOSITORY_IN_USE); } } else if (dto.getStatus() == STATUS_SUCCESS) { String repositoryLocation = null; try { repositoryLocation = getAMConfiguredRepositoryLocation(dto.getUri()); } catch (AMClientException amEx) { addConflictErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); } if (repositoryLocation != null && infrastructureRepo.existRepositoryInOtherDatacenter(old.getDatacenter(), repositoryLocation)) { addConflictErrors(APIError.APPLIANCE_MANAGER_REPOSITORY_ALREADY_DEFINED); } else if (repositoryLocation != null) { infrastructureRepo .updateRepositoryLocation(old.getDatacenter(), repositoryLocation); } } else // the old repository is not being used and the new am is not properly configured { infrastructureRepo.deleteRepository(old.getDatacenter()); } // ABICLOUDPREMIUM-719 Do not allow the appliance manager modification if the repository is // being used and it changes it location. flushErrors(); } @Transactional(propagation = Propagation.REQUIRED) public void removeRemoteService(final Integer id) { RemoteService remoteService = getRemoteService(id); checkRemoteServiceStatusBeforeRemoving(remoteService); infrastructureRepo.deleteRemoteService(remoteService); tracer.log(SeverityType.INFO, ComponentType.DATACENTER, EventType.REMOTE_SERVICES_DELETE, "remoteServices.deleted", remoteService.getType().getName()); } protected void checkRemoteServiceStatusBeforeRemoving(final RemoteService remoteService) { if (remoteService.getType() == RemoteServiceType.APPLIANCE_MANAGER) { if (infrastructureRepo.isRepositoryBeingUsed(remoteService.getDatacenter())) { addConflictErrors(APIError.APPLIANCE_MANAGER_REPOSITORY_IN_USE); flushErrors(); } infrastructureRepo.deleteRepository(remoteService.getDatacenter()); } if (remoteService.getType() == RemoteServiceType.DHCP_SERVICE || remoteService.getType() == RemoteServiceType.VIRTUAL_SYSTEM_MONITOR) { if (infrastructureRepo.existDeployedVirtualMachines(remoteService.getDatacenter())) { if (remoteService.getType() == RemoteServiceType.VIRTUAL_SYSTEM_MONITOR) { addConflictErrors(APIError.REMOTE_SERVICE_VSM_IS_BEING_USED); } // JIRA: ABICLOUDPREMIUM-3009 - Final decision: DHCP service can be deleted always // else // { // addConflictErrors(APIError.REMOTE_SERVICE_DHCP_IS_BEING_USED); // } flushErrors(); } } } public boolean isAssignedTo(final Integer datacenterId, final String remoteServiceMapping) { RemoteServiceType type = RemoteServiceType.valueOf(remoteServiceMapping.toUpperCase()); return isAssignedTo(datacenterId, type); } public boolean isAssignedTo(final Integer datacenterId, final RemoteServiceType type) { RemoteService remoteService = null; if (type != null) { remoteService = getRemoteService(datacenterId, type); } return type != null && remoteService != null && remoteService.getDatacenter().getId().equals(datacenterId); } public ErrorsDto checkRemoteServiceStatus(final Datacenter datacenter, final RemoteServiceType type, final String url) { return checkRemoteServiceStatus(datacenter, type, url, false); } @Transactional(propagation = Propagation.REQUIRES_NEW) public ErrorsDto checkRemoteServiceStatus(final Datacenter datacenter, final RemoteServiceType type, final String url, final boolean flushErrors) { ErrorsDto configurationErrors = new ErrorsDto(); if (type.canBeChecked()) { ClientConfig config = new ClientConfig(); config.connectTimeout(5000); RestClient restClient = new RestClient(config); String uriToCheck = UriHelper.appendPathToBaseUri(url, CHECK_RESOURCE); Resource checkResource = restClient.resource(uriToCheck); try { ClientResponse response = checkResource.get(); if (response.getStatusCode() != 200) { configurationErrors.add(createRemoteServiceConnectionError(type, response)); if (flushErrors) { switch (response.getStatusCode()) { case 404: addNotFoundErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); break; case 503: addServiceUnavailableErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); break; default: addNotFoundErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); break; } } }// remote service check fail else { if (type.checkDatacenterId()) { final String rsDatacenterUuid = response.getEntity(String.class); if (!StringUtils.hasText(rsDatacenterUuid)) { final APIError error = APIError.REMOTE_SERVICE_DATACENTER_UUID_NOT_FOUND; configurationErrors.add(new ErrorDto(error.getCode(), type.getName() + ", " + error.getMessage())); if (flushErrors) { addConflictErrors(error); } } else if (!isValidDatacenterUuid(rsDatacenterUuid, datacenter)) { final APIError error = APIError.REMOTE_SERVICE_DATACENTER_UUID_INCONSISTENT; configurationErrors.add(new ErrorDto(error.getCode(), type.getName() + ", " + error.getMessage() + "\n Current datacenter UUID is " + datacenter.getUuid())); if (flushErrors) { addConflictErrors(error); } } }// datacenter uuid } } catch (Exception e) { configurationErrors.add(new ErrorDto(APIError.REMOTE_SERVICE_CONNECTION_FAILED .getCode(), type.getName() + ", " + APIError.REMOTE_SERVICE_CONNECTION_FAILED.getMessage() + ", " + e.getMessage())); if (flushErrors) { addNotFoundErrors(APIError.REMOTE_SERVICE_CONNECTION_FAILED); } } } else if (flushErrors) { addConflictErrors(APIError.REMOTE_SERVICE_CANNOT_BE_CHECKED); } if (flushErrors) { flushErrors(); } return configurationErrors; } /** * Crates a REMOTE_SERVICE_CONNECTION_FAILED Error containing the response body (if any) or the * status message */ private ErrorDto createRemoteServiceConnectionError(final RemoteServiceType type, final ClientResponse clientResponse) { String failedBody = null; try { failedBody = clientResponse.getEntity(String.class); } catch (Exception e) { } return new ErrorDto(APIError.REMOTE_SERVICE_CONNECTION_FAILED.getCode(), type.getName() + ", " + String.format("%s\nCaused by:[%d] - [%s]", APIError.REMOTE_SERVICE_CONNECTION_FAILED .getMessage(), clientResponse.getStatusCode(), org.apache.commons.lang.StringUtils .isEmpty(failedBody) ? clientResponse.getMessage() : failedBody)); } /** * Checks the datacenter uuid (or set it if not already defined) * * @param rsDatacenterId, UUID from the remote service * @param datacenter, current datacenter * @return true if the informed datacenter uuid is consistent. */ private boolean isValidDatacenterUuid(final String rsDatacenterId, final Datacenter datacenter) { final String datacenterUuid = datacenter.getUuid(); if (!StringUtils.hasText(datacenterUuid)) { datacenter.setUuid(rsDatacenterId); infrastructureRepo.update(datacenter); return true; } else if (rsDatacenterId.equals(datacenterUuid)) { return true; } else { return false; } } // --------------- // // PRIVATE METHODS // // --------------- // private ErrorsDto checkUniqueness(final Datacenter datacenter, final RemoteService remoteService, final boolean flushErrors) { ErrorsDto configurationErrors = new ErrorsDto(); if (infrastructureRepo.existAnyRemoteServiceWithTypeInDatacenter(datacenter, remoteService.getType())) { APIError error = APIError.REMOTE_SERVICE_TYPE_EXISTS; configurationErrors.add(new ErrorDto(error.getCode(), remoteService.getType().getName() + " : " + error.getMessage())); if (flushErrors) { addConflictErrors(error); } } try { URI uriChecked = new URI(remoteService.getUri()); if (uriChecked.getPort() < 0) { APIError error = APIError.REMOTE_SERVICE_UNDEFINED_PORT; configurationErrors.add(new ErrorDto(error.getCode(), remoteService.getType() .getName() + " : " + error.getMessage())); if (flushErrors) { addConflictErrors(error); } } else { if (remoteService.getType().checkUniqueness()) { if (infrastructureRepo.existAnyRemoteServiceWithUri(uriChecked.toString())) { APIError error = APIError.REMOTE_SERVICE_URL_ALREADY_EXISTS; configurationErrors.add(new ErrorDto(error.getCode(), remoteService .getType().getName() + " : " + error.getMessage())); if (flushErrors) { addConflictErrors(error); } } } } } catch (URISyntaxException e) { APIError error = APIError.REMOTE_SERVICE_MALFORMED_URL; configurationErrors.add(new ErrorDto(error.getCode(), remoteService.getType().getName() + " : " + error.getMessage())); if (flushErrors) { addValidationErrors(error); } } if (flushErrors) { flushErrors(); } return configurationErrors; } private ErrorsDto validateRemoteService(final Datacenter datacenter, final RemoteService rs, final RemoteService remoteService, final RemoteServiceDto responseDto) { if (!remoteService.isValid()) { addValidationErrors(remoteService.getValidationErrors()); flushErrors(); } ErrorsDto configurationErrors = checkRemoteServiceStatus(datacenter, remoteService.getType(), remoteService.getUri()); int status = configurationErrors.isEmpty() ? STATUS_SUCCESS : STATUS_ERROR; remoteService.setStatus(status); if (rs.getType() == RemoteServiceType.APPLIANCE_MANAGER) { configurationErrors.addAll(createApplianceManager(datacenter, remoteService)); } return configurationErrors; } }