Return-Path: X-Original-To: apmail-cloudstack-commits-archive@www.apache.org Delivered-To: apmail-cloudstack-commits-archive@www.apache.org Received: from mail.apache.org (hermes.apache.org [140.211.11.3]) by minotaur.apache.org (Postfix) with SMTP id 8566C10BB5 for ; Mon, 4 Nov 2013 13:57:15 +0000 (UTC) Received: (qmail 71853 invoked by uid 500); 4 Nov 2013 13:51:35 -0000 Delivered-To: apmail-cloudstack-commits-archive@cloudstack.apache.org Received: (qmail 70549 invoked by uid 500); 4 Nov 2013 13:50:12 -0000 Mailing-List: contact commits-help@cloudstack.apache.org; run by ezmlm Precedence: bulk List-Help: List-Unsubscribe: List-Post: List-Id: Reply-To: dev@cloudstack.apache.org Delivered-To: mailing list commits@cloudstack.apache.org Received: (qmail 69055 invoked by uid 99); 4 Nov 2013 13:48:31 -0000 Received: from tyr.zones.apache.org (HELO tyr.zones.apache.org) (140.211.11.114) by apache.org (qpsmtpd/0.29) with ESMTP; Mon, 04 Nov 2013 13:48:31 +0000 Received: by tyr.zones.apache.org (Postfix, from userid 65534) id EC6733A5A8; Mon, 4 Nov 2013 13:48:30 +0000 (UTC) Content-Type: text/plain; charset="us-ascii" MIME-Version: 1.0 Content-Transfer-Encoding: 7bit From: devdeep@apache.org To: commits@cloudstack.apache.org Date: Mon, 04 Nov 2013 13:48:31 -0000 Message-Id: In-Reply-To: References: X-Mailer: ASF-Git Admin Mailer Subject: [02/85] [abbrv] A plugin for Hyper-V control is available for CloudStack. The plugin implements basic VM control; however, its architecture allows additional functionality to be easily added. Incorporating the plugin in CloudStack will allow the commu http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/conf/log4j-cloud.xml.in ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/conf/log4j-cloud.xml.in b/plugins/hypervisors/hyperv/conf/log4j-cloud.xml.in new file mode 100644 index 0000000..fdbba19 --- /dev/null +++ b/plugins/hypervisors/hyperv/conf/log4j-cloud.xml.in @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/conf/log4j.xml ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/conf/log4j.xml b/plugins/hypervisors/hyperv/conf/log4j.xml new file mode 100644 index 0000000..1e97ce9 --- /dev/null +++ b/plugins/hypervisors/hyperv/conf/log4j.xml @@ -0,0 +1,93 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/pom.xml ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/pom.xml b/plugins/hypervisors/hyperv/pom.xml new file mode 100644 index 0000000..e9f0371 --- /dev/null +++ b/plugins/hypervisors/hyperv/pom.xml @@ -0,0 +1,158 @@ + + + 4.0.0 + cloud-plugin-hypervisor-hyperv + Apache CloudStack Plugin - Hypervisor Hyper-V + + org.apache.cloudstack + cloudstack-plugins + 4.3.0-SNAPSHOT + ../../pom.xml + + + true + + + + org.apache.cloudstack + cloud-agent + ${project.version} + + + org.apache.cloudstack + cloud-core + ${project.version} + + + org.mortbay.jetty + jetty + 6.1.26 + + + org.apache.cloudstack + cloud-utils + ${project.version} + tests + test + + + + install + src + test + + + conf + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + java + com.cloud.agent.AgentShell + + + + org.apache.maven.plugins + maven-jar-plugin + + + + test-jar + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + + **/HypervDirectConnectResourceTest.* + + + none + + ${skipTests} + + + + + + + + hyperv-agent + + + hyperv-agent + + + + + + + org.codehaus.mojo + exec-maven-plugin + 1.2.1 + + + compile + + exec + + + + + bash + + ./buildagent.sh + + + + + org.apache.maven.plugins + maven-surefire-plugin + 2.12 + + + none + + + **/HypervDirectConnectResourceTest.java + + ${skipTests} + + + + + + + http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/scripts/dev_extra_setup.sh ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/scripts/dev_extra_setup.sh b/plugins/hypervisors/hyperv/scripts/dev_extra_setup.sh new file mode 100644 index 0000000..aa77c4b --- /dev/null +++ b/plugins/hypervisors/hyperv/scripts/dev_extra_setup.sh @@ -0,0 +1,8 @@ +#!/bin/bash +mvn -P developer -pl developer -Ddeploydb +cp client/target/cloud-client-ui-4.2.0-SNAPSHOT/WEB-INF/classes/log4j{-cloud,}.xml +mysql --user=root --password="" cloud -e "update configuration set value='false' where name='developer';" +mysql --user=root --password="" cloud -e "INSERT INTO configuration (instance, name,value) VALUE('DEFAULT','system.vm.use.local.storage', 'true');" +update template_view set url='http://10.70.176.29/pub/systemvmtemplate-2013-07-04-master-hyperv.vhd' where name='SystemVM Template (HyperV)'; +export MAVEN_OPTS="-XX:MaxPermSize=256m -Xmx1g" +mvn -pl :cloud-client-ui jetty:run http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java new file mode 100644 index 0000000..11df222 --- /dev/null +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/discoverer/HypervServerDiscoverer.java @@ -0,0 +1,467 @@ +// 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.util.HashMap; +import java.util.List; +import java.util.Map; +import java.util.UUID; + +import javax.ejb.Local; +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.dc.ClusterDetailsDao; +import com.cloud.dc.ClusterVO; +import com.cloud.dc.DataCenterVO; +import com.cloud.dc.HostPodVO; +import com.cloud.dc.dao.ClusterDao; +import com.cloud.dc.dao.DataCenterDao; +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.host.dao.HostDao; +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.ResourceManager; +import com.cloud.resource.ResourceStateAdapter; +import com.cloud.resource.ServerResource; +import com.cloud.resource.UnableDeleteHostException; + +/** + * 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. + */ +@Local(value = Discoverer.class) +public class HypervServerDiscoverer extends DiscovererBase implements + Discoverer, Listener, ResourceStateAdapter { + private static final Logger s_logger = Logger + .getLogger(HypervServerDiscoverer.class); + + @Inject + private HostDao _hostDao = null; + @Inject + private ClusterDao _clusterDao; + @Inject + private ClusterDetailsDao _clusterDetailsDao; + @Inject + private ResourceManager _resourceMgr; + @Inject + private HostPodDao _podDao; + @Inject + private DataCenterDao _dcDao; + + // 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 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 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> find( + final long dcId, final Long podId, final Long clusterId, + final URI uri, final String username, final String password, + final List 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()).toString()); + _clusterDao.update(clusterId, cluster); + } + + // Settings required by all server resources managing a hypervisor + Map params = new HashMap(); + 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 details = new HashMap(); + details.put("url", uri.getHost()); + details.put("username", username); + details.put("password", password); + details.put("cluster.guid", cluster.getGuid()); + + params.putAll(details); + + 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> resources = + new HashMap>(); + resources.put(resource, details); + + // TODO: does the resource have to create a connection? + return resources; + } catch (ConfigurationException e) { + _alertMgr.sendAlert(AlertManager.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.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()).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 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 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 details, final List 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 + +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java new file mode 100644 index 0000000..8b79cae --- /dev/null +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/guru/HypervGuru.java @@ -0,0 +1,68 @@ +// 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.guru; + +import javax.ejb.Local; +import javax.inject.Inject; + +import com.cloud.agent.api.to.VirtualMachineTO; +import com.cloud.hypervisor.HypervisorGuru; +import com.cloud.hypervisor.HypervisorGuruBase; +import com.cloud.hypervisor.Hypervisor.HypervisorType; +import com.cloud.storage.GuestOSVO; +import com.cloud.storage.dao.GuestOSDao; +import com.cloud.vm.VirtualMachine; +import com.cloud.vm.VirtualMachineProfile; + +/** + * Implementation of Hypervisor guru for Hyper-V. + **/ +@Local(value = HypervisorGuru.class) +public class HypervGuru extends HypervisorGuruBase implements HypervisorGuru { + + @Inject + private GuestOSDao _guestOsDao; + + @Override + public final HypervisorType getHypervisorType() { + return HypervisorType.Hyperv; + } + /** + * Prevent direct creation. + */ + protected HypervGuru() { + super(); + } + + @Override + public final VirtualMachineTO implement( + VirtualMachineProfile vm) { + VirtualMachineTO to = toVirtualMachineTO(vm); + + // Determine the VM's OS description + GuestOSVO guestOS = _guestOsDao.findById(vm.getVirtualMachine() + .getGuestOSId()); + to.setOs(guestOS.getDisplayName()); + + return to; + } + + @Override + public final boolean trackVmHostChange() { + return false; + } +} http://git-wip-us.apache.org/repos/asf/cloudstack/blob/f9f196a4/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java ---------------------------------------------------------------------- diff --git a/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java new file mode 100644 index 0000000..e22f284 --- /dev/null +++ b/plugins/hypervisors/hyperv/src/com/cloud/hypervisor/hyperv/resource/HypervDirectConnectResource.java @@ -0,0 +1,454 @@ +// 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.resource; + +import java.io.IOException; +import java.net.URI; +import java.net.URISyntaxException; +import java.util.HashMap; +import java.util.Map; + +import javax.naming.ConfigurationException; + +import org.apache.http.HttpResponse; +import org.apache.http.client.ClientProtocolException; +import org.apache.http.client.HttpClient; +import org.apache.http.HttpStatus; +import org.apache.http.client.methods.HttpPost; +import org.apache.http.entity.StringEntity; +import org.apache.http.impl.client.DefaultHttpClient; +import org.apache.http.util.EntityUtils; +import org.apache.log4j.Logger; + +import com.cloud.agent.api.Answer; +import com.cloud.agent.api.Command; +import com.cloud.agent.api.PingCommand; +import com.cloud.agent.api.PingRoutingCommand; +import com.cloud.agent.api.StartupCommand; +import com.cloud.agent.api.StartupRoutingCommand; +import com.cloud.agent.api.StartupStorageCommand; +import com.cloud.agent.api.UnsupportedAnswer; +import com.cloud.agent.api.StartupRoutingCommand.VmState; +import com.cloud.host.Host.Type; +import com.cloud.hypervisor.Hypervisor; +import com.cloud.network.Networks.RouterPrivateIpStrategy; +import com.cloud.resource.ServerResource; +import com.cloud.resource.ServerResourceBase; +import com.cloud.serializer.GsonHelper; +import com.google.gson.Gson; + +/** + * Implementation of dummy resource to be returned from discoverer. + **/ + +public class HypervDirectConnectResource extends ServerResourceBase implements + ServerResource { + public static final int DEFAULT_AGENT_PORT = 8250; + private static final Logger s_logger = Logger + .getLogger(HypervDirectConnectResource.class.getName()); + + private static final Gson s_gson = GsonHelper.getGson(); + private String _zoneId; + private String _podId; + private String _clusterId; + private String _guid; + private String _agentIp; + private int _port = DEFAULT_AGENT_PORT; + + private String _clusterGuid; + + // Used by initialize to assert object configured before + // initialize called. + private boolean _configureCalled = false; + + private String _username; + private String _password; + + @Override + public final Type getType() { + return Type.Routing; + } + + @Override + public final StartupCommand[] initialize() { + // assert + if (!_configureCalled) { + String errMsg = + this.getClass().getName() + + " requires configure() be called before" + + " initialize()"; + s_logger.error(errMsg); + } + + // Create default StartupRoutingCommand, then customise + StartupRoutingCommand defaultStartRoutCmd = + new StartupRoutingCommand(0, 0, 0, 0, null, + Hypervisor.HypervisorType.Hyperv, + RouterPrivateIpStrategy.HostLocal, + new HashMap()); + + // Identity within the data centre is decided by CloudStack kernel, + // and passed via ServerResource.configure() + defaultStartRoutCmd.setDataCenter(_zoneId); + defaultStartRoutCmd.setPod(_podId); + defaultStartRoutCmd.setCluster(_clusterId); + defaultStartRoutCmd.setGuid(_guid); + defaultStartRoutCmd.setName(_name); + defaultStartRoutCmd.setPrivateIpAddress(_agentIp); + defaultStartRoutCmd.setStorageIpAddress(_agentIp); + defaultStartRoutCmd.setPool(_clusterGuid); + + s_logger.debug("Generated StartupRoutingCommand for _agentIp \"" + + _agentIp + "\""); + + // TODO: does version need to be hard coded. + defaultStartRoutCmd.setVersion("4.2.0"); + + // Specifics of the host's resource capacity and network configuration + // comes from the host itself. CloudStack sanity checks network + // configuration + // and uses capacity info for resource allocation. + Command[] startCmds = + requestStartupCommand(new Command[] {defaultStartRoutCmd}); + + // TODO: may throw, is this okay? + StartupRoutingCommand startCmd = (StartupRoutingCommand) startCmds[0]; + + // Assert that host identity is consistent with existing values. + if (startCmd == null) { + String errMsg = + String.format("Host %s (IP %s)" + + "did not return a StartupRoutingCommand", + _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getDataCenter().equals( + defaultStartRoutCmd.getDataCenter())) { + String errMsg = + String.format( + "Host %s (IP %s) changed zone/data center. Was " + + defaultStartRoutCmd.getDataCenter() + + " NOW its " + startCmd.getDataCenter(), + _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getPod().equals(defaultStartRoutCmd.getPod())) { + String errMsg = + String.format("Host %s (IP %s) changed pod. Was " + + defaultStartRoutCmd.getPod() + " NOW its " + + startCmd.getPod(), _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getCluster().equals(defaultStartRoutCmd.getCluster())) { + String errMsg = + String.format("Host %s (IP %s) changed cluster. Was " + + defaultStartRoutCmd.getCluster() + " NOW its " + + startCmd.getCluster(), _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getGuid().equals(defaultStartRoutCmd.getGuid())) { + String errMsg = + String.format("Host %s (IP %s) changed guid. Was " + + defaultStartRoutCmd.getGuid() + " NOW its " + + startCmd.getGuid(), _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getPrivateIpAddress().equals( + defaultStartRoutCmd.getPrivateIpAddress())) { + String errMsg = + String.format("Host %s (IP %s) IP address. Was " + + defaultStartRoutCmd.getPrivateIpAddress() + + " NOW its " + startCmd.getPrivateIpAddress(), + _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + if (!startCmd.getName().equals(defaultStartRoutCmd.getName())) { + String errMsg = + String.format( + "Host %s (IP %s) name. Was " + startCmd.getName() + + " NOW its " + + defaultStartRoutCmd.getName(), _name, + _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + + // Host will also supply details of an existing StoragePool if it has + // been configured with one. + // + // NB: if the host was configured + // with a local storage pool, CloudStack may not be able to use it + // unless + // it is has service offerings configured to recognise this storage + // type. + StartupStorageCommand storePoolCmd = null; + if (startCmds.length > 1) { + storePoolCmd = (StartupStorageCommand) startCmds[1]; + // TODO: is this assertion required? + if (storePoolCmd == null) { + String frmtStr = + "Host %s (IP %s) sent incorrect Command, " + + "second parameter should be a " + + "StartupStorageCommand"; + String errMsg = String.format(frmtStr, _name, _agentIp); + s_logger.error(errMsg); + // TODO: valid to return null, or should we throw? + return null; + } + s_logger.info("Host " + _name + " (IP " + _agentIp + + ") already configured with a storeage pool, details " + + s_gson.toJson(startCmds[1])); + } else { + s_logger.info("Host " + _name + " (IP " + _agentIp + + ") already configured with a storeage pool, details "); + } + return new StartupCommand[] {startCmd, storePoolCmd}; + } + + @Override + public final PingCommand getCurrentStatus(final long id) { + PingCommand pingCmd = new PingRoutingCommand(getType(), id, null); + + if (s_logger.isDebugEnabled()) { + s_logger.debug("Ping host " + _name + " (IP " + _agentIp + ")"); + } + + Answer pingAns = this.executeRequest(pingCmd); + + if (pingAns == null || !pingAns.getResult()) { + s_logger.info("Cannot ping host " + _name + " (IP " + _agentIp + + "), pingAns (blank means null) is:" + pingAns); + return null; + } + return pingCmd; + } + + // TODO: Is it valid to return NULL, or should we throw on error? + // Returns StartupCommand with fields revised with values known only to the + // host + public final Command[] requestStartupCommand(final Command[] cmd) { + // Set HTTP POST destination URI + // Using java.net.URI, see + // http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URI.html + URI agentUri = null; + try { + String cmdName = StartupCommand.class.getName(); + agentUri = + new URI("http", null, _agentIp, _port, + "/api/HypervResource/" + cmdName, null, null); + } catch (URISyntaxException e) { + // TODO add proper logging + String errMsg = "Could not generate URI for Hyper-V agent"; + s_logger.error(errMsg, e); + return null; + } + String incomingCmd = postHttpRequest(s_gson.toJson(cmd), agentUri); + + if (incomingCmd == null) { + return null; + } + Command[] result = null; + try { + result = s_gson.fromJson(incomingCmd, Command[].class); + } catch (Exception ex) { + String errMsg = "Failed to deserialize Command[] " + incomingCmd; + s_logger.error(errMsg, ex); + } + s_logger.debug("requestStartupCommand received response " + + s_gson.toJson(result)); + if (result.length > 0) { + return result; + } + return null; + } + + // TODO: Is it valid to return NULL, or should we throw on error? + @Override + public final Answer executeRequest(final Command cmd) { + // Set HTTP POST destination URI + // Using java.net.URI, see + // http://docs.oracle.com/javase/1.5.0/docs/api/java/net/URI.html + URI agentUri = null; + try { + String cmdName = cmd.getClass().getName(); + agentUri = + new URI("http", null, _agentIp, _port, + "/api/HypervResource/" + cmdName, null, null); + } catch (URISyntaxException e) { + // TODO add proper logging + String errMsg = "Could not generate URI for Hyper-V agent"; + s_logger.error(errMsg, e); + return null; + } + String ansStr = postHttpRequest(s_gson.toJson(cmd), agentUri); + + if (ansStr == null) { + return null; + } + // Only Answer instances are returned by remote agents. + // E.g. see Response.getAnswers() + Answer[] result = s_gson.fromJson(ansStr, Answer[].class); + s_logger.debug("executeRequest received response " + + s_gson.toJson(result)); + if (result.length > 0) { + return result[0]; + } + return null; + } + + public static String postHttpRequest(final String jsonCmd, + final URI agentUri) { + // Using Apache's HttpClient for HTTP POST + // Java-only approach discussed at on StackOverflow concludes with + // comment to use Apache HttpClient + // http://stackoverflow.com/a/2793153/939250, but final comment is to + // use Apache. + s_logger.debug("POST request to" + agentUri.toString() + + " with contents" + jsonCmd); + + // Create request + HttpClient httpClient = new DefaultHttpClient(); + String result = null; + + // TODO: are there timeout settings and worker thread settings to tweak? + try { + HttpPost request = new HttpPost(agentUri); + + // JSON encode command + // Assumes command sits comfortably in a string, i.e. not used for + // large data transfers + StringEntity cmdJson = new StringEntity(jsonCmd); + request.addHeader("content-type", "application/json"); + request.setEntity(cmdJson); + s_logger.debug("Sending cmd to " + agentUri.toString() + + " cmd data:" + jsonCmd); + HttpResponse response = httpClient.execute(request); + + // Unsupported commands will not route. + if (response.getStatusLine().getStatusCode() + == HttpStatus.SC_NOT_FOUND) { + String errMsg = + "Failed to send : HTTP error code : " + + response.getStatusLine().getStatusCode(); + s_logger.error(errMsg); + String unsupportMsg = + "Unsupported command " + + agentUri.getPath() + + ". Are you sure you got the right type of" + + " server?"; + Answer ans = new UnsupportedAnswer(null, unsupportMsg); + s_logger.error(ans); + result = s_gson.toJson(new Answer[] {ans}); + } else if (response.getStatusLine().getStatusCode() + != HttpStatus.SC_OK) { + String errMsg = + "Failed send to " + agentUri.toString() + + " : HTTP error code : " + + response.getStatusLine().getStatusCode(); + s_logger.error(errMsg); + return null; + } else { + result = EntityUtils.toString(response.getEntity()); + s_logger.debug("POST response is" + result); + } + } catch (ClientProtocolException protocolEx) { + // Problem with HTTP message exchange + s_logger.error(protocolEx); + } catch (IOException connEx) { + // Problem with underlying communications + s_logger.error(connEx); + } finally { + httpClient.getConnectionManager().shutdown(); + } + return result; + } + + @Override + protected final String getDefaultScriptsDir() { + // TODO Auto-generated method stub + return null; + } + + // NB: 'params' can come from one of two places. + // For a new host, HypervServerDiscoverer.find(). + // For an existing host, DiscovererBase.reloadResource(). + // In the later case, the params Map is populated with predefined keys + // and custom keys from the database that were passed out by the find() + // call. + // the custom keys go by the variable name 'details'. + // Thus, in find(), you see that 'details' are added to the params Map. + @Override + public final boolean configure(final String name, + final Map params) throws ConfigurationException { + /* todo: update, make consistent with the xen server equivalent. */ + _guid = (String) params.get("guid"); + _zoneId = (String) params.get("zone"); + _podId = (String) params.get("pod"); + _clusterId = (String) params.get("cluster"); + _agentIp = (String) params.get("ipaddress"); // was agentIp + _name = name; + + _clusterGuid = (String) params.get("cluster.guid"); + _username = (String) params.get("url"); + _password = (String) params.get("password"); + _username = (String) params.get("username"); + + _configureCalled = true; + return true; + } + + @Override + public void setName(final String name) { + // TODO Auto-generated method stub + } + + @Override + public void setConfigParams(final Map params) { + // TODO Auto-generated method stub + } + + @Override + public final Map getConfigParams() { + // TODO Auto-generated method stub + return null; + } + + @Override + public final int getRunLevel() { + // TODO Auto-generated method stub + return 0; + } + + @Override + public void setRunLevel(final int level) { + // TODO Auto-generated method stub + } + +}