// 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 com.cloud.hypervisor.xenserver.discoverer;

import java.net.InetAddress;
import java.net.URI;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.List;
import java.util.Map;
import java.util.Queue;
import java.util.Set;

import javax.inject.Inject;
import javax.naming.ConfigurationException;
import javax.persistence.EntityExistsException;

import com.cloud.hypervisor.xenserver.resource.XcpServer83Resource;
import com.cloud.hypervisor.xenserver.resource.Xenserver84Resource;
import org.apache.cloudstack.hypervisor.xenserver.XenserverConfigs;
import org.apache.commons.collections.CollectionUtils;
import org.apache.commons.lang3.StringUtils;
import org.apache.maven.artifact.versioning.ComparableVersion;
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.SetupAnswer;
import com.cloud.agent.api.SetupCommand;
import com.cloud.agent.api.StartupCommand;
import com.cloud.agent.api.StartupRoutingCommand;
import com.cloud.alert.AlertManager;
import com.cloud.configuration.Config;
import com.cloud.dc.ClusterVO;
import com.cloud.dc.DataCenterVO;
import com.cloud.dc.HostPodVO;
import com.cloud.dc.dao.HostPodDao;
import com.cloud.exception.AgentUnavailableException;
import com.cloud.exception.ConnectionException;
import com.cloud.exception.DiscoveredWithErrorException;
import com.cloud.exception.DiscoveryException;
import com.cloud.exception.OperationTimedoutException;
import com.cloud.host.HostEnvironment;
import com.cloud.host.HostInfo;
import com.cloud.host.HostVO;
import com.cloud.host.Status;
import com.cloud.hypervisor.Hypervisor;
import com.cloud.hypervisor.Hypervisor.HypervisorType;
import com.cloud.hypervisor.xenserver.resource.CitrixHelper;
import com.cloud.hypervisor.xenserver.resource.CitrixResourceBase;
import com.cloud.hypervisor.xenserver.resource.XcpOssResource;
import com.cloud.hypervisor.xenserver.resource.XcpServerResource;
import com.cloud.hypervisor.xenserver.resource.XenServer56FP1Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer56Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer56SP2Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer600Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer610Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer620Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer620SP1Resource;
import com.cloud.hypervisor.xenserver.resource.XenServer650Resource;
import com.cloud.hypervisor.xenserver.resource.XenServerConnectionPool;
import com.cloud.hypervisor.xenserver.resource.Xenserver625Resource;
import com.cloud.resource.Discoverer;
import com.cloud.resource.DiscovererBase;
import com.cloud.resource.ResourceStateAdapter;
import com.cloud.resource.ServerResource;
import com.cloud.resource.UnableDeleteHostException;
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.template.TemplateManager;
import com.cloud.user.Account;
import com.cloud.utils.NumbersUtil;
import com.cloud.utils.db.QueryBuilder;
import com.cloud.utils.db.SearchCriteria.Op;
import com.cloud.utils.exception.CloudRuntimeException;
import com.cloud.utils.exception.HypervisorVersionChangedException;
import com.xensource.xenapi.Connection;
import com.xensource.xenapi.Host;
import com.xensource.xenapi.HostPatch;
import com.xensource.xenapi.Pool;
import com.xensource.xenapi.PoolPatch;
import com.xensource.xenapi.Session;
import com.xensource.xenapi.Types.SessionAuthenticationFailed;
import com.xensource.xenapi.Types.UuidInvalid;
import com.xensource.xenapi.Types.XenAPIException;


public class XcpServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter {
    private int _wait;
    private XenServerConnectionPool _connPool;
    private boolean _checkHvm;
    private boolean _setupMultipath;
    private String _instance;

    @Inject
    private AlertManager _alertMgr;
    @Inject
    private AgentManager _agentMgr;
    @Inject
    private VMTemplateDao _tmpltDao;
    @Inject
    private HostPodDao _podDao;

    private String xenServerIsoName = TemplateManager.XS_TOOLS_ISO;
    private String xenServerIsoDisplayText = "XenServer Tools Installer ISO (xen-pv-drv-iso)";

    public final static String MIN_UEFI_SUPPORTED_VERSION = "8.2";

    public static boolean isUefiSupported(String hostProductVersion) {
        if (StringUtils.isEmpty(hostProductVersion)) {
            return false;
        }
        ComparableVersion version = new ComparableVersion(hostProductVersion);
        return version.compareTo(new ComparableVersion(MIN_UEFI_SUPPORTED_VERSION)) >= 0;
    }

    protected XcpServerDiscoverer() {
    }

    void setClusterGuid(ClusterVO cluster, String guid) {
        cluster.setGuid(guid);
        try {
            _clusterDao.update(cluster.getId(), cluster);
        } catch (EntityExistsException e) {
            QueryBuilder<ClusterVO> sc = QueryBuilder.create(ClusterVO.class);
            sc.and(sc.entity().getGuid(), Op.EQ, guid);
            List<ClusterVO> clusters = sc.list();
            ClusterVO clu = clusters.get(0);
            List<Long> clusterHostIds = _hostDao.listIdsByClusterId(clu.getId());
            if (CollectionUtils.isEmpty(clusterHostIds)) {
                clu.setGuid(null);
                _clusterDao.update(clu.getId(), clu);
                _clusterDao.update(cluster.getId(), cluster);
                return;
            }
            throw e;
        }
    }

    protected boolean poolHasHotFix(Connection conn, String hostIp, String hotFixUuid) {
        try {
            Map<Host, Host.Record> hosts = Host.getAllRecords(conn);
            for (Map.Entry<Host, Host.Record> entry : hosts.entrySet()) {

                Host.Record re = entry.getValue();
                if (!re.address.equalsIgnoreCase(hostIp)){
                    continue;
                }
                Set<HostPatch> patches = re.patches;
                PoolPatch poolPatch = PoolPatch.getByUuid(conn, hotFixUuid);
                for(HostPatch patch : patches) {
                    PoolPatch pp = patch.getPoolPatch(conn);
                    if (pp != null && pp.equals(poolPatch) && patch.getApplied(conn)) {
                        logger.debug("host " + hostIp + " does have " + hotFixUuid +" Hotfix.");
                        return true;
                    }
                }
            }
            return false;
        } catch (UuidInvalid e) {
            logger.debug("host " + hostIp + " doesn't have " + hotFixUuid + " Hotfix");
        } catch (Exception e) {
            logger.debug("can't get patches information, consider it doesn't have " + hotFixUuid + " Hotfix");
        }
        return false;
    }



    @Override
    public Map<? extends ServerResource, Map<String, String>>
    find(long dcId, Long podId, Long clusterId, URI url, String username, String password, List<String> hostTags) throws DiscoveryException {
        Map<CitrixResourceBase, Map<String, String>> resources = new HashMap<CitrixResourceBase, Map<String, String>>();
        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;
            logger.debug(msg);
            return null;
        }
        if (clusterId == null) {
            String msg = "must specify cluster Id when add host";
            logger.debug(msg);
            throw new RuntimeException(msg);
        }

        if (podId == null) {
            String msg = "must specify pod Id when add host";
            logger.debug(msg);
            throw new RuntimeException(msg);
        }

        ClusterVO cluster = _clusterDao.findById(clusterId);
        if (cluster == null || cluster.getHypervisorType() != HypervisorType.XenServer) {
            if (logger.isInfoEnabled()) {
                logger.info("invalid cluster id or cluster is not for XenServer hypervisors");
            }
            return null;
        }

        try {
            String hostname = url.getHost();
            InetAddress ia = InetAddress.getByName(hostname);
            String hostIp = ia.getHostAddress();
            Queue<String> pass = new LinkedList<String>();
            pass.add(password);
            conn = _connPool.getConnect(hostIp, username, pass);
            if (conn == null) {
                String msg = "Unable to get a connection to " + url;
                logger.debug(msg);
                throw new DiscoveryException(msg);
            }

            Set<Pool> pools = Pool.getAll(conn);
            Pool pool = pools.iterator().next();
            Pool.Record pr = pool.getRecord(conn);
            String poolUuid = pr.uuid;
            Map<Host, Host.Record> hosts = Host.getAllRecords(conn);
            String latestHotFix = "";
            if (poolHasHotFix(conn, hostIp, XenserverConfigs.XSHotFix62ESP1004)) {
                latestHotFix = XenserverConfigs.XSHotFix62ESP1004;
            } else if (poolHasHotFix(conn, hostIp, XenserverConfigs.XSHotFix62ESP1)) {
                latestHotFix = XenserverConfigs.XSHotFix62ESP1;
            }

            /*set cluster hypervisor type to xenserver*/
            ClusterVO clu = _clusterDao.findById(clusterId);
            if (clu.getGuid() == null) {
                setClusterGuid(clu, poolUuid);
            } else {
                List<Long> clusterHostIds = _hostDao.listIdsByClusterId(clusterId);
                if (CollectionUtils.isNotEmpty(clusterHostIds)) {
                    if (!clu.getGuid().equals(poolUuid)) {
                        String msg = "Please join the host " +  hostIp + " to XS pool  "
                                + clu.getGuid() + " through XC/XS before adding it through CS UI";
                        logger.warn(msg);
                        throw new DiscoveryException(msg);
                    }
                } else {
                    setClusterGuid(clu, poolUuid);
                }
            }
            // 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) {
                    logger.debug("Caught exception during logout", e);
                }
                conn = null;
            }

            poolUuid = clu.getGuid();
            _clusterDao.update(clusterId, clu);

            if (_checkHvm) {
                for (Map.Entry<Host, Host.Record> 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.AlertType.ALERT_TYPE_HOST, dcId, podId, msg, msg);
                        logger.debug(msg);
                        throw new RuntimeException(msg);
                    }
                }
            }

            for (Map.Entry<Host, Host.Record> entry : hosts.entrySet()) {
                Host.Record record = entry.getValue();
                String hostAddr = record.address;

                String prodVersion = CitrixHelper.getProductVersion(record);
                String xenVersion = record.softwareVersion.get("xen");
                String hostOS = record.softwareVersion.get("product_brand");
                if (hostOS == null) {
                    hostOS = record.softwareVersion.get("platform_name");
                }

                String hostOSVer = prodVersion;
                String hostKernelVer = record.softwareVersion.get("linux");

                if (_resourceMgr.findHostByGuid(record.uuid) != null) {
                    logger.debug("Skipping " + record.address + " because " + record.uuid + " is already in the database.");
                    continue;
                }

                CitrixResourceBase resource = createServerResource(dcId, podId, record, latestHotFix);
                logger.info("Found host " + record.hostname + " ip=" + record.address + " product version=" + prodVersion);

                Map<String, String> details = new HashMap<String, String>();
                Map<String, Object> params = new HashMap<String, Object>();
                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);

                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);

                String privateNetworkLabel = _networkMgr.getDefaultManagementTrafficLabel(dcId, HypervisorType.XenServer);
                String storageNetworkLabel = _networkMgr.getDefaultStorageTrafficLabel(dcId, HypervisorType.XenServer);

                if (!params.containsKey("private.network.device") && privateNetworkLabel != null) {
                    params.put("private.network.device", privateNetworkLabel);
                    details.put("private.network.device", privateNetworkLabel);
                }

                if (!params.containsKey("storage.network.device1") && storageNetworkLabel != null) {
                    params.put("storage.network.device1", storageNetworkLabel);
                    details.put("storage.network.device1", storageNetworkLabel);
                }

                DataCenterVO zone = _dcDao.findById(dcId);
                boolean securityGroupEnabled = zone.isSecurityGroupEnabled() || _networkMgr.isSecurityGroupSupportedForZone(zone.getId());
                params.put("securitygroupenabled", Boolean.toString(securityGroupEnabled));

                params.put("router.aggregation.command.each.timeout", _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString()));
                params.put("wait", Integer.toString(_wait));
                details.put("wait", Integer.toString(_wait));
                params.put("migratewait", _configDao.getValue(Config.MigrateWait.toString()));
                params.put(Config.XenServerMaxNics.toString().toLowerCase(), _configDao.getValue(Config.XenServerMaxNics.toString()));
                params.put(Config.XenServerHeartBeatTimeout.toString().toLowerCase(), _configDao.getValue(Config.XenServerHeartBeatTimeout.toString()));
                params.put(Config.XenServerHeartBeatInterval.toString().toLowerCase(), _configDao.getValue(Config.XenServerHeartBeatInterval.toString()));
                params.put(Config.InstanceName.toString().toLowerCase(), _instance);
                details.put(Config.InstanceName.toString().toLowerCase(), _instance);
                try {
                    resource.configure("XenServer", params);
                } catch (ConfigurationException e) {
                    _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, dcId, podId, "Unable to add " + record.address, "Error is " + e.getMessage());
                    logger.warn("Unable to instantiate " + record.address, e);
                    continue;
                }
                resource.start();
                resources.put(resource, details);
            }
        } catch (SessionAuthenticationFailed e) {
            throw new DiscoveredWithErrorException("Authentication error");
        } catch (XenAPIException e) {
            logger.warn("XenAPI exception", e);
            return null;
        } catch (XmlRpcException e) {
            logger.warn("Xml Rpc Exception", e);
            return null;
        } catch (UnknownHostException e) {
            logger.warn("Unable to resolve the host name", e);
            return null;
        } catch (Exception e) {
            logger.warn("other exceptions: " + e.toString(), e);
            return null;
        }
        return resources;
    }

    String getPoolUuid(Connection conn) throws XenAPIException, XmlRpcException {
        Map<Pool, Pool.Record> 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<CitrixResourceBase, Map<String, String>> resources) throws XenAPIException, XmlRpcException {
        Map<Pool, Pool.Record> 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<String, String> details : resources.values()) {
            details.put("pool", poolUuid);
        }
    }

    protected CitrixResourceBase createServerResource(String prodBrand, String prodVersion, String prodVersionTextShort, String hotfix) {
        // Xen Cloud Platform group of hypervisors
        if (prodBrand.equals("XCP") && (prodVersion.equals("1.0.0") || prodVersion.equals("1.1.0")
                || prodVersion.equals("5.6.100") || prodVersion.startsWith("1.4") || prodVersion.startsWith("1.6"))) {
            return new XcpServerResource();
        } // Citrix Xenserver group of hypervisors
        else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.0")) {
            return new XenServer56Resource();
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.0")) {
            return new XenServer600Resource();
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.0.2")) {
            return new XenServer600Resource();
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.1.0")) {
            return new XenServer610Resource();
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("6.2.0")) {
            if (hotfix != null && hotfix.equals(XenserverConfigs.XSHotFix62ESP1004)) {
                return new Xenserver625Resource();
            } else if (hotfix != null && hotfix.equals(XenserverConfigs.XSHotFix62ESP1)) {
                return new XenServer620SP1Resource();
            } else {
                return new XenServer620Resource();
            }
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("5.6.100")) {
            if ("5.6 SP2".equals(prodVersionTextShort.trim())) {
                return new XenServer56SP2Resource();
            } else if ("5.6 FP1".equals(prodVersionTextShort.trim())) {
                return new XenServer56FP1Resource();
            }
        } else if (prodBrand.equals("XCP_Kronos")) {
            return new XcpOssResource();
        } else if (prodBrand.equals("XenServer") && prodVersion.equals("8.4.0")) {
            return new Xenserver84Resource();
        }  else if (prodBrand.equals("XCP-ng") && (prodVersion.equals("8.3.0"))) {
            return new XcpServer83Resource();
        } else if (prodBrand.equals("XenServer") || prodBrand.equals("XCP-ng") || prodBrand.equals("Citrix Hypervisor")) {
            final String[] items = prodVersion.split("\\.");
            if ((Integer.parseInt(items[0]) > 6) ||
                    (Integer.parseInt(items[0]) == 6 && Integer.parseInt(items[1]) >= 4)) {
                logger.warn("defaulting to xenserver650 resource for product brand: " + prodBrand + " with product " +
                        "version: " + prodVersion);
                //default to xenserver650 resource.
                return new XenServer650Resource();
            }
        }
        String msg =
                "Only support XCP 1.0.0, 1.1.0, 1.4.x, 1.5 beta, 1.6.x; XenServer 5.6,  XenServer 5.6 FP1, XenServer 5.6 SP2, Xenserver 6.0, 6.0.2, 6.1.0, 6.2.0, >6.4.0, Citrix Hypervisor > 8.0.0 but this one is " +
                        prodBrand + " " + prodVersion;
        logger.warn(msg);
        throw new RuntimeException(msg);
    }



    protected CitrixResourceBase createServerResource(long dcId, Long podId, Host.Record record, String hotfix) {
        String prodBrand = record.softwareVersion.get("product_brand");
        if (prodBrand == null) {
            prodBrand = record.softwareVersion.get("platform_name").trim();
        } else {
            prodBrand = prodBrand.trim();
        }
        String prodVersion = CitrixHelper.getProductVersion(record);

        String prodVersionTextShort = record.softwareVersion.get("product_version_text_short");
        return createServerResource(prodBrand, prodVersion, prodVersionTextShort, hotfix);
    }

    protected void serverConfig() {
        String value = _params.get(Config.XenServerSetupMultipath.key());
        _setupMultipath = Boolean.parseBoolean(value);
    }

    @Override
    public boolean configure(String name, Map<String, Object> params) throws ConfigurationException {
        super.configure(name, params);
        serverConfig();

        String value = _params.get(Config.XapiWait.toString());
        _wait = NumbersUtil.parseInt(value, Integer.parseInt(Config.XapiWait.getDefaultValue()));

        _instance = _params.get(Config.InstanceName.key());

        value = _params.get("xenserver.check.hvm");
        _checkHvm = Boolean.parseBoolean(value);
        _connPool = XenServerConnectionPool.getInstance();

        _agentMgr.registerForHostEvents(this, true, false, true);

        createXenServerToolsIsoEntryInDatabase();
        _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this);
        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<HostVO> 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;
    }

    /**
     * Create the XenServer tools ISO entry in the database.
     * If there is already an entry with 'isoName' equals to {@value #xenServerIsoName} , we update its 'displayText' to {@value #xenServerIsoDisplayText}.
     * Otherwise, we create a new entry.
     */
    protected void createXenServerToolsIsoEntryInDatabase() {
        VMTemplateVO tmplt = _tmpltDao.findByTemplateName(xenServerIsoName);
        if (tmplt == null) {
            long id = _tmpltDao.getNextInSequence(Long.class, "id");
            VMTemplateVO template = VMTemplateVO.createPreHostIso(id, xenServerIsoName, xenServerIsoName, ImageFormat.ISO, true, true, TemplateType.PERHOST, null, null, true, 64,
                    Account.ACCOUNT_ID_SYSTEM, null, xenServerIsoDisplayText, false, 1, false, HypervisorType.XenServer);
            _tmpltDao.persist(template);
        } else {
            long id = tmplt.getId();
            tmplt.setTemplateType(TemplateType.PERHOST);
            tmplt.setUrl(null);
            tmplt.setDisplayText(xenServerIsoDisplayText);
            _tmpltDao.update(id, tmplt);
        }
    }

    @Override
    public void processHostAdded(long hostId) {
    }

    @Override
    public void processConnect(com.cloud.host.Host agent, StartupCommand cmd, boolean forRebalance) throws ConnectionException {
        if (!(cmd instanceof StartupRoutingCommand)) {
            return;
        }
        long agentId = agent.getId();

        StartupRoutingCommand startup = (StartupRoutingCommand)cmd;
        if (startup.getHypervisorType() != HypervisorType.XenServer) {
            logger.debug("Not XenServer so moving on.");
            return;
        }

        HostVO host = _hostDao.findById(agentId);

        ClusterVO cluster = _clusterDao.findById(host.getClusterId());
        if (cluster.getGuid() == null) {
            cluster.setGuid(startup.getPool());
            _clusterDao.update(cluster.getId(), cluster);
        } else if (!cluster.getGuid().equals(startup.getPool())) {
            String msg = "pool uuid for cluster " + cluster.getId() + " changed from " + cluster.getGuid() + " to " + startup.getPool();
            logger.warn(msg);
            throw new CloudRuntimeException(msg);
        }

        Map<String, String> details = startup.getHostDetails();
        String prodBrand = details.get("product_brand").trim();
        String prodVersion = details.get("product_version").trim();
        String hotfix = details.get(XenserverConfigs.XS620HotFix);
        String prodVersionTextShort = details.get("product_version_text_short");

        String resource = createServerResource(prodBrand, prodVersion, prodVersionTextShort, hotfix).getClass().getName();

        if (!resource.equals(host.getResource())) {
            String msg = "host " + host.getPrivateIpAddress() + " changed from " + host.getResource() + " to " + resource;
            logger.debug(msg);
            host.setResource(resource);
            host.setSetup(false);
            _hostDao.update(agentId, host);
            throw new HypervisorVersionChangedException(msg);
        }

        if (logger.isDebugEnabled()) {
            logger.debug("Setting up host " + agentId);
        }
        HostEnvironment env = new HostEnvironment();

        SetupCommand setup = new SetupCommand(env);
        if (_setupMultipath) {
            setup.setMultipathOn();
        }
        if (!host.isSetup()) {
            setup.setNeedSetup(true);
        }

        try {
            Answer answer = _agentMgr.send(agentId, setup);
            if (answer != null && answer.getResult() && answer instanceof SetupAnswer) {
                host.setSetup(true);
                host.setLastPinged((System.currentTimeMillis() >> 10) - 5 * 60);
                host.setHypervisorVersion(prodVersion);
                _hostDao.update(host.getId(), host);
                if (((SetupAnswer)answer).needReconnect()) {
                    throw new ConnectionException(false, "Reinitialize agent after setup.");
                }
                return;
            } else {
                logger.warn("Unable to setup agent " + agentId + " due to " + ((answer != null) ? answer.getDetails() : "return null"));
            }
        } catch (AgentUnavailableException e) {
            logger.warn("Unable to setup agent " + agentId + " because it became unavailable.", e);
        } catch (OperationTimedoutException e) {
            logger.warn("Unable to setup agent " + agentId + " because it timed out", e);
        }
        throw new ConnectionException(true, "Reinitialize agent after setup.");
    }

    @Override
    public AgentControlAnswer processControlCommand(long agentId, AgentControlCommand cmd) {
        return null;
    }

    @Override
    public boolean processDisconnect(long agentId, Status state) {
        return false;
    }

    @Override
    public void processHostAboutToBeRemoved(long hostId) {
    }

    @Override
    public void processHostRemoved(long hostId, long clusterId) {
    }

    @Override
    public boolean processTimeout(long agentId, long seq) {
        return false;
    }

    @Override
    public HostVO createHostVOForConnectedAgent(HostVO host, StartupCommand[] cmd) {
        // TODO Auto-generated method stub
        return null;
    }

    @Override
    public HostVO createHostVOForDirectConnectAgent(HostVO host, StartupCommand[] startup, ServerResource resource, Map<String, String> details, List<String> hostTags) {
        StartupCommand firstCmd = startup[0];
        if (!(firstCmd instanceof StartupRoutingCommand)) {
            return null;
        }

        StartupRoutingCommand ssCmd = ((StartupRoutingCommand)firstCmd);
        if (ssCmd.getHypervisorType() != HypervisorType.XenServer) {
            return null;
        }

        HostPodVO pod = _podDao.findById(host.getPodId());
        DataCenterVO dc = _dcDao.findById(host.getDataCenterId());
        logger.info("Host: " + host.getName() + " connected with hypervisor type: " + HypervisorType.XenServer + ". Checking CIDR...");
        _resourceMgr.checkCIDR(pod, dc, ssCmd.getPrivateIpAddress(), ssCmd.getPrivateNetmask());
        return _resourceMgr.fillRoutingHostVO(host, ssCmd, HypervisorType.XenServer, details, hostTags);
    }

    @Override
    public DeleteHostAnswer deleteHost(HostVO host, boolean isForced, boolean isForceDeleteStorage) throws UnableDeleteHostException {
        if (host.getType() != com.cloud.host.Host.Type.Routing || host.getHypervisorType() != HypervisorType.XenServer) {
            return null;
        }

        _resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage);
        return new DeleteHostAnswer(true);
    }

    @Override
    protected HashMap<String, Object> buildConfigParams(HostVO host) {
        HashMap<String, Object> params = super.buildConfigParams(host);
        DataCenterVO zone = _dcDao.findById(host.getDataCenterId());
        if (zone != null) {
            boolean securityGroupEnabled = zone.isSecurityGroupEnabled() || _networkMgr.isSecurityGroupSupportedForZone(zone.getId());
            params.put("securitygroupenabled", Boolean.toString(securityGroupEnabled));
        }
        return params;
    }

    @Override
    public boolean stop() {
        _resourceMgr.unregisterResourceStateAdapter(this.getClass().getSimpleName());
        return super.stop();
    }
}
