| // 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.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.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() + " HypervDirectConnectResource 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 |
| |
| } |