/*
* NOTE: This copyright does *not* cover user programs that use Hyperic
* program services by normal system calls through the application
* program interfaces provided as part of the Hyperic Plug-in Development
* Kit or the Hyperic Client Development Kit - this is merely considered
* normal use of the program, and does *not* fall under the heading of
* "derived work".
*
* Copyright (C) [2004-2011], VMware, Inc.
* This file is part of Hyperic.
*
* Hyperic is free software; you can redistribute it and/or modify
* it under the terms version 2 of the GNU General Public License as
* published by the Free Software Foundation. This program is distributed
* in the hope that it will be useful, but WITHOUT ANY WARRANTY; without
* even the implied warranty of MERCHANTABILITY or FITNESS FOR A
* PARTICULAR PURPOSE. See the GNU General Public License for more
* details.
*
* You should have received a copy of the GNU General Public License
* along with this program; if not, write to the Free Software
* Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307
* USA.
*/
package org.hyperic.hq.plugin.cloudfoundry.inventory;
import java.net.MalformedURLException;
import java.util.ArrayList;
import java.util.Iterator;
import java.util.List;
import java.util.Map;
import java.util.Properties;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.agent.AgentRemoteValue;
import org.hyperic.hq.agent.server.AgentDaemon;
import org.hyperic.hq.agent.server.AgentStorageProvider;
import org.hyperic.hq.agent.server.ConfigStorage;
import org.hyperic.hq.agent.AgentCommand;
import org.hyperic.hq.authz.shared.AuthzConstants;
import org.hyperic.hq.autoinventory.agent.client.AICommandsUtils;
import org.hyperic.hq.plugin.cloudfoundry.util.CloudFoundryProxy;
import org.hyperic.hq.product.AutoServerDetector;
import org.hyperic.hq.product.MetricUnreachableException;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.ServerDetector;
import org.hyperic.hq.product.ServerResource;
import org.hyperic.hq.product.ServiceResource;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.util.timer.StopWatch;
import org.json.JSONArray;
import org.json.JSONException;
import org.json.JSONObject;
import org.cloudfoundry.client.lib.ApplicationStats;
import org.cloudfoundry.client.lib.CloudApplication;
import org.cloudfoundry.client.lib.CloudInfo;
import org.cloudfoundry.client.lib.CloudService;
import org.cloudfoundry.client.lib.InstanceStats;
public class CloudFoundryDetector extends ServerDetector implements AutoServerDetector {
private static final Log _log =
LogFactory.getLog(CloudFoundryDetector.class.getName());
private static final String PROTOTYPE_CLOUD_FOUNDRY = "Cloud Foundry";
private static final String PROTOTYPE_CLOUD_FOUNDRY_APP = "Cloud Foundry Application";
private static final String PROTOTYPE_CLOUD_FOUNDRY_MONGODB = "Cloud Foundry MongoDB Service";
private static final String PROTOTYPE_CLOUD_FOUNDRY_MYSQL = "Cloud Foundry MySQL Service";
private static final String PROTOTYPE_CLOUD_FOUNDRY_RABBITMQ = "Cloud Foundry RabbitMQ Service";
private static final String PROTOTYPE_CLOUD_FOUNDRY_REDIS = "Cloud Foundry Redis Service";
private static final String HIERARCHY_KEY = "resource.hierarchy";
private static final int ARV_DUMMY_INSTANCE_ID = 0;
// TODO: these constants are part of RuntimeAutodiscoverer as private
// constants, so we need to define them again here.
private static final String STORAGE_PREFIX = "runtimeautodiscovery";
private static final String STORAGE_KEYLIST = "runtimeAD-keylist";
private void runAutoDiscovery(ConfigResponse cf) {
_log.debug("[runAutoDiscovery] >> start");
try {
AgentRemoteValue configARV = AICommandsUtils.createArgForRuntimeDiscoveryConfig(0, ARV_DUMMY_INSTANCE_ID, PROTOTYPE_CLOUD_FOUNDRY, null, cf);
_log.debug("[runAutoDiscovery] configARV=" + configARV);
AgentCommand ac = new AgentCommand(1, 1, "autoinv:pushRuntimeDiscoveryConfig", configARV);
AgentDaemon.getMainInstance().getCommandDispatcher().processRequest(ac, null, null);
_log.debug("[runAutoDiscovery] << OK");
} catch (Exception ex) {
_log.debug("[runAutoDiscovery]" + ex.getMessage(), ex);
}
}
public List getServerResources(ConfigResponse platformConfig)
throws PluginException {
AgentDaemon agent = AgentDaemon.getMainInstance();
return recreateServerResources(agent);
}
/**
* Recreate manually-added server as an autodiscovered
* server so that custom properties can be autodiscovered.
*
* @param agent
* @return
*/
private List recreateServerResources(AgentDaemon agent) {
List<ServerResource> resources = new ArrayList<ServerResource>();
try {
// get config for existing servers
AgentStorageProvider storageProvider = agent.getStorageProvider();
ConfigStorage storage = new ConfigStorage(storageProvider,
STORAGE_KEYLIST,
STORAGE_PREFIX);
Map configs = storage.load();
for (Iterator i = configs.entrySet().iterator(); i.hasNext();) {
Map.Entry entry = (Map.Entry)i.next();
ConfigStorage.Key key = (ConfigStorage.Key)entry.getKey();
String typeName = key.getTypeName();
if (_log.isDebugEnabled()) {
_log.debug("recreateServerResources typeName=" + typeName
+ ", id=" + key.getId()
+ ", key=" + key);
}
if (PROTOTYPE_CLOUD_FOUNDRY.equals(typeName)
&& key.getId() != ARV_DUMMY_INSTANCE_ID) {
ConfigResponse serverConfig = (ConfigResponse)entry.getValue();
// discover any new apps or services during default scan
// to improve response time
runAutoDiscovery(serverConfig);
// recreate manually-added server as an autodiscovered
// server so that custom properties can be autodiscovered
ServerResource s = recreateServerResource(serverConfig);
if (s != null) {
resources.add(s);
}
}
}
} catch (Exception e) {
_log.error("Could not discover apps and services during the default scan: "
+ e.getMessage(), e);
}
_log.debug("[recreateServerResources] servers=" + resources.size());
return resources;
}
private ServerResource recreateServerResource(ConfigResponse serverConfig) {
ServerResource server = null;
try {
Properties props = new Properties();
props.putAll(serverConfig.toProperties());
props.putAll(getManager().getProperties());
CloudFoundryResourceManager manager = new CloudFoundryResourceManager(props);
server = manager.createServerResource();
if (server != null) {
discoverServerConfig(server);
}
} catch (Exception e) {
_log.debug("Could not recreate server resource", e);
server = null;
}
return server;
}
/**
* Further configure the Cloud Foundry server resource.
* @param server
*/
private void discoverServerConfig(ServerResource server) {
try {
ConfigResponse serverConfig = server.getProductConfig();
CloudFoundryProxy cf = new CloudFoundryProxy(serverConfig.toProperties());
if (cf != null) {
ConfigResponse custom = new ConfigResponse();
CloudInfo info = cf.getCloudInfo();
custom.setValue("info.build", info.getBuild().toString());
custom.setValue("info.version", info.getVersion());
custom.setValue("info.name", info.getName());
custom.setValue("info.support", info.getSupport());
custom.setValue("info.limits.apps", Integer.toString(info.getLimits().getMaxApps()));
custom.setValue("info.limits.services", Integer.toString(info.getLimits().getMaxServices()));
// display max memory in GB
double maxMemory = info.getLimits().getMaxTotalMemory() / 1024d;
custom.setValue("info.limits.memory", Double.toString(maxMemory) + " GB");
server.setCustomProperties(custom);
}
} catch (Exception e) {
_log.info("Could not set custom server properties", e);
}
}
protected List discoverServices(ConfigResponse serverConfig)
throws PluginException {
List<ServiceResource> services = new ArrayList<ServiceResource>();
CloudFoundryProxy cf = new CloudFoundryProxy(serverConfig.toProperties());
services.addAll(discoverCloudApplications(cf, serverConfig));
services.addAll(discoverCloudServices(cf, serverConfig));
// TODO: what if all apps and services were deleted in cloud foundry
if (!services.isEmpty()) {
syncServices(serverConfig, services);
}
return services;
}
private List<ServiceResource> discoverCloudApplications(CloudFoundryProxy cf,
ConfigResponse serverConfig) {
List<ServiceResource> services = new ArrayList<ServiceResource>();
for (CloudApplication app : cf.getApplications()) {
try {
JSONObject jsonConfig = discoverResourceHierarchy(app);
JSONObject jsonServices = jsonConfig.getJSONArray("service").getJSONObject(0);
JSONArray jsonServiceNames = jsonServices.getJSONArray("name");
ServiceResource service = new ServiceResource();
service.setType(PROTOTYPE_CLOUD_FOUNDRY_APP);
ConfigResponse productCfg = new ConfigResponse();
productCfg.setValue("resource.name", app.getName());
productCfg.setValue(HIERARCHY_KEY, jsonConfig.toString());
service.setProductConfig(productCfg);
service.setMeasurementConfig(new ConfigResponse());
ConfigResponse custom = new ConfigResponse();
custom.setValue("app.model", app.getStaging().get("model"));
custom.setValue("app.stack", app.getStaging().get("stack"));
custom.setValue("app.name", app.getName());
custom.setValue("app.memory", app.getMemory() + " MB");
String uris = app.getUris().toString();
uris = uris.substring(1, uris.length()-1);
custom.setValue("app.uri", uris);
// display max disk in GB
double maxDisk = 0;
int maxCores = 0;
ApplicationStats stats = cf.getApplicationStats(app.getName());
List<InstanceStats> records = stats.getRecords();
for (InstanceStats stat : records) {
if (stat.getCores() > maxCores) {
maxCores = stat.getCores();
}
double quota = (double) stat.getDiskQuota();
if (quota > maxDisk) {
maxDisk = quota;
}
}
if (!records.isEmpty()) {
maxDisk = maxDisk / (1024 * 1024 * 1024);
custom.setValue("app.disk", maxDisk + " GB");
custom.setValue("app.core", Integer.toString(maxCores));
}
String appServices = "";
if (jsonServiceNames.length() > 0) {
appServices = jsonServiceNames.toString().replace("\"", "");
appServices = appServices.substring(1, appServices.length()-1);
}
custom.setValue("app.services", appServices);
service.setCustomProperties(custom);
String name = formatAutoInventoryName(service.getType(), serverConfig, productCfg, custom);
service.setName(name);
services.add(service);
} catch (Exception e) {
_log.debug("Could not discover cloud application: " + e.getMessage(), e);
}
}
return services;
}
private JSONObject discoverResourceHierarchy(CloudApplication app)
throws JSONException {
JSONObject jsonConfig = new JSONObject();
jsonConfig.put("service", getApplicationServices(app));
jsonConfig.put("createTs", System.currentTimeMillis());
return jsonConfig;
}
private JSONArray getApplicationServices(CloudApplication app) {
JSONArray jsonServices = new JSONArray();
try {
JSONObject jsonService = new JSONObject();
JSONArray jsonServiceNames = new JSONArray();
for (String s : app.getServices()) {
jsonServiceNames.put(s);
}
jsonService.put("rid", new JSONArray());
jsonService.put("name", jsonServiceNames);
jsonServices.put(jsonService);
} catch (Exception e) {
_log.debug("Cannot get application services for " + app.getName(), e);
}
return jsonServices;
}
private List<ServiceResource> discoverCloudServices(CloudFoundryProxy cf,
ConfigResponse serverConfig) {
List<ServiceResource> services = new ArrayList<ServiceResource>();
for (CloudService cs : cf.getServices()) {
ServiceResource service = new ServiceResource();
String vendor = cs.getVendor();
if ("mysql".equalsIgnoreCase(vendor)) {
service.setType(PROTOTYPE_CLOUD_FOUNDRY_MYSQL);
} else if ("mongodb".equalsIgnoreCase(vendor)) {
service.setType(PROTOTYPE_CLOUD_FOUNDRY_MONGODB);
} else if ("redis".equalsIgnoreCase(vendor)) {
service.setType(PROTOTYPE_CLOUD_FOUNDRY_REDIS);
} else if ("rabbitmq".equalsIgnoreCase(vendor)) {
service.setType(PROTOTYPE_CLOUD_FOUNDRY_RABBITMQ);
} else {
_log.info("Unsupported Cloud Foundry service: " + vendor);
continue;
}
ConfigResponse productCfg = new ConfigResponse();
productCfg.setValue("resource.name", cs.getName());
service.setProductConfig(productCfg);
service.setMeasurementConfig(new ConfigResponse());
ConfigResponse custom = new ConfigResponse();
custom.setValue("service.name", cs.getName());
custom.setValue("service.tier", cs.getTier());
custom.setValue("service.type", cs.getType());
custom.setValue("service.vendor", vendor);
custom.setValue("service.version", cs.getVersion());
service.setCustomProperties(custom);
String name = formatAutoInventoryName(service.getType(), serverConfig, productCfg, custom);
service.setName(name);
services.add(service);
}
return services;
}
private void syncServices(ConfigResponse serverConfig, List<ServiceResource> cloudResources) {
// TODO: make auto-sync a configurable property?
boolean autoSync = true;
_log.debug("[syncServices] autoSync=" + autoSync + ", resources=" + cloudResources.size());
if (autoSync) {
try {
Properties props = new Properties();
props.putAll(serverConfig.toProperties());
props.putAll(getManager().getProperties());
TransientResourceManager manager = new CloudFoundryResourceManager(props);
manager.syncServices(cloudResources);
} catch (Throwable e) {
_log.debug("Could not sync transient services: " + e.getMessage(), e);
}
}
}
}