bvt: marvin test for the affinity groups feature The test deploys two VMs in the simulator context and verifies that the default host -antiaffinity processor placed the VMs on two distinct hosts.
Signed-off-by: Prasanna Santhanam <[email protected]> Project: http://git-wip-us.apache.org/repos/asf/cloudstack/repo Commit: http://git-wip-us.apache.org/repos/asf/cloudstack/commit/79812c25 Tree: http://git-wip-us.apache.org/repos/asf/cloudstack/tree/79812c25 Diff: http://git-wip-us.apache.org/repos/asf/cloudstack/diff/79812c25 Branch: refs/heads/marvin_refactor Commit: 79812c253f26841523c9fe3c04ce7c6c47d98314 Parents: e52bf52 Author: Prasanna Santhanam <[email protected]> Authored: Mon Apr 8 23:18:00 2013 +0530 Committer: Prachi Damle <[email protected]> Committed: Thu Apr 11 13:23:32 2013 -0700 ---------------------------------------------------------------------- client/tomcatconf/simulatorComponentContext.xml.in | 23 +- test/integration/smoke/test_affinity_groups.py | 297 ++++++++------- tools/marvin/marvin/integration/lib/base.py | 29 ++- 3 files changed, 212 insertions(+), 137 deletions(-) ---------------------------------------------------------------------- http://git-wip-us.apache.org/repos/asf/cloudstack/blob/79812c25/client/tomcatconf/simulatorComponentContext.xml.in ---------------------------------------------------------------------- diff --git a/client/tomcatconf/simulatorComponentContext.xml.in b/client/tomcatconf/simulatorComponentContext.xml.in index fc5cf54..435b9e2 100644 --- a/client/tomcatconf/simulatorComponentContext.xml.in +++ b/client/tomcatconf/simulatorComponentContext.xml.in @@ -1,4 +1,3 @@ - <!-- ~ Licensed to the Apache Software Foundation (ASF) under one ~ or more contributor license agreements. See the NOTICE file @@ -34,8 +33,8 @@ <!-- OSS deployment component configuration --> - <bean id="databaseUpgradeChecker" class="com.cloud.upgrade.DatabaseUpgradeChecker" /> - <bean id="configurationDaoImpl" class="com.cloud.configuration.dao.ConfigurationDaoImpl" /> + <bean id="databaseUpgradeChecker" class="com.cloud.upgrade.DatabaseUpgradeChecker"/> + <bean id="configurationDaoImpl" class="com.cloud.configuration.dao.ConfigurationDaoImpl"/> <!-- simulator components --> <bean id="SimulatorSecondaryDiscoverer" class="com.cloud.resource.SimulatorSecondaryDiscoverer"> @@ -122,9 +121,9 @@ <bean id="deploymentPlanners" class="com.cloud.utils.component.AdapterList"> <property name="Adapters"> <list> - <ref bean="FirstFitPlanner" /> - <ref bean="UserDispersingPlanner" /> - <ref bean="UserConcentratedPodPlanner" /> + <ref bean="FirstFitPlanner"/> + <ref bean="UserDispersingPlanner"/> + <ref bean="UserConcentratedPodPlanner"/> <!-- <ref bean="BareMetalPlanner" /> @@ -215,6 +214,16 @@ </property> </bean> - <bean id="GlobalLoadBalancingRulesServiceImpl" class ="org.apache.cloudstack.region.gslb.GlobalLoadBalancingRulesServiceImpl" /> + <bean id="GlobalLoadBalancingRulesServiceImpl" + class="org.apache.cloudstack.region.gslb.GlobalLoadBalancingRulesServiceImpl"/> + + <!-- + AffinityGroup Processors + --> + <bean id="HostAntiAffinityProcessor" class="org.apache.cloudstack.affinity.HostAntiAffinityProcessor"> + <property name="name" value="HostAntiAffinityProcessor"/> + <property name="type" value="host anti-affinity"/> + </bean> + </beans> http://git-wip-us.apache.org/repos/asf/cloudstack/blob/79812c25/test/integration/smoke/test_affinity_groups.py ---------------------------------------------------------------------- diff --git a/test/integration/smoke/test_affinity_groups.py b/test/integration/smoke/test_affinity_groups.py index 432751c..83fccf5 100644 --- a/test/integration/smoke/test_affinity_groups.py +++ b/test/integration/smoke/test_affinity_groups.py @@ -16,142 +16,181 @@ # specific language governing permissions and limitations # under the License. +from marvin.cloudstackTestCase import * +from marvin.cloudstackAPI import * +from marvin.integration.lib.utils import * +from marvin.integration.lib.base import * +from marvin.integration.lib.common import * +from marvin import remoteSSHClient +from nose.plugins.attrib import attr + +class Services: + """Test Account Services + """ + def __init__(self): + self.services = { + "domain": { + "name": "Domain", + }, + "account": { + "email": "[email protected]", + "firstname": "Test", + "lastname": "User", + "username": "test", + # Random characters are appended for unique + # username + "password": "password", + }, + "service_offering": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 100, + # in MHz + "memory": 64, + # In MBs + }, + "ostype": 'CentOS 5.3 (64-bit)', + "mode": 'advanced', + "affinity": { + "name": "webvms", + "type": "host anti-affinity", + } + } -import marvin -from marvin.cloudstackTestCase import * -from marvin.remoteSSHClient import remoteSSHClient -import hashlib -import random class TestDeployVmWithAffinityGroup(cloudstackTestCase): """ This test deploys a virtual machine into a user account using the small service offering and builtin template """ - def setUp(self): - """ - CloudStack internally saves its passwords in md5 form and that is how we - specify it in the API. Python's hashlib library helps us to quickly hash - strings as follows - """ - mdf = hashlib.md5() - mdf.update('password') - mdf_pass = mdf.hexdigest() - - self.apiClient = self.testClient.getApiClient() #Get ourselves an API client - - self.acct = createAccount.createAccountCmd() #The createAccount command - self.acct.accounttype = 0 #We need a regular user. admins have accounttype=1 - self.acct.firstname = 'test' - self.acct.lastname = 'user' #What's up doc? - self.acct.password = mdf_pass #The md5 hashed password string - self.acct.username = 'testuser' - self.acct.email = '[email protected]' - self.acct.account = 'testacct' - self.acct.domainid = 1 #The default ROOT domain - self.acctResponse = self.apiClient.createAccount(self.acct) - # And upon successful creation we'll log a helpful message in our logs - # using the default debug logger of the test framework - self.debug("successfully created account: %s, user: %s, id: \ - %s"%(self.acctResponse.account.account, \ - self.acctResponse.account.username, \ - self.acctResponse.account.id)) - - - self.zone = listZones.listZonesCmd() - self.zone.uuid = self.apiClient.listZones(self.zone)[0].id - - self.service_offering = listServiceOfferings.listServiceOfferingsCmd() - self.service_offering.uuid = self.apiClient.listServiceOfferings(self.service_offering)[0].id - - self.template = listTemplates.listTemplatesCmd() - self.template.templatefilter = 'featured' - self.template.name = 'CentOS' - self.template.uuid = self.apiClient.listTemplates(self.template)[0].id - - def test_DeployVm(self): - """ - Let's start by defining the attributes of our VM that we will be - deploying on CloudStack. We will be assuming a single zone is available - and is configured and all templates are Ready - - The hardcoded values are used only for brevity. - First create the host anti-affinity group for this account + @classmethod + def setUpClass(cls): + cls.api_client = super(TestDeployVmWithAffinityGroup, cls).getClsTestClient().getApiClient() + cls.services = Services().services + # Get Zone, Domain and templates + cls.domain = get_domain(cls.api_client, cls.services) + cls.zone = get_zone(cls.api_client, cls.services) + cls.disk_offering = DiskOffering.create( + cls.api_client, + cls.services["disk_offering"] + ) + cls.template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["virtual_machine"]["zoneid"] = cls.zone.id + cls.services["volume"]["zoneid"] = cls.zone.id + + cls.services["template"] = cls.template.id + cls.services["zoneid"] = cls.zone.id + + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + + cls.ag = AffinityGroup.create(cls.api_client, cls.services["affinity"], domainid=cls.domain.id) + + cls._cleanup = [ + cls.service_offering, + cls.disk_offering, + cls.account, + ] + return + + @attr(tags=["simulator", "basic", "advanced"]) + def test_DeployVmAntiAffinityGroup(self): + """ + Deploys a couple of VMs in the same affinity group and verifies they are not on the same host """ - createAGCmd = createAffinityGroup.createAffinityGroupCmd() - createAGCmd.name = 'webvms1' - createAGCmd.type = 'host anti-affinity' - createAGCmd.account = self.acct.account - createAGCmd.domainid = self.acct.domainid - - createAGResponse = self.apiClient.createAffinityGroup(createAGCmd) - self.debug("AffinityGroup %s was created in the job %s"%(createAGResponse.id, createAGResponse.jobid)) - - - deployVmCmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVmCmd.zoneid = self.zone.uuid - deployVmCmd.templateid = self.template.uuid #CentOS 5.6 builtin - deployVmCmd.serviceofferingid = self.service_offering.uuid - deployVmCmd.account = self.acct.account - deployVmCmd.domainid = self.acct.domainid - deployVmCmd.affinitygroupnames=[] - deployVmCmd.affinitygroupnames.append(str(createAGResponse.name)) - deployVmResponse = self.apiClient.deployVirtualMachine(deployVmCmd) - self.debug("VM %s was deployed in the job %s"%(deployVmResponse.id, deployVmResponse.jobid)) - - # At this point our VM is expected to be Running. Let's find out what - # listVirtualMachines tells us about VMs in this account - - listVmCmd = listVirtualMachines.listVirtualMachinesCmd() - listVmCmd.id = deployVmResponse.id - listVmResponse = self.apiClient.listVirtualMachines(listVmCmd) - - self.assertNotEqual(len(listVmResponse), 0, "Check if the list API \ - returns a non-empty response") - - vm = listVmResponse[0] - self.assertEqual(vm.state, "Running", "Check if VM has reached Running state in CS") - - VM1hostid = vm.hostid - - #Deploy another VM in same affinity group - deployVm2Cmd = deployVirtualMachine.deployVirtualMachineCmd() - deployVm2Cmd.zoneid = self.zone.uuid - deployVm2Cmd.templateid = self.template.uuid #CentOS 5.6 builtin - deployVm2Cmd.serviceofferingid = self.service_offering.uuid - deployVm2Cmd.account = self.acct.account - deployVm2Cmd.domainid = self.acct.domainid - deployVm2Cmd.affinitygroupnames=[] - deployVm2Cmd.affinitygroupnames.append(str(createAGResponse.name)) - - deployVm2Response = self.apiClient.deployVirtualMachine(deployVm2Cmd) - self.debug("VM2 %s was deployed in the job %s"%(deployVm2Response.id, deployVm2Response.jobid)) - - # At this point our VM is expected to be Running. Let's find out what - # listVirtualMachines tells us about VMs in this account - - listVm2Cmd = listVirtualMachines.listVirtualMachinesCmd() - listVm2Cmd.id = deployVm2Response.id - listVm2Response = self.apiClient.listVirtualMachines(listVm2Cmd) - - self.assertNotEqual(len(listVm2Response), 0, "Check if the list API \ - returns a non-empty response") - - vm2 = listVm2Response[0] - self.assertEqual(vm2.state, "Running", "Check if VM has reached Running state in CS") - - VM2hostid = vm2.hostid - - self.assertNotEqual(VM1hostid, VM2hostid, "The hosts of the 2 VM's in the host anti-affinity group are not different, test failed") - - def tearDown(self): - """ - And finally let us cleanup the resources we created by deleting the - account. All good unittests are atomic and rerunnable this way - """ - deleteAcct = deleteAccount.deleteAccountCmd() - deleteAcct.id = self.acctResponse.account.id - self.apiClient.deleteAccount(deleteAcct) - self.testClient.close() + #deploy VM1 in affinity group created in setUp + vm1 = VirtualMachine.create( + self.api_client, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + affinitygroupnames=self.ag.name, + mode=self.services["mode"] + ) + + list_vm1 = list_virtual_machines( + self.api_client, + id=vm1.id + ) + self.assertEqual( + isinstance(list_vm1, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vm1), + 0, + "Check VM available in List Virtual Machines" + ) + vm1_response = list_vm1[0] + self.assertEqual( + vm1_response.state, + 'Running', + msg="VM is not in Running state" + ) + host_of_vm1 = vm1_response.hostid + + #deploy VM2 in affinity group created in setUp + vm2 = VirtualMachine.create( + self.api_client, + self.services["virtual_machine"], + templateid=self.template.id, + accountid=self.account.account.name, + domainid=self.account.account.domainid, + serviceofferingid=self.service_offering.id, + affinitygroupnames=self.ag.name, + mode=self.services["mode"] + ) + list_vm2 = list_virtual_machines( + self.api_client, + id=self.vm1.id + ) + self.assertEqual( + isinstance(list_vm2, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(list_vm2), + 0, + "Check VM available in List Virtual Machines" + ) + vm2_response = list_vm2[0] + self.assertEqual( + vm2_response.state, + 'Running', + msg="VM is not in Running state" + ) + host_of_vm2 = vm2_response.hostid + + self.assertNotEqual(host_of_vm1, host_of_vm2, + msg="Both VMs of affinity group %s are on the same host" % self.ag.name) + + + @classmethod + def tearDown(cls): + try: + cls.api_client = super(TestDeployVmWithAffinityGroup, cls).getClsTestClient().getApiClient() + #Clean up, terminate the created templates + cleanup_resources(cls.api_client, cls.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) http://git-wip-us.apache.org/repos/asf/cloudstack/blob/79812c25/tools/marvin/marvin/integration/lib/base.py ---------------------------------------------------------------------- diff --git a/tools/marvin/marvin/integration/lib/base.py b/tools/marvin/marvin/integration/lib/base.py index f3370eb..8c7470e 100644 --- a/tools/marvin/marvin/integration/lib/base.py +++ b/tools/marvin/marvin/integration/lib/base.py @@ -223,7 +223,7 @@ class VirtualMachine: def create(cls, apiclient, services, templateid=None, accountid=None, domainid=None, zoneid=None, networkids=None, serviceofferingid=None, securitygroupids=None, projectid=None, startvm=None, - diskofferingid=None, hostid=None, mode='basic'): + diskofferingid=None, affinitygroupname=None, hostid=None, mode='basic'): """Create the instance""" cmd = deployVirtualMachine.deployVirtualMachineCmd() @@ -268,6 +268,9 @@ class VirtualMachine: if "userdata" in services: cmd.userdata = base64.b64encode(services["userdata"]) + if "affinitygroupnames" in services: + cmd.affinitygroupnames = services["affinitygroupnames"] + if projectid: cmd.projectid = projectid @@ -2424,3 +2427,27 @@ class VPC: cmd = listVPCs.listVPCsCmd() [setattr(cmd, k, v) for k, v in kwargs.items()] return(apiclient.listVPCs(cmd)) + + +class AffinityGroup: + def __init__(self, items): + self.__dict__.update(items) + + @classmethod + def create(cls, apiclient, services, account=None, domainid=None): + agCmd = createAffinityGroup.createAffinityGroupCmd() + agCmd.name = services['name'] + agCmd.displayText = services['displaytext'] if 'displaytext' in services else services['name'] + agCmd.type = services['type'] + agCmd.account = services['account'] if 'account' in services else account + agCmd.domainid = services['domainid'] if 'domainid' in services else domainid + + def update(self): + pass + + def delete(self): + pass + + @classmethod + def list(cls): + pass \ No newline at end of file
