You are viewing a plain text version of this content. The canonical link for it is here.
Posted to commits@brooklyn.apache.org by he...@apache.org on 2016/02/18 16:47:35 UTC
[17/34] brooklyn-server git commit: [BROOKLYN-183] REST API using CXF
JAX-RS 2.0 implementation
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";
+ }
+}