/* * 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.mgmt.internal; import static com.google.common.base.Preconditions.checkNotNull; import static org.apache.brooklyn.util.JavaGroovyEquivalents.elvis; import java.util.Arrays; import java.util.Collection; import java.util.Collections; import java.util.List; import java.util.Map; import java.util.Set; import java.util.WeakHashMap; import java.util.concurrent.Callable; import java.util.concurrent.CopyOnWriteArrayList; import java.util.concurrent.atomic.AtomicBoolean; import org.apache.brooklyn.api.effector.Effector; import org.apache.brooklyn.api.entity.Application; import org.apache.brooklyn.api.entity.Entity; import org.apache.brooklyn.api.location.Location; import org.apache.brooklyn.api.mgmt.AccessController; import org.apache.brooklyn.api.mgmt.ExecutionContext; import org.apache.brooklyn.api.mgmt.ExecutionManager; import org.apache.brooklyn.api.mgmt.ManagementContext; import org.apache.brooklyn.api.mgmt.SubscriptionManager; import org.apache.brooklyn.api.mgmt.Task; import org.apache.brooklyn.api.mgmt.TaskAdaptable; import org.apache.brooklyn.core.BrooklynFeatureEnablement; import org.apache.brooklyn.core.effector.Effectors; import org.apache.brooklyn.core.entity.drivers.downloads.BasicDownloadsManager; import org.apache.brooklyn.core.internal.BrooklynProperties; import org.apache.brooklyn.core.internal.BrooklynProperties.Factory.Builder; import org.apache.brooklyn.core.internal.storage.DataGridFactory; import org.apache.brooklyn.core.mgmt.entitlement.Entitlements; import org.apache.brooklyn.core.mgmt.ha.OsgiManager; import org.apache.brooklyn.core.objs.proxy.InternalEntityFactory; import org.apache.brooklyn.core.objs.proxy.InternalLocationFactory; import org.apache.brooklyn.core.objs.proxy.InternalPolicyFactory; import org.apache.brooklyn.util.core.task.BasicExecutionContext; import org.apache.brooklyn.util.core.task.BasicExecutionManager; import org.apache.brooklyn.util.core.task.DynamicTasks; import org.apache.brooklyn.util.core.task.TaskTags; import org.apache.brooklyn.util.core.task.Tasks; import org.apache.brooklyn.util.exceptions.Exceptions; import org.apache.brooklyn.util.guava.Maybe; import org.apache.brooklyn.util.text.Strings; import org.slf4j.Logger; import org.slf4j.LoggerFactory; import com.google.common.annotations.Beta; import com.google.common.annotations.VisibleForTesting; import com.google.common.base.Throwables; import com.google.common.collect.ImmutableSet; /** * A local (single node) implementation of the {@link ManagementContext} API. */ public class LocalManagementContext extends AbstractManagementContext { private static final Logger log = LoggerFactory.getLogger(LocalManagementContext.class); private static final Set<LocalManagementContext> INSTANCES = Collections.synchronizedSet(Collections.newSetFromMap(new WeakHashMap<LocalManagementContext, Boolean>())); private final Builder builder; private final List<ManagementContext.PropertiesReloadListener> reloadListeners = new CopyOnWriteArrayList<ManagementContext.PropertiesReloadListener>(); @VisibleForTesting static Set<LocalManagementContext> getInstances() { synchronized (INSTANCES) { return ImmutableSet.copyOf(INSTANCES); } } // Note also called reflectively by BrooklynLeakListener public static void logAll(Logger logger){ for (LocalManagementContext context : getInstances()) { logger.warn("Management Context "+context+" running, creation stacktrace:\n" + Throwables.getStackTraceAsString(context.constructionStackTrace)); } } /** terminates all (best effort); returns count of sessions closed; if exceptions thrown, returns negative number. * semantics might change, particular in dealing with interminable mgmt contexts. */ // Note also called reflectively by BrooklynLeakListener @Beta public static int terminateAll() { int closed=0,dangling=0; for (LocalManagementContext context : getInstances()) { try { context.terminate(); closed++; }catch (Throwable t) { Exceptions.propagateIfFatal(t); log.warn("Failed to terminate management context", t); dangling++; } } if (dangling>0) return -dangling; return closed; } private AtomicBoolean terminated = new AtomicBoolean(false); private String managementPlaneId; private String managementNodeId; private BasicExecutionManager execution; private SubscriptionManager subscriptions; private LocalEntityManager entityManager; private final LocalLocationManager locationManager; private final LocalAccessManager accessManager; private final LocalUsageManager usageManager; private OsgiManager osgiManager; public final Throwable constructionStackTrace = new Throwable("for construction stacktrace").fillInStackTrace(); private final Map<String, Object> brooklynAdditionalProperties; /** * Creates a LocalManagement with default BrooklynProperties. */ public LocalManagementContext() { this(BrooklynProperties.Factory.builderDefault()); } public LocalManagementContext(BrooklynProperties brooklynProperties) { this(brooklynProperties, (DataGridFactory)null); } /** * Creates a new LocalManagementContext. * * @param brooklynProperties the BrooklynProperties. * @param datagridFactory the DataGridFactory to use. If this instance is null, it means that the system * is going to use BrooklynProperties to figure out which instance to load or otherwise * use a default instance. */ @VisibleForTesting public LocalManagementContext(BrooklynProperties brooklynProperties, DataGridFactory datagridFactory) { this(Builder.fromProperties(brooklynProperties), datagridFactory); } public LocalManagementContext(Builder builder) { this(builder, null, null); } public LocalManagementContext(Builder builder, DataGridFactory datagridFactory) { this(builder, null, datagridFactory); } public LocalManagementContext(Builder builder, Map<String, Object> brooklynAdditionalProperties) { this(builder, brooklynAdditionalProperties, null); } public LocalManagementContext(BrooklynProperties brooklynProperties, Map<String, Object> brooklynAdditionalProperties) { this(Builder.fromProperties(brooklynProperties), brooklynAdditionalProperties, null); } public LocalManagementContext(Builder builder, Map<String, Object> brooklynAdditionalProperties, DataGridFactory datagridFactory) { super(builder.build(), datagridFactory); checkNotNull(configMap, "brooklynProperties"); // TODO in a persisted world the planeId may be injected this.managementPlaneId = Strings.makeRandomId(8); this.managementNodeId = Strings.makeRandomId(8); this.builder = builder; this.brooklynAdditionalProperties = brooklynAdditionalProperties; if (brooklynAdditionalProperties != null) configMap.addFromMap(brooklynAdditionalProperties); BrooklynFeatureEnablement.init(configMap); this.locationManager = new LocalLocationManager(this); this.accessManager = new LocalAccessManager(); this.usageManager = new LocalUsageManager(this); if (configMap.getConfig(OsgiManager.USE_OSGI)) { this.osgiManager = new OsgiManager(this); osgiManager.start(); } INSTANCES.add(this); log.debug("Created management context "+this); } @Override public String getManagementPlaneId() { return managementPlaneId; } @Override public String getManagementNodeId() { return managementNodeId; } @Override public void prePreManage(Entity entity) { getEntityManager().prePreManage(entity); } @Override public void prePreManage(Location location) { getLocationManager().prePreManage(location); } @Override public synchronized Collection<Application> getApplications() { return getEntityManager().getApplications(); } @Override public void addEntitySetListener(CollectionChangeListener<Entity> listener) { getEntityManager().addEntitySetListener(listener); } @Override public void removeEntitySetListener(CollectionChangeListener<Entity> listener) { getEntityManager().removeEntitySetListener(listener); } @Override protected void manageIfNecessary(Entity entity, Object context) { getEntityManager().manageIfNecessary(entity, context); } @Override public synchronized LocalEntityManager getEntityManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); if (entityManager == null) { entityManager = new LocalEntityManager(this); } return entityManager; } @Override public InternalEntityFactory getEntityFactory() { return getEntityManager().getEntityFactory(); } @Override public InternalLocationFactory getLocationFactory() { return getLocationManager().getLocationFactory(); } @Override public InternalPolicyFactory getPolicyFactory() { return getEntityManager().getPolicyFactory(); } @Override public synchronized LocalLocationManager getLocationManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); return locationManager; } @Override public synchronized LocalAccessManager getAccessManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); return accessManager; } @Override public synchronized LocalUsageManager getUsageManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); return usageManager; } @Override public synchronized Maybe<OsgiManager> getOsgiManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); if (osgiManager==null) return Maybe.absent("OSGi not available in this instance"); return Maybe.of(osgiManager); } @Override public synchronized AccessController getAccessController() { return getAccessManager().getAccessController(); } @Override public synchronized SubscriptionManager getSubscriptionManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); if (subscriptions == null) { subscriptions = new LocalSubscriptionManager(getExecutionManager()); } return subscriptions; } @Override public synchronized ExecutionManager getExecutionManager() { if (!isRunning()) throw new IllegalStateException("Management context no longer running"); if (execution == null) { execution = new BasicExecutionManager(getManagementNodeId()); gc = new BrooklynGarbageCollector(configMap, execution, getStorage()); } return execution; } @Override public void terminate() { synchronized (terminated) { if (terminated.getAndSet(true)) { log.trace("Already terminated management context "+this); // no harm in doing it twice, but it makes logs ugly! return; } log.debug("Terminating management context "+this); INSTANCES.remove(this); super.terminate(); if (osgiManager!=null) { osgiManager.stop(); osgiManager = null; } if (usageManager != null) usageManager.terminate(); if (execution != null) execution.shutdownNow(); if (gc != null) gc.shutdownNow(); log.debug("Terminated management context "+this); } } @Override protected void finalize() { terminate(); } @SuppressWarnings({ "unchecked", "rawtypes" }) @Override public <T> Task<T> runAtEntity(Map flags, Entity entity, Callable<T> c) { manageIfNecessary(entity, elvis(Arrays.asList(flags.get("displayName"), flags.get("description"), flags, c))); return runAtEntity(entity, Tasks.<T>builder().dynamic(true).body(c).flags(flags).build()); } protected <T> Task<T> runAtEntity(Entity entity, TaskAdaptable<T> task) { getExecutionContext(entity).submit(task); if (DynamicTasks.getTaskQueuingContext()!=null) { // put it in the queueing context so it appears in the GUI // mark it inessential as this is being invoked from code, // the caller will do 'get' to handle errors TaskTags.markInessential(task); DynamicTasks.getTaskQueuingContext().queue(task.asTask()); } return task.asTask(); } @Override protected <T> Task<T> runAtEntity(final Entity entity, final Effector<T> eff, @SuppressWarnings("rawtypes") final Map parameters) { manageIfNecessary(entity, eff); // prefer to submit this from the current execution context so it sets up correct cross-context chaining ExecutionContext ec = BasicExecutionContext.getCurrentExecutionContext(); if (ec == null) { log.debug("Top-level effector invocation: {} on {}", eff, entity); ec = getExecutionContext(entity); } return runAtEntity(entity, Effectors.invocation(entity, eff, parameters)); } @Override public boolean isManagedLocally(Entity e) { return true; } @Override public String toString() { return LocalManagementContext.class.getSimpleName()+"["+getManagementPlaneId()+"-"+getManagementNodeId()+"]"; } @Override public void reloadBrooklynProperties() { log.info("Reloading brooklyn properties from " + builder); if (builder.hasDelegateOriginalProperties()) log.warn("When reloading, mgmt context "+this+" properties are fixed, so reload will be of limited utility"); BrooklynProperties properties = builder.build(); configMap = new DeferredBrooklynProperties(properties, this); if (brooklynAdditionalProperties != null) { log.info("Reloading additional brooklyn properties from " + brooklynAdditionalProperties); configMap.addFromMap(brooklynAdditionalProperties); } this.downloadsManager = BasicDownloadsManager.newDefault(configMap); this.entitlementManager = Entitlements.newManager(this, configMap); clearLocationRegistry(); BrooklynFeatureEnablement.init(configMap); // Notify listeners that properties have been reloaded for (PropertiesReloadListener listener : reloadListeners) { listener.reloaded(); } } @VisibleForTesting public void clearLocationRegistry() { // Force reload of location registry this.locationRegistry = null; } @Override public void addPropertiesReloadListener(PropertiesReloadListener listener) { reloadListeners.add(checkNotNull(listener, "listener")); } @Override public void removePropertiesReloadListener(PropertiesReloadListener listener) { reloadListeners.remove(listener); } public void noteStartupComplete() { startupComplete = true; } }