/*
* NOTE: This copyright does *not* cover user programs that use HQ
* 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, 2005, 2006], Hyperic, Inc.
* This file is part of HQ.
*
* HQ 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.vmware;
import java.io.File;
import java.io.FileInputStream;
import java.io.IOException;
import java.net.URLEncoder;
import java.util.ArrayList;
import java.util.List;
import java.util.Properties;
import java.util.StringTokenizer;
import org.apache.commons.logging.Log;
import org.apache.commons.logging.LogFactory;
import org.hyperic.hq.product.AutoServerDetector;
import org.hyperic.hq.product.PluginException;
import org.hyperic.hq.product.ProductPlugin;
import org.hyperic.hq.product.ServerDetector;
import org.hyperic.hq.product.ServerResource;
import org.hyperic.hq.product.ServiceResource;
import org.hyperic.util.StringUtil;
import org.hyperic.util.config.ConfigResponse;
import org.hyperic.sigar.win32.RegistryKey;
import org.hyperic.sigar.win32.Win32Exception;
import org.hyperic.sigar.vmware.VM;
import org.hyperic.sigar.vmware.VMControlLibrary;
import org.hyperic.sigar.vmware.VMwareException;
import org.hyperic.sigar.vmware.VMwareServer;
public class VMwareDetector
extends ServerDetector
implements AutoServerDetector {
static final String WIN32_EVENTLOG_DIR =
"vmserverdRoot\\eventlog\\";
static final String UNIX_EVENTLOG_DIR =
"/var/log/vmware/";
static final String PROP_VM_CONFIG = "vm.config";
static final String PROP_VM_DISK = "vm.disk";
static final String PROP_VM_NIC = "vm.nic";
static final String VM_NAME = "VM";
static final String VM_DISK_NAME = "VM Disk";
static final String VM_NIC_NAME = "VM NIC";
private static final Log log =
LogFactory.getLog(VMwareDetector.class.getName());
private static String getProductKey(String product) {
return VMControlLibrary.REGISTRY_ROOT + "\\VMware " + product;
}
private static String unquote(String value) {
if (value.charAt(0) == '"') {
value = value.substring(1, value.length()-1);
}
return value;
}
private static class VMwareProperties extends Properties {
private VMwareProperties() {
super();
}
public synchronized Object put(Object key, Object value) {
value = unquote((String)value);
return super.put(key, value);
}
}
private static Properties loadProperties(File file) {
Properties props = new VMwareProperties();
FileInputStream is = null;
try {
is = new FileInputStream(file);
props.load(is);
} catch (IOException e) {
log.error("Loading " + file + ": " +
e.getMessage(), e);
} finally {
if (is != null) {
try { is.close(); } catch (IOException e) { }
}
}
return props;
}
private void setCustomProperties(ServerResource server,
VMwareProductInfo info) {
if (info == null) {
return;
}
ConfigResponse cprops = new ConfigResponse();
cprops.setValue("version", info.release);
cprops.setValue("build", String.valueOf(info.build));
setCustomProperties(server, cprops);
}
public List getServerResources(ConfigResponse platformConfig)
throws PluginException {
ServerResource server;
if (!VMControlLibrary.isLoaded()) {
return null;
}
if (isWin32()) {
server = getWin32Server();
}
else {
server = getLinuxServer();
}
if (server == null) {
return null;
}
ArrayList servers = new ArrayList();
servers.add(server);
return servers;
}
private boolean isESX() {
return getTypeInfo().getName().indexOf(VM.ESX) != -1;
}
private VMwareProductInfo getProductInfo(Properties props) {
VMwareProductInfo info;
try {
info = VMwareProductInfo.getInfo(props);
} catch (VMwareException e) {
log.info("Unable to get VMware product info", e);
return null;
}
if (info == null) {
log.info("Unable to get VMware product info: " +
"User id does not have permission to " +
"read VM config files?");
return null;
}
if (!getTypeInfo().getName().equals(info.toString())) {
return null;
}
return info;
}
private ServerResource getWin32Server()
throws PluginException {
Properties props = new Properties();
props.setProperty(VMwareConnectParams.PROP_AUTHD_PORT,
VMwareConnectParams.DEFAULT_AUTHD_PORT);
VMwareProductInfo info = getProductInfo(props);
if (info == null) {
return null;
}
String[] products = {
"GSX Server", "Server"
};
String path = null;
for (int i=0; i<products.length; i++) {
RegistryKey key = null;
try {
key =
RegistryKey.LocalMachine.openSubKey(getProductKey(products[i]));
path = key.getStringValue("InstallPath").trim();
break;
} catch (Win32Exception e) {
continue;
} finally {
if (key != null) {
key.close();
}
}
}
if (path == null) {
return null;
}
ServerResource server = createServerResource(path);
server.setProductConfig(new ConfigResponse(props));
server.setMeasurementConfig();
setCustomProperties(server, info);
return server;
}
private ServerResource getLinuxServer()
throws PluginException {
File config = new File("/etc/vmware/config");
if (!config.exists()) {
return null;
}
Properties props = loadProperties(config);
String path =
props.getProperty("defaultVMPath");
String port =
props.getProperty(VMwareConnectParams.PROP_AUTHD_PORT);
if (port == null) {
//this is vmware workstation
return null;
}
props.clear();
VMwareProductInfo info = getProductInfo(props);
if (info == null) {
return null;
}
if (isESX()) {
if ((info.major == 3) && (info.minor > 0)) {
//only 3.0.x and lower are supported
log.warn(info.version + " not supported, see: " +
"http://support.hyperic.com/display/hypcomm/VMware");
return null;
}
}
if (path == null) {
path = "/etc/vmware";
}
ServerResource server = createServerResource(path);
server.setProductConfig(new ConfigResponse(props));
server.setMeasurementConfig();
setCustomProperties(server, info);
return server;
}
static List getDisks(VM vm) throws VMwareException {
List disks = new ArrayList();
String diskRes = VMwareMetrics.getResource(vm, "disk.HTL");
StringTokenizer tok = new StringTokenizer(diskRes, ",");
while (tok.hasMoreTokens()) {
disks.add(tok.nextToken());
}
return disks;
}
static List getNICs(VM vm) throws VMwareException {
List nics = new ArrayList();
String nicRes = VMwareMetrics.getResource(vm, "net.adapters");
StringTokenizer tok = new StringTokenizer(nicRes, ",");
while (tok.hasMoreTokens()) {
nics.add(tok.nextToken());
}
return nics;
}
private String serviceName(String vmName, String type) {
return vmName + " " + type;
}
private String serviceName(String vmName, String type, String name) {
return serviceName(vmName, type) + " (" + name + ")";
}
private void discoverDisks(VM vm, List services,
String vmName, String vmConfig)
throws VMwareException {
List disks = getDisks(vm);
for (int i=0; i<disks.size(); i++) {
String disk = (String)disks.get(i);
ServiceResource service = new ServiceResource();
service.setType(this, VM_DISK_NAME);
service.setServiceName(serviceName(vmName, VM_DISK_NAME, disk));
ConfigResponse config = new ConfigResponse();
config.setValue(PROP_VM_CONFIG, vmConfig);
config.setValue(PROP_VM_DISK, disk);
service.setProductConfig(config);
service.setMeasurementConfig();
services.add(service);
}
}
private void discoverNICs(VM vm, List services,
String vmName, String vmConfig)
throws VMwareException {
List nics = getNICs(vm);
for (int i=0; i<nics.size(); i++) {
String nic = (String)nics.get(i);
ServiceResource service = new ServiceResource();
service.setType(this, VM_NIC_NAME);
service.setServiceName(serviceName(vmName, VM_NIC_NAME, nic));
ConfigResponse config = new ConfigResponse();
config.setValue(PROP_VM_CONFIG, vmConfig);
config.setValue(PROP_VM_NIC, nic);
service.setProductConfig(config);
service.setMeasurementConfig();
services.add(service);
}
}
private void getGuestProps(VM vm, ConfigResponse cprops) {
try {
cprops.setValue("ip", vm.getGuestInfo("ip"));
} catch (VMwareException e) {
}
}
//pass these vars to the guest OS where plugin on the
//otherside can link back to the host
private void setGuestInfo(String name, VM vm, ConfigResponse config) {
String[] props = {
ProductPlugin.PROP_PLATFORM_ID, //not present w/ PluginDumper
ProductPlugin.PROP_PLATFORM_NAME,
ProductPlugin.PROP_PLATFORM_FQDN,
ProductPlugin.PROP_PLATFORM_IP,
};
for (int i=0; i<props.length; i++) {
String key = props[i];
String val = config.getValue(key);
String gkey = "hq." + key;
//log.debug(name + ": guestinfo." + gkey + "=" + val);
if (val == null) {
//dont want PluginDumper to set the rest
return;
}
//XXX works as root, would be good
//to figure out howto set perms for another user
try {
vm.setGuestInfo(gkey, val);
} catch (VMwareException e) {
String msg =
"Cannot link guest to host: " +
e.getMessage();
log.info(msg);
break; //will be permission denied
}
}
}
protected List discoverServices(ConfigResponse serverConfig)
throws PluginException {
synchronized (VMwareConnectParams.LOCK) {
return getServices(serverConfig);
}
}
private String vmLogName(String name) {
String encoded = URLEncoder.encode(name);
return StringUtil.replace(encoded, "+", "%20");
}
private List getServices(ConfigResponse serverConfig,
VM vm, String name)
throws VMwareException {
List services = new ArrayList();
int state = vm.getExecutionState();
String displayName = vm.getDisplayName();
ServiceResource service = new ServiceResource();
service.setType(this, VM_NAME);
service.setServiceName(serviceName(displayName, VM_NAME));
ConfigResponse config = new ConfigResponse();
config.setValue(PROP_VM_CONFIG, name);
String logdir =
isWin32() ? WIN32_EVENTLOG_DIR : UNIX_EVENTLOG_DIR;
String logfile =
logdir + "event-" + vmLogName(name) + ".log";
config.setValue(VMwareEventLogPlugin.PROP_FILES_SERVICE,
logfile);
config.setValue(VMwareConfigTrackPlugin.PROP_FILES_SERVICE,
name);
service.setProductConfig(config);
log.debug(displayName + "=" + VM.EXECUTION_STATES[state]);
ConfigResponse cprops = new ConfigResponse();
String[] keys =
getCustomPropertiesSchema(service.getType()).getOptionNames();
//XXX use VM.getConfig if remote
Properties props = loadProperties(new File(name));
for (int k=0; k<keys.length; k++) {
String value = props.getProperty(keys[k]);
if (value != null) {
cprops.setValue(keys[k], value);
}
}
if (state == VM.EXECUTION_STATE_ON) {
service.setMeasurementConfig();
if (isESX()) {
try {
discoverDisks(vm, services, displayName, name);
} catch (VMwareException e) {
//XXX 3.0.0 bug disk.HTL not defined
log.error("Failed to discover VM Disks: " + e);
}
discoverNICs(vm, services, displayName, name);
}
getGuestProps(vm, cprops);
setGuestInfo(displayName, vm, serverConfig);
}
service.setCustomProperties(cprops);
service.setControlConfig();
services.add(service);
return services;
}
private List getServices(ConfigResponse serverConfig)
throws PluginException {
List services = new ArrayList();
boolean isServerConnected = false;
VMwareConnectParams params =
new VMwareConnectParams(serverConfig.toProperties());
VMwareServer server = new VMwareServer();
try {
server.connect(params);
isServerConnected = true;
List names = server.getRegisteredVmNames();
log.debug("Found " + names.size() + " registered VMs");
for (int i=0; i<names.size(); i++) {
String name = (String)names.get(i);
VM vm = new VM();
boolean isVmConnected = false;
try {
vm.connect(params, name);
isVmConnected = true;
services.addAll(getServices(serverConfig, vm, name));
} catch (VMwareException e) {
log.error("Error discovering " + name + " services: " +
e.getMessage(), e);
} finally {
if (isVmConnected) {
vm.disconnect();
}
vm.dispose();
}
}
} catch (VMwareException e) {
throw new PluginException("Error discovering VMs: " +
e.getMessage(), e);
} finally {
params.dispose();
if (isServerConnected) {
server.disconnect();
}
server.dispose();
}
return services;
}
}