blob: 6ac61ce4af21aa9b50400a1c9b9e35a999c4e220 [file] [log] [blame]
/*
* 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 ConfigurableEntityFactory<WebAppService> getFactory() {
return (ConfigurableEntityFactory<WebAppService>) getAttribute(FACTORY);
}
// TODO convert to an entity reference which is serializable
@Override
public 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();
}
}