// 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.hyperv.discoverer; import java.net.InetAddress; import java.net.URI; import java.net.UnknownHostException; import java.nio.charset.Charset; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.Random; import java.util.UUID; import javax.inject.Inject; import javax.naming.ConfigurationException; import org.apache.log4j.Logger; 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.ReadyCommand; 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.configuration.Config; import com.cloud.alert.AlertManager; 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.DiscoveryException; import com.cloud.exception.OperationTimedoutException; import com.cloud.host.Host; import com.cloud.host.HostEnvironment; 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.hyperv.resource.HypervDirectConnectResource; 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.StorageLayer; /** * Methods to discover and managem a Hyper-V agent. Prepares a * HypervDirectConnectResource corresponding to the agent on a Hyper-V * hypervisor and manages its lifecycle. */ public class HypervServerDiscoverer extends DiscovererBase implements Discoverer, Listener, ResourceStateAdapter { private static final Logger s_logger = Logger.getLogger(HypervServerDiscoverer.class); Random _rand = new Random(System.currentTimeMillis()); Map<String, String> _storageMounts = new HashMap<String, String>(); StorageLayer _storage; @Inject private HostPodDao _podDao; // TODO: AgentManager and AlertManager not being used to transmit info, // may want to reconsider. @Inject private AgentManager _agentMgr; @Inject private AlertManager _alertMgr; // Listener interface methods @Override public final boolean processAnswers(final long agentId, final long seq, final Answer[] answers) { return false; } @Override public final boolean processCommands(final long agentId, final long seq, final Command[] commands) { return false; } @Override public final AgentControlAnswer processControlCommand(final long agentId, final AgentControlCommand cmd) { return null; } @Override public void processHostAdded(long hostId) { } @Override public final void processConnect(final Host agent, final StartupCommand cmd, final boolean forRebalance) throws ConnectionException { // Limit the commands we can process if (!(cmd instanceof StartupRoutingCommand)) { return; } StartupRoutingCommand startup = (StartupRoutingCommand)cmd; // assert if (startup.getHypervisorType() != HypervisorType.Hyperv) { s_logger.debug("Not Hyper-V hypervisor, so moving on."); return; } long agentId = agent.getId(); HostVO host = _hostDao.findById(agentId); // Our Hyper-V machines are not participating in pools, and the pool id // we provide them is not persisted. // This means the pool id can vary. ClusterVO cluster = _clusterDao.findById(host.getClusterId()); if (cluster.getGuid() == null) { cluster.setGuid(startup.getPool()); _clusterDao.update(cluster.getId(), cluster); } if (s_logger.isDebugEnabled()) { s_logger.debug("Setting up host " + agentId); } HostEnvironment env = new HostEnvironment(); SetupCommand setup = new SetupCommand(env); if (!host.isSetup()) { setup.setNeedSetup(true); } try { SetupAnswer answer = (SetupAnswer)_agentMgr.send(agentId, setup); if (answer != null && answer.getResult()) { host.setSetup(true); // TODO: clean up magic numbers below host.setLastPinged((System.currentTimeMillis() >> 10) - 5 * 60); _hostDao.update(host.getId(), host); if (answer.needReconnect()) { throw new ConnectionException(false, "Reinitialize agent after setup."); } return; } else { String reason = answer.getDetails(); if (reason == null) { reason = " details were null"; } s_logger.warn("Unable to setup agent " + agentId + " due to " + reason); } // Error handling borrowed from XcpServerDiscoverer, may need to be // updated. } catch (AgentUnavailableException e) { s_logger.warn("Unable to setup agent " + agentId + " because it became unavailable.", e); } catch (OperationTimedoutException e) { s_logger.warn("Unable to setup agent " + agentId + " because it timed out", e); } throw new ConnectionException(true, "Reinitialize agent after setup."); } @Override public final boolean processDisconnect(final long agentId, final Status state) { return false; } @Override public void processHostAboutToBeRemoved(long hostId) { } @Override public void processHostRemoved(long hostId, long clusterId) { } @Override public final boolean isRecurring() { return false; } @Override public final int getTimeout() { return 0; } @Override public final boolean processTimeout(final long agentId, final long seq) { return false; } // End Listener implementation // Returns server component used by server manager to operate the plugin. // Server component is a ServerResource. If a connected agent is used, the // ServerResource is // ignored in favour of another created in response to @Override public final Map<? extends ServerResource, Map<String, String>> find(final long dcId, final Long podId, final Long clusterId, final URI uri, final String username, final String password, final List<String> hostTags) throws DiscoveryException { if (s_logger.isInfoEnabled()) { s_logger.info("Discover host. dc(zone): " + dcId + ", pod: " + podId + ", cluster: " + clusterId + ", uri host: " + uri.getHost()); } // Assertions if (podId == null) { if (s_logger.isInfoEnabled()) { s_logger.info("No pod is assigned, skipping the discovery in" + " Hyperv discoverer"); } return null; } ClusterVO cluster = _clusterDao.findById(clusterId); // ClusterVO exists // in the // database if (cluster == null) { if (s_logger.isInfoEnabled()) { s_logger.info("No cluster in database for cluster id " + clusterId); } return null; } if (cluster.getHypervisorType() != HypervisorType.Hyperv) { if (s_logger.isInfoEnabled()) { s_logger.info("Cluster " + clusterId + "is not for Hyperv hypervisors"); } return null; } if (!uri.getScheme().equals("http")) { String msg = "urlString is not http so we're not taking care of" + " the discovery for this: " + uri; s_logger.debug(msg); return null; } try { String hostname = uri.getHost(); InetAddress ia = InetAddress.getByName(hostname); String agentIp = ia.getHostAddress(); String uuidSeed = agentIp; String guidWithTail = calcServerResourceGuid(uuidSeed) + "-HypervResource"; if (_resourceMgr.findHostByGuid(guidWithTail) != null) { s_logger.debug("Skipping " + agentIp + " because " + guidWithTail + " is already in the database."); return null; } s_logger.info("Creating" + HypervDirectConnectResource.class.getName() + " HypervDummyResourceBase for zone/pod/cluster " + dcId + "/" + podId + "/" + clusterId); // Some Hypervisors organise themselves in pools. // The startup command tells us what pool they are using. // In the meantime, we have to place a GUID corresponding to the // pool in the database // This GUID may change. if (cluster.getGuid() == null) { cluster.setGuid(UUID.nameUUIDFromBytes(String.valueOf(clusterId).getBytes(Charset.forName("UTF-8"))).toString()); _clusterDao.update(clusterId, cluster); } // Settings required by all server resources managing a hypervisor Map<String, Object> params = new HashMap<String, Object>(); params.put("zone", Long.toString(dcId)); params.put("pod", Long.toString(podId)); params.put("cluster", Long.toString(clusterId)); params.put("guid", guidWithTail); params.put("ipaddress", agentIp); // Hyper-V specific settings Map<String, String> details = new HashMap<String, String>(); details.put("url", uri.getHost()); details.put("username", username); details.put("password", password); details.put("cluster.guid", cluster.getGuid()); params.putAll(details); params.put("router.aggregation.command.each.timeout", _configDao.getValue(Config.RouterAggregationCommandEachTimeout.toString())); HypervDirectConnectResource resource = new HypervDirectConnectResource(); resource.configure(agentIp, params); // Assert // TODO: test by using bogus URL and bogus virtual path in URL ReadyCommand ping = new ReadyCommand(); Answer pingAns = resource.executeRequest(ping); if (pingAns == null || !pingAns.getResult()) { String errMsg = "Agent not running, or no route to agent on at " + uri; s_logger.debug(errMsg); throw new DiscoveryException(errMsg); } Map<HypervDirectConnectResource, Map<String, String>> resources = new HashMap<HypervDirectConnectResource, Map<String, String>>(); resources.put(resource, details); // TODO: does the resource have to create a connection? return resources; } catch (ConfigurationException e) { _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, dcId, podId, "Unable to add " + uri.getHost(), "Error is " + e.getMessage()); s_logger.warn("Unable to instantiate " + uri.getHost(), e); } catch (UnknownHostException e) { _alertMgr.sendAlert(AlertManager.AlertType.ALERT_TYPE_HOST, dcId, podId, "Unable to add " + uri.getHost(), "Error is " + e.getMessage()); s_logger.warn("Unable to instantiate " + uri.getHost(), e); } catch (Exception e) { String msg = " can't setup agent, due to " + e.toString() + " - " + e.getMessage(); s_logger.warn(msg); } return null; } /** * Encapsulate GUID calculation in public method to allow access to test * programs. Works by converting a string to a GUID using * UUID.nameUUIDFromBytes * * @param uuidSeed * string to use to generate GUID * * @return GUID in form of a string. */ public static String calcServerResourceGuid(final String uuidSeed) { String guid = UUID.nameUUIDFromBytes(uuidSeed.getBytes(Charset.forName("UTF-8"))).toString(); return guid; } // Adapter implementation: (facilitates plug in loading) // Required because Discoverer extends Adapter // Overrides Adapter.configure to always return true // Inherit Adapter.getName // Inherit Adapter.stop // Inherit Adapter.start @Override public final boolean configure(final String name, final Map<String, Object> params) throws ConfigurationException { super.configure(name, params); // TODO: allow timeout on we HTTPRequests to be configured _agentMgr.registerForHostEvents(this, true, false, true); _resourceMgr.registerResourceStateAdapter(this.getClass().getSimpleName(), this); return true; } // end of Adapter @Override public void postDiscovery(final List<HostVO> hosts, final long msId) throws DiscoveryException { } @Override public final Hypervisor.HypervisorType getHypervisorType() { return Hypervisor.HypervisorType.Hyperv; } // TODO: verify that it is okay to return true on null hypervisor @Override public final boolean matchHypervisor(final String hypervisor) { if (hypervisor == null) { return true; } return Hypervisor.HypervisorType.Hyperv.toString().equalsIgnoreCase(hypervisor); } // end of Discoverer // ResourceStateAdapter @Override public final HostVO createHostVOForConnectedAgent(final HostVO host, final StartupCommand[] cmd) { return null; } // TODO: add test for method @Override public final HostVO createHostVOForDirectConnectAgent(final HostVO host, final StartupCommand[] startup, final ServerResource resource, final Map<String, String> details, final List<String> hostTags) { StartupCommand firstCmd = startup[0]; if (!(firstCmd instanceof StartupRoutingCommand)) { return null; } StartupRoutingCommand ssCmd = ((StartupRoutingCommand)firstCmd); if (ssCmd.getHypervisorType() != HypervisorType.Hyperv) { return null; } s_logger.info("Host: " + host.getName() + " connected with hypervisor type: " + HypervisorType.Hyperv + ". Checking CIDR..."); HostPodVO pod = _podDao.findById(host.getPodId()); DataCenterVO dc = _dcDao.findById(host.getDataCenterId()); _resourceMgr.checkCIDR(pod, dc, ssCmd.getPrivateIpAddress(), ssCmd.getPrivateNetmask()); return _resourceMgr.fillRoutingHostVO(host, ssCmd, HypervisorType.Hyperv, details, hostTags); } // TODO: add test for method @Override public final DeleteHostAnswer deleteHost(final HostVO host, final boolean isForced, final boolean isForceDeleteStorage) throws UnableDeleteHostException { // assert if (host.getType() != Host.Type.Routing || host.getHypervisorType() != HypervisorType.Hyperv) { return null; } _resourceMgr.deleteRoutingHost(host, isForced, isForceDeleteStorage); return new DeleteHostAnswer(true); } @Override public final boolean stop() { _resourceMgr.unregisterResourceStateAdapter(this.getClass().getSimpleName()); return super.stop(); } // end of ResourceStateAdapter }