http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java b/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java new file mode 100644 index 0000000..33aa4ca --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/classloading/ClassLoaderFromBrooklynClassLoadingContext.java @@ -0,0 +1,66 @@ +/* + * 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.core.management.classloading; + +import java.net.URL; + +import org.apache.brooklyn.api.management.classloading.BrooklynClassLoadingContext; + +public class ClassLoaderFromBrooklynClassLoadingContext extends ClassLoader { + + /** Constructs a {@link ClassLoader} which delegates to the given {@link BrooklynClassLoadingContext} */ + public static ClassLoader of(BrooklynClassLoadingContext clc) { + return new ClassLoaderFromBrooklynClassLoadingContext(clc); + } + + protected final BrooklynClassLoadingContext clc; + + protected ClassLoaderFromBrooklynClassLoadingContext(BrooklynClassLoadingContext clc) { + this.clc = clc; + } + + @Override + public Class<?> findClass(String className) throws ClassNotFoundException { + Class<?> result = clc.loadClass(className); + if (result!=null) return result; + + // last resort. see comment in XStream CompositeClassLoader + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + result = contextClassLoader.loadClass(className); + if (result!=null) return result; + } + return null; + } + + @Override + protected URL findResource(String name) { + URL result = clc.getResource(name); + if (result!=null) return result; + + // last resort. see comment in XStream CompositeClassLoader + ClassLoader contextClassLoader = Thread.currentThread().getContextClassLoader(); + if (contextClassLoader != null) { + result = contextClassLoader.getResource(name); + if (result!=null) return result; + } + return null; + } + +}
http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java b/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java new file mode 100644 index 0000000..99fa15b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/classloading/JavaBrooklynClassLoadingContext.java @@ -0,0 +1,123 @@ +/* + * 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.core.management.classloading; + +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; + +import java.io.IOException; +import java.net.URL; +import java.util.Collections; +import java.util.Enumeration; + +import org.apache.brooklyn.api.management.ManagementContext; + +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.guava.Maybe; + +import com.google.common.base.Objects; + +public class JavaBrooklynClassLoadingContext extends AbstractBrooklynClassLoadingContext { + + // on deserialization this loader is replaced with the catalog's root loader; + // may cause problems for non-osgi catalog items, but that's a reasonable trade-off, + // should this be serialized (e.g. in SpecialFlagsTransformer) in such a case! + private final transient ClassLoader loader; + + /** + * @deprecated since 0.7.0 only for legacy catalog items which provide a non-osgi loader; see {@link #newDefault(ManagementContext)} + */ @Deprecated + public static JavaBrooklynClassLoadingContext create(ClassLoader loader) { + return new JavaBrooklynClassLoadingContext(null, checkNotNull(loader, "loader")); + } + + /** + * At least one of mgmt or loader must not be null. + * @deprecated since 0.7.0 only for legacy catalog items which provide a non-osgi loader; see {@link #newDefault(ManagementContext)} + */ @Deprecated + public static JavaBrooklynClassLoadingContext create(ManagementContext mgmt, ClassLoader loader) { + checkState(mgmt != null || loader != null, "mgmt and loader must not both be null"); + return new JavaBrooklynClassLoadingContext(mgmt, loader); + } + + public static JavaBrooklynClassLoadingContext create(ManagementContext mgmt) { + return new JavaBrooklynClassLoadingContext(checkNotNull(mgmt, "mgmt"), null); + } + + @Deprecated /** @deprecated since 0.7.0 use {@link #create(ManagementContext)} */ + public static JavaBrooklynClassLoadingContext newDefault(ManagementContext mgmt) { + return new JavaBrooklynClassLoadingContext(checkNotNull(mgmt, "mgmt"), null); + } + + @Deprecated /** @deprecated since 0.7.0 will become private; use one of the static methods to instantiate */ + public JavaBrooklynClassLoadingContext(ManagementContext mgmt, ClassLoader loader) { + super(mgmt); + this.loader = loader; + } + + private ClassLoader getClassLoader() { + if (loader != null) return loader; + if (mgmt!=null) return mgmt.getCatalogClassLoader(); + return JavaBrooklynClassLoadingContext.class.getClassLoader(); + } + + @SuppressWarnings({ "rawtypes", "unchecked" }) + public Maybe<Class<?>> tryLoadClass(String className) { + try { + return (Maybe) Maybe.of(getClassLoader().loadClass(className)); + } catch (Exception e) { + Exceptions.propagateIfFatal(e); + return Maybe.absent("Invalid class: "+className, e); + } + } + + @Override + public String toString() { + return "java:"+getClassLoader(); + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), getClassLoader()); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) return false; + if (!(obj instanceof JavaBrooklynClassLoadingContext)) return false; + if (!Objects.equal(getClassLoader(), ((JavaBrooklynClassLoadingContext)obj).getClassLoader())) return false; + return true; + } + + @Override + public URL getResource(String name) { + return getClassLoader().getResource(name); + } + + @Override + public Iterable<URL> getResources(String name) { + Enumeration<URL> resources; + try { + resources = getClassLoader().getResources(name); + } catch (IOException e) { + throw Exceptions.propagate(e); + } + return Collections.list(resources); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java b/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java new file mode 100644 index 0000000..af5920b --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/classloading/OsgiBrooklynClassLoadingContext.java @@ -0,0 +1,147 @@ +/* + * 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.core.management.classloading; + +import java.net.URL; +import java.util.Collection; +import java.util.Collections; + +import org.apache.brooklyn.api.catalog.CatalogItem; +import org.apache.brooklyn.api.catalog.CatalogItem.CatalogBundle; +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.core.management.entitlement.Entitlements; +import org.apache.brooklyn.core.management.ha.OsgiManager; +import org.apache.brooklyn.core.management.internal.ManagementContextInternal; + +import brooklyn.catalog.internal.CatalogUtils; +import brooklyn.util.guava.Maybe; + +import com.google.common.base.Objects; + +public class OsgiBrooklynClassLoadingContext extends AbstractBrooklynClassLoadingContext { + + private final String catalogItemId; + private boolean hasBundles = false; + private transient Collection<CatalogBundle> _bundles; + + public OsgiBrooklynClassLoadingContext(ManagementContext mgmt, String catalogItemId, Collection<CatalogBundle> bundles) { + super(mgmt); + this._bundles = bundles; + this.hasBundles = bundles!=null && !bundles.isEmpty(); + this.catalogItemId = catalogItemId; + } + + public Collection<CatalogBundle> getBundles() { + if (_bundles!=null || !hasBundles) return _bundles; + CatalogItem<?, ?> cat = CatalogUtils.getCatalogItemOptionalVersion(mgmt, catalogItemId); + if (cat==null) { + throw new IllegalStateException("Catalog item not found for "+catalogItemId+"; cannot create loading context"); + } + _bundles = cat.getLibraries(); + return _bundles; + } + + @Override + @SuppressWarnings({ "unchecked", "rawtypes" }) + public Maybe<Class<?>> tryLoadClass(String className) { + Maybe<Class<Object>> clazz = null; + Maybe<OsgiManager> osgi = null; + if (mgmt!=null) { + osgi = ((ManagementContextInternal)mgmt).getOsgiManager(); + if (osgi.isPresent() && getBundles()!=null && !getBundles().isEmpty()) { + if (!Entitlements.isEntitled(mgmt.getEntitlementManager(), Entitlements.SEE_CATALOG_ITEM, catalogItemId)) + return Maybe.absent("Not entitled to use this catalog entry"); + + clazz = osgi.get().tryResolveClass(className, getBundles()); + if (clazz.isPresent()) + return (Maybe)clazz; + } + } + + if (clazz!=null) { + // if OSGi bundles were defined and failed, then use its error message + return (Maybe)clazz; + } + // else determine best message + if (mgmt==null) return Maybe.absent("No mgmt context available for loading "+className); + if (osgi!=null && osgi.isAbsent()) return Maybe.absent("OSGi not available on mgmt for loading "+className); + if (!hasBundles) + return Maybe.absent("No bundles available for loading "+className); + return Maybe.absent("Inconsistent state ("+mgmt+"/"+osgi+"/"+getBundles()+" loading "+className); + } + + @Override + public String toString() { + return "OSGi:"+catalogItemId+"["+getBundles()+"]"; + } + + @Override + public int hashCode() { + return Objects.hashCode(super.hashCode(), getBundles(), catalogItemId); + } + + @Override + public boolean equals(Object obj) { + if (!super.equals(obj)) return false; + if (!(obj instanceof OsgiBrooklynClassLoadingContext)) return false; + + OsgiBrooklynClassLoadingContext other = (OsgiBrooklynClassLoadingContext)obj; + if (!catalogItemId.equals(other.catalogItemId)) return false; + if (!Objects.equal(getBundles(), other.getBundles())) return false; + return true; + } + + @Override + public URL getResource(String name) { + if (mgmt != null && isEntitledToSeeCatalogItem()) { + Maybe<OsgiManager> osgi = ((ManagementContextInternal) mgmt).getOsgiManager(); + if (osgi.isPresent() && hasBundles) { + return osgi.get().getResource(name, getBundles()); + } + } + return null; + } + + @Override + public Iterable<URL> getResources(String name) { + if (mgmt != null && isEntitledToSeeCatalogItem()) { + Maybe<OsgiManager> osgi = ((ManagementContextInternal) mgmt).getOsgiManager(); + if (osgi.isPresent() && hasBundles) { + return osgi.get().getResources(name, getBundles()); + } + } + return Collections.emptyList(); + } + + public String getCatalogItemId() { + return catalogItemId; + } + + /** + * @return true if the current entitlement context may {@link Entitlements#SEE_CATALOG_ITEM see} + * {@link #getCatalogItemId}. + */ + private boolean isEntitledToSeeCatalogItem() { + return Entitlements.isEntitled(mgmt.getEntitlementManager(), + Entitlements.SEE_CATALOG_ITEM, + catalogItemId); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.java new file mode 100644 index 0000000..07544a6 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/BasicEntitlementClassDefinition.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.core.management.entitlement; + +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; + +import com.google.common.base.Objects; +import com.google.common.reflect.TypeToken; + + +public class BasicEntitlementClassDefinition<T> implements EntitlementClass<T> { + + private final String identifier; + private final TypeToken<T> argumentType; + + public BasicEntitlementClassDefinition(String identifier, TypeToken<T> argumentType) { + this.identifier = identifier; + this.argumentType = argumentType; + } + + public BasicEntitlementClassDefinition(String identifier, Class<T> argumentType) { + this.identifier = identifier; + this.argumentType = TypeToken.of(argumentType); + } + + @Override + public String entitlementClassIdentifier() { + return identifier; + } + + @Override + public TypeToken<T> entitlementClassArgumentType() { + return argumentType; + } + + @Override + public String toString() { + return Objects.toStringHelper(this).add("identitifier", identifier).add("argumentType", argumentType).toString(); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java new file mode 100644 index 0000000..a331a48 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementManagerAdapter.java @@ -0,0 +1,133 @@ +/* + * 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.core.management.entitlement; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.api.management.entitlement.EntitlementContext; +import org.apache.brooklyn.api.management.entitlement.EntitlementManager; +import org.apache.brooklyn.core.management.entitlement.Entitlements.EntitlementClassesHandler; +import org.apache.brooklyn.core.management.entitlement.Entitlements.EntityAndItem; +import org.apache.brooklyn.core.management.entitlement.Entitlements.StringAndArgument; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; + +/** + * provides an easy entry point to supplying entitlements, by providing the dispatch and defining the additional methods + * which have to be supplied. + * <p> + * note that this class may change as versions change, deliberately breaking backwards compatibility + * to ensure all permissions are used. + * <p> + * @since 0.7.0 */ +@Beta +public abstract class EntitlementManagerAdapter implements EntitlementManager { + + private static final Logger log = LoggerFactory.getLogger(EntitlementManagerAdapter.class); + + protected class Handler implements EntitlementClassesHandler<Boolean> { + final EntitlementContext context; + protected Handler(EntitlementContext context) { + this.context = context; + } + + @Override + public Boolean handleSeeCatalogItem(String catalogItemId) { + return isEntitledToSeeCatalogItem(context, catalogItemId); + } + @Override + public Boolean handleAddCatalogItem(Object catalogItemBeingAdded) { + return isEntitledToAddCatalogItem(context, catalogItemBeingAdded); + } + @Override + public Boolean handleModifyCatalogItem(StringAndArgument catalogItemIdAndModification) { + return isEntitledToModifyCatalogItem(context, catalogItemIdAndModification==null ? null : catalogItemIdAndModification.getString(), + catalogItemIdAndModification==null ? null : catalogItemIdAndModification.getArgument()); + } + + @Override + public Boolean handleSeeEntity(Entity entity) { + return isEntitledToSeeEntity(context, entity); + } + @Override + public Boolean handleSeeSensor(EntityAndItem<String> sensorInfo) { + return isEntitledToSeeSensor(context, sensorInfo.getEntity(), sensorInfo.getItem()); + } + @Override + public Boolean handleInvokeEffector(EntityAndItem<StringAndArgument> effectorInfo) { + StringAndArgument item = effectorInfo.getItem(); + return isEntitledToInvokeEffector(context, effectorInfo.getEntity(), item==null ? null : item.getString(), item==null ? null : item.getArgument()); + } + @Override + public Boolean handleModifyEntity(Entity entity) { + return isEntitledToModifyEntity(context, entity); + } + + @Override + public Boolean handleDeployApplication(Object app) { + return isEntitledToDeployApplication(context, app); + } + + @Override + public Boolean handleSeeAllServerInfo() { + return isEntitledToSeeAllServerInfo(context); + } + + @Override + public Boolean handleSeeServerStatus() { + return isEntitledToSeeServerStatus(context); + } + + @Override + public Boolean handleRoot() { + return isEntitledToRoot(context); + } + } + + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) { + if (log.isTraceEnabled()) { + log.trace("Checking entitlement of "+context+" to "+entitlementClass+" "+entitlementClassArgument); + } + + if (isEntitledToRoot( context )) return true; + + Handler handler = new Handler(context); + return Entitlements.EntitlementClassesEnum.of(entitlementClass).handle( + handler, entitlementClassArgument); + } + + protected abstract boolean isEntitledToSeeCatalogItem(EntitlementContext context, String catalogItemId); + /** passes item to be added, either yaml, or possibly null if any addition allowed (eg when resetting) */ + protected abstract boolean isEntitledToAddCatalogItem(EntitlementContext context, Object catalogItemBeingAdded); + /** passes item being modified, as ID and description of modification, both possibly null if any modification is allowed (eg when resetting) */ + protected abstract boolean isEntitledToModifyCatalogItem(EntitlementContext context, String catalogItemId, Object catalogItemModification); + protected abstract boolean isEntitledToSeeSensor(EntitlementContext context, Entity entity, String sensorName); + protected abstract boolean isEntitledToSeeEntity(EntitlementContext context, Entity entity); + /** arguments might be null, a map, or a list, depending how/where invoked */ + protected abstract boolean isEntitledToInvokeEffector(EntitlementContext context, Entity entity, String effectorName, Object arguments); + protected abstract boolean isEntitledToModifyEntity(EntitlementContext context, Entity entity); + protected abstract boolean isEntitledToDeployApplication(EntitlementContext context, Object app); + protected abstract boolean isEntitledToSeeAllServerInfo(EntitlementContext context); + protected abstract boolean isEntitledToSeeServerStatus(EntitlementContext context); + protected abstract boolean isEntitledToRoot(EntitlementContext context); + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.java new file mode 100644 index 0000000..085fb9e --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/EntitlementPredicates.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.core.management.entitlement; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.api.management.entitlement.EntitlementManager; + +import com.google.common.base.Predicate; + +public class EntitlementPredicates { + + public static <T> Predicate<T> isEntitled(final EntitlementManager entitlementManager, final EntitlementClass<T> entitlementClass) { + + return new Predicate<T>() { + @Override + public boolean apply(@Nullable T t) { + return Entitlements.isEntitled(entitlementManager, entitlementClass, t); + } + }; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java new file mode 100644 index 0000000..6bf9329 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/Entitlements.java @@ -0,0 +1,418 @@ +/* + * 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.core.management.entitlement; + +import java.util.Arrays; +import java.util.List; + +import javax.annotation.Nullable; + +import org.apache.brooklyn.api.entity.Entity; +import org.apache.brooklyn.api.management.ManagementContext; +import org.apache.brooklyn.api.management.Task; +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.api.management.entitlement.EntitlementContext; +import org.apache.brooklyn.api.management.entitlement.EntitlementManager; +import org.apache.brooklyn.core.management.internal.ManagementContextInternal; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import com.google.common.annotations.Beta; +import com.google.common.base.Joiner; +import com.google.common.base.Objects; +import com.google.common.base.Optional; +import com.google.common.base.Predicate; +import com.google.common.base.Predicates; +import com.google.common.collect.ImmutableList; +import com.google.common.reflect.TypeToken; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.ConfigKey; +import brooklyn.entity.basic.BrooklynTaskTags; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.entity.basic.Entities; +import brooklyn.util.exceptions.Exceptions; +import brooklyn.util.javalang.Reflections; +import brooklyn.util.task.Tasks; +import brooklyn.util.text.Strings; + +/** @since 0.7.0 */ +@Beta +public class Entitlements { + + private static final Logger log = LoggerFactory.getLogger(Entitlements.class); + + // ------------------- individual permissions + + public static EntitlementClass<String> SEE_CATALOG_ITEM = new BasicEntitlementClassDefinition<String>("catalog.see", String.class); + public static EntitlementClass<Object> ADD_CATALOG_ITEM = new BasicEntitlementClassDefinition<Object>("catalog.add", Object.class); + public static EntitlementClass<StringAndArgument> MODIFY_CATALOG_ITEM = new BasicEntitlementClassDefinition<StringAndArgument>("catalog.modify", StringAndArgument.class); + + public static EntitlementClass<Entity> SEE_ENTITY = new BasicEntitlementClassDefinition<Entity>("entity.see", Entity.class); + public static EntitlementClass<EntityAndItem<String>> SEE_SENSOR = new BasicEntitlementClassDefinition<EntityAndItem<String>>("sensor.see", EntityAndItem. typeToken(String.class)); + // string is effector name; argument may be a map or a list, depending how the args were supplied + public static EntitlementClass<EntityAndItem<StringAndArgument>> INVOKE_EFFECTOR = new BasicEntitlementClassDefinition<EntityAndItem<StringAndArgument>>("effector.invoke", EntityAndItem.typeToken(StringAndArgument.class)); + public static EntitlementClass<Entity> MODIFY_ENTITY = new BasicEntitlementClassDefinition<Entity>("entity.modify", Entity.class); + + /** the permission to deploy an application, where parameter is some representation of the app to be deployed (spec instance or yaml plan) */ + public static EntitlementClass<Object> DEPLOY_APPLICATION = new BasicEntitlementClassDefinition<Object>("app.deploy", Object.class); + + /** catch-all for catalog, locations, scripting, usage, etc - exporting persistence, shutting down, etc; + * this is significantly more powerful than {@link #SERVER_STATUS}. + * NB: this may be refactored and deprecated in future */ + public static EntitlementClass<Void> SEE_ALL_SERVER_INFO = new BasicEntitlementClassDefinition<Void>("server.info.all.see", Void.class); + + /** permission to see general server status info: basically HA status; not nearly as much as {@link #SEE_ALL_SERVER_INFO} */ + public static EntitlementClass<Void> SERVER_STATUS = new BasicEntitlementClassDefinition<Void>("server.status", Void.class); + + /** permission to run untrusted code or embedded scripts at the server; + * secondary check required for any operation which could potentially grant root-level access */ + public static EntitlementClass<Void> ROOT = new BasicEntitlementClassDefinition<Void>("root", Void.class); + + @SuppressWarnings("unchecked") + public static enum EntitlementClassesEnum { + ENTITLEMENT_SEE_CATALOG_ITEM(SEE_CATALOG_ITEM) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleSeeCatalogItem((String)argument); } }, + ENTITLEMENT_ADD_CATALOG_ITEM(ADD_CATALOG_ITEM) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleAddCatalogItem(argument); } }, + ENTITLEMENT_MODIFY_CATALOG_ITEM(MODIFY_CATALOG_ITEM) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleModifyCatalogItem((StringAndArgument)argument); } }, + + ENTITLEMENT_SEE_ENTITY(SEE_ENTITY) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleSeeEntity((Entity)argument); } }, + ENTITLEMENT_SEE_SENSOR(SEE_SENSOR) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleSeeSensor((EntityAndItem<String>)argument); } }, + ENTITLEMENT_INVOKE_EFFECTOR(INVOKE_EFFECTOR) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleInvokeEffector((EntityAndItem<StringAndArgument>)argument); } }, + ENTITLEMENT_MODIFY_ENTITY(MODIFY_ENTITY) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleModifyEntity((Entity)argument); } }, + + ENTITLEMENT_DEPLOY_APPLICATION(DEPLOY_APPLICATION) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleDeployApplication(argument); } }, + + ENTITLEMENT_SEE_ALL_SERVER_INFO(SEE_ALL_SERVER_INFO) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleSeeAllServerInfo(); } }, + ENTITLEMENT_SERVER_STATUS(SERVER_STATUS) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleSeeServerStatus(); } }, + ENTITLEMENT_ROOT(ROOT) { public <T> T handle(EntitlementClassesHandler<T> handler, Object argument) { return handler.handleRoot(); } }, + ; + + private EntitlementClass<?> entitlementClass; + + private EntitlementClassesEnum(EntitlementClass<?> specificClass) { + this.entitlementClass = specificClass; + } + public EntitlementClass<?> getEntitlementClass() { + return entitlementClass; + } + + public abstract <T> T handle(EntitlementClassesHandler<T> handler, Object argument); + + public static EntitlementClassesEnum of(EntitlementClass<?> entitlementClass) { + for (EntitlementClassesEnum x: values()) { + if (entitlementClass.equals(x.getEntitlementClass())) return x; + } + return null; + } + } + + public interface EntitlementClassesHandler<T> { + public T handleSeeCatalogItem(String catalogItemId); + public T handleSeeServerStatus(); + public T handleAddCatalogItem(Object catalogItemBeingAdded); + public T handleModifyCatalogItem(StringAndArgument catalogItemIdAndModification); + public T handleSeeEntity(Entity entity); + public T handleSeeSensor(EntityAndItem<String> sensorInfo); + public T handleInvokeEffector(EntityAndItem<StringAndArgument> effectorInfo); + public T handleModifyEntity(Entity entity); + public T handleDeployApplication(Object app); + public T handleSeeAllServerInfo(); + public T handleRoot(); + } + + protected static class Pair<T1,T2> { + protected final T1 p1; + protected final T2 p2; + protected Pair(T1 p1, T2 p2) { this.p1 = p1; this.p2 = p2; } + } + public static class EntityAndItem<T> extends Pair<Entity,T> { + public static <TT> TypeToken<EntityAndItem<TT>> typeToken(Class<TT> type) { + return new TypeToken<Entitlements.EntityAndItem<TT>>() { + private static final long serialVersionUID = -738154831809025407L; + }; + } + public EntityAndItem(Entity entity, T item) { super (entity, item); } + public Entity getEntity() { return p1; } + public T getItem() { return p2; } + public static <T> EntityAndItem<T> of(Entity entity, T item) { + return new EntityAndItem<T>(entity, item); + } + } + + public static class StringAndArgument extends Pair<String,Object> { + public StringAndArgument(String string, Object argument) { super(string, argument); } + public String getString() { return p1; } + public Object getArgument() { return p2; } + public static StringAndArgument of(String string, Object argument) { + return new StringAndArgument(string, argument); + } + } + + /** + * These lifecycle operations are currently treated as effectors. This may change in the future. + * @since 0.7.0 */ + @Beta + public static class LifecycleEffectors { + public static final String DELETE = "delete"; + } + + // ------------- permission sets ------------- + + /** always ALLOW access to everything */ + public static EntitlementManager root() { + return new EntitlementManager() { + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> permission, T typeArgument) { + return true; + } + @Override + public String toString() { + return "Entitlements.root"; + } + }; + } + + /** always DENY access to anything which requires entitlements */ + public static EntitlementManager minimal() { + return new EntitlementManager() { + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> permission, T typeArgument) { + return false; + } + @Override + public String toString() { + return "Entitlements.minimal"; + } + }; + } + + public static class FineGrainedEntitlements { + + private static final Joiner COMMA_JOINER = Joiner.on(','); + + public static EntitlementManager anyOf(final EntitlementManager... checkers) { + return anyOf(Arrays.asList(checkers)); + } + + public static EntitlementManager anyOf(final Iterable<? extends EntitlementManager> checkers) { + return new EntitlementManager() { + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> permission, T typeArgument) { + for (EntitlementManager checker: checkers) + if (checker.isEntitled(context, permission, typeArgument)) + return true; + return false; + } + @Override + public String toString() { + return "Entitlements.anyOf(" + COMMA_JOINER.join(checkers) + ")"; + } + }; + } + + public static EntitlementManager allOf(final EntitlementManager... checkers) { + return allOf(Arrays.asList(checkers)); + } + + public static EntitlementManager allOf(final Iterable<? extends EntitlementManager> checkers) { + return new EntitlementManager() { + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> permission, T typeArgument) { + for (EntitlementManager checker: checkers) + if (checker.isEntitled(context, permission, typeArgument)) + return true; + return false; + } + @Override + public String toString() { + return "Entitlements.allOf(" + COMMA_JOINER.join(checkers) + ")"; + } + }; + } + + public static <U> EntitlementManager allowing(EntitlementClass<U> permission, Predicate<U> test) { + return new SinglePermissionEntitlementChecker<U>(permission, test); + } + + public static <U> EntitlementManager allowing(EntitlementClass<U> permission) { + return new SinglePermissionEntitlementChecker<U>(permission, Predicates.<U>alwaysTrue()); + } + + public static class SinglePermissionEntitlementChecker<U> implements EntitlementManager { + final EntitlementClass<U> permission; + final Predicate<U> test; + + protected SinglePermissionEntitlementChecker(EntitlementClass<U> permission, Predicate<U> test) { + this.permission = permission; + this.test = test; + } + + @SuppressWarnings("unchecked") + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> permission, T typeArgument) { + if (!Objects.equal(this.permission, permission)) return false; + return test.apply((U)typeArgument); + } + @Override + public String toString() { + return "Entitlements.allowing(" + permission + " -> " + test + ")"; + } + } + public static EntitlementManager seeNonSecretSensors() { + return allowing(SEE_SENSOR, new Predicate<EntityAndItem<String>>() { + @Override + public boolean apply(EntityAndItem<String> input) { + if (input == null) return false; + return !Entities.isSecret(input.getItem()); + } + @Override + public String toString() { + return "Predicates.nonSecret"; + } + }); + } + + } + + /** allow read-only */ + public static EntitlementManager readOnly() { + return FineGrainedEntitlements.anyOf( + FineGrainedEntitlements.allowing(SEE_ENTITY), + FineGrainedEntitlements.seeNonSecretSensors() + ); + } + + /** allow healthcheck */ + public static EntitlementManager serverStatusOnly() { + return FineGrainedEntitlements.allowing(SERVER_STATUS); + } + + // ------------- lookup conveniences ------------- + + private static class PerThreadEntitlementContextHolder { + public static final ThreadLocal<EntitlementContext> perThreadEntitlementsContextHolder = new ThreadLocal<EntitlementContext>(); + } + + /** + * Finds the currently applicable {@link EntitlementContext} by examining the current thread + * then by investigating the current task, its submitter, etc. */ + // NOTE: entitlements are propagated to tasks whenever they are created, as tags + // (see BrooklynTaskTags.tagForEntitlement and BasicExecutionContext.submitInternal). + // It might be cheaper to only do this lookup, not to propagate as tags, and to ensure + // all entitlement operations are wrapped in a task at source; but currently we do not + // do that so we need at least to set entitlement on the outermost task. + // Setting it on tasks submitted by a task is not strictly necessary (i.e. in BasicExecutionContext) + // but seems cheap enough, and means checking entitlements is fast, if we choose to do that more often. + public static EntitlementContext getEntitlementContext() { + EntitlementContext context; + context = PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.get(); + if (context!=null) return context; + + Task<?> task = Tasks.current(); + while (task!=null) { + context = BrooklynTaskTags.getEntitlement(task); + if (context!=null) return context; + task = task.getSubmittedByTask(); + } + + // no entitlements set -- assume entitlements not used, or system internal + return null; + } + + public static void setEntitlementContext(EntitlementContext context) { + EntitlementContext oldContext = PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.get(); + if (oldContext!=null && context!=null) { + log.warn("Changing entitlement context from "+oldContext+" to "+context+"; context should have been reset or extended, not replaced"); + log.debug("Trace for entitlement context duplicate overwrite", new Throwable("Trace for entitlement context overwrite")); + } + PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.set(context); + } + + public static void clearEntitlementContext() { + PerThreadEntitlementContextHolder.perThreadEntitlementsContextHolder.set(null); + } + + public static <T> boolean isEntitled(EntitlementManager checker, EntitlementClass<T> permission, T typeArgument) { + return checker.isEntitled(getEntitlementContext(), permission, typeArgument); + } + + /** throws {@link NotEntitledException} if entitlement not available for current {@link #getEntitlementContext()} */ + public static <T> void checkEntitled(EntitlementManager checker, EntitlementClass<T> permission, T typeArgument) { + if (!isEntitled(checker, permission, typeArgument)) { + throw new NotEntitledException(getEntitlementContext(), permission, typeArgument); + } + } + /** throws {@link NotEntitledException} if entitlement not available for current {@link #getEntitlementContext()} + * @since 0.7.0 + * @deprecated since 0.7.0, use {@link #checkEntitled(EntitlementManager, EntitlementClass, Object)}; + * kept briefly because there is some downstream usage*/ + public static <T> void requireEntitled(EntitlementManager checker, EntitlementClass<T> permission, T typeArgument) { + checkEntitled(checker, permission, typeArgument); + } + + // ----------------- initialization ---------------- + + public final static String ENTITLEMENTS_CONFIG_PREFIX = "brooklyn.entitlements"; + + public static ConfigKey<String> GLOBAL_ENTITLEMENT_MANAGER = ConfigKeys.newStringConfigKey(ENTITLEMENTS_CONFIG_PREFIX+".global", + "Class for entitlements in effect globally; " + + "short names 'minimal', 'readonly', or 'root' are permitted here, with the default 'root' giving full access to all declared users; " + + "or supply the name of an "+EntitlementManager.class+" class to instantiate, taking a 1-arg BrooklynProperties constructor or a 0-arg constructor", + "root"); + + public static EntitlementManager newManager(ManagementContext mgmt, BrooklynProperties brooklynProperties) { + return newGlobalManager(mgmt, brooklynProperties); + } + private static EntitlementManager newGlobalManager(ManagementContext mgmt, BrooklynProperties brooklynProperties) { + return load(mgmt, brooklynProperties, brooklynProperties.getConfig(GLOBAL_ENTITLEMENT_MANAGER)); + } + + public static EntitlementManager load(@Nullable ManagementContext mgmt, BrooklynProperties brooklynProperties, String type) { + if ("root".equalsIgnoreCase(type)) return root(); + if ("readonly".equalsIgnoreCase(type) || "read_only".equalsIgnoreCase(type)) return readOnly(); + if ("minimal".equalsIgnoreCase(type)) return minimal(); + if (Strings.isNonBlank(type)) { + try { + ClassLoader cl = mgmt==null ? null : ((ManagementContextInternal)mgmt).getCatalogClassLoader(); + if (cl==null) cl = Entitlements.class.getClassLoader(); + Class<?> clazz = cl.loadClass(type); + return (EntitlementManager) instantiate(clazz, ImmutableList.of( + new Object[] {mgmt, brooklynProperties}, + new Object[] {mgmt}, + new Object[] {brooklynProperties}, + new Object[0])); + } catch (Exception e) { + throw Exceptions.propagate(e); + } + } + throw new IllegalStateException("Invalid entitlement manager specified: '"+type+"'"); + } + + private static Object instantiate(Class<?> clazz, List<Object[]> constructorArgOptions) { + try { + for (Object[] constructorArgOption : constructorArgOptions) { + Optional<?> result = Reflections.invokeConstructorWithArgs(clazz, constructorArgOption); + if (result.isPresent()) return result.get(); + } + } catch (Exception e) { + throw Exceptions.propagate(e); + } + throw new IllegalStateException("No matching constructor to instantiate "+clazz); + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java new file mode 100644 index 0000000..aab7c0d --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/NotEntitledException.java @@ -0,0 +1,44 @@ +/* + * 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.core.management.entitlement; + +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.api.management.entitlement.EntitlementContext; + + +public class NotEntitledException extends RuntimeException { + + private static final long serialVersionUID = -4001882260980589181L; + + EntitlementContext entitlementContext; + EntitlementClass<?> permission; + Object typeArgument; + + public <T> NotEntitledException(EntitlementContext entitlementContext, EntitlementClass<T> permission, T typeArgument) { + this.entitlementContext = entitlementContext; + this.permission = permission; + this.typeArgument = typeArgument; + } + + @Override + public String toString() { + return super.toString()+"["+entitlementContext+":"+permission+(typeArgument!=null ? "("+typeArgument+")" : "")+"]"; + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java new file mode 100644 index 0000000..c5914e8 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManager.java @@ -0,0 +1,100 @@ +/* + * 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.core.management.entitlement; + +import java.util.Map; + +import org.apache.brooklyn.api.management.entitlement.EntitlementClass; +import org.apache.brooklyn.api.management.entitlement.EntitlementContext; +import org.apache.brooklyn.api.management.entitlement.EntitlementManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.config.BrooklynProperties; +import brooklyn.config.ConfigKey; +import brooklyn.config.ConfigPredicates; +import brooklyn.entity.basic.ConfigKeys; +import brooklyn.util.collections.MutableMap; +import brooklyn.util.text.Strings; + +import com.google.common.base.Preconditions; + +public class PerUserEntitlementManager implements EntitlementManager { + + private static final Logger log = LoggerFactory.getLogger(PerUserEntitlementManager.class); + + public final static String PER_USER_ENTITLEMENTS_CONFIG_PREFIX = Entitlements.ENTITLEMENTS_CONFIG_PREFIX+".perUser"; + + public final static ConfigKey<String> DEFAULT_MANAGER = ConfigKeys.newStringConfigKey(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+ + ".default", "Default entitlements manager for users without further specification", "minimal"); + + protected final EntitlementManager defaultManager; + protected final Map<String,EntitlementManager> perUserManagers = MutableMap.of(); + + private final static ThreadLocal<Boolean> ACTIVE = new ThreadLocal<Boolean>(); + + private static EntitlementManager load(BrooklynProperties properties, String type) { + if (Boolean.TRUE.equals(ACTIVE.get())) { + // prevent infinite loop + throw new IllegalStateException("Cannot set "+PerUserEntitlementManager.class.getName()+" within config for itself"); + } + try { + ACTIVE.set(true); + return Entitlements.load(null, properties, type); + } finally { + ACTIVE.remove(); + } + } + + public PerUserEntitlementManager(BrooklynProperties properties) { + this(load(properties, properties.getConfig(DEFAULT_MANAGER))); + + BrooklynProperties users = properties.submap(ConfigPredicates.startingWith(PER_USER_ENTITLEMENTS_CONFIG_PREFIX+".")); + for (Map.Entry<ConfigKey<?>,?> key: users.getAllConfig().entrySet()) { + if (key.getKey().getName().equals(DEFAULT_MANAGER.getName())) continue; + String user = Strings.removeFromStart(key.getKey().getName(), PER_USER_ENTITLEMENTS_CONFIG_PREFIX+"."); + addUser(user, load(properties, Strings.toString(key.getValue()))); + } + + log.info(getClass().getSimpleName()+" created with "+perUserManagers.size()+" user"+Strings.s(perUserManagers)+" and " + + "default "+defaultManager+" (users: "+perUserManagers+")"); + } + + public PerUserEntitlementManager(EntitlementManager defaultManager) { + this.defaultManager = Preconditions.checkNotNull(defaultManager); + } + + public void addUser(String user, EntitlementManager managerForThisUser) { + perUserManagers.put(Preconditions.checkNotNull(user, "user"), Preconditions.checkNotNull(managerForThisUser, "managerForThisUser")); + } + + @Override + public <T> boolean isEntitled(EntitlementContext context, EntitlementClass<T> entitlementClass, T entitlementClassArgument) { + EntitlementManager entitlementInEffect = null; + if (context==null || context.user()==null) { + // no user means it is running as an internal process, always has root + entitlementInEffect = Entitlements.root(); + } else { + if (context!=null) entitlementInEffect = perUserManagers.get(context.user()); + if (entitlementInEffect==null) entitlementInEffect = defaultManager; + } + return entitlementInEffect.isEntitled(context, entitlementClass, entitlementClassArgument); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java new file mode 100644 index 0000000..b4c7e38 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/PerUserEntitlementManagerWithDefault.java @@ -0,0 +1,31 @@ +/* + * 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.core.management.entitlement; + +import org.apache.brooklyn.api.management.entitlement.EntitlementManager; + +@Deprecated +/** @deprecated since 0.7.0 use {@link PerUserEntitlementManager} */ +public class PerUserEntitlementManagerWithDefault extends PerUserEntitlementManager { + + public PerUserEntitlementManagerWithDefault(EntitlementManager defaultManager) { + super(defaultManager); + } + +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java new file mode 100644 index 0000000..2efed42 --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/entitlement/WebEntitlementContext.java @@ -0,0 +1,57 @@ +/* + * 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.core.management.entitlement; + +import org.apache.brooklyn.api.management.entitlement.EntitlementContext; + +import brooklyn.util.javalang.JavaClassNames; + +/** + * Indicates an authenticated web request as the entitlements context; + * note user may still be null if no authentication was requested + */ +public class WebEntitlementContext implements EntitlementContext { + + final String user; + final String sourceIp; + final String requestUri; + + /** + * A mostly-unique identifier for the inbound request, to distinguish + * between duplicate requests and for cross-referencing with URIs + */ + final String requestUniqueIdentifier; + + public WebEntitlementContext(String user, String sourceIp, String requestUri, String requestUniqueIdentifier) { + this.user = user; + this.sourceIp = sourceIp; + this.requestUri = requestUri; + this.requestUniqueIdentifier = requestUniqueIdentifier; + } + + @Override public String user() { return user; } + public String sourceIp() { return sourceIp; } + public String requestUri() { return requestUri; } + public String requestUniqueIdentifier() { return requestUniqueIdentifier; } + + @Override + public String toString() { + return JavaClassNames.simpleClassName(getClass())+"["+user+"@"+sourceIp+":"+requestUniqueIdentifier+"]"; + } +} http://git-wip-us.apache.org/repos/asf/incubator-brooklyn/blob/6602f694/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java ---------------------------------------------------------------------- diff --git a/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java b/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java new file mode 100644 index 0000000..034a6bd --- /dev/null +++ b/core/src/main/java/org/apache/brooklyn/core/management/ha/BasicMasterChooser.java @@ -0,0 +1,204 @@ +/* + * 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.core.management.ha; + +import java.util.ArrayList; +import java.util.Collections; +import java.util.List; + +import org.apache.brooklyn.api.entity.trait.Identifiable; +import org.apache.brooklyn.api.management.ha.ManagementNodeState; +import org.apache.brooklyn.api.management.ha.ManagementNodeSyncRecord; +import org.apache.brooklyn.api.management.ha.ManagementPlaneSyncRecord; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import brooklyn.util.collections.MutableList; +import brooklyn.util.text.NaturalOrderComparator; +import brooklyn.util.time.Duration; + +import com.google.common.annotations.Beta; +import com.google.common.annotations.VisibleForTesting; +import com.google.common.collect.ComparisonChain; +import com.google.common.collect.Ordering; + +/** + * @since 0.7.0 + * + * @author aled + */ +@Beta +public abstract class BasicMasterChooser implements MasterChooser { + + private static final Logger LOG = LoggerFactory.getLogger(BasicMasterChooser.class); + + protected static class ScoredRecord<T extends Comparable<T>> implements Identifiable, Comparable<ScoredRecord<T>> { + String id; + ManagementNodeSyncRecord record; + T score; + + @Override + public String getId() { + return id; + } + + @Override + public int compareTo(ScoredRecord<T> o) { + return score.compareTo(o.score); + } + } + + public ManagementNodeSyncRecord choose(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, String ownNodeId) { + if (LOG.isDebugEnabled()) LOG.debug("Choosing new master from "+memento.getManagementNodes()); + ManagementNodeSyncRecord me = memento.getManagementNodes().get(ownNodeId); + if (me==null) { + LOG.warn("Management node details not known when choosing new master: "+memento+" / "+ownNodeId); + return null; + } + Long nowIsh = me.getRemoteTimestamp(); + if (nowIsh==null) { + LOG.warn("Management node for self missing timestamp when choosing new master: "+memento); + return null; + } + + List<ScoredRecord<?>> contenders = filterHealthy(memento, heartbeatTimeout, nowIsh); + + if (!contenders.isEmpty()) { + return pick(contenders); + } else { + LOG.info("No valid management node found for choosing new master: contender="+memento.getManagementNodes()); + return null; + } + } + + /** pick the best contender; argument guaranteed to be non-null and non-empty, + * filtered for health reasons */ + @SuppressWarnings({ "rawtypes", "unchecked" }) + protected ManagementNodeSyncRecord pick(List<ScoredRecord<?>> contenders) { + ScoredRecord min = null; + for (ScoredRecord x: contenders) { + if (min==null || x.score.compareTo(min.score)<0) min = x; + } + return min.record; + } + + public static class AlphabeticChooserScore implements Comparable<AlphabeticChooserScore> { + long priority; + int versionBias; + String brooklynVersion; + int statePriority; + String nodeId; + + @Override + public int compareTo(AlphabeticChooserScore o) { + if (o==null) return -1; + return ComparisonChain.start() + // invert the order where we prefer higher values + .compare(o.priority, this.priority) + .compare(o.versionBias, this.versionBias) + .compare(o.brooklynVersion, this.brooklynVersion, + Ordering.from(NaturalOrderComparator.INSTANCE).nullsFirst()) + .compare(o.statePriority, this.statePriority) + .compare(this.nodeId, o.nodeId, Ordering.usingToString().nullsLast()) + .result(); + } + } + + /** comparator which prefers, in order: + * <li> higher explicit priority + * <li> non-snapshot Brooklyn version, then any Brooklyn version, and lastly null version + * (using {@link NaturalOrderComparator} so e.g. v10 > v3.20 > v3.9 ) + * <li> higher version (null last) + * <li> node which reports it's master, hot standby, then standby + * <li> finally (common case): lower (alphabetically) node id + */ + public static class AlphabeticMasterChooser extends BasicMasterChooser { + final boolean preferHotStandby; + public AlphabeticMasterChooser(boolean preferHotStandby) { this.preferHotStandby = preferHotStandby; } + public AlphabeticMasterChooser() { this.preferHotStandby = true; } + @Override + protected AlphabeticChooserScore score(ManagementNodeSyncRecord contender) { + AlphabeticChooserScore score = new AlphabeticChooserScore(); + score.priority = contender.getPriority()!=null ? contender.getPriority() : 0; + score.brooklynVersion = contender.getBrooklynVersion(); + score.versionBias = contender.getBrooklynVersion()==null ? -2 : + contender.getBrooklynVersion().toLowerCase().indexOf("snapshot")>=0 ? -1 : + 0; + if (preferHotStandby) { + // other master should be preferred before we get invoked, but including for good measure + score.statePriority = contender.getStatus()==ManagementNodeState.MASTER ? 3 : + contender.getStatus()==ManagementNodeState.HOT_STANDBY ? 2 : + contender.getStatus()==ManagementNodeState.STANDBY ? 1 : -1; + } else { + score.statePriority = 0; + } + score.nodeId = contender.getNodeId(); + return score; + } + } + + /** + * Filters the {@link ManagementPlaneSyncRecord#getManagementNodes()} to only those in an appropriate state, + * and with heartbeats that have not timed out. + */ + protected List<ScoredRecord<?>> filterHealthy(ManagementPlaneSyncRecord memento, Duration heartbeatTimeout, long nowIsh) { + long oldestAcceptableTimestamp = nowIsh - heartbeatTimeout.toMilliseconds(); + List<ScoredRecord<?>> contenders = MutableList.of(); + for (ManagementNodeSyncRecord contender : memento.getManagementNodes().values()) { + boolean statusOk = (contender.getStatus() == ManagementNodeState.STANDBY || contender.getStatus() == ManagementNodeState.HOT_STANDBY || contender.getStatus() == ManagementNodeState.MASTER); + Long remoteTimestamp = contender.getRemoteTimestamp(); + boolean heartbeatOk; + if (remoteTimestamp==null) { + throw new IllegalStateException("Missing remote timestamp when performing master election: "+this+" / "+contender); + // if the above exception happens in some contexts we could either fallback to local, or fail: +// remoteTimestamp = contender.getLocalTimestamp(); + // or +// heartbeatOk=false; + } else { + heartbeatOk = remoteTimestamp >= oldestAcceptableTimestamp; + } + if (statusOk && heartbeatOk) { + contenders.add(newScoredRecord(contender)); + } + if (LOG.isTraceEnabled()) LOG.trace("Filtering choices of new master: contender="+contender+"; statusOk="+statusOk+"; heartbeatOk="+heartbeatOk); + } + return contenders; + } + + @VisibleForTesting + //Java 6 compiler workaround, using parameterized types fails + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected List<ScoredRecord<?>> sort(List<ScoredRecord<?>> input) { + ArrayList copy = new ArrayList<ScoredRecord<?>>(input); + Collections.sort(copy); + return copy; + } + + @SuppressWarnings({ "unchecked", "rawtypes" }) + protected ScoredRecord<?> newScoredRecord(ManagementNodeSyncRecord contender) { + ScoredRecord r = new ScoredRecord(); + r.id = contender.getNodeId(); + r.record = contender; + r.score = score(contender); + return r; + } + + protected abstract Comparable<?> score(ManagementNodeSyncRecord contender); + +}
