blob: 1cf4a24c8b1a27aca4f69d7baadb48a46071f7cb [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.osgi.karaf;
import java.io.File;
import java.net.URI;
import java.net.URISyntaxException;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import java.util.concurrent.Callable;
import java.util.concurrent.TimeUnit;
import javax.management.ObjectName;
import javax.management.openmbean.CompositeData;
import javax.management.openmbean.CompositeDataSupport;
import javax.management.openmbean.OpenDataException;
import javax.management.openmbean.TabularData;
import org.apache.brooklyn.api.sensor.SensorEvent;
import org.apache.brooklyn.api.sensor.SensorEventListener;
import org.apache.brooklyn.core.annotation.Effector;
import org.apache.brooklyn.core.annotation.EffectorParam;
import org.apache.brooklyn.core.feed.ConfigToAttributes;
import org.apache.brooklyn.entity.java.JmxSupport;
import org.apache.brooklyn.entity.software.base.SoftwareProcessImpl;
import org.apache.brooklyn.feed.jmx.JmxAttributePollConfig;
import org.apache.brooklyn.feed.jmx.JmxFeed;
import org.apache.brooklyn.feed.jmx.JmxHelper;
import org.apache.brooklyn.feed.jmx.JmxValueFunctions;
import org.apache.brooklyn.util.collections.MutableMap;
import org.apache.brooklyn.util.exceptions.Exceptions;
import org.apache.brooklyn.util.os.Os;
import org.apache.brooklyn.util.repeat.Repeater;
import org.osgi.jmx.JmxConstants;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.google.common.base.Function;
import com.google.common.collect.Maps;
/**
* This sets up a Karaf OSGi container
*/
public class KarafContainerImpl extends SoftwareProcessImpl implements KarafContainer {
// TODO Better way of setting/overriding defaults for config keys that are defined in super class SoftwareProcess
private static final Logger LOG = LoggerFactory.getLogger(KarafContainerImpl.class);
public static final String KARAF_ADMIN = "org.apache.karaf:type=admin,name=%s";
public static final String KARAF_FEATURES = "org.apache.karaf:type=features,name=%s";
public static final String OSGI_BUNDLE_STATE = "osgi.core:type=bundleState,version=1.5";
public static final String OSGI_FRAMEWORK = "osgi.core:type=framework,version=1.5";
public static final String OSGI_COMPENDIUM = "osgi.compendium:service=cm,version=1.3";
protected JmxHelper jmxHelper;
private JmxFeed jmxFeed;
public KarafContainerImpl() {
super();
}
@Override
public Class<KarafDriver> getDriverInterface() {
return KarafDriver.class;
}
@Override
public KarafDriver getDriver() {
return (KarafDriver) super.getDriver();
}
@Override
public void init() {
super.init();
new JmxSupport(this, null).recommendJmxRmiCustomAgent();
}
@Override
protected void postDriverStart() {
super.postDriverStart();
uploadPropertyFiles(getConfig(NAMED_PROPERTY_FILES));
jmxHelper = new JmxHelper(this);
jmxHelper.connect(0); // i.e. don't block
}
@Override
protected void connectSensors() {
super.connectSensors();
//FIXME should have a better way of setting config -- firstly, not here!
//preferred style is to have config auto-applied to attributes, and have default values in their definition, not here
//use of "properties.{user,password}" is non-standard; is that requried? use default jmxUser, jmxPassword flags?
sensors().set(JMX_CONTEXT, String.format("karaf-%s", getConfig(KARAF_NAME.getConfigKey())));
ConfigToAttributes.apply(this);
ObjectName karafAdminObjectName = JmxHelper.createObjectName(String.format(KARAF_ADMIN, getConfig(KARAF_NAME.getConfigKey())));
jmxFeed = JmxFeed.builder()
.entity(this)
.helper(jmxHelper)
.period(500, TimeUnit.MILLISECONDS)
.pollAttribute(new JmxAttributePollConfig<Map>(KARAF_INSTANCES)
.objectName(karafAdminObjectName)
.attributeName("Instances")
.onSuccess(new Function<Object, Map>() {
@Override
public Map apply(Object input) {
return JmxValueFunctions.tabularDataToMap((TabularData)input);
}
})
.onException(new Function<Exception,Map>() {
@Override public Map apply(Exception input) {
// If MBean is unreachable, then mark as service-down
if (Boolean.TRUE.equals(getAttribute(SERVICE_UP))) {
LOG.debug("Entity "+this+" is not reachable on JMX");
sensors().set(SERVICE_UP, false);
}
return null;
}}))
.build();
// INSTANCES aggregates data for the other sensors.
subscriptions().subscribe(this, KARAF_INSTANCES, new SensorEventListener<Map>() {
@Override public void onEvent(SensorEvent<Map> event) {
Map<?,?> map = event.getValue();
if (map == null) return;
sensors().set(SERVICE_UP, "Started".equals(map.get("State")));
sensors().set(KARAF_ROOT, (Boolean) map.get("Is Root"));
sensors().set(KARAF_JAVA_OPTS, (String) map.get("JavaOpts"));
sensors().set(KARAF_INSTALL_LOCATION, (String) map.get("Location"));
sensors().set(KARAF_NAME, (String) map.get("Name"));
sensors().set(KARAF_PID, (Integer) map.get("Pid"));
sensors().set(KARAF_SSH_PORT, (Integer) map.get("Ssh Port"));
sensors().set(KARAF_RMI_REGISTRY_PORT, (Integer) map.get("RMI Registry Port"));
sensors().set(KARAF_RMI_SERVER_PORT, (Integer) map.get("RMI Server Port"));
sensors().set(KARAF_STATE, (String) map.get("State"));
}});
}
@Override
protected void disconnectSensors() {
super.disconnectSensors();
if (jmxFeed != null) jmxFeed.stop();
}
@Override
protected void preStop() {
super.preStop();
if (jmxHelper != null) jmxHelper.terminate();
}
@Override
@Effector(description="Updates the OSGi Service's properties, adding (and overriding) the given key-value pairs")
public void updateServiceProperties(
@EffectorParam(name="serviceName", description="Name of the OSGi service") String serviceName,
Map<String,String> additionalVals) {
TabularData table = (TabularData) jmxHelper.operation(OSGI_COMPENDIUM, "getProperties", serviceName);
try {
for (Map.Entry<String, String> entry: additionalVals.entrySet()) {
String key = entry.getKey();
String value = entry.getValue();
CompositeData data = new CompositeDataSupport(
JmxConstants.PROPERTY_TYPE,
MutableMap.of(JmxConstants.KEY, key, JmxConstants.TYPE, "String", JmxConstants.VALUE, value));
table.remove(data.getAll(new String[] {JmxConstants.KEY}));
table.put(data);
}
} catch (OpenDataException e) {
throw Exceptions.propagate(e);
}
LOG.info("Updating monterey-service configuration with changes {}", additionalVals);
if (LOG.isTraceEnabled()) LOG.trace("Updating monterey-service configuration with new configuration {}", table);
jmxHelper.operation(OSGI_COMPENDIUM, "update", serviceName, table);
}
@Override
@Effector(description="Updates the OSGi Service's properties, adding (and overriding) the given key-value pairs")
public void installFeature(
@EffectorParam(name="featureName", description="Name of the feature - see org.apache.karaf:type=features#installFeature()") final String featureName) throws Exception {
LOG.info("Installing feature {} via JMX", featureName);
Repeater.create("Wait for Karaf, to install feature "+featureName)
.limitIterationsTo(40)
.every(500, TimeUnit.MILLISECONDS)
.until(new Callable<Boolean>() {
@Override
public Boolean call() {
jmxHelper.operation(String.format(KARAF_FEATURES, getConfig(KARAF_NAME.getConfigKey())), "installFeature", featureName);
return true;
}})
.rethrowException()
.run();
}
@Override
public Map<Long,Map<String,?>> listBundles() {
TabularData table = (TabularData) jmxHelper.operation(OSGI_BUNDLE_STATE, "listBundles");
Map<List<?>, Map<String, Object>> map = JmxValueFunctions.tabularDataToMapOfMaps(table);
Map<Long,Map<String,?>> result = Maps.newLinkedHashMap();
for (Map.Entry<List<?>, Map<String, Object>> entry : map.entrySet()) {
result.put((Long)entry.getKey().get(0), entry.getValue());
}
return result;
}
/**
* throws URISyntaxException If bundle name is not a valid URI
*/
@Override
@Effector(description="Deploys the given bundle, returning the bundle id - see osgi.core:type=framework#installBundle()")
public long installBundle(
@EffectorParam(name="bundle", description="URI of bundle to be deployed") String bundle) throws URISyntaxException {
// TODO Consider switching to use either:
// - org.apache.karaf:type=bundles#install(String), or
// - dropping file into $RUN_DIR/deploy (but that would be async)
URI uri = new URI(bundle);
boolean wrap = false;
if (WRAP_SCHEME.equals(uri.getScheme())) {
bundle = bundle.substring(WRAP_SCHEME.length() + 1);
uri = new URI(bundle);
wrap = true;
}
if (FILE_SCHEME.equals(uri.getScheme())) {
LOG.info("Deploying bundle {} via file copy", bundle);
File source = new File(uri);
String target = getDriver().getRunDir() + "/" + source.getName();
getDriver().copyResource(source, target);
return (Long) jmxHelper.operation(OSGI_FRAMEWORK, "installBundle", (wrap ? WRAP_SCHEME + ":" : "") + FILE_SCHEME + "://" + target);
} else {
LOG.info("Deploying bundle {} via JMX", bundle);
return (Long) jmxHelper.operation(OSGI_FRAMEWORK, "installBundle", (wrap ? WRAP_SCHEME + ":" : "") + bundle);
}
}
@Override
@Effector(description="Undeploys the bundle with the given id")
public void uninstallBundle(
@EffectorParam(name="bundleId", description="Id of the bundle") Long bundleId) {
// TODO Consider switching to use either:
// - org.apache.karaf:type=bundles#install(String), or
// - dropping file into $RUN_DIR/deploy (but that would be async)
jmxHelper.operation(OSGI_FRAMEWORK, "uninstallBundle", bundleId);
}
protected void uploadPropertyFiles(Map<String,Map<String,String>> propertyFiles) {
if (propertyFiles == null) return;
for (Map.Entry<String,Map<String,String>> entry : propertyFiles.entrySet()) {
String file = entry.getKey();
Map<String,String> contents = entry.getValue();
Properties props = new Properties();
for (Map.Entry<String,String> prop : contents.entrySet()) {
props.setProperty(prop.getKey(), prop.getValue());
}
File local = Os.writePropertiesToTempFile(props, "karaf-"+getId(), ".cfg");
local.setReadable(true);
try {
String remote = getDriver().getRunDir() + "/" + file;
getDriver().copyResource(local, remote);
} finally {
local.delete();
}
}
}
}