| /* |
| * 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.entity.webapp; |
| |
| import java.util.Collection; |
| import java.util.List; |
| import java.util.Map; |
| |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.entity.EntitySpec; |
| import org.apache.brooklyn.api.entity.Group; |
| import org.apache.brooklyn.api.location.Location; |
| import org.apache.brooklyn.api.mgmt.Task; |
| import org.apache.brooklyn.api.sensor.SensorEvent; |
| import org.apache.brooklyn.api.sensor.SensorEventListener; |
| import org.apache.brooklyn.core.entity.Attributes; |
| import org.apache.brooklyn.core.entity.Entities; |
| import org.apache.brooklyn.core.entity.EntityPredicates; |
| import org.apache.brooklyn.core.entity.factory.ConfigurableEntityFactory; |
| import org.apache.brooklyn.core.entity.lifecycle.Lifecycle; |
| import org.apache.brooklyn.core.entity.lifecycle.ServiceStateLogic; |
| import org.apache.brooklyn.core.entity.trait.Startable; |
| import org.apache.brooklyn.core.entity.trait.StartableMethods; |
| import org.apache.brooklyn.core.feed.ConfigToAttributes; |
| import org.apache.brooklyn.core.location.Locations; |
| import org.apache.brooklyn.enricher.stock.Enrichers; |
| import org.apache.brooklyn.entity.group.DynamicGroupImpl; |
| import org.apache.brooklyn.entity.proxy.LoadBalancer; |
| import org.apache.brooklyn.entity.proxy.nginx.NginxController; |
| import org.apache.brooklyn.entity.webapp.tomcat.TomcatServer; |
| import org.apache.brooklyn.util.collections.MutableList; |
| import org.apache.brooklyn.util.collections.MutableMap; |
| import org.apache.brooklyn.util.collections.QuorumCheck.QuorumChecks; |
| import org.apache.brooklyn.util.exceptions.Exceptions; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.ImmutableMap; |
| import com.google.common.collect.Lists; |
| |
| public class ControlledDynamicWebAppClusterImpl extends DynamicGroupImpl implements ControlledDynamicWebAppCluster { |
| |
| public static final Logger log = LoggerFactory.getLogger(ControlledDynamicWebAppClusterImpl.class); |
| |
| public ControlledDynamicWebAppClusterImpl() { |
| this(MutableMap.of(), null); |
| } |
| |
| public ControlledDynamicWebAppClusterImpl(Map<?,?> flags) { |
| this(flags, null); |
| } |
| |
| public ControlledDynamicWebAppClusterImpl(Entity parent) { |
| this(MutableMap.of(), parent); |
| } |
| |
| @Deprecated |
| public ControlledDynamicWebAppClusterImpl(Map<?,?> flags, Entity parent) { |
| super(flags, parent); |
| } |
| |
| @Override |
| public void init() { |
| super.init(); |
| |
| ConfigToAttributes.apply(this, FACTORY); |
| ConfigToAttributes.apply(this, MEMBER_SPEC); |
| ConfigToAttributes.apply(this, CONTROLLER); |
| ConfigToAttributes.apply(this, CONTROLLER_SPEC); |
| ConfigToAttributes.apply(this, WEB_CLUSTER_SPEC); |
| ConfigToAttributes.apply(this, CONTROLLED_GROUP); |
| |
| ConfigurableEntityFactory<? extends WebAppService> webServerFactory = getAttribute(FACTORY); |
| EntitySpec<? extends WebAppService> webServerSpec = getAttribute(MEMBER_SPEC); |
| if (webServerFactory == null && webServerSpec == null) { |
| log.debug("creating default web server spec for {}", this); |
| webServerSpec = EntitySpec.create(TomcatServer.class); |
| sensors().set(MEMBER_SPEC, webServerSpec); |
| } |
| |
| log.debug("creating cluster child for {}", this); |
| // Note relies on initial_size being inherited by DynamicWebAppCluster, because key id is identical |
| EntitySpec<? extends DynamicWebAppCluster> webClusterSpec = getAttribute(WEB_CLUSTER_SPEC); |
| Map<String,Object> webClusterFlags; |
| if (webServerSpec != null) { |
| webClusterFlags = MutableMap.<String,Object>of("memberSpec", webServerSpec); |
| } else { |
| webClusterFlags = MutableMap.<String,Object>of("factory", webServerFactory); |
| } |
| if (webClusterSpec == null) { |
| log.debug("creating default web cluster spec for {}", this); |
| webClusterSpec = EntitySpec.create(DynamicWebAppCluster.class); |
| } |
| boolean hasMemberSpec = webClusterSpec.getConfig().containsKey(DynamicWebAppCluster.MEMBER_SPEC) || webClusterSpec.getFlags().containsKey("memberSpec"); |
| @SuppressWarnings("deprecation") |
| boolean hasMemberFactory = webClusterSpec.getConfig().containsKey(DynamicWebAppCluster.FACTORY) || webClusterSpec.getFlags().containsKey("factory"); |
| if (!(hasMemberSpec || hasMemberFactory)) { |
| webClusterSpec.configure(webClusterFlags); |
| } else { |
| log.warn("In {}, not setting cluster's {} because already set on webClusterSpec", new Object[] {this, webClusterFlags.keySet()}); |
| } |
| sensors().set(WEB_CLUSTER_SPEC, webClusterSpec); |
| |
| DynamicWebAppCluster cluster = addChild(webClusterSpec); |
| sensors().set(CLUSTER, cluster); |
| setEntityFilter(EntityPredicates.isMemberOf(cluster)); |
| |
| LoadBalancer controller = getAttribute(CONTROLLER); |
| if (controller == null) { |
| EntitySpec<? extends LoadBalancer> controllerSpec = getAttribute(CONTROLLER_SPEC); |
| if (controllerSpec == null) { |
| log.debug("creating controller using default spec for {}", this); |
| controllerSpec = EntitySpec.create(NginxController.class); |
| sensors().set(CONTROLLER_SPEC, controllerSpec); |
| } else { |
| log.debug("creating controller using custom spec for {}", this); |
| } |
| controller = addChild(controllerSpec); |
| enrichers().add(Enrichers.builder() |
| .propagating(LoadBalancer.PROXY_HTTP_PORT, LoadBalancer.PROXY_HTTPS_PORT) |
| .from(controller) |
| .build()); |
| sensors().set(CONTROLLER, controller); |
| } |
| |
| Group controlledGroup = getAttribute(CONTROLLED_GROUP); |
| if (controlledGroup == null) { |
| log.debug("using cluster as controlledGroup for {}", this); |
| controlledGroup = cluster; |
| sensors().set(CONTROLLED_GROUP, cluster); |
| } else { |
| log.debug("using custom controlledGroup {} for {}", controlledGroup, this); |
| } |
| |
| doBind(); |
| } |
| |
| @Override |
| protected void initEnrichers() { |
| if (config().getLocalRaw(UP_QUORUM_CHECK).isAbsent()) { |
| config().set(UP_QUORUM_CHECK, QuorumChecks.newInstance(2, 1.0, false)); |
| } |
| super.initEnrichers(); |
| ServiceStateLogic.newEnricherFromChildrenUp().checkChildrenOnly().requireUpChildren(getConfig(UP_QUORUM_CHECK)).addTo(this); |
| } |
| |
| @Override |
| public void rebind() { |
| super.rebind(); |
| doBind(); |
| } |
| |
| protected void doBind() { |
| DynamicWebAppCluster cluster = getAttribute(CLUSTER); |
| if (cluster != null) { |
| subscriptions().subscribe(cluster, DynamicWebAppCluster.GROUP_MEMBERS, new SensorEventListener<Object>() { |
| @Override public void onEvent(SensorEvent<Object> event) { |
| // TODO inefficient impl; also worth extracting this into a mixin of some sort. |
| rescanEntities(); |
| }}); |
| } |
| } |
| |
| @Override |
| public LoadBalancer getController() { |
| return getAttribute(CONTROLLER); |
| } |
| |
| @SuppressWarnings("unchecked") |
| @Override |
| public synchronized ConfigurableEntityFactory<WebAppService> getFactory() { |
| return (ConfigurableEntityFactory<WebAppService>) getAttribute(FACTORY); |
| } |
| |
| // TODO convert to an entity reference which is serializable |
| @Override |
| public synchronized DynamicWebAppCluster getCluster() { |
| return getAttribute(CLUSTER); |
| } |
| |
| @Override |
| public Group getControlledGroup() { |
| return getAttribute(CONTROLLED_GROUP); |
| } |
| |
| @Override |
| public void start(Collection<? extends Location> locations) { |
| ServiceStateLogic.setExpectedState(this, Lifecycle.STARTING); |
| |
| try { |
| if (isLegacyConstruction()) { |
| init(); |
| } |
| |
| locations = Locations.getLocationsCheckingAncestors(locations, this); |
| // store inherited locations |
| addLocations(locations); |
| |
| LoadBalancer loadBalancer = getController(); |
| loadBalancer.bind(MutableMap.of("serverPool", getControlledGroup())); |
| |
| List<Entity> childrenToStart = MutableList.<Entity>of(getCluster()); |
| // Set controller as child of cluster, if it does not already have a parent |
| if (getController().getParent() == null) { |
| addChild(getController()); |
| } |
| |
| // And only start controller if we are parent. Favour locations defined on controller, if present |
| Task<List<Void>> startControllerTask = null; |
| if (this.equals(getController().getParent())) { |
| if (getController().getLocations().size() == 0) { |
| childrenToStart.add(getController()); |
| } else { |
| startControllerTask = Entities.invokeEffectorList(this, MutableList.<Entity>of(getController()), Startable.START, ImmutableMap.of("locations", getController().getLocations())); |
| } |
| } |
| |
| // don't propagate start locations |
| Entities.invokeEffectorList(this, childrenToStart, Startable.START, ImmutableMap.of("locations", MutableList.of())).get(); |
| if (startControllerTask != null) { |
| startControllerTask.get(); |
| } |
| |
| // wait for everything to start, then update controller, to ensure it is up to date |
| // (will happen asynchronously as members come online, but we want to force it to happen) |
| getController().update(); |
| |
| ServiceStateLogic.setExpectedState(this, Lifecycle.RUNNING); |
| } catch (Exception e) { |
| ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE); |
| throw Exceptions.propagate(e); |
| } finally { |
| connectSensors(); |
| } |
| } |
| |
| @Override |
| public void stop() { |
| ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPING); |
| |
| try { |
| List<Startable> tostop = Lists.newArrayList(); |
| if (this.equals(getController().getParent())) tostop.add(getController()); |
| tostop.add(getCluster()); |
| |
| StartableMethods.stopSequentially(tostop); |
| |
| clearLocations(); |
| |
| ServiceStateLogic.setExpectedState(this, Lifecycle.STOPPED); |
| } catch (Exception e) { |
| ServiceStateLogic.setExpectedState(this, Lifecycle.ON_FIRE); |
| throw Exceptions.propagate(e); |
| } |
| } |
| |
| @Override |
| public void restart() { |
| // TODO prod the entities themselves to restart, instead? |
| Collection<Location> locations = Lists.newArrayList(getLocations()); |
| |
| stop(); |
| start(locations); |
| } |
| |
| void connectSensors() { |
| // FIXME no longer needed |
| enrichers().add(Enrichers.builder() |
| .propagatingAllButUsualAnd(Attributes.MAIN_URI, ROOT_URL, GROUP_MEMBERS, GROUP_SIZE) |
| .from(getCluster()) |
| .build()); |
| enrichers().add(Enrichers.builder() |
| // include hostname and address of controller (need both in case hostname only resolves to internal/private ip) |
| .propagating(LoadBalancer.HOSTNAME, Attributes.ADDRESS, Attributes.MAIN_URI, ROOT_URL) |
| .from(getController()) |
| .build()); |
| } |
| |
| @Override |
| public Integer resize(Integer desiredSize) { |
| return getCluster().resize(desiredSize); |
| } |
| |
| @Override |
| public String replaceMember(String memberId) { |
| return getCluster().replaceMember(memberId); |
| } |
| |
| /** |
| * @return the current size of the group. |
| */ |
| @Override |
| public Integer getCurrentSize() { |
| return getCluster().getCurrentSize(); |
| } |
| |
| @Override |
| public void deploy(String url, String targetName) { |
| DynamicWebAppClusterImpl.addToWarsByContext(this, url, targetName); |
| getCluster().deploy(url, targetName); |
| } |
| |
| @Override |
| public void undeploy(String targetName) { |
| DynamicWebAppClusterImpl.removeFromWarsByContext(this, targetName); |
| getCluster().undeploy(targetName); |
| } |
| |
| @Override |
| public void redeployAll() { |
| getCluster().redeployAll(); |
| } |
| |
| } |