http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java new file mode 100644 index 0000000..c8a7962 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyConfigResource.java @@ -0,0 +1,108 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.objs.BrooklynObjectInternal; +import org.apache.brooklyn.rest.api.PolicyConfigApi; +import org.apache.brooklyn.rest.domain.PolicyConfigSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.PolicyTransformer; +import org.apache.brooklyn.rest.util.BrooklynRestResourceUtils; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.core.flags.TypeCoercions; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +@HaHotStateRequired +public class PolicyConfigResource extends AbstractBrooklynRestResource implements PolicyConfigApi { + + @Override + public List<PolicyConfigSummary> list( + final String application, final String entityToken, final String policyToken) { + Entity entity = brooklyn().getEntity(application, entityToken); + Policy policy = brooklyn().getPolicy(entity, policyToken); + + List<PolicyConfigSummary> result = Lists.newArrayList(); + for (ConfigKey<?> key : policy.getPolicyType().getConfigKeys()) { + result.add(PolicyTransformer.policyConfigSummary(brooklyn(), entity, policy, key, ui.getBaseUriBuilder())); + } + return result; + } + + // TODO support parameters ?show=value,summary&name=xxx &format={string,json,xml} + // (and in sensors class) + @Override + public Map<String, Object> batchConfigRead(String application, String entityToken, String policyToken) { + // TODO: add test + Policy policy = brooklyn().getPolicy(application, entityToken, policyToken); + Map<String, Object> source = ((BrooklynObjectInternal)policy).config().getBag().getAllConfig(); + Map<String, Object> result = Maps.newLinkedHashMap(); + for (Map.Entry<String, Object> ek : source.entrySet()) { + result.put(ek.getKey(), getStringValueForDisplay(brooklyn(), policy, ek.getValue())); + } + return result; + } + + @Override + public String get(String application, String entityToken, String policyToken, String configKeyName) { + Policy policy = brooklyn().getPolicy(application, entityToken, policyToken); + ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName); + if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken); + + return getStringValueForDisplay(brooklyn(), policy, policy.getConfig(ck)); + } + + @Override + @Deprecated + public Response set(String application, String entityToken, String policyToken, String configKeyName, String value) { + return set(application, entityToken, policyToken, configKeyName, (Object) value); + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + @Override + public Response set(String application, String entityToken, String policyToken, String configKeyName, Object value) { + Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + Policy policy = brooklyn().getPolicy(application, entityToken, policyToken); + ConfigKey<?> ck = policy.getPolicyType().getConfigKey(configKeyName); + if (ck == null) throw WebResourceUtils.notFound("Cannot find config key '%s' in policy '%s' of entity '%s'", configKeyName, policy, entityToken); + + policy.config().set((ConfigKey) ck, TypeCoercions.coerce(value, ck.getTypeToken())); + + return Response.status(Response.Status.OK).build(); + } + + public static String getStringValueForDisplay(BrooklynRestResourceUtils utils, Policy policy, Object value) { + return utils.getStringValueForDisplay(value); + } +}
http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java new file mode 100644 index 0000000..2696440 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/PolicyResource.java @@ -0,0 +1,135 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import java.util.List; +import java.util.Map; + +import javax.ws.rs.core.Response; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.policy.Policy; +import org.apache.brooklyn.api.policy.PolicySpec; +import org.apache.brooklyn.core.policy.Policies; +import org.apache.brooklyn.rest.api.PolicyApi; +import org.apache.brooklyn.rest.domain.PolicySummary; +import org.apache.brooklyn.rest.domain.Status; +import org.apache.brooklyn.rest.domain.SummaryComparators; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.ApplicationTransformer; +import org.apache.brooklyn.rest.transform.PolicyTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Function; +import com.google.common.collect.FluentIterable; +import com.google.common.collect.Maps; +import javax.ws.rs.core.Context; +import javax.ws.rs.core.UriInfo; + +@HaHotStateRequired +public class PolicyResource extends AbstractBrooklynRestResource implements PolicyApi { + + private static final Logger log = LoggerFactory.getLogger(PolicyResource.class); + + private @Context UriInfo ui; + + @Override + public List<PolicySummary> list( final String application, final String entityToken ) { + final Entity entity = brooklyn().getEntity(application, entityToken); + return FluentIterable.from(entity.policies()) + .transform(new Function<Policy, PolicySummary>() { + @Override + public PolicySummary apply(Policy policy) { + return PolicyTransformer.policySummary(entity, policy, ui.getBaseUriBuilder()); + } + }) + .toSortedList(SummaryComparators.nameComparator()); + } + + // TODO support parameters ?show=value,summary&name=xxx + // (and in sensors class) + @Override + public Map<String, Boolean> batchConfigRead( String application, String entityToken) { + // TODO: add test + Entity entity = brooklyn().getEntity(application, entityToken); + Map<String, Boolean> result = Maps.newLinkedHashMap(); + for (Policy p : entity.policies()) { + result.put(p.getId(), !p.isSuspended()); + } + return result; + } + + // TODO would like to make 'config' arg optional but jersey complains if we do + @SuppressWarnings("unchecked") + @Override + public PolicySummary addPolicy( String application,String entityToken, String policyTypeName, + Map<String, String> config) { + Entity entity = brooklyn().getEntity(application, entityToken); + Class<? extends Policy> policyType; + try { + policyType = (Class<? extends Policy>) Class.forName(policyTypeName); + } catch (ClassNotFoundException e) { + throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName); + } catch (ClassCastException e) { + throw WebResourceUtils.badRequest("No policy with type %s found", policyTypeName); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + + Policy policy = entity.policies().add(PolicySpec.create(policyType).configure(config)); + log.debug("REST API added policy " + policy + " to " + entity); + + return PolicyTransformer.policySummary(entity, policy, ui.getBaseUriBuilder()); + } + + @Override + public Status getStatus(String application, String entityToken, String policyId) { + Policy policy = brooklyn().getPolicy(application, entityToken, policyId); + return ApplicationTransformer.statusFromLifecycle(Policies.getPolicyStatus(policy)); + } + + @Override + public Response start( String application, String entityToken, String policyId) { + Policy policy = brooklyn().getPolicy(application, entityToken, policyId); + + policy.resume(); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + @Override + public Response stop(String application, String entityToken, String policyId) { + Policy policy = brooklyn().getPolicy(application, entityToken, policyId); + + policy.suspend(); + return Response.status(Response.Status.NO_CONTENT).build(); + } + + @Override + public Response destroy(String application, String entityToken, String policyToken) { + Entity entity = brooklyn().getEntity(application, entityToken); + Policy policy = brooklyn().getPolicy(entity, policyToken); + + policy.suspend(); + entity.policies().remove(policy); + return Response.status(Response.Status.NO_CONTENT).build(); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java new file mode 100644 index 0000000..77989c3 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java @@ -0,0 +1,102 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import org.apache.brooklyn.rest.api.ScriptApi; +import org.apache.brooklyn.rest.domain.ScriptExecutionSummary; +import org.apache.brooklyn.util.stream.ThreadLocalPrintStream; +import org.apache.brooklyn.util.stream.ThreadLocalPrintStream.OutputCapturingContext; + +import groovy.lang.Binding; +import groovy.lang.GroovyShell; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import javax.servlet.http.HttpServletRequest; +import javax.servlet.http.HttpSession; + +import java.util.LinkedHashMap; +import java.util.Map; + +public class ScriptResource extends AbstractBrooklynRestResource implements ScriptApi { + + private static final Logger log = LoggerFactory.getLogger(ScriptResource.class); + + public static final String USER_DATA_MAP_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.data"; + public static final String USER_LAST_VALUE_SESSION_ATTRIBUTE = "brooklyn.script.groovy.user.last"; + + @SuppressWarnings("rawtypes") + @Override + public ScriptExecutionSummary groovy(HttpServletRequest request, String script) { + log.info("Web REST executing user-supplied script"); + if (log.isDebugEnabled()) { + log.debug("Web REST user-supplied script contents:\n"+script); + } + + Binding binding = new Binding(); + binding.setVariable("mgmt", mgmt()); + + HttpSession session = request!=null ? request.getSession() : null; + if (session!=null) { + Map data = (Map) session.getAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE); + if (data==null) { + data = new LinkedHashMap(); + session.setAttribute(USER_DATA_MAP_SESSION_ATTRIBUTE, data); + } + binding.setVariable("data", data); + + Object last = session.getAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE); + binding.setVariable("last", last); + } + + GroovyShell shell = new GroovyShell(binding); + + OutputCapturingContext stdout = ThreadLocalPrintStream.stdout().captureTee(); + OutputCapturingContext stderr = ThreadLocalPrintStream.stderr().captureTee(); + + Object value = null; + Throwable problem = null; + try { + value = shell.evaluate(script); + if (session!=null) + session.setAttribute(USER_LAST_VALUE_SESSION_ATTRIBUTE, value); + } catch (Throwable t) { + log.warn("Problem in user-supplied script: "+t, t); + problem = t; + } finally { + stdout.end(); + stderr.end(); + } + + if (log.isDebugEnabled()) { + log.debug("Web REST user-supplied script completed:\n"+ + (value!=null ? "RESULT: "+value.toString()+"\n" : "")+ + (problem!=null ? "ERROR: "+problem.toString()+"\n" : "")+ + (!stdout.isEmpty() ? "STDOUT: "+stdout.toString()+"\n" : "")+ + (!stderr.isEmpty() ? "STDERR: "+stderr.toString()+"\n" : "")); + } + + // call toString on the result, in case it is not serializable + return new ScriptExecutionSummary( + value!=null ? value.toString() : null, + problem!=null ? problem.toString() : null, + stdout.toString(), stderr.toString()); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java new file mode 100644 index 0000000..750b7de --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java @@ -0,0 +1,184 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import static com.google.common.collect.Iterables.filter; + +import java.util.List; +import java.util.Map; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.sensor.AttributeSensor; +import org.apache.brooklyn.api.sensor.Sensor; +import org.apache.brooklyn.core.entity.EntityInternal; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements.EntityAndItem; +import org.apache.brooklyn.core.sensor.BasicAttributeSensor; +import org.apache.brooklyn.rest.api.SensorApi; +import org.apache.brooklyn.rest.domain.SensorSummary; +import org.apache.brooklyn.rest.filter.HaHotStateRequired; +import org.apache.brooklyn.rest.transform.SensorTransformer; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.core.task.ValueResolver; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Duration; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.collect.Lists; +import com.google.common.collect.Maps; + +@HaHotStateRequired +public class SensorResource extends AbstractBrooklynRestResource implements SensorApi { + + private static final Logger log = LoggerFactory.getLogger(SensorResource.class); + + @Override + public List<SensorSummary> list(final String application, final String entityToken) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + List<SensorSummary> result = Lists.newArrayList(); + + for (AttributeSensor<?> sensor : filter(entity.getEntityType().getSensors(), AttributeSensor.class)) { + // Exclude config that user is not allowed to see + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) { + log.trace("User {} not authorized to see sensor {} of entity {}; excluding from AttributeSensor list results", + new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity}); + continue; + } + result.add(SensorTransformer.sensorSummary(entity, sensor, ui.getBaseUriBuilder())); + } + + return result; + } + + @Override + public Map<String, Object> batchSensorRead(final String application, final String entityToken, final Boolean raw) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + Map<String, Object> sensorMap = Maps.newHashMap(); + @SuppressWarnings("rawtypes") + Iterable<AttributeSensor> sensors = filter(entity.getEntityType().getSensors(), AttributeSensor.class); + + for (AttributeSensor<?> sensor : sensors) { + // Exclude sensors that user is not allowed to see + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) { + log.trace("User {} not authorized to see sensor {} of entity {}; excluding from current-state results", + new Object[] {Entitlements.getEntitlementContext().user(), sensor.getName(), entity}); + continue; + } + + Object value = entity.getAttribute(findSensor(entity, sensor.getName())); + sensorMap.put(sensor.getName(), + resolving(value).preferJson(true).asJerseyOutermostReturnValue(false).raw(raw).context(entity).timeout(Duration.ZERO).renderAs(sensor).resolve()); + } + return sensorMap; + } + + protected Object get(boolean preferJson, String application, String entityToken, String sensorName, Boolean raw) { + final Entity entity = brooklyn().getEntity(application, entityToken); + AttributeSensor<?> sensor = findSensor(entity, sensorName); + + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_SENSOR, new EntityAndItem<String>(entity, sensor.getName()))) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to see entity '%s' sensor '%s'", + Entitlements.getEntitlementContext().user(), entity, sensor.getName()); + } + + Object value = entity.getAttribute(sensor); + return resolving(value).preferJson(preferJson).asJerseyOutermostReturnValue(true).raw(raw).context(entity).timeout(ValueResolver.PRETTY_QUICK_WAIT).renderAs(sensor).resolve(); + } + + @Override + public String getPlain(String application, String entityToken, String sensorName, final Boolean raw) { + return (String) get(false, application, entityToken, sensorName, raw); + } + + @Override + public Object get(final String application, final String entityToken, String sensorName, final Boolean raw) { + return get(true, application, entityToken, sensorName, raw); + } + + private AttributeSensor<?> findSensor(Entity entity, String name) { + Sensor<?> s = entity.getEntityType().getSensor(name); + if (s instanceof AttributeSensor) return (AttributeSensor<?>) s; + return new BasicAttributeSensor<Object>(Object.class, name); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void setFromMap(String application, String entityToken, Map newValues) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + if (log.isDebugEnabled()) + log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensors "+newValues); + for (Object entry: newValues.entrySet()) { + String sensorName = Strings.toString(((Map.Entry)entry).getKey()); + Object newValue = ((Map.Entry)entry).getValue(); + + AttributeSensor sensor = findSensor(entity, sensorName); + entity.sensors().set(sensor, newValue); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void set(String application, String entityToken, String sensorName, Object newValue) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + AttributeSensor sensor = findSensor(entity, sensorName); + if (log.isDebugEnabled()) + log.debug("REST user "+Entitlements.getEntitlementContext()+" setting sensor "+sensorName+" to "+newValue); + entity.sensors().set(sensor, newValue); + } + + @Override + public void delete(String application, String entityToken, String sensorName) { + final Entity entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized to modify entity '%s'", + Entitlements.getEntitlementContext().user(), entity); + } + + AttributeSensor<?> sensor = findSensor(entity, sensorName); + if (log.isDebugEnabled()) + log.debug("REST user "+Entitlements.getEntitlementContext()+" deleting sensor "+sensorName); + ((EntityInternal)entity).sensors().remove(sensor); + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java new file mode 100644 index 0000000..fe7893b --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java @@ -0,0 +1,497 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import java.io.ByteArrayOutputStream; +import java.io.File; +import java.io.IOException; +import java.io.InputStream; +import java.util.ArrayList; +import java.util.List; +import java.util.Map; +import java.util.Properties; +import java.util.concurrent.TimeUnit; +import java.util.concurrent.TimeoutException; +import java.util.concurrent.atomic.AtomicBoolean; + +import javax.ws.rs.core.Context; +import javax.ws.rs.core.MediaType; +import javax.ws.rs.core.Response; +import javax.ws.rs.ext.ContextResolver; + +import org.apache.brooklyn.api.entity.Application; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.api.mgmt.Task; +import org.apache.brooklyn.api.mgmt.entitlement.EntitlementContext; +import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityManager; +import org.apache.brooklyn.api.mgmt.ha.HighAvailabilityMode; +import org.apache.brooklyn.api.mgmt.ha.ManagementNodeState; +import org.apache.brooklyn.api.mgmt.ha.ManagementPlaneSyncRecord; +import org.apache.brooklyn.api.mgmt.ha.MementoCopyMode; +import org.apache.brooklyn.config.ConfigKey; +import org.apache.brooklyn.core.BrooklynVersion; +import org.apache.brooklyn.core.config.ConfigKeys; +import org.apache.brooklyn.core.entity.Attributes; +import org.apache.brooklyn.core.entity.Entities; +import org.apache.brooklyn.core.entity.StartableApplication; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.persist.BrooklynPersistenceUtils; +import org.apache.brooklyn.core.mgmt.persist.FileBasedObjectStore; +import org.apache.brooklyn.core.mgmt.persist.PersistenceObjectStore; +import org.apache.brooklyn.rest.api.ServerApi; +import org.apache.brooklyn.rest.domain.BrooklynFeatureSummary; +import org.apache.brooklyn.rest.domain.HighAvailabilitySummary; +import org.apache.brooklyn.rest.domain.VersionSummary; +import org.apache.brooklyn.rest.transform.BrooklynFeatureTransformer; +import org.apache.brooklyn.rest.transform.HighAvailabilityTransformer; +import org.apache.brooklyn.rest.util.ShutdownHandler; +import org.apache.brooklyn.rest.util.WebResourceUtils; +import org.apache.brooklyn.util.collections.MutableMap; +import org.apache.brooklyn.util.core.ResourceUtils; +import org.apache.brooklyn.util.core.file.ArchiveBuilder; +import org.apache.brooklyn.util.core.flags.TypeCoercions; +import org.apache.brooklyn.util.exceptions.Exceptions; +import org.apache.brooklyn.util.guava.Maybe; +import org.apache.brooklyn.util.os.Os; +import org.apache.brooklyn.util.text.Identifiers; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.CountdownTimer; +import org.apache.brooklyn.util.time.Duration; +import org.apache.brooklyn.util.time.Time; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; + +public class ServerResource extends AbstractBrooklynRestResource implements ServerApi { + + private static final int SHUTDOWN_TIMEOUT_CHECK_INTERVAL = 200; + + private static final Logger log = LoggerFactory.getLogger(ServerResource.class); + + private static final String BUILD_SHA_1_PROPERTY = "git-sha-1"; + private static final String BUILD_BRANCH_PROPERTY = "git-branch-name"; + + @Context + private ContextResolver<ShutdownHandler> shutdownHandler; + + @Override + public void reloadBrooklynProperties() { + brooklyn().reloadBrooklynProperties(); + } + + private boolean isMaster() { + return ManagementNodeState.MASTER.equals(mgmt().getHighAvailabilityManager().getNodeState()); + } + + @Override + public void shutdown(final boolean stopAppsFirst, final boolean forceShutdownOnError, + String shutdownTimeoutRaw, String requestTimeoutRaw, String delayForHttpReturnRaw, + Long delayMillis) { + + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + + log.info("REST call to shutdown server, stopAppsFirst="+stopAppsFirst+", delayForHttpReturn="+shutdownTimeoutRaw); + + if (stopAppsFirst && !isMaster()) { + log.warn("REST call to shutdown non-master server while stopping apps is disallowed"); + throw WebResourceUtils.forbidden("Not allowed to stop all apps when server is not master"); + } + final Duration shutdownTimeout = parseDuration(shutdownTimeoutRaw, Duration.of(20, TimeUnit.SECONDS)); + Duration requestTimeout = parseDuration(requestTimeoutRaw, Duration.of(20, TimeUnit.SECONDS)); + final Duration delayForHttpReturn; + if (delayMillis == null) { + delayForHttpReturn = parseDuration(delayForHttpReturnRaw, Duration.FIVE_SECONDS); + } else { + log.warn("'delayMillis' is deprecated, use 'delayForHttpReturn' instead."); + delayForHttpReturn = Duration.of(delayMillis, TimeUnit.MILLISECONDS); + } + + Preconditions.checkState(delayForHttpReturn.nanos() >= 0, "Only positive or 0 delay allowed for delayForHttpReturn"); + + boolean isSingleTimeout = shutdownTimeout.equals(requestTimeout); + final AtomicBoolean completed = new AtomicBoolean(); + final AtomicBoolean hasAppErrorsOrTimeout = new AtomicBoolean(); + + //shutdownHandler is thread local + final ShutdownHandler handler = shutdownHandler.getContext(ShutdownHandler.class); + new Thread("shutdown") { + @Override + public void run() { + boolean terminateTried = false; + ManagementContext mgmt = mgmt(); + try { + if (stopAppsFirst) { + CountdownTimer shutdownTimeoutTimer = null; + if (!shutdownTimeout.equals(Duration.ZERO)) { + shutdownTimeoutTimer = shutdownTimeout.countdownTimer(); + } + + log.debug("Stopping applications"); + List<Task<?>> stoppers = new ArrayList<Task<?>>(); + int allStoppableApps = 0; + for (Application app: mgmt.getApplications()) { + allStoppableApps++; + Lifecycle appState = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL); + if (app instanceof StartableApplication && + // Don't try to stop an already stopping app. Subsequent stops will complete faster + // cancelling the first stop task. + appState != Lifecycle.STOPPING) { + stoppers.add(Entities.invokeEffector(app, app, StartableApplication.STOP)); + } else { + log.debug("App " + app + " is already stopping, will not stop second time. Will wait for original stop to complete."); + } + } + + log.debug("Waiting for " + allStoppableApps + " apps to stop, of which " + stoppers.size() + " stopped explicitly."); + for (Task<?> t: stoppers) { + if (!waitAppShutdown(shutdownTimeoutTimer, t)) { + //app stop error + hasAppErrorsOrTimeout.set(true); + } + } + + // Wait for apps which were already stopping when we tried to shut down. + if (hasStoppableApps(mgmt)) { + log.debug("Apps are still stopping, wait for proper unmanage."); + while (hasStoppableApps(mgmt) && (shutdownTimeoutTimer == null || !shutdownTimeoutTimer.isExpired())) { + Duration wait; + if (shutdownTimeoutTimer != null) { + wait = Duration.min(shutdownTimeoutTimer.getDurationRemaining(), Duration.ONE_SECOND); + } else { + wait = Duration.ONE_SECOND; + } + Time.sleep(wait); + } + if (hasStoppableApps(mgmt)) { + hasAppErrorsOrTimeout.set(true); + } + } + } + + terminateTried = true; + ((ManagementContextInternal)mgmt).terminate(); + + } catch (Throwable e) { + Throwable interesting = Exceptions.getFirstInteresting(e); + if (interesting instanceof TimeoutException) { + //timeout while waiting for apps to stop + log.warn("Timeout shutting down: "+Exceptions.collapseText(e)); + log.debug("Timeout shutting down: "+e, e); + hasAppErrorsOrTimeout.set(true); + + } else { + // swallow fatal, so we notify the outer loop to continue with shutdown + log.error("Unexpected error shutting down: "+Exceptions.collapseText(e), e); + + } + hasAppErrorsOrTimeout.set(true); + + if (!terminateTried) { + ((ManagementContextInternal)mgmt).terminate(); + } + } finally { + + complete(); + + if (!hasAppErrorsOrTimeout.get() || forceShutdownOnError) { + //give the http request a chance to complete gracefully, the server will be stopped in a shutdown hook + Time.sleep(delayForHttpReturn); + + if (handler != null) { + handler.onShutdownRequest(); + } else { + log.warn("ShutdownHandler not set, exiting process"); + System.exit(0); + } + + } else { + // There are app errors, don't exit the process, allowing any exception to continue throwing + log.warn("Abandoning shutdown because there were errors and shutdown was not forced."); + + } + } + } + + private boolean hasStoppableApps(ManagementContext mgmt) { + for (Application app : mgmt.getApplications()) { + if (app instanceof StartableApplication) { + Lifecycle state = app.getAttribute(Attributes.SERVICE_STATE_ACTUAL); + if (state != Lifecycle.STOPPING && state != Lifecycle.STOPPED) { + log.warn("Shutting down, expecting all apps to be in stopping state, but found application " + app + " to be in state " + state + ". Just started?"); + } + return true; + } + } + return false; + } + + private void complete() { + synchronized (completed) { + completed.set(true); + completed.notifyAll(); + } + } + + private boolean waitAppShutdown(CountdownTimer shutdownTimeoutTimer, Task<?> t) throws TimeoutException { + Duration waitInterval = null; + //wait indefinitely if no shutdownTimeoutTimer (shutdownTimeout == 0) + if (shutdownTimeoutTimer != null) { + waitInterval = Duration.of(SHUTDOWN_TIMEOUT_CHECK_INTERVAL, TimeUnit.MILLISECONDS); + } + // waitInterval == null - blocks indefinitely + while(!t.blockUntilEnded(waitInterval)) { + if (shutdownTimeoutTimer.isExpired()) { + log.warn("Timeout while waiting for applications to stop at "+t+".\n"+t.getStatusDetail(true)); + throw new TimeoutException(); + } + } + if (t.isError()) { + log.warn("Error stopping application "+t+" during shutdown (ignoring)\n"+t.getStatusDetail(true)); + return false; + } else { + return true; + } + } + }.start(); + + synchronized (completed) { + if (!completed.get()) { + try { + long waitTimeout = 0; + //If the timeout for both shutdownTimeout and requestTimeout is equal + //then better wait until the 'completed' flag is set, rather than timing out + //at just about the same time (i.e. always wait for the shutdownTimeout in this case). + //This will prevent undefined behaviour where either one of shutdownTimeout or requestTimeout + //will be first to expire and the error flag won't be set predictably, it will + //toggle depending on which expires first. + //Note: shutdownTimeout is checked at SHUTDOWN_TIMEOUT_CHECK_INTERVAL interval, meaning it is + //practically rounded up to the nearest SHUTDOWN_TIMEOUT_CHECK_INTERVAL. + if (!isSingleTimeout) { + waitTimeout = requestTimeout.toMilliseconds(); + } + completed.wait(waitTimeout); + } catch (InterruptedException e) { + throw Exceptions.propagate(e); + } + } + } + + if (hasAppErrorsOrTimeout.get()) { + WebResourceUtils.badRequest("Error or timeout while stopping applications. See log for details."); + } + } + + private Duration parseDuration(String str, Duration defaultValue) { + if (Strings.isEmpty(str)) { + return defaultValue; + } else { + return Duration.parse(str); + } + } + + @Override + public VersionSummary getVersion() { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + + // TODO + // * "build-metadata.properties" is probably the wrong name + // * we should include brooklyn.version and a build timestamp in this file + // * the authority for brooklyn should probably be core rather than brooklyn-rest-server + InputStream input = ResourceUtils.create().getResourceFromUrl("classpath://build-metadata.properties"); + Properties properties = new Properties(); + String gitSha1 = null, gitBranch = null; + try { + properties.load(input); + gitSha1 = properties.getProperty(BUILD_SHA_1_PROPERTY); + gitBranch = properties.getProperty(BUILD_BRANCH_PROPERTY); + } catch (IOException e) { + log.error("Failed to load build-metadata.properties", e); + } + gitSha1 = BrooklynVersion.INSTANCE.getSha1FromOsgiManifest(); + + FluentIterable<BrooklynFeatureSummary> features = FluentIterable.from(BrooklynVersion.getFeatures(mgmt())) + .transform(BrooklynFeatureTransformer.FROM_FEATURE); + + return new VersionSummary(BrooklynVersion.get(), gitSha1, gitBranch, features.toList()); + } + + @Override + public boolean isUp() { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + + Maybe<ManagementContext> mm = mgmtMaybe(); + return !mm.isAbsent() && mm.get().isStartupComplete() && mm.get().isRunning(); + } + + @Override + public boolean isShuttingDown() { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + Maybe<ManagementContext> mm = mgmtMaybe(); + return !mm.isAbsent() && mm.get().isStartupComplete() && !mm.get().isRunning(); + } + + @Override + public boolean isHealthy() { + return isUp() && ((ManagementContextInternal) mgmt()).errors().isEmpty(); + } + + @Override + public Map<String,Object> getUpExtended() { + return MutableMap.<String,Object>of( + "up", isUp(), + "shuttingDown", isShuttingDown(), + "healthy", isHealthy(), + "ha", getHighAvailabilityPlaneStates()); + } + + + @Deprecated + @Override + public String getStatus() { + return getHighAvailabilityNodeState().toString(); + } + + @Override + public String getConfig(String configKey) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) { + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + } + ConfigKey<String> config = ConfigKeys.newStringConfigKey(configKey); + return mgmt().getConfig().getConfig(config); + } + + @Deprecated + @Override + public HighAvailabilitySummary getHighAvailability() { + return getHighAvailabilityPlaneStates(); + } + + @Override + public ManagementNodeState getHighAvailabilityNodeState() { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + + Maybe<ManagementContext> mm = mgmtMaybe(); + if (mm.isAbsent()) return ManagementNodeState.INITIALIZING; + return mm.get().getHighAvailabilityManager().getNodeState(); + } + + @Override + public ManagementNodeState setHighAvailabilityNodeState(HighAvailabilityMode mode) { + if (mode==null) + throw new IllegalStateException("Missing parameter: mode"); + + HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager(); + ManagementNodeState existingState = haMgr.getNodeState(); + haMgr.changeMode(mode); + return existingState; + } + + @Override + public Map<String, Object> getHighAvailabilityMetrics() { + return mgmt().getHighAvailabilityManager().getMetrics(); + } + + @Override + public long getHighAvailabitlityPriority() { + return mgmt().getHighAvailabilityManager().getPriority(); + } + + @Override + public long setHighAvailabilityPriority(long priority) { + HighAvailabilityManager haMgr = mgmt().getHighAvailabilityManager(); + long oldPrio = haMgr.getPriority(); + haMgr.setPriority(priority); + return oldPrio; + } + + @Override + public HighAvailabilitySummary getHighAvailabilityPlaneStates() { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SERVER_STATUS, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + ManagementPlaneSyncRecord memento = mgmt().getHighAvailabilityManager().getLastManagementPlaneSyncRecord(); + if (memento==null) memento = mgmt().getHighAvailabilityManager().loadManagementPlaneSyncRecord(true); + if (memento==null) return null; + return HighAvailabilityTransformer.highAvailabilitySummary(mgmt().getManagementNodeId(), memento); + } + + @Override + public Response clearHighAvailabilityPlaneStates() { + mgmt().getHighAvailabilityManager().publishClearNonMaster(); + return Response.ok().build(); + } + + @Override + public String getUser() { + EntitlementContext entitlementContext = Entitlements.getEntitlementContext(); + if (entitlementContext!=null && entitlementContext.user()!=null){ + return entitlementContext.user(); + } else { + return null; //User can be null if no authentication was requested + } + } + + @Override + public Response exportPersistenceData(String preferredOrigin) { + return exportPersistenceData(TypeCoercions.coerce(preferredOrigin, MementoCopyMode.class)); + } + + protected Response exportPersistenceData(MementoCopyMode preferredOrigin) { + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.SEE_ALL_SERVER_INFO, null)) + throw WebResourceUtils.forbidden("User '%s' is not authorized for this operation", Entitlements.getEntitlementContext().user()); + + File dir = null; + try { + String label = mgmt().getManagementNodeId()+"-"+Time.makeDateSimpleStampString(); + PersistenceObjectStore targetStore = BrooklynPersistenceUtils.newPersistenceObjectStore(mgmt(), null, + "tmp/web-persistence-"+label+"-"+Identifiers.makeRandomId(4)); + dir = ((FileBasedObjectStore)targetStore).getBaseDir(); + // only register the parent dir because that will prevent leaks for the random ID + Os.deleteOnExitEmptyParentsUpTo(dir.getParentFile(), dir.getParentFile()); + BrooklynPersistenceUtils.writeMemento(mgmt(), targetStore, preferredOrigin); + + ByteArrayOutputStream baos = new ByteArrayOutputStream(); + ArchiveBuilder.zip().addDirContentsAt( ((FileBasedObjectStore)targetStore).getBaseDir(), ((FileBasedObjectStore)targetStore).getBaseDir().getName() ).stream(baos); + Os.deleteRecursively(dir); + String filename = "brooklyn-state-"+label+".zip"; + return Response.ok(baos.toByteArray(), MediaType.APPLICATION_OCTET_STREAM_TYPE) + .header("Content-Disposition","attachment; filename = "+filename) + .build(); + } catch (Exception e) { + log.warn("Unable to serve persistence data (rethrowing): "+e, e); + if (dir!=null) { + try { + Os.deleteRecursively(dir); + } catch (Exception e2) { + log.warn("Ignoring error deleting '"+dir+"' after another error, throwing original error ("+e+"); ignored error deleting is: "+e2); + } + } + throw Exceptions.propagate(e); + } + } + +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java new file mode 100644 index 0000000..ebcd2fa --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java @@ -0,0 +1,256 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import static org.apache.brooklyn.rest.util.WebResourceUtils.notFound; + +import java.net.URI; +import java.util.Date; +import java.util.List; +import java.util.Set; + +import javax.annotation.Nullable; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; +import org.apache.brooklyn.core.mgmt.internal.ManagementContextInternal; +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage; +import org.apache.brooklyn.core.mgmt.usage.LocationUsage; +import org.apache.brooklyn.core.mgmt.usage.ApplicationUsage.ApplicationEvent; +import org.apache.brooklyn.rest.api.UsageApi; +import org.apache.brooklyn.rest.domain.UsageStatistic; +import org.apache.brooklyn.rest.domain.UsageStatistics; +import org.apache.brooklyn.rest.transform.ApplicationTransformer; +import org.apache.brooklyn.util.exceptions.UserFacingException; +import org.apache.brooklyn.util.text.Strings; +import org.apache.brooklyn.util.time.Time; + +import com.google.common.base.Objects; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableMap; +import com.google.common.collect.ImmutableSet; +import com.google.common.collect.Lists; + + +public class UsageResource extends AbstractBrooklynRestResource implements UsageApi { + + private static final Logger log = LoggerFactory.getLogger(UsageResource.class); + + private static final Set<Lifecycle> WORKING_LIFECYCLES = ImmutableSet.of(Lifecycle.RUNNING, Lifecycle.CREATED, Lifecycle.STARTING); + + @Override + public List<UsageStatistics> listApplicationsUsage(@Nullable String start, @Nullable String end) { + log.debug("REST call to get application usage for all applications: dates {} -> {}", new Object[] {start, end}); + + List<UsageStatistics> response = Lists.newArrayList(); + + Date startDate = parseDate(start, new Date(0)); + Date endDate = parseDate(end, new Date()); + + checkDates(startDate, endDate); + + Set<ApplicationUsage> usages = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(Predicates.alwaysTrue()); + + for (ApplicationUsage usage : usages) { + List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate); + if (statistics.size() > 0) { + response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of())); + } + } + return response; + } + + @Override + public UsageStatistics getApplicationUsage(String application, String start, String end) { + log.debug("REST call to get application usage for application {}: dates {} -> {}", new Object[] {application, start, end}); + + Date startDate = parseDate(start, new Date(0)); + Date endDate = parseDate(end, new Date()); + + checkDates(startDate, endDate); + + ApplicationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getApplicationUsage(application); + if (usage != null) { + List<UsageStatistic> statistics = retrieveApplicationUsage(usage, startDate, endDate); + return new UsageStatistics(statistics, ImmutableMap.<String,URI>of()); + } else { + throw notFound("Application '%s' not found", application); + } + } + + private List<UsageStatistic> retrieveApplicationUsage(ApplicationUsage usage, Date startDate, Date endDate) { + log.debug("Determining application usage for application {}: dates {} -> {}", new Object[] {usage.getApplicationId(), startDate, endDate}); + log.trace("Considering application usage events of {}: {}", usage.getApplicationId(), usage.getEvents()); + + List<UsageStatistic> result = Lists.newArrayList(); + + // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)... + for (int i = 0; i < usage.getEvents().size(); i++) { + ApplicationEvent current = usage.getEvents().get(i); + Date eventStartDate = current.getDate(); + Date eventEndDate; + + if (i < usage.getEvents().size() - 1) { + ApplicationEvent next = usage.getEvents().get(i + 1); + eventEndDate = next.getDate(); + } else if (current.getState() == Lifecycle.DESTROYED) { + eventEndDate = eventStartDate; + } else { + eventEndDate = new Date(); + } + + if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) { + continue; + } + + if (eventStartDate.compareTo(startDate) < 0) { + eventStartDate = startDate; + } + if (eventEndDate.compareTo(endDate) > 0) { + eventEndDate = endDate; + } + long duration = eventEndDate.getTime() - eventStartDate.getTime(); + UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getApplicationId(), usage.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata()); + log.trace("Adding application usage statistic to response for app {}: {}", usage.getApplicationId(), statistic); + result.add(statistic); + } + + return result; + } + + @Override + public List<UsageStatistics> listMachinesUsage(final String application, final String start, final String end) { + log.debug("REST call to get machine usage for application {}: dates {} -> {}", new Object[] {application, start, end}); + + final Date startDate = parseDate(start, new Date(0)); + final Date endDate = parseDate(end, new Date()); + + checkDates(startDate, endDate); + + // Note currently recording ALL metrics for a machine that contains an Event from given Application + Set<LocationUsage> matches = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(new Predicate<LocationUsage>() { + @Override + public boolean apply(LocationUsage input) { + LocationUsage.LocationEvent first = input.getEvents().get(0); + if (endDate.compareTo(first.getDate()) < 0) { + return false; + } + LocationUsage.LocationEvent last = input.getEvents().get(input.getEvents().size() - 1); + if (!WORKING_LIFECYCLES.contains(last.getState()) && startDate.compareTo(last.getDate()) > 0) { + return false; + } + if (application != null) { + for (LocationUsage.LocationEvent e : input.getEvents()) { + if (Objects.equal(application, e.getApplicationId())) { + return true; + } + } + return false; + } + return true; + } + }); + + List<UsageStatistics> response = Lists.newArrayList(); + for (LocationUsage usage : matches) { + List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate); + if (statistics.size() > 0) { + response.add(new UsageStatistics(statistics, ImmutableMap.<String,URI>of())); + } + } + return response; + } + + @Override + public UsageStatistics getMachineUsage(final String machine, final String start, final String end) { + log.debug("REST call to get machine usage for machine {}: dates {} -> {}", new Object[] {machine, start, end}); + + final Date startDate = parseDate(start, new Date(0)); + final Date endDate = parseDate(end, new Date()); + + checkDates(startDate, endDate); + + // Note currently recording ALL metrics for a machine that contains an Event from given Application + LocationUsage usage = ((ManagementContextInternal) mgmt()).getUsageManager().getLocationUsage(machine); + + if (usage == null) { + throw notFound("Machine '%s' not found", machine); + } + + List<UsageStatistic> statistics = retrieveMachineUsage(usage, startDate, endDate); + return new UsageStatistics(statistics, ImmutableMap.<String,URI>of()); + } + + private List<UsageStatistic> retrieveMachineUsage(LocationUsage usage, Date startDate, Date endDate) { + log.debug("Determining machine usage for location {}", usage.getLocationId()); + log.trace("Considering machine usage events of {}: {}", usage.getLocationId(), usage.getEvents()); + + List<UsageStatistic> result = Lists.newArrayList(); + + // Getting duration of state by comparing with next event (if next event is of same type, we just generate two statistics)... + for (int i = 0; i < usage.getEvents().size(); i++) { + LocationUsage.LocationEvent current = usage.getEvents().get(i); + Date eventStartDate = current.getDate(); + Date eventEndDate; + + if (i < usage.getEvents().size() - 1) { + LocationUsage.LocationEvent next = usage.getEvents().get(i + 1); + eventEndDate = next.getDate(); + } else if (current.getState() == Lifecycle.DESTROYED || current.getState() == Lifecycle.STOPPED) { + eventEndDate = eventStartDate; + } else { + eventEndDate = new Date(); + } + + if (eventStartDate.compareTo(endDate) > 0 || eventEndDate.compareTo(startDate) < 0) { + continue; + } + + if (eventStartDate.compareTo(startDate) < 0) { + eventStartDate = startDate; + } + if (eventEndDate.compareTo(endDate) > 0) { + eventEndDate = endDate; + } + long duration = eventEndDate.getTime() - eventStartDate.getTime(); + UsageStatistic statistic = new UsageStatistic(ApplicationTransformer.statusFromLifecycle(current.getState()), usage.getLocationId(), current.getApplicationId(), format(eventStartDate), format(eventEndDate), duration, usage.getMetadata()); + log.trace("Adding machine usage statistic to response for app {}: {}", usage.getLocationId(), statistic); + result.add(statistic); + } + + return result; + } + + private void checkDates(Date startDate, Date endDate) { + if (startDate.compareTo(endDate) > 0) { + throw new UserFacingException(new IllegalArgumentException("Start must be less than or equal to end: " + startDate + " > " + endDate + + " (" + startDate.getTime() + " > " + endDate.getTime() + ")")); + } + } + + private Date parseDate(String toParse, Date def) { + return Strings.isBlank(toParse) ? def : Time.parseDate(toParse); + } + + private String format(Date date) { + return Time.makeDateString(date, Time.DATE_FORMAT_ISO8601_NO_MILLIS, Time.TIME_ZONE_UTC); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java new file mode 100644 index 0000000..7492af6 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.resources; + +import org.apache.brooklyn.core.BrooklynVersion; +import org.apache.brooklyn.rest.api.VersionApi; + +/** @deprecated since 0.7.0; use /v1/server/version */ +@Deprecated +public class VersionResource extends AbstractBrooklynRestResource implements VersionApi { + + @Override + public String getVersion() { + return BrooklynVersion.get(); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java new file mode 100644 index 0000000..928a6bd --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java @@ -0,0 +1,32 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security; + +import com.google.common.base.Charsets; +import com.google.common.hash.HashCode; +import com.google.common.hash.Hashing; + +public class PasswordHasher { + public static String sha256(String salt, String password) { + if (salt == null) salt = ""; + byte[] bytes = (salt + password).getBytes(Charsets.UTF_8); + HashCode hash = Hashing.sha256().hashBytes(bytes); + return hash.toString(); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java new file mode 100644 index 0000000..91c2523 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java @@ -0,0 +1,56 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security.provider; + +import javax.servlet.http.HttpSession; + +import org.apache.brooklyn.util.text.Strings; + +/** + * Provides default implementations of {@link #isAuthenticated(HttpSession)} and + * {@link #logout(HttpSession)}. + */ +public abstract class AbstractSecurityProvider implements SecurityProvider { + + @Override + public boolean isAuthenticated(HttpSession session) { + if (session == null) return false; + Object value = session.getAttribute(getAuthenticationKey()); + return Strings.isNonBlank(Strings.toString(value)); + } + + @Override + public boolean logout(HttpSession session) { + if (session == null) return false; + session.removeAttribute(getAuthenticationKey()); + return true; + } + + /** + * Sets an authentication token for the user on the session. Always returns true. + */ + protected boolean allow(HttpSession session, String user) { + session.setAttribute(getAuthenticationKey(), user); + return true; + } + + protected String getAuthenticationKey() { + return getClass().getName() + ".AUTHENTICATED"; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java new file mode 100644 index 0000000..97b4fe1 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security.provider; + +import javax.servlet.http.HttpSession; + +/** provider who allows everyone */ +public class AnyoneSecurityProvider implements SecurityProvider { + + @Override + public boolean isAuthenticated(HttpSession session) { + return true; + } + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + return true; + } + + @Override + public boolean logout(HttpSession session) { + return true; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java new file mode 100644 index 0000000..a976975 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java @@ -0,0 +1,40 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security.provider; + +import javax.servlet.http.HttpSession; + +/** provider who disallows everyone */ +public class BlackholeSecurityProvider implements SecurityProvider { + + @Override + public boolean isAuthenticated(HttpSession session) { + return false; + } + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + return false; + } + + @Override + public boolean logout(HttpSession session) { + return true; + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java new file mode 100644 index 0000000..7b8e4a5 --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java @@ -0,0 +1,73 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security.provider; + +import javax.servlet.http.HttpSession; + +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import org.apache.brooklyn.util.javalang.JavaClassNames; +import org.apache.brooklyn.util.net.Networking; +import org.apache.brooklyn.util.text.Identifiers; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +public class BrooklynUserWithRandomPasswordSecurityProvider extends AbstractSecurityProvider implements SecurityProvider { + + public static final Logger LOG = LoggerFactory.getLogger(BrooklynUserWithRandomPasswordSecurityProvider.class); + private static final String USER = "brooklyn"; + private final String password; + + public BrooklynUserWithRandomPasswordSecurityProvider() { + this.password = Identifiers.makeRandomId(10); + LOG.info("Allowing access to web console from localhost or with {}:{}", USER, password); + } + + public BrooklynUserWithRandomPasswordSecurityProvider(ManagementContext mgmt) { + this(); + } + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + if ((USER.equals(user) && this.password.equals(password)) || isRemoteAddressLocalhost(session)) { + return allow(session, user); + } else { + return false; + } + } + + private boolean isRemoteAddressLocalhost(HttpSession session) { + Object remoteAddress = session.getAttribute(BrooklynWebConfig.REMOTE_ADDRESS_SESSION_ATTRIBUTE); + if (!(remoteAddress instanceof String)) return false; + if (Networking.isLocalhost((String)remoteAddress)) { + if (LOG.isTraceEnabled()) { + LOG.trace(this+": granting passwordless access to "+session+" originating from "+remoteAddress); + } + return true; + } else { + LOG.debug(this+": password required for "+session+" originating from "+remoteAddress); + return false; + } + } + + @Override + public String toString() { + return JavaClassNames.cleanSimpleClassName(this); + } +} http://git-wip-us.apache.org/repos/asf/brooklyn-server/blob/6f624c78/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java ---------------------------------------------------------------------- diff --git a/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java new file mode 100644 index 0000000..8b2b9da --- /dev/null +++ b/rest/rest-resources/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java @@ -0,0 +1,165 @@ +/* + * Licensed to the Apache Software Foundation (ASF) under one + * or more contributor license agreements. See the NOTICE file + * distributed with this work for additional information + * regarding copyright ownership. The ASF licenses this file + * to you 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 org.apache.brooklyn.rest.security.provider; + +import java.lang.reflect.Constructor; +import java.util.concurrent.atomic.AtomicLong; + +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; +import org.apache.brooklyn.api.mgmt.ManagementContext; +import org.apache.brooklyn.config.StringConfigMap; +import org.apache.brooklyn.core.internal.BrooklynProperties; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import org.apache.brooklyn.util.text.Strings; + +public class DelegatingSecurityProvider implements SecurityProvider { + + private static final Logger log = LoggerFactory.getLogger(DelegatingSecurityProvider.class); + protected final ManagementContext mgmt; + + public DelegatingSecurityProvider(ManagementContext mgmt) { + this.mgmt = mgmt; + mgmt.addPropertiesReloadListener(new PropertiesListener()); + } + + private SecurityProvider delegate; + private final AtomicLong modCount = new AtomicLong(); + + private class PropertiesListener implements ManagementContext.PropertiesReloadListener { + private static final long serialVersionUID = 8148722609022378917L; + + @Override + public void reloaded() { + log.debug("{} reloading security provider", DelegatingSecurityProvider.this); + synchronized (DelegatingSecurityProvider.this) { + loadDelegate(); + invalidateExistingSessions(); + } + } + } + + public synchronized SecurityProvider getDelegate() { + if (delegate == null) { + delegate = loadDelegate(); + } + return delegate; + } + + @SuppressWarnings("unchecked") + private synchronized SecurityProvider loadDelegate() { + StringConfigMap brooklynProperties = mgmt.getConfig(); + + SecurityProvider presetDelegate = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE); + if (presetDelegate!=null) { + log.info("REST using pre-set security provider " + presetDelegate); + return presetDelegate; + } + + String className = brooklynProperties.getConfig(BrooklynWebConfig.SECURITY_PROVIDER_CLASSNAME); + + if (delegate != null && BrooklynWebConfig.hasNoSecurityOptions(mgmt.getConfig())) { + log.debug("{} refusing to change from {}: No security provider set in reloaded properties.", + this, delegate); + return delegate; + } + log.info("REST using security provider " + className); + + try { + Class<? extends SecurityProvider> clazz; + try { + clazz = (Class<? extends SecurityProvider>) Class.forName(className); + } catch (Exception e) { + String oldPackage = "brooklyn.web.console.security."; + if (className.startsWith(oldPackage)) { + className = Strings.removeFromStart(className, oldPackage); + className = DelegatingSecurityProvider.class.getPackage().getName() + "." + className; + clazz = (Class<? extends SecurityProvider>) Class.forName(className); + log.warn("Deprecated package " + oldPackage + " detected; please update security provider to point to " + className); + } else throw e; + } + + Constructor<? extends SecurityProvider> constructor; + try { + constructor = clazz.getConstructor(ManagementContext.class); + delegate = constructor.newInstance(mgmt); + } catch (Exception e) { + constructor = clazz.getConstructor(); + Object delegateO = constructor.newInstance(); + if (!(delegateO instanceof SecurityProvider)) { + // if classloaders get mangled it will be a different CL's SecurityProvider + throw new ClassCastException("Delegate is either not a security provider or has an incompatible classloader: "+delegateO); + } + delegate = (SecurityProvider) delegateO; + } + } catch (Exception e) { + log.warn("REST unable to instantiate security provider " + className + "; all logins are being disallowed", e); + delegate = new BlackholeSecurityProvider(); + } + + ((BrooklynProperties)mgmt.getConfig()).put(BrooklynWebConfig.SECURITY_PROVIDER_INSTANCE, delegate); + + return delegate; + } + + /** + * Causes all existing sessions to be invalidated. + */ + protected void invalidateExistingSessions() { + modCount.incrementAndGet(); + } + + @Override + public boolean isAuthenticated(HttpSession session) { + if (session == null) return false; + Object modCountWhenFirstAuthenticated = session.getAttribute(getModificationCountKey()); + boolean authenticated = getDelegate().isAuthenticated(session) && + Long.valueOf(modCount.get()).equals(modCountWhenFirstAuthenticated); + return authenticated; + } + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + boolean authenticated = getDelegate().authenticate(session, user, password); + if (authenticated) { + session.setAttribute(getModificationCountKey(), modCount.get()); + } + if (log.isTraceEnabled() && authenticated) { + log.trace("User {} authenticated with provider {}", user, getDelegate()); + } else if (!authenticated && log.isDebugEnabled()) { + log.debug("Failed authentication for user {} with provider {}", user, getDelegate()); + } + return authenticated; + } + + @Override + public boolean logout(HttpSession session) { + boolean logout = getDelegate().logout(session); + if (logout) { + session.removeAttribute(getModificationCountKey()); + } + return logout; + } + + private String getModificationCountKey() { + return getClass().getName() + ".ModCount"; + } +}