package org.cloudfoundry.community.servicebroker.brooklyn.service;
import java.net.URI;
import java.util.ArrayDeque;
import java.util.Collections;
import java.util.Deque;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Optional;
import java.util.Set;
import java.util.concurrent.Future;
import java.util.function.Function;
import java.util.function.Predicate;
import java.util.stream.Collectors;
import javax.ws.rs.core.Response;
import org.apache.brooklyn.rest.client.BrooklynApi;
import org.apache.brooklyn.rest.domain.CatalogItemSummary;
import org.apache.brooklyn.rest.domain.EntitySummary;
import org.apache.brooklyn.rest.domain.LocationSummary;
import org.apache.brooklyn.rest.domain.SensorSummary;
import org.apache.brooklyn.rest.domain.TaskSummary;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.http.HttpTool;
import org.apache.brooklyn.util.http.HttpToolResponse;
import org.apache.brooklyn.util.repeat.Repeater;
import org.apache.brooklyn.util.text.Strings;
import org.apache.brooklyn.util.time.Duration;
import org.apache.http.client.HttpClient;
import org.cloudfoundry.community.servicebroker.brooklyn.config.BrooklynConfig;
import org.cloudfoundry.community.servicebroker.brooklyn.repository.Repositories;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.scheduling.annotation.Async;
import org.springframework.scheduling.annotation.AsyncResult;
import org.springframework.stereotype.Service;
import com.google.common.annotations.VisibleForTesting;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Maps;
import com.google.common.io.BaseEncoding;
@Service
public class BrooklynRestAdmin {
private static final Logger LOG = LoggerFactory.getLogger(BrooklynRestAdmin.class);
private static final Predicate<String> ENTITY_GLOBAL_BLACKLIST_PREDICATE = s -> !ImmutableSet.of(
"org.apache.brooklyn.entity.group.QuarantineGroup",
"brooklyn.networking.subnet.SubnetTier"
).contains(s);
private static final Predicate<String> ENTITY_GLOBAL_WHITELIST_PREDICATE = s -> ImmutableSet.of(
).contains(s);
private static final Predicate<String> SENSOR_GLOBAL_BLACKLIST_PREDICATE = s -> !ImmutableSet.of(
"download.url",
"expandedinstall.dir",
"install.dir",
"download.url.debian",
"download.url.mac",
"download.url.rhelcentos",
"download.url.ubuntu"
).contains(s);
private static final Predicate<String> SENSOR_GLOBAL_WHITELIST_PREDICATE = s -> ImmutableSet.of(
"host.name",
"host.address",
"host.sshAddress"
).contains(s);
private HttpClient httpClient;
private BrooklynApi brooklynApi;
private BrooklynConfig config;
@Autowired
public BrooklynRestAdmin(BrooklynApi restApi, HttpClient httpClient, BrooklynConfig config) {
this.brooklynApi = restApi;
this.httpClient = httpClient;
this.config = config;
}
public void createRepositoryIfNotExists() {
try {
Repositories.createRepositories(getRestApi());
} catch (Exception e) {
e.printStackTrace();
}
}
@Async
public Future<List<CatalogItemSummary>> getCatalogApplications(boolean includeAllVersions) {
return new AsyncResult<>(getRestApi().getCatalogApi().listApplications("", "", includeAllVersions));
}
@Async
public Future<List<LocationSummary>> getLocations() {
return new AsyncResult<>(getRestApi().getLocationApi().list());
}
@Async
public Future<TaskSummary> createApplication(String applicationSpec) {
Response response = getRestApi().getApplicationApi().createFromForm(applicationSpec);
if (response.getStatus()>=400) {
LOG.error("Unable to create application: "+response+"\n"+applicationSpec);
throw new IllegalStateException("Could not create application: "+response);
}
return new AsyncResult<>(BrooklynApi.getEntity(response, TaskSummary.class));
}
@Async
public Future<TaskSummary> deleteApplication(String id) {
Response response = getRestApi().getEntityApi().expunge(id, id, true);
return new AsyncResult<>(BrooklynApi.getEntity(response, TaskSummary.class));
}
@Async
public Future<Map<String, Object>> getApplicationSensors(String application) {
return new AsyncResult<>(getApplicationSensors(application, getRestApi().getEntityApi().list(application), x -> true, x -> true, x -> true, x -> true));
}
@Async
public Future<Map<String, Object>> getCredentialsFromSensors(String application, String entity,
Predicate<? super String> sensorWhitelist,
Predicate<? super String> sensorBlacklist,
Predicate<? super String> entityWhitelist,
Predicate<? super String> entityBlacklist) {
List<EntitySummary> entities = getRestApi().getEntityApi().getChildren(application, entity);
if (entities.size() == 0) {
return new AsyncResult<>(getEntitySensors(application, entity, sensorWhitelist, sensorBlacklist, entityWhitelist, entityBlacklist));
} else if (entities.size() == 1) {
String entityId = entities.get(0).getId();
return new AsyncResult<>(getEntitySensors(application, entityId, sensorWhitelist, sensorBlacklist, entityWhitelist, entityBlacklist));
}
return new AsyncResult<>(getApplicationSensors(application, entities, sensorWhitelist, sensorBlacklist, entityWhitelist, entityBlacklist));
}
private Map<String, Object> getApplicationSensors(String application, List<EntitySummary> entities,
Predicate<? super String> sensorWhitelist,
Predicate<? super String> sensorBlacklist,
Predicate<? super String> entityWhitelist,
Predicate<? super String> entityBlacklist) {
Map<String, Object> result = new HashMap<>();
for (EntitySummary s : entities) {
Map<String, Object> entitySensors = getEntitySensors(application, s.getId(), sensorWhitelist, sensorBlacklist, entityWhitelist, entityBlacklist);
if (ENTITY_GLOBAL_WHITELIST_PREDICATE.or(entityWhitelist).and(ENTITY_GLOBAL_BLACKLIST_PREDICATE.and(entityBlacklist)).test(s.getType())) {
result.put(s.getName(), entitySensors);
} else {
if (entitySensors.containsKey("children")) {
result.putAll((Map<String, Object>) entitySensors.get("children"));
}
}
}
return result;
}
private Map<String, Object> getEntitySensors(String application, String entity,
Predicate<? super String> sensorWhitelist,
Predicate<? super String> sensorBlacklist,
Predicate<? super String> entityWhitelist,
Predicate<? super String> entityBlacklist) {
Map<String, Object> sensors = getSensors(application, entity, sensorWhitelist, sensorBlacklist);
Map<String, Object> childSensors = getApplicationSensors(application, getRestApi().getEntityApi().getChildren(application, entity), sensorWhitelist, sensorBlacklist, entityWhitelist, entityBlacklist);
if (childSensors.size() > 0) {
sensors.put("children", childSensors);
}
return sensors;
}
private Map<String, Object> getSensors(String application, String entity, Predicate<? super String> sensorWhitelistfilter, Predicate<? super String> sensorBlacklistFilter) {
List<SensorSummary> sensorSummaries = getRestApi().getSensorApi().list(application, entity);
Set<String> sensorNames = sensorSummaries.stream()
.map(SensorSummary::getName)
.collect(Collectors.toSet());
Map<String, Object> result = Maps.newHashMap();
sensorNames.stream()
.filter(sensorName -> !sensorName.startsWith("mapped."))
.filter(SENSOR_GLOBAL_BLACKLIST_PREDICATE.and(sensorBlacklistFilter).and(SENSOR_GLOBAL_WHITELIST_PREDICATE.or(sensorWhitelistfilter)))
.forEach(sensorName -> {
Object value = sensorNames.contains("mapped." + sensorName) ?
getRestApi().getSensorApi().get(application, entity, "mapped." + sensorName, false) :
getRestApi().getSensorApi().get(application, entity, sensorName, false);
if (value != null) {
result.put(sensorName, value);
}
});
return result;
}
@Async
public Future<String> postBlueprint(String file) {
Response response = getRestApi().getCatalogApi().create(file);
return new AsyncResult<>(BrooklynApi.getEntity(response, String.class));
}
@Async
public void deleteCatalogEntry(String name, String version) throws Exception {
getRestApi().getCatalogApi().deleteEntity(name, version);
}
@Async
public Future<Boolean> hasEffector(String application, String entity, String effector) {
try {
return new AsyncResult<>(getRestApi().getEffectorApi().list(application, entity).stream()
.anyMatch(effectorSummary -> effectorSummary.getName().equals(effector)));
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
LOG.info("unable to list effectors while looking for={}, message={}", effector, e.getMessage());
return new AsyncResult<>(false);
}
}
@Async
public Future<String> invokeEffector(String application, String entity, String effector, Map<String, Object> params) {
return invokeEffector(application, entity, effector, "", params);
}
@Async
public Future<String> invokeEffector(String application, String entity, String effector, String timeout, Map<String, Object> params) {
try {
Response response = getRestApi().getEffectorApi().invoke(application, entity, effector, timeout, params);
return new AsyncResult<>(BrooklynApi.getEntity(response, String.class));
} catch (Exception e) {
Exceptions.propagateIfFatal(e);
LOG.info("unable to invoke effector={}, message={}", effector, e.getMessage());
return new AsyncResult<>(null);
}
}
@Async
public Future<Map<String, Object>> getApplicationEffectors(String application) {
Map<String, Object> result = new HashMap<>();
result.put("children", getApplicationEffectors(application, getRestApi().getEntityApi().list(application)));
result.put(application, getEffectors(application, application));
return new AsyncResult<>(result);
}
@Async
public Future<Map<String, Object>> getApplicationEffectors(String application, List<EntitySummary> entities) {
return new AsyncResult<>(_getApplicationEffectors(application, entities));
}
private Map<String, Object> _getApplicationEffectors(String application, List<EntitySummary> entities) {
return entities.stream().collect(Collectors.toMap(
entitySummary -> entitySummary.getName(),
entitySummary -> {
String entity = entitySummary.getId();
return getEffectors(application, entity).put("children", _getApplicationEffectors(application,
getRestApi().getEntityApi().getChildren(application, entity)));
}
));
}
private Map<String, Object> getEffectors(String application, String entity) {
return getRestApi().getEffectorApi().list(application, entity).stream().collect(Collectors.toMap(
effectorSummary -> entity + ":" + effectorSummary.getName(),
Function.identity()
));
}
@Async
public Future<Boolean> isApplicationRunning(String application) {
Object result = getRestApi().getSensorApi().get(application, application, "service.state", false);
if (result instanceof String) {
return new AsyncResult<>(result.toString().toUpperCase().equals("RUNNING"));
}
return new AsyncResult<>(false);
}
@Async
public Future<String> getDashboardUrl(String application) {
// search in breadth first order for first sensor that matches
List<EntitySummary> entities = getRestApi().getEntityApi().list(application);
Deque<EntitySummary> q = new ArrayDeque<>(entities);
while (!q.isEmpty()) {
EntitySummary e = q.remove();
List<SensorSummary> sensors = getRestApi().getSensorApi().list(application, e.getId());
for (SensorSummary sensor : sensors) {
if (sensor.getName().equals("management.url")) {
String url = String.valueOf(getRestApi().getSensorApi().get(application, e.getId(), sensor.getName(), false));
LOG.info("found dashboard url={} for application={}", url, application);
return new AsyncResult<>(url);
}
}
q.addAll(getRestApi().getEntityApi().getChildren(application, e.getId()));
}
LOG.info("no dashboard url found for application={}", application);
return new AsyncResult<>(null);
}
@Async
public Future<Map<String, Object>> getConfigAsMap(String application, String entity, String key) {
Object object;
try {
object = getRestApi().getEntityConfigApi().get(application, entity, key, false);
} catch (Exception e) {
LOG.error("Unable to get config with key={}", key);
return new AsyncResult<>(null);
}
if (object == null || !(object instanceof Map)) {
LOG.error("Unable to get Map with key={}", key);
return new AsyncResult<>(null);
}
Map<String, Object> map = (Map<String, Object>) object;
return new AsyncResult<>(map);
}
@Async
public Future<String> getServiceState(String application) {
try {
Object object = getRestApi().getSensorApi().get(application, application, "service.state", false);
return new AsyncResult<>(object.toString());
} catch (Exception e) {
return new AsyncResult<>(null);
}
}
@Async
public void deleteConfig(String application, String entity, String key) {
try {
getRestApi().getEntityConfigApi().set(application, entity, key, false, "");
} catch (Exception e) {
LOG.error("unable to delete {} {}", key, e.getMessage());
}
}
@Async
public Future<Object> setConfig(String application, String entity, String key, Object value) {
try {
getRestApi().getEntityConfigApi().set(application, entity, key, false, value);
return new AsyncResult<>(value);
} catch (Exception e) {
LOG.error("unable to save {} {}", value, e.getMessage());
return new AsyncResult<>(null);
}
}
@Async
public Future<String> getIconAsBase64(String url) {
if (Strings.isEmpty(url)) return new AsyncResult<>(null);
try {
HttpToolResponse response = HttpTool.httpGet(httpClient, new URI(config.toFullUrl(url)), Collections.<String, String>emptyMap());
if (response.getResponseCode() / 100 != 2) {
// url is not relative, assume it is absolute
return new AsyncResult<>(url);
}
Optional<Entry<String, List<String>>> entry = response.getHeaderLists().entrySet()
.stream()
.filter(e -> e.getKey().toLowerCase().equals("content-type"))
.findFirst();
if (entry.isPresent() && !entry.get().getValue().get(0).startsWith("image")) {
LOG.error("expected content type to start 'image' but found: {}", entry.get().getValue().get(0));
return new AsyncResult<>(null);
}
return new AsyncResult<>("data:img/png;base64," + BaseEncoding.base64().encode(response.getContent()));
} catch (Exception e) {
LOG.error("unable to encode icon as base64");
return new AsyncResult<>(url);
}
}
public Object blockUntilTaskCompletes(String id) throws PollingException {
Object[] results = new Object[1];
blockUntilTaskCompletes(id, Duration.PRACTICALLY_FOREVER, results);
return results[0];
}
public void blockUntilTaskCompletes(String id, Duration timeout, Object[] results) throws PollingException {
try {
Repeater.create()
.every(Duration.ONE_SECOND)
.until(() -> {
TaskSummary summary = getRestApi().getActivityApi().get(id);
if (summary.isError() || summary.isCancelled() || (summary.getSubmitTimeUtc() == null)) {
throw new PollingException(new IllegalStateException("Effector call failed: " + summary));
}
if (summary.getEndTimeUtc() != null) {
results[0] = summary.getResult();
return true;
}
return false;
})
.rethrowExceptionImmediately()
.limitTimeTo(timeout)
.runRequiringTrue();
} catch (Exception e) {
LOG.error("Failed to get task summary: " + e);
throw new PollingException(e);
}
}
@VisibleForTesting
public BrooklynApi getRestApi() {
return brooklynApi;
}
}