/** * Copyright (C) 2010 Cloud.com, Inc. All rights reserved. * * This software is licensed under the GNU General Public License v3 or later. * * It is free software: you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation, either version 3 of the License, or any later version. * 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, see . * */ package com.cloud.hypervisor.xen.discoverer; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.util.HashMap; import java.util.LinkedHashMap; import java.util.List; import java.util.Map; import java.util.Set; import javax.ejb.Local; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; import org.apache.xmlrpc.XmlRpcException; import com.cloud.agent.AgentManager; import com.cloud.agent.Listener; import com.cloud.agent.api.AgentControlAnswer; import com.cloud.agent.api.AgentControlCommand; import com.cloud.agent.api.Answer; import com.cloud.agent.api.Command; import com.cloud.agent.api.StartupCommand; import com.cloud.alert.AlertManager; import com.cloud.configuration.Config; import com.cloud.dc.ClusterVO; import com.cloud.dc.dao.ClusterDao; import com.cloud.exception.ConnectionException; import com.cloud.exception.DiscoveryException; import com.cloud.host.HostInfo; import com.cloud.host.HostVO; import com.cloud.host.Status; import com.cloud.host.dao.HostDao; import com.cloud.hypervisor.Hypervisor; import com.cloud.hypervisor.Hypervisor.HypervisorType; import com.cloud.hypervisor.xen.resource.CitrixResourceBase; import com.cloud.hypervisor.xen.resource.XcpServerResource; import com.cloud.hypervisor.xen.resource.XenServer56FP1Resource; import com.cloud.hypervisor.xen.resource.XenServerConnectionPool; import com.cloud.hypervisor.xen.resource.XenServerResource; import com.cloud.resource.Discoverer; import com.cloud.resource.DiscovererBase; import com.cloud.resource.ServerResource; import com.cloud.storage.Storage.ImageFormat; import com.cloud.storage.Storage.TemplateType; import com.cloud.storage.VMTemplateVO; import com.cloud.storage.dao.VMTemplateDao; import com.cloud.storage.dao.VMTemplateHostDao; import com.cloud.user.Account; import com.cloud.utils.NumbersUtil; import com.cloud.utils.component.Inject; import com.cloud.utils.exception.CloudRuntimeException; import com.xensource.xenapi.Connection; import com.xensource.xenapi.Host; import com.xensource.xenapi.Pool; import com.xensource.xenapi.Session; import com.xensource.xenapi.Types.SessionAuthenticationFailed; import com.xensource.xenapi.Types.XenAPIException; @Local(value=Discoverer.class) public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, Listener { private static final Logger s_logger = Logger.getLogger(XcpServerDiscoverer.class); protected String _publicNic; protected String _privateNic; protected String _storageNic1; protected String _storageNic2; protected int _wait; protected XenServerConnectionPool _connPool; protected String _increase; protected boolean _checkHvm; protected String _guestNic; @Inject protected AlertManager _alertMgr; @Inject protected AgentManager _agentMgr; @Inject protected HostDao _hostDao; @Inject VMTemplateDao _tmpltDao; @Inject VMTemplateHostDao _vmTemplateHostDao; @Inject ClusterDao _clusterDao; protected XcpServerDiscoverer() { } @Override public Map> find(long dcId, Long podId, Long clusterId, URI url, String username, String password) throws DiscoveryException { Map> resources = new HashMap>(); Connection conn = null; if (!url.getScheme().equals("http")) { String msg = "urlString is not http so we're not taking care of the discovery for this: " + url; s_logger.debug(msg); return null; } if (clusterId == null) { String msg = "must specify cluster Id when add host"; s_logger.debug(msg); throw new RuntimeException(msg); } if (podId == null) { String msg = "must specify pod Id when add host"; s_logger.debug(msg); throw new RuntimeException(msg); } ClusterVO cluster = _clusterDao.findById(clusterId); if(cluster == null || (cluster.getHypervisorType() != HypervisorType.XenServer && cluster.getHypervisorType() != HypervisorType.Xen)) { if(s_logger.isInfoEnabled()) s_logger.info("invalid cluster id or cluster is not for Xen/XenServer hypervisors"); return null; } try { List eHosts = _hostDao.listByCluster(clusterId); if( eHosts.size() > 0 ) { HostVO eHost = eHosts.get(0); _hostDao.loadDetails(eHost); } String hostname = url.getHost(); InetAddress ia = InetAddress.getByName(hostname); String hostIp = ia.getHostAddress(); String masterIp = _connPool.getMasterIp(hostIp, username, password); conn = _connPool.masterConnect(masterIp, username, password); if (conn == null) { String msg = "Unable to get a connection to " + url; s_logger.debug(msg); throw new DiscoveryException(msg); } Set pools = Pool.getAll(conn); Pool pool = pools.iterator().next(); Pool.Record pr = pool.getRecord(conn); String poolUuid = pr.uuid; Map hosts = Host.getAllRecords(conn); /*set cluster hypervisor type to xenserver*/ ClusterVO clu = _clusterDao.findById(clusterId); if ( clu.getGuid()== null ) { clu.setGuid(poolUuid); } else { if( !clu.getGuid().equals(poolUuid)) { if (hosts.size() == 1 ) { if( !addHostsToPool(conn, hostIp, clusterId)){ String msg = "Unable to add host(" + hostIp + ") to cluster " + clusterId; s_logger.warn(msg); throw new DiscoveryException(msg); } } else { String msg = "Host (" + hostIp + ") is already in pool(" + poolUuid +"), can to join pool(" + clu.getGuid() + ")"; s_logger.warn(msg); throw new DiscoveryException(msg); } } } // can not use this conn after this point, because this host may join a pool, this conn is retired if (conn != null) { try{ Session.logout(conn); } catch (Exception e ) { } conn.dispose(); conn = null; } poolUuid = clu.getGuid(); _clusterDao.update(clusterId, clu); if (_checkHvm) { for (Map.Entry entry : hosts.entrySet()) { Host.Record record = entry.getValue(); boolean support_hvm = false; for ( String capability : record.capabilities ) { if(capability.contains("hvm")) { support_hvm = true; break; } } if( !support_hvm ) { String msg = "Unable to add host " + record.address + " because it doesn't support hvm"; _alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, dcId, podId, msg, msg); s_logger.debug(msg); throw new RuntimeException(msg); } } } for (Map.Entry entry : hosts.entrySet()) { Host.Record record = entry.getValue(); String hostAddr = record.address; String prodVersion = record.softwareVersion.get("product_version"); String xenVersion = record.softwareVersion.get("xen"); String hostOS = record.softwareVersion.get("product_brand"); String hostOSVer = prodVersion; String hostKernelVer = record.softwareVersion.get("linux"); if (_hostDao.findByGuid(record.uuid) != null) { s_logger.debug("Skipping " + record.address + " because " + record.uuid + " is already in the database."); continue; } CitrixResourceBase resource = createServerResource(dcId, podId, record); s_logger.info("Found host " + record.hostname + " ip=" + record.address + " product version=" + prodVersion); Map details = new HashMap(); Map params = new HashMap(); details.put("url", hostAddr); details.put("username", username); params.put("username", username); details.put("password", password); params.put("password", password); params.put("zone", Long.toString(dcId)); params.put("guid", record.uuid); params.put("pod", podId.toString()); params.put("cluster", clusterId.toString()); params.put("pool", poolUuid); params.put("ipaddress", record.address); if (_increase != null) { params.put(Config.XenPreallocatedLunSizeRange.name(), _increase); } details.put(HostInfo.HOST_OS, hostOS); details.put(HostInfo.HOST_OS_VERSION, hostOSVer); details.put(HostInfo.HOST_OS_KERNEL_VERSION, hostKernelVer); details.put(HostInfo.HYPERVISOR_VERSION, xenVersion); if (!params.containsKey("public.network.device") && _publicNic != null) { params.put("public.network.device", _publicNic); details.put("public.network.device", _publicNic); } if (!params.containsKey("guest.network.device") && _guestNic != null) { params.put("guest.network.device", _guestNic); details.put("guest.network.device", _guestNic); } if (!params.containsKey("private.network.device") && _privateNic != null) { params.put("private.network.device", _privateNic); details.put("private.network.device", _privateNic); } if (!params.containsKey("storage.network.device1") && _storageNic1 != null) { params.put("storage.network.device1", _storageNic1); details.put("storage.network.device1", _storageNic1); } if (!params.containsKey("storage.network.device2") && _storageNic2 != null) { params.put("storage.network.device2", _storageNic2); details.put("storage.network.device2", _storageNic2); } params.put(Config.Wait.toString().toLowerCase(), Integer.toString(_wait)); details.put(Config.Wait.toString().toLowerCase(), Integer.toString(_wait)); try { resource.configure("Xen Server", params); } catch (ConfigurationException e) { _alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, dcId, podId, "Unable to add " + record.address, "Error is " + e.getMessage()); s_logger.warn("Unable to instantiate " + record.address, e); continue; } resource.start(); resources.put(resource, details); } } catch (SessionAuthenticationFailed e) { s_logger.warn("Authentication error", e); return null; } catch (XenAPIException e) { s_logger.warn("XenAPI exception", e); return null; } catch (XmlRpcException e) { s_logger.warn("Xml Rpc Exception", e); return null; } catch (UnknownHostException e) { s_logger.warn("Unable to resolve the host name", e); return null; } catch (Exception e) { s_logger.debug("other exceptions: " + e.toString(), e); return null; } return resources; } String getPoolUuid(Connection conn) throws XenAPIException, XmlRpcException { Map pools = Pool.getAllRecords(conn); assert pools.size() == 1 : "Pools size is " + pools.size(); return pools.values().iterator().next().uuid; } protected void addSamePool(Connection conn, Map> resources) throws XenAPIException, XmlRpcException { Map hps = Pool.getAllRecords(conn); assert (hps.size() == 1) : "How can it be more than one but it's actually " + hps.size(); // This is the pool. String poolUuid = hps.values().iterator().next().uuid; for (Map details : resources.values()) { details.put("pool", poolUuid); } } protected boolean addHostsToPool(Connection conn, String hostIp, Long clusterId) throws XenAPIException, XmlRpcException, DiscoveryException { List hosts; hosts = _hostDao.listByCluster(clusterId); String masterIp = null; String username = null; String password = null; for (HostVO host : hosts) { _hostDao.loadDetails(host); username = host.getDetail("username"); password = host.getDetail("password"); String address = host.getPrivateIpAddress(); Connection hostConn = _connPool.slaveConnect(address, username, password); if (hostConn == null) { continue; } try { Set pools = Pool.getAll(hostConn); Pool pool = pools.iterator().next(); masterIp = pool.getMaster(hostConn).getAddress(hostConn); break; } catch (Exception e ) { s_logger.warn("Can not get master ip address from host " + address); } finally { try{ Session.localLogout(hostConn); } catch (Exception e ) { } hostConn.dispose(); hostConn = null; } } if (masterIp == null) { s_logger.warn("Unable to reach the pool master of the existing cluster"); throw new CloudRuntimeException("Unable to reach the pool master of the existing cluster"); } if( !_connPool.joinPool(conn, hostIp, masterIp, username, password) ){ s_logger.warn("Unable to join the pool"); throw new DiscoveryException("Unable to join the pool"); } return true; } protected CitrixResourceBase createServerResource(long dcId, Long podId, Host.Record record) { String prodBrand = record.softwareVersion.get("product_brand").trim(); String prodVersion = record.softwareVersion.get("product_version").trim(); if(prodBrand.equals("XenCloudPlatform") && prodVersion.equals("0.1.1")) return new XcpServerResource(); if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) return new XenServerResource(); if(prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) return new XenServer56FP1Resource(); String msg = "Only support XCP 0.1.1, XenServer 5.6 and XenServer 5.6 FP1 , but this one is " + prodBrand + " " + prodVersion; _alertMgr.sendAlert(AlertManager.ALERT_TYPE_HOST, dcId, podId, msg, msg); s_logger.debug(msg); throw new RuntimeException(msg); } protected void serverConfig() { } @Override public boolean configure(String name, Map params) throws ConfigurationException { super.configure(name, params); serverConfig(); _publicNic = _params.get(Config.XenPublicNetwork.key()); _privateNic = _params.get(Config.XenPrivateNetwork.key()); _storageNic1 = _params.get(Config.XenStorageNetwork1.key()); _storageNic2 = _params.get(Config.XenStorageNetwork2.key()); _guestNic = _params.get(Config.XenGuestNetwork.key()); _increase = _params.get(Config.XenPreallocatedLunSizeRange.key()); String value = _params.get(Config.Wait.toString()); _wait = NumbersUtil.parseInt(value, Integer.parseInt(Config.Wait.getDefaultValue())); value = _params.get(Config.XenSetupMultipath.key()); Boolean.parseBoolean(value); value = _params.get("xen.check.hvm"); _checkHvm = value == null ? true : Boolean.parseBoolean(value); _connPool = XenServerConnectionPool.getInstance(); _agentMgr.registerForHostEvents(this, true, false, true); createXsToolsISO(); return true; } @Override public boolean matchHypervisor(String hypervisor) { if(hypervisor == null) return true; return Hypervisor.HypervisorType.XenServer.toString().equalsIgnoreCase(hypervisor); } @Override public Hypervisor.HypervisorType getHypervisorType() { return Hypervisor.HypervisorType.XenServer; } @Override public void postDiscovery(List hosts, long msId) throws DiscoveryException{ //do nothing } @Override public int getTimeout() { return 0; } @Override public boolean isRecurring() { return false; } @Override public boolean processAnswers(long agentId, long seq, Answer[] answers) { return false; } @Override public boolean processCommands(long agentId, long seq, Command[] commands) { return false; } private void createXsToolsISO() { String isoName = "xs-tools.iso"; VMTemplateVO tmplt = _tmpltDao.findByTemplateName(isoName); Long id; if (tmplt == null) { id = _tmpltDao.getNextInSequence(Long.class, "id"); VMTemplateVO template = new VMTemplateVO(id, isoName, isoName, ImageFormat.ISO, true, true, TemplateType.PERHOST, null, null, true, 64, Account.ACCOUNT_ID_SYSTEM, null, "xen-pv-drv-iso", false, 1, false, HypervisorType.None); _tmpltDao.persist(template); } else { id = tmplt.getId(); tmplt.setTemplateType(TemplateType.PERHOST); tmplt.setUrl(null); _tmpltDao.update(id, tmplt); } } @Override public void processConnect(HostVO agent, StartupCommand cmd) throws ConnectionException { } @Override public AgentControlAnswer processControlCommand(long agentId, AgentControlCommand cmd) { return null; } @Override public boolean processDisconnect(long agentId, Status state) { return false; } @Override public boolean processTimeout(long agentId, long seq) { return false; } }