package alien4cloud.orchestrators.locations.services; import static alien4cloud.utils.AlienUtils.array; import java.util.Collection; import java.util.Date; import java.util.Iterator; import java.util.List; import java.util.Map; import java.util.Set; import java.util.UUID; import javax.annotation.Resource; import javax.inject.Inject; import lombok.extern.slf4j.Slf4j; import org.apache.commons.collections4.CollectionUtils; import org.apache.commons.collections4.MapUtils; import org.apache.commons.lang3.StringUtils; import org.elasticsearch.index.query.QueryBuilder; import org.elasticsearch.index.query.QueryBuilders; import org.springframework.context.ApplicationContext; import org.springframework.context.annotation.Lazy; import org.springframework.stereotype.Service; import alien4cloud.component.ICSARRepositorySearchService; import alien4cloud.dao.IGenericSearchDAO; import alien4cloud.dao.model.GetMultipleDataResult; import alien4cloud.events.LocationTemplateCreated; import alien4cloud.exception.AlreadyExistException; import alien4cloud.exception.MissingCSARDependenciesException; import alien4cloud.exception.NotFoundException; import alien4cloud.model.common.MetaPropConfiguration; import alien4cloud.model.common.Usage; import org.alien4cloud.tosca.model.CSARDependency; import org.alien4cloud.tosca.model.Csar; import org.alien4cloud.tosca.model.types.NodeType; import alien4cloud.model.deployment.Deployment; import alien4cloud.model.orchestrators.Orchestrator; import alien4cloud.model.orchestrators.OrchestratorState; import alien4cloud.model.orchestrators.locations.Location; import alien4cloud.model.orchestrators.locations.LocationResourceTemplate; import alien4cloud.orchestrators.plugin.ILocationAutoConfigurer; import alien4cloud.orchestrators.plugin.ILocationConfiguratorPlugin; import alien4cloud.orchestrators.plugin.ILocationResourceAccessor; import alien4cloud.orchestrators.plugin.IOrchestratorPlugin; import alien4cloud.orchestrators.plugin.IOrchestratorPluginFactory; import alien4cloud.orchestrators.services.OrchestratorService; import alien4cloud.paas.OrchestratorPluginService; import alien4cloud.security.AuthorizationUtil; import alien4cloud.security.model.DeployerRole; import alien4cloud.topology.TopologyUtils; import alien4cloud.utils.AlienUtils; import alien4cloud.utils.MapUtil; import alien4cloud.utils.PropertyUtil; import com.google.common.base.Objects; import com.google.common.collect.Lists; import com.google.common.collect.Maps; /** * Manages a locations. */ @Slf4j @Service public class LocationService { @Resource(name = "alien-es-dao") private IGenericSearchDAO alienDAO; @Inject private OrchestratorPluginService orchestratorPluginService; @Inject private OrchestratorService orchestratorService; @Inject private PluginArchiveIndexer locationArchiveIndexer; @Inject @Lazy(true) private ILocationResourceService locationResourceService; @Resource private ICSARRepositorySearchService csarRepoSearchService; @Inject private ApplicationContext applicationContext; /** * Auto-configure locations using the given location auto-configurer. * * @param orchestrator The id of the orchestrator that own the locations. * @param locationAutoConfigurer The auto-configurer to use for getting locations. */ public void autoConfigure(Orchestrator orchestrator, ILocationAutoConfigurer locationAutoConfigurer) { List<Location> locations = locationAutoConfigurer.getLocations(); for (Location location : locations) { // location.setId(UUID.randomUUID().toString()); location.setOrchestratorId(orchestrator.getId()); try { createLocation(orchestrator, location, location.getInfrastructureType()); } catch (AlreadyExistException e) { log.debug("Location <" + location.getName() + "> is already configured for this location - skipping", e); } } } /** * Add a new locations for a given orchestrator. */ public String create(String orchestratorId, String locationName, String infrastructureType) { Orchestrator orchestrator = orchestratorService.getOrFail(orchestratorId); if (!OrchestratorState.CONNECTED.equals(orchestrator.getState())) { // we cannot configure locations for orchestrator that are not connected. // TODO throw exception } Location location = new Location(); location.setId(UUID.randomUUID().toString()); location.setName(locationName); location.setOrchestratorId(orchestratorId); createLocation(orchestrator, location, infrastructureType); return location.getId(); } private void createLocation(Orchestrator orchestrator, Location location, String infrastructureType) { ensureNameUnicityAndSave(location); // TODO checks that the infrastructure type is valid location.setInfrastructureType(infrastructureType); // TODO add User and Group managed by the Orchestrator security Set<CSARDependency> dependencies = locationArchiveIndexer.indexLocationArchives(orchestrator, location); location.setDependencies(dependencies); // initialize meta properties location.setMetaProperties(Maps.<String, String> newHashMap()); // add existing meta properties to the cloud GetMultipleDataResult<MetaPropConfiguration> result = alienDAO.find(MetaPropConfiguration.class, null, Integer.MAX_VALUE); for (MetaPropConfiguration element : result.getData()) { if (element.getTarget().toString().equals("cloud")) { location.setMetaProperties(Maps.<String, String> newHashMap()); // we only support string values for meta properties PropertyUtil.setScalarDefaultValueOrNull(location.getMetaProperties(), element.getId(), element.getDefault()); log.debug("Adding meta property <{}> to the new location <{}> ", element.getName(), location.getName()); } } // save the new location alienDAO.save(location); autoConfigure(orchestrator, location); // We call the LocationRessourceService to check the dependencies try { locationResourceService.getLocationResourcesFromOrchestrator(location); } catch (NotFoundException e) { // WARN: FIXME we load orch twice !!!!!!!!!!!!!!!!!!!!!!!!! delete(orchestrator.getId(), location.getId()); throw new MissingCSARDependenciesException(e.getMessage()); } } /** * Trigger plugin auto-configuration for the given location. * * @param locationId Id of the location. */ public List<LocationResourceTemplate> autoConfigure(String locationId) { Location location = getOrFail(locationId); Orchestrator orchestrator = orchestratorService.getOrFail(location.getOrchestratorId()); List<LocationResourceTemplate> generatedLocationResources = autoConfigure(orchestrator, location); if (CollectionUtils.isEmpty(generatedLocationResources)) { // if the orchestrator doesn't support auto-configuration // TODO throw exception or just return false ? } return generatedLocationResources; } /** * This method calls the orchestrator plugin to try to auto-configure the * * @param orchestrator The orchestrator for which to auto-configure a location. * @param location The location to auto-configure * @return the List of {@link LocationResourceTemplate} generated from the location auto-configuration call, null is a valid answer. */ private List<LocationResourceTemplate> autoConfigure(Orchestrator orchestrator, Location location) { // get the orchestrator plugin instance IOrchestratorPlugin orchestratorInstance = (IOrchestratorPlugin) orchestratorPluginService.getOrFail(orchestrator.getId()); ILocationConfiguratorPlugin configuratorPlugin = orchestratorInstance.getConfigurator(location.getInfrastructureType()); IOrchestratorPluginFactory orchestratorFactory = orchestratorService.getPluginFactory(orchestrator); ILocationResourceAccessor accessor = locationResourceService.accessor(location.getId()); // let's try to auto-configure the location List<LocationResourceTemplate> templates = configuratorPlugin.instances(accessor); if (templates != null) { // save the instances for (LocationResourceTemplate template : templates) { // initialize the instances from data. template.setId(UUID.randomUUID().toString()); template.setLocationId(location.getId()); template.setGenerated(true); template.setEnabled(true); NodeType nodeType = csarRepoSearchService.getRequiredElementInDependencies(NodeType.class, template.getTemplate().getType(), location.getDependencies()); nodeType.getDerivedFrom().add(0, template.getTemplate().getType()); template.setTypes(nodeType.getDerivedFrom()); // FIXME Workaround to remove default scalable properties from compute TopologyUtils.setNullScalingPolicy(template.getTemplate(), nodeType); LocationTemplateCreated event = new LocationTemplateCreated(this); event.setTemplate(template); event.setLocation(location); event.setNodeType(nodeType); applicationContext.publishEvent(event); } alienDAO.save(templates.toArray(new LocationResourceTemplate[templates.size()])); alienDAO.save(location); } return templates; } /** * Get the location matching the given id or throw a NotFoundException * * @param id If of the location that we want to get. * @return An instance of the location. */ public Location getOrFail(String id) { Location location = alienDAO.findById(Location.class, id); if (location == null) { throw new NotFoundException("Location [" + id + "] doesn't exists."); } return location; } /** * Get multiple locations from list of ids * * @param ids ids of location to get * @return map of id to location */ public Map<String, Location> getMultiple(Collection<String> ids) { List<Location> locations = alienDAO.findByIds(Location.class, ids.toArray(new String[ids.size()])); Map<String, Location> locationMap = Maps.newHashMap(); for (Location location : locations) { locationMap.put(location.getId(), location); } return locationMap; } /** * Return all locations for a given orchestrator. * * @param orchestratorId The id of the orchestrator for which to get locations. * @return */ public List<Location> getAll(String orchestratorId) { List<Location> locations = alienDAO.customFindAll(Location.class, QueryBuilders.termQuery("orchestratorId", orchestratorId)); if (locations == null) { return Lists.newArrayList(); } return locations; } /** * Delete a locations. * * @param id id of the locations to delete. * @return true if the location was successfully , false if not. */ public boolean delete(String orchestratorId, String id) { Orchestrator orchestrator = orchestratorService.getOrFail(orchestratorId); if (!OrchestratorState.CONNECTED.equals(orchestrator.getState())) { // we cannot configure locations for orchestrator that are not connected. // TODO throw exception } Map<String, String[]> filters = Maps.newHashMap(); addFilter(filters, "locationIds", id); addFilter(filters, "endDate", "null"); long count = alienDAO.count(Deployment.class, null, filters); if (count > 0) { return false; } Location location = getOrFail(id); // delete all location resources for the given location alienDAO.delete(LocationResourceTemplate.class, QueryBuilders.termQuery("locationId", id)); // delete the location alienDAO.delete(Location.class, id); // delete all archives associated with this location only, if possible of course Map<Csar, List<Usage>> usages = locationArchiveIndexer.deleteArchives(orchestrator, location); if (MapUtils.isNotEmpty(usages)) { // TODO what to do when some archives were not deleted? log.warn("Some archives for location were not deleted! \n" + usages); } return true; } /** * Query for all locations given an orchestrator * * @param orchestratorId Id of the orchestrators for which to get locations. * @return An array that contains all locations for the given orchestrators. */ public Location[] getOrchestratorLocations(String orchestratorId) { GetMultipleDataResult<Location> locations = alienDAO.search(Location.class, null, MapUtil.newHashMap(array("orchestratorId"), AlienUtils.<String> arOfArray(array(orchestratorId))), Integer.MAX_VALUE); return locations.getData(); } /** * Get all locations managed by all the provided orchestrators ids * * @param orchestratorIds * @return */ public List<Location> getOrchestratorsLocations(Collection<String> orchestratorIds) { List<Location> locations = null; locations = alienDAO.customFindAll(Location.class, QueryBuilders.termsQuery("orchestratorId", orchestratorIds)); return locations == null ? Lists.<Location> newArrayList() : locations; } /** * Retrieve location given a list of ids, and a specific context * Only retrieves the authorized ones. * * @param fetchContext The fetch context to recover only the required field (Note that this should be simplified to directly use the given field...). * @param ids array of id of the applications to find * @return Map of locations that has the given ids and for which the user is authorized (key is application Id), or null if no location matching the * request is found. */ public Map<String, Location> findByIdsIfAuthorized(String fetchContext, String... ids) { List<Location> results = alienDAO.findByIdsWithContext(Location.class, fetchContext, ids); if (results == null) { return null; } Map<String, Location> locations = Maps.newHashMap(); Iterator<Location> iterator = results.iterator(); while (iterator.hasNext()) { Location location = iterator.next(); if (!AuthorizationUtil.hasAuthorizationForLocation(location, DeployerRole.values())) { iterator.remove(); continue; } locations.put(location.getId(), location); } return locations.isEmpty() ? null : locations; } private void addFilter(Map<String, String[]> filters, String property, String... values) { filters.put(property, values); } /** * Ensure that the location name is unique on the orchestrator before saving it. * * @param location The location to save. * @param oldName */ public synchronized void ensureNameUnicityAndSave(Location location, String oldName) { if (StringUtils.isBlank(oldName) || !Objects.equal(location.getName(), oldName)) { // check that a location of this name and managed by the same orchestrator doesn't already exists QueryBuilder mustQuery = QueryBuilders.boolQuery().must(QueryBuilders.termQuery("name", location.getName())) .must(QueryBuilders.termQuery("orchestratorId", location.getOrchestratorId())); if (alienDAO.count(Location.class, mustQuery) > 0) { throw new AlreadyExistException("a location with the given name <" + location.getName() + "> already exists on this orchestrator ."); } } alienDAO.save(location); } private void ensureNameUnicityAndSave(Location location) { ensureNameUnicityAndSave(location, null); } }