http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java new file mode 100644 index 0000000..4137263 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ScriptResource.java @@ -0,0 +1,99 @@ +/* + * 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 brooklyn.rest.api.ScriptApi; +import brooklyn.rest.domain.ScriptExecutionSummary; +import brooklyn.util.stream.ThreadLocalPrintStream; +import 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java new file mode 100644 index 0000000..6689829 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/SensorResource.java @@ -0,0 +1,150 @@ +/* + * 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 static com.google.common.collect.Iterables.transform; + +import java.util.List; +import java.util.Map; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.entity.basic.EntityInternal; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.event.AttributeSensor; +import brooklyn.event.Sensor; +import brooklyn.event.basic.BasicAttributeSensor; +import brooklyn.management.entitlement.Entitlements; +import brooklyn.rest.api.SensorApi; +import 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 brooklyn.util.task.ValueResolver; +import brooklyn.util.text.Strings; +import brooklyn.util.time.Duration; + +import com.google.common.base.Function; +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); + + @SuppressWarnings("rawtypes") + @Override + public List<SensorSummary> list(final String application, final String entityToken) { + final EntityLocal entity = brooklyn().getEntity(application, entityToken); + + return Lists.newArrayList(transform(filter(entity.getEntityType().getSensors(), AttributeSensor.class), + new Function<AttributeSensor, SensorSummary>() { + @Override + public SensorSummary apply(AttributeSensor sensor) { + return SensorTransformer.sensorSummary(entity, sensor); + } + })); + } + + @Override + public Map<String, Object> batchSensorRead(final String application, final String entityToken, final Boolean raw) { + final EntityLocal entity = brooklyn().getEntity(application, entityToken); + Map<String, Object> sensorMap = Maps.newHashMap(); + @SuppressWarnings("rawtypes") + Iterable<AttributeSensor> sensors = filter(entity.getEntityType().getSensors(), AttributeSensor.class); + + for (AttributeSensor<?> sensor : sensors) { + 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 EntityLocal entity = brooklyn().getEntity(application, entityToken); + AttributeSensor<?> sensor = findSensor(entity, sensorName); + 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(EntityLocal 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 EntityLocal entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.unauthorized("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.setAttribute(sensor, newValue); + } + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public void set(String application, String entityToken, String sensorName, Object newValue) { + final EntityLocal entity = brooklyn().getEntity(application, entityToken); + if (!Entitlements.isEntitled(mgmt().getEntitlementManager(), Entitlements.MODIFY_ENTITY, entity)) { + throw WebResourceUtils.unauthorized("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.setAttribute(sensor, newValue); + } + + @Override + public void delete(String application, String entityToken, String sensorName) { + final EntityLocal entity = brooklyn().getEntity(application, entityToken); + AttributeSensor<?> sensor = findSensor(entity, sensorName); + if (log.isDebugEnabled()) + log.debug("REST user "+Entitlements.getEntitlementContext()+" deleting sensor "+sensorName); + ((EntityInternal)entity).removeAttribute(sensor); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java new file mode 100644 index 0000000..24d2e0c --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/ServerResource.java @@ -0,0 +1,496 @@ +/* + * 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 org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.Preconditions; +import com.google.common.collect.FluentIterable; + +import brooklyn.BrooklynVersion; +import brooklyn.config.ConfigKey; +import brooklyn.entity.Application; +import brooklyn.entity.basic.Attributes; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Entities; +import brooklyn.entity.basic.EntityLocal; +import brooklyn.entity.basic.Lifecycle; +import brooklyn.entity.basic.StartableApplication; +import brooklyn.entity.rebind.persister.BrooklynPersistenceUtils; +import brooklyn.entity.rebind.persister.FileBasedObjectStore; +import brooklyn.entity.rebind.persister.PersistenceObjectStore; +import brooklyn.management.ManagementContext; +import brooklyn.management.Task; +import brooklyn.management.entitlement.EntitlementContext; +import brooklyn.management.entitlement.Entitlements; +import brooklyn.management.ha.HighAvailabilityManager; +import brooklyn.management.ha.HighAvailabilityMode; +import brooklyn.management.ha.ManagementNodeState; +import brooklyn.management.ha.ManagementPlaneSyncRecord; +import brooklyn.management.ha.MementoCopyMode; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.rest.api.ServerApi; +import brooklyn.rest.domain.BrooklynFeatureSummary; +import brooklyn.rest.domain.HighAvailabilitySummary; +import 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 brooklyn.util.ResourceUtils; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.file.ArchiveBuilder; +import brooklyn.util.flags.TypeCoercions; +import brooklyn.util.guava.Maybe; +import brooklyn.util.os.Os; +import brooklyn.util.text.Identifiers; +import brooklyn.util.text.Strings; +import brooklyn.util.time.CountdownTimer; +import brooklyn.util.time.Duration; +import brooklyn.util.time.Time; + +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 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.unauthorized("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(); + + 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((EntityLocal)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 (shutdownHandler != null) { + shutdownHandler.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.unauthorized("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.unauthorized("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.unauthorized("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.unauthorized("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.unauthorized("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.unauthorized("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.unauthorized("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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java new file mode 100644 index 0000000..315dedb --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/UsageResource.java @@ -0,0 +1,257 @@ +/* + * 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 brooklyn.entity.basic.Lifecycle; +import brooklyn.management.internal.ManagementContextInternal; +import brooklyn.management.usage.ApplicationUsage; +import brooklyn.management.usage.ApplicationUsage.ApplicationEvent; +import brooklyn.management.usage.LocationUsage; +import brooklyn.rest.api.UsageApi; +import brooklyn.rest.domain.UsageStatistic; +import brooklyn.rest.domain.UsageStatistics; +import org.apache.brooklyn.rest.transform.ApplicationTransformer; +import brooklyn.util.exceptions.UserFacingException; +import brooklyn.util.text.Strings; +import 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/resources/VersionResource.java new file mode 100644 index 0000000..dc6db6f --- /dev/null +++ b/usage/rest-server/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 brooklyn.BrooklynVersion; +import 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/PasswordHasher.java new file mode 100644 index 0000000..928a6bd --- /dev/null +++ b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AbstractSecurityProvider.java new file mode 100644 index 0000000..bbdece8 --- /dev/null +++ b/usage/rest-server/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 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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/AnyoneSecurityProvider.java new file mode 100644 index 0000000..97b4fe1 --- /dev/null +++ b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BlackholeSecurityProvider.java new file mode 100644 index 0000000..a976975 --- /dev/null +++ b/usage/rest-server/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/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java new file mode 100644 index 0000000..7315e4d --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/BrooklynUserWithRandomPasswordSecurityProvider.java @@ -0,0 +1,68 @@ +/* + * 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.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.management.ManagementContext; +import org.apache.brooklyn.rest.filter.BrooklynPropertiesSecurityFilter; +import brooklyn.util.net.Networking; +import brooklyn.util.text.Identifiers; + +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(BrooklynPropertiesSecurityFilter.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; + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java new file mode 100644 index 0000000..98d5776 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/DelegatingSecurityProvider.java @@ -0,0 +1,156 @@ +/* + * 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 brooklyn.config.StringConfigMap; +import brooklyn.management.ManagementContext; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import 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(); + + 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(); + } + 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"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java new file mode 100644 index 0000000..37f3aca --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/ExplicitUsersSecurityProvider.java @@ -0,0 +1,103 @@ +/* + * 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.util.LinkedHashSet; +import java.util.Set; +import java.util.StringTokenizer; + +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.StringConfigMap; +import brooklyn.management.ManagementContext; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import org.apache.brooklyn.rest.security.PasswordHasher; + +/** + * Security provider which validates users against passwords according to property keys, + * as set in {@link BrooklynWebConfig#USERS} and {@link BrooklynWebConfig#PASSWORD_FOR_USER(String)} + */ +public class ExplicitUsersSecurityProvider extends AbstractSecurityProvider implements SecurityProvider { + + public static final Logger LOG = LoggerFactory.getLogger(ExplicitUsersSecurityProvider.class); + + protected final ManagementContext mgmt; + private boolean allowAnyUserWithValidPass; + private Set<String> allowedUsers = null; + + public ExplicitUsersSecurityProvider(ManagementContext mgmt) { + this.mgmt = mgmt; + } + + private synchronized void initialize() { + if (allowedUsers != null) return; + + StringConfigMap properties = mgmt.getConfig(); + + allowedUsers = new LinkedHashSet<String>(); + String users = properties.getConfig(BrooklynWebConfig.USERS); + if (users == null) { + // TODO unfortunately this is only activated *when* someone tries to log in + // (NB it seems like this class is not even instantiated until first log in) + LOG.warn("REST has no users configured; no one will be able to log in!"); + } else if ("*".equals(users)) { + LOG.info("REST allowing any user (so long as valid password is set)"); + allowAnyUserWithValidPass = true; + } else { + StringTokenizer t = new StringTokenizer(users, ","); + while (t.hasMoreElements()) { + allowedUsers.add(("" + t.nextElement()).trim()); + } + LOG.info("REST allowing users: " + allowedUsers); + } + } + + + @Override + public boolean authenticate(HttpSession session, String user, String password) { + if (session==null || user==null) return false; + + initialize(); + + if (!allowAnyUserWithValidPass) { + if (!allowedUsers.contains(user)) { + LOG.debug("REST rejecting unknown user "+user); + return false; + } + } + + BrooklynProperties properties = (BrooklynProperties) mgmt.getConfig(); + String expectedP = properties.getConfig(BrooklynWebConfig.PASSWORD_FOR_USER(user)); + String salt = properties.getConfig(BrooklynWebConfig.SALT_FOR_USER(user)); + String expectedSha256 = properties.getConfig(BrooklynWebConfig.SHA256_FOR_USER(user)); + + if (expectedP != null) { + return expectedP.equals(password) && allow(session, user); + } else if (expectedSha256 != null) { + String hashedPassword = PasswordHasher.sha256(salt, password); + return expectedSha256.equals(hashedPassword) && allow(session, user); + } + + return false; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java new file mode 100644 index 0000000..151772a --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/LdapSecurityProvider.java @@ -0,0 +1,131 @@ +/* + * 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.util.Hashtable; + +import javax.naming.Context; +import javax.naming.NamingException; +import javax.naming.directory.InitialDirContext; +import javax.servlet.http.HttpSession; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.base.CharMatcher; + +import brooklyn.config.StringConfigMap; +import brooklyn.management.ManagementContext; +import org.apache.brooklyn.rest.BrooklynWebConfig; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.text.Strings; +import com.google.common.base.Function; +import com.google.common.base.Joiner; +import com.google.common.collect.Lists; + +import java.util.Arrays; +import java.util.List; + +/** + * A {@link SecurityProvider} implementation that relies on LDAP to authenticate. + * + * @author Peter Veentjer. + */ +public class LdapSecurityProvider extends AbstractSecurityProvider implements SecurityProvider { + + public static final Logger LOG = LoggerFactory.getLogger(LdapSecurityProvider.class); + + public static final String LDAP_CONTEXT_FACTORY = "com.sun.jndi.ldap.LdapCtxFactory"; + + private final String ldapUrl; + private final String ldapRealm; + private final String organizationUnit; + + public LdapSecurityProvider(ManagementContext mgmt) { + StringConfigMap properties = mgmt.getConfig(); + ldapUrl = properties.getConfig(BrooklynWebConfig.LDAP_URL); + Strings.checkNonEmpty(ldapUrl, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_URL); + ldapRealm = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_REALM)); + Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_REALM); + + if(Strings.isBlank(properties.getConfig(BrooklynWebConfig.LDAP_OU))) { + LOG.info("Setting LDAP ou attribute to: Users"); + organizationUnit = "Users"; + } else { + organizationUnit = CharMatcher.isNot('"').retainFrom(properties.getConfig(BrooklynWebConfig.LDAP_OU)); + } + Strings.checkNonEmpty(ldapRealm, "LDAP security provider configuration missing required property "+BrooklynWebConfig.LDAP_OU); + } + + public LdapSecurityProvider(String ldapUrl, String ldapRealm, String organizationUnit) { + this.ldapUrl = ldapUrl; + this.ldapRealm = ldapRealm; + this.organizationUnit = organizationUnit; + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + @Override + public boolean authenticate(HttpSession session, String user, String password) { + if (session==null || user==null) return false; + checkCanLoad(); + + Hashtable env = new Hashtable(); + env.put(Context.INITIAL_CONTEXT_FACTORY, "com.sun.jndi.ldap.LdapCtxFactory"); + env.put(Context.PROVIDER_URL, ldapUrl); + env.put(Context.SECURITY_AUTHENTICATION, "simple"); + env.put(Context.SECURITY_PRINCIPAL, getUserDN(user)); + env.put(Context.SECURITY_CREDENTIALS, password); + + try { + new InitialDirContext(env); + return allow(session, user); + } catch (NamingException e) { + return false; + } + } + + /** + * Returns the LDAP path for the user + * + * @param user + * @return String + */ + protected String getUserDN(String user) { + List<String> domain = Lists.transform(Arrays.asList(ldapRealm.split("\\.")), new Function<String, String>() { + @Override + public String apply(String input) { + return "dc=" + input; + } + }); + + String dc = Joiner.on(",").join(domain).toLowerCase(); + return "cn=" + user + ",ou=" + organizationUnit + "," + dc; + } + + static boolean triedLoading = false; + public synchronized static void checkCanLoad() { + if (triedLoading) return; + try { + Class.forName(LDAP_CONTEXT_FACTORY); + triedLoading = true; + } catch (Throwable e) { + throw Exceptions.propagate(new ClassNotFoundException("Unable to load LDAP classes ("+LDAP_CONTEXT_FACTORY+") required for Brooklyn LDAP security provider")); + } + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java new file mode 100644 index 0000000..57d1400 --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/security/provider/SecurityProvider.java @@ -0,0 +1,35 @@ +/* + * 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; + +/** + * The SecurityProvider is responsible for doing authentication. + * + * A class should either have a constructor receiving a BrooklynProperties or it should have a no-arg constructor. + */ +public interface SecurityProvider { + + public boolean isAuthenticated(HttpSession session); + + public boolean authenticate(HttpSession session, String user, String password); + + public boolean logout(HttpSession session); +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/a9e5ca55/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java ---------------------------------------------------------------------- diff --git a/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java new file mode 100644 index 0000000..e0ebe2a --- /dev/null +++ b/usage/rest-server/src/main/java/org/apache/brooklyn/rest/transform/AccessTransformer.java @@ -0,0 +1,39 @@ +/* + * 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.transform; + +import java.net.URI; + +import brooklyn.management.internal.AccessManager; +import brooklyn.rest.domain.AccessSummary; + +import com.google.common.collect.ImmutableMap; + +/** + * @author Adam Lowe + */ +public class AccessTransformer { + + public static AccessSummary accessSummary(AccessManager manager) { + String selfUri = "/v1/access/"; + ImmutableMap<String, URI> links = ImmutableMap.of("self", URI.create(selfUri)); + + return new AccessSummary(manager.isLocationProvisioningAllowed(), links); + } +}