/**
* Abiquo community edition
* cloud management application for hybrid clouds
* Copyright (C) 2008-2010 - Abiquo Holdings S.L.
*
* This application is free software; you can redistribute it and/or
* modify it under the terms of the GNU LESSER GENERAL PUBLIC
* LICENSE as published by the Free Software Foundation under
* version 3 of the License
*
* This software 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
* LESSER GENERAL PUBLIC LICENSE v.3 for more details.
*
* You should have received a copy of the GNU Lesser General Public
* License along with this library; if not, write to the
* Free Software Foundation, Inc., 59 Temple Place - Suite 330,
* Boston, MA 02111-1307, USA.
*/
package com.abiquo.nodecollector.domain.collectors;
import java.net.URL;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import org.apache.commons.lang.StringUtils;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
import com.abiquo.model.enumerator.HypervisorType;
import com.abiquo.nodecollector.constants.MessageValues;
import com.abiquo.nodecollector.domain.Collector;
import com.abiquo.nodecollector.domain.collectors.xenserver.CpuProperties;
import com.abiquo.nodecollector.domain.collectors.xenserver.SRType;
import com.abiquo.nodecollector.domain.collectors.xenserver.SoftwareVersion;
import com.abiquo.nodecollector.exception.CollectorException;
import com.abiquo.nodecollector.exception.ConnectionException;
import com.abiquo.nodecollector.exception.LoginException;
import com.abiquo.nodecollector.exception.NoManagedException;
import com.abiquo.nodecollector.utils.ResourceComparator;
import com.abiquo.server.core.infrastructure.nodecollector.HostDto;
import com.abiquo.server.core.infrastructure.nodecollector.HostStatusEnumType;
import com.abiquo.server.core.infrastructure.nodecollector.ResourceEnumType;
import com.abiquo.server.core.infrastructure.nodecollector.ResourceType;
import com.abiquo.server.core.infrastructure.nodecollector.VirtualDiskEnumType;
import com.abiquo.server.core.infrastructure.nodecollector.VirtualSystemCollectionDto;
import com.abiquo.server.core.infrastructure.nodecollector.VirtualSystemDto;
import com.abiquo.server.core.infrastructure.nodecollector.VirtualSystemStatusEnumType;
import com.xensource.xenapi.APIVersion;
import com.xensource.xenapi.Connection;
import com.xensource.xenapi.Host;
import com.xensource.xenapi.PBD;
import com.xensource.xenapi.PIF;
import com.xensource.xenapi.SR;
import com.xensource.xenapi.Session;
import com.xensource.xenapi.Types.SessionAuthenticationFailed;
import com.xensource.xenapi.Types.SessionInvalid;
import com.xensource.xenapi.Types.VbdType;
import com.xensource.xenapi.Types.VmPowerState;
import com.xensource.xenapi.VBD;
import com.xensource.xenapi.VDI;
import com.xensource.xenapi.VM;
/**
* XenServer collector plugin.
*
* @author destevez
*/
@Collector(type = HypervisorType.XENSERVER, order = 1)
public class XenServerCollector extends AbstractCollector
{
/** The logger. */
private static final Logger LOGGER = LoggerFactory.getLogger(XenServerCollector.class);
/** XenServer connection. */
private Connection connection;
@Override
public void connect(final String user, final String password) throws ConnectionException,
LoginException
{
try
{
connection = new Connection(new URL("http://" + getIpAddress()));
Session session =
Session.loginWithPassword(connection, user, password, APIVersion.latest()
.toString());
LOGGER.debug("Session started with UUID {}", session.getUuid(connection));
}
catch (SessionAuthenticationFailed e)
{
LOGGER.warn("Invalid credentials for hypervisor {} at cloud node ", this
.getHypervisorType().name(), getIpAddress());
throw new LoginException(MessageValues.LOG_EXCP, e);
}
catch (Exception e)
{
LOGGER.warn("Could not connect at hypervisor {} at cloud node {}", this
.getHypervisorType().name(), getIpAddress());
throw new ConnectionException(MessageValues.CONN_EXCP_I, e);
}
}
@Override
public void disconnect() throws CollectorException
{
LOGGER.info("Disconnecting...");
try
{
if (connection.getSessionReference() != null)
{
Session.logout(connection);
connection.dispose();
}
}
catch (SessionInvalid e)
{
// Do nothing. It means the previous connection was non-authenticated.
return;
}
catch (Exception e)
{
throw new CollectorException(MessageValues.CONN_EXCP_III, e);
}
}
@Override
public HostDto getHostInfo() throws CollectorException
{
LOGGER.info("Getting physical information in XenServer collector...");
HostDto info = new HostDto();
try
{
// Currently only one host is supported
Map<Host, Host.Record> hosts = Host.getAllRecords(connection);
Host host = hosts.keySet().iterator().next();
Host.Record hostRecord = hosts.get(host);
CpuProperties cpuProperties = CpuProperties.of(hostRecord);
String repositoryLocation =
System.getProperty("abiquo.appliancemanager.repositoryLocation");
info.setCpu(cpuProperties.getRealCores());
info.setName(hostRecord.nameLabel);
info.setRam(host.getMetrics(connection).getMemoryTotal(connection));
info.setHypervisor(HypervisorType.XENSERVER.getValue());
info.setVersion(StringUtils.join(new String[] {
hostRecord.softwareVersion.get("product_brand"), " v",
hostRecord.softwareVersion.get("product_version"), " build",
hostRecord.softwareVersion.get("build_number")}));
info.setInitiatorIQN(hostRecord.otherConfig.get("iscsi_iqn"));
info.setHypervisor(HypervisorType.XENSERVER.getValue());
info.getResources().addAll(getHostResources(hostRecord, repositoryLocation));
logHostDetails(hostRecord);
try
{
checkPhysicalState(hostRecord, repositoryLocation);
info.setStatus(HostStatusEnumType.MANAGED);
}
catch (NoManagedException e)
{
info.setStatus(HostStatusEnumType.NOT_MANAGED);
info.setStatusInfo(e.getMessage());
}
}
catch (Exception e)
{
throw new CollectorException(MessageValues.COLL_EXCP_PH, e);
}
return info;
}
/**
* Returns all the virtual systems except the one named <code>Domain-0</code>. That virtual
* system is the hypervisor itself and must not be returned as a virtual system.
*
* @return The list of virtual systems in the hypervisor.
* @throws CollectorException if any problem occurs.
*/
@Override
public VirtualSystemCollectionDto getVirtualMachines() throws CollectorException
{
LOGGER.info("Getting virtual machine information in XenServer collector...");
try
{
VirtualSystemCollectionDto virtualSystems = new VirtualSystemCollectionDto();
Map<VM, VM.Record> retrievedVMs = VM.getAllRecords(getConnection());
for (VM.Record vm : retrievedVMs.values())
{
if (!vm.isControlDomain && !vm.isATemplate)
{
LOGGER.debug("Found virtual machine: {}. Getting info...", vm.nameLabel);
VirtualSystemDto virtualSystem = getVirtualMachineInfo(vm);
virtualSystems.getVirtualSystems().add(virtualSystem);
}
}
return virtualSystems;
}
catch (Exception e)
{
throw new CollectorException(MessageValues.COLL_EXCP_VM, e);
}
}
/**
* Checks if the Physical machine is properly configured.
*
* @param hostRecord The Host information.
* @param repositoryLocation The location of the Appliance Library Storage Repository.
* @throws NoManagedException If the physical machine is not properly configured.
*/
private void checkPhysicalState(final Host.Record hostRecord, final String repositoryLocation)
throws NoManagedException
{
LOGGER.debug("Checking Repository configuration...");
try
{
// Find the Appliance Library Repository
Map<PBD, PBD.Record> pbds = PBD.getAllRecords(connection);
for (PBD.Record pbd : pbds.values())
{
SR.Record srRecord = pbd.SR.getRecord(connection);
if (isApplianceLibraryRepository(srRecord, pbd, repositoryLocation))
{
// Fail if it is not attached, success otherwise
if (!pbd.currentlyAttached)
{
LOGGER.debug(MessageValues.NOMAN_NFS_VI);
throw new NoManagedException(MessageValues.NOMAN_NFS_VI);
}
// Appliance Repository found; there is nothing else to check
break;
}
}
// If no Appliance Repository is found, return a MANAGED state, since Virtual Factory
// will create it when deploying a Virtual Appliance
}
catch (Exception e)
{
LOGGER.debug(MessageValues.NOMAN_NFS_VI);
throw new NoManagedException(MessageValues.NOMAN_NFS_VI, e);
}
// Check if Linux Guest support package is installed
LOGGER.debug("Checking Linux Guest support installation...");
SoftwareVersion version = SoftwareVersion.of(hostRecord);
if (!version.hasLinuxPack())
{
LOGGER.debug(MessageValues.NOMAN_XEN_SERVER_I);
throw new NoManagedException(MessageValues.NOMAN_XEN_SERVER_I);
}
}
/**
* Gets all resources from the specified host.
*
* @param hostRecord The host.
* @param repositoryLocation The location of the Appliance Library Storage Repository.
* @return The list of the host resources.
* @throws Exception If host resources cannot be retrieved.
*/
protected List<ResourceType> getHostResources(final Host.Record hostRecord,
final String repositoryLocation) throws Exception
{
List<ResourceType> resources = new ArrayList<ResourceType>();
resources.addAll(getNetworkInterfaces(hostRecord));
resources.addAll(getStorageRepositories(repositoryLocation));
Collections.sort(resources, new ResourceComparator());
return resources;
}
/**
* Get information of all network interfaces in a given host.
*
* @param host The target Host.
* @return A list containing the information of all network interfaces.
* @throws Exception If information of all network interfaces cannot be retrieved.
*/
protected List<ResourceType> getNetworkInterfaces(final Host.Record host) throws Exception
{
List<ResourceType> resources = new ArrayList<ResourceType>();
for (PIF pif : host.PIFs)
{
PIF.Record pifRecord = pif.getRecord(connection);
if (pifRecord.VLAN == -1) // Ignore VLAN specific PIF
{
LOGGER.debug("Found Network Device {} - MAC {}", pifRecord.device, pifRecord.MAC);
ResourceType resource = new ResourceType();
resource.setResourceType(ResourceEnumType.NETWORK_INTERFACE);
resource.setAddress(pifRecord.MAC);
resource.setElementName(pifRecord.device);
resources.add(resource);
}
}
return resources;
}
/**
* Gets information of Storage Repositories where Virtual Appliances will be deployed.
*
* @param repositoryLocation The location of the Appliance Library Storage Repository.
* @return A list containing all Storage Repositories where Virtual Appliances will be deployed.
* @throws Exception If Storage Repository information cannot be retrieved.
*/
protected List<ResourceType> getStorageRepositories(final String repositoryLocation)
throws Exception
{
List<ResourceType> resources = new ArrayList<ResourceType>();
Map<PBD, PBD.Record> pbds = PBD.getAllRecords(connection);
for (PBD.Record pbd : pbds.values())
{
SR.Record srRecord = pbd.SR.getRecord(connection);
SRType type = SRType.fromValue(srRecord.type);
// Return only valid SRs and ignore Appliance Library repository
if (type.isValidForDeployment()
&& !isApplianceLibraryRepository(srRecord, pbd, repositoryLocation))
{
LOGGER.debug("Found Storage Repository {}", srRecord.nameLabel);
ResourceType resource = new ResourceType();
resource.setResourceType(ResourceEnumType.HARD_DISK);
resource.setResourceSubType(type.name());
resource.setAddress(srRecord.uuid);
resource.setElementName(srRecord.nameLabel);
resource.setUnits(srRecord.physicalSize);
resource.setAvailableUnits(srRecord.physicalSize - srRecord.physicalUtilisation);
resources.add(resource);
}
}
return resources;
}
/**
* Get the information of the given Virtual Machine.
*
* @param vm The Virtual Machine.
* @return The Virtual Machine information.
*/
protected VirtualSystemDto getVirtualMachineInfo(final VM.Record vm)
{
VirtualSystemDto vSystem = new VirtualSystemDto();
vSystem.setName(vm.nameLabel);
vSystem.setStatus(getState(vm.powerState));
vSystem.setUuid(vm.uuid);
vSystem.setCpu(vm.VCPUsAtStartup);
vSystem.setRam(vm.memoryStaticMax);
vSystem.setVport(0L); // Remote access is not supported by XenServer
vSystem.getResources().addAll(getVirtualDisks(vm));
return vSystem;
}
/**
* Converts a {@link VmPowerState} to a {@link VirtualSystemStatusEnumType}.
*
* @param state The <code>VmPowerState</code>.
* @return The <code>VirtualSystemStatusEnumType</code>.
*/
private VirtualSystemStatusEnumType getState(final VmPowerState state)
{
switch (state)
{
case HALTED:
return VirtualSystemStatusEnumType.OFF;
case RUNNING:
return VirtualSystemStatusEnumType.ON;
case SUSPENDED:
return VirtualSystemStatusEnumType.OFF;
case PAUSED:
return VirtualSystemStatusEnumType.PAUSED;
case UNRECOGNIZED:
default:
return VirtualSystemStatusEnumType.OFF;
}
}
/**
* Gets the virtual disks of the given Virtual Machine.
* <p>
* The way in which the virtual disk image is represented on physical storage depends on the
* type of the Storage Repository in which the created VDI resides. For example, if the SR is of
* type <code>lvm</code> the VIRTUAL disk image will be rendered as an LVM volume. if the SR is
* of type <code>nfs</code> then the new disk image will be a sparse VHD file created on an NFS
* filer.
* <p>
* The Storage Repository type can be retrieved API using the SR.get_type() API call.
*
* @param vm The Virtual Machine.
* @return A list with all the Virtual Disks.
*/
private List<ResourceType> getVirtualDisks(final VM.Record vm)
{
List<ResourceType> diskList = new ArrayList<ResourceType>();
for (VBD vbd : vm.VBDs)
{
try
{
if (vbd.getType(connection).equals(VbdType.DISK))
{
VDI vdi = vbd.getVDI(connection);
VDI.Record vdiRecord = vdi.getRecord(connection);
ResourceType disk = new ResourceType();
disk.setUnits(vdiRecord.virtualSize);
SRType srType = SRType.fromValue(vdiRecord.SR.getType(connection));
disk.setResourceType(srType == SRType.CSLG ? ResourceEnumType.VOLUME_DISK
: ResourceEnumType.HARD_DISK);
// Datastore information
disk.setConnection(vdiRecord.SR.getUuid(connection)); // Datastore root path
disk.setAddress(""); // Datastore directory
disk.setElementName(vdiRecord.SR.getNameLabel(connection)); // Datastore name
// User device is the attachment number
if (srType == SRType.CSLG && vbd.getUserdevice(connection).equals("0"))
{
disk.setResourceSubType(VirtualDiskEnumType.STATEFUL.value());
}
else
{
// TODO: How to determine if it is sparse or flat ?
disk.setResourceSubType(VirtualDiskEnumType.VHD_SPARSE.value());
}
diskList.add(disk);
}
}
catch (Exception e)
{
LOGGER.error(e.getMessage(), e);
}
}
return diskList;
}
/**
* Get the Repository Location of a {@link SRType#NFS} Storage Repository.
* <p>
* XenServer API does not provide a way to retrieve the device-config attibute of the SR, so the
* repository location must be searched in another attribute.
* <p>
* By default, XenCenter and Abiquo store this information in the <b>description</b> field, so
* we will assume there will be present the repository location.
*
* @param sr The Storage Repository.
* @param pbd The Physical Block Device associated with the Storage Repository.
* @param repositoryLocation The location of the Appliance Library repository.
* @return A boolean indicating if the SR corresponds to the Appliance Library repository.
* @throws If Repository information cannot be retrieved.
*/
private boolean isApplianceLibraryRepository(final SR.Record sr, final PBD.Record pbd,
final String repositoryLocation) throws Exception
{
if (repositoryLocation == null)
{
return false;
}
// First of all check Storage Repository type
SRType type = SRType.fromValue(sr.type);
if (type != SRType.NFS)
{
return false;
}
// Check if NFS repository points to Appliance Library
String pbdLocation =
pbd.deviceConfig.get("server") + ":" + pbd.deviceConfig.get("serverpath");
return removeTrailingSlash(pbdLocation).equalsIgnoreCase(
removeTrailingSlash(repositoryLocation));
}
/**
* Logs host details.
*
* @param hostRecord The host information
*/
private void logHostDetails(final Host.Record hostRecord)
{
// Do not perform XenServer API calls if debug is disabled
if (LOGGER.isDebugEnabled())
{
LOGGER.debug("Host details for {}:", hostRecord.nameLabel);
for (String key : hostRecord.softwareVersion.keySet())
{
String value = hostRecord.softwareVersion.get(key);
LOGGER.debug(" {}: {}", key, value);
}
}
}
/**
* Gets the connection.
*
* @return the connection.
*/
public Connection getConnection()
{
return connection;
}
/**
* Sets the connection.
*
* @param connection the connection to set.
*/
public void setConnection(final Connection connection)
{
this.connection = connection;
}
}