| # 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. |
| |
| # Test from the Marvin - Testing in Python wiki |
| |
| # All tests inherit from cloudstackTestCase |
| from marvin.cloudstackTestCase import cloudstackTestCase |
| |
| # Import Integration Libraries |
| |
| # base - contains all resources as entities and defines create, delete, |
| # list operations on them |
| from marvin.lib.base import (Account, |
| VirtualMachine, |
| ServiceOffering, |
| Template, |
| DiskOffering, |
| Volume, |
| Host, |
| GuestOs) |
| |
| # common - commonly used methods for all tests are listed here |
| from marvin.lib.common import get_zone, get_domain, get_pod |
| |
| from marvin.sshClient import SshClient |
| |
| from marvin.codes import FAILED |
| |
| from nose.plugins.attrib import attr |
| |
| import xml.etree.ElementTree as ET |
| import logging |
| |
| |
| class Templates: |
| """Test data for templates |
| """ |
| |
| def __init__(self): |
| self.templates = { |
| "kvmvirtioscsi": { |
| "kvm": { |
| "name": "tiny-kvm-scsi", |
| "displaytext": "virtio-scsi kvm", |
| "format": "qcow2", |
| "hypervisor": "kvm", |
| "ostype": "Other PV Virtio-SCSI (64-bit)", |
| "url": "http://dl.openvm.eu/cloudstack/ubuntu/x86_64/ubuntu-16.04-kvm.qcow2.bz2", |
| "requireshvm": True, |
| "ispublic": True, |
| "passwordenabled": True |
| } |
| } |
| } |
| |
| |
| class TestDeployVirtioSCSIVM(cloudstackTestCase): |
| """ |
| Test deploy a kvm virtio scsi template |
| """ |
| |
| @classmethod |
| def setUpClass(cls): |
| cls.logger = logging.getLogger('TestDeployVirtioSCSIVM') |
| cls.stream_handler = logging.StreamHandler() |
| cls.logger.setLevel(logging.DEBUG) |
| cls.logger.addHandler(cls.stream_handler) |
| |
| testClient = super(TestDeployVirtioSCSIVM, cls).getClsTestClient() |
| cls.apiclient = testClient.getApiClient() |
| cls.services = cls.testClient.getParsedTestDataConfig() |
| |
| cls.hostConfig = cls.config.__dict__["zones"][0].__dict__["pods"][0].__dict__["clusters"][0].__dict__["hosts"][0].__dict__ |
| cls.hypervisorNotSupported = False |
| cls.hypervisor = testClient.getHypervisorInfo() |
| |
| # Get Zone, Domain and templates |
| cls.domain = get_domain(cls.apiclient) |
| cls.zone = get_zone(cls.apiclient, testClient.getZoneForTests()) |
| cls.pod = get_pod(cls.apiclient, cls.zone.id) |
| cls.services['mode'] = cls.zone.networktype |
| cls._cleanup = [] |
| if cls.hypervisor.lower() not in ['kvm']: |
| cls.hypervisorNotSupported = True |
| return |
| kvmvirtioscsi = Templates().templates["kvmvirtioscsi"] |
| cls.template = Template.register( |
| cls.apiclient, |
| kvmvirtioscsi[cls.hypervisor.lower()], |
| cls.zone.id, |
| hypervisor=cls.hypervisor.lower(), |
| domainid=cls.domain.id) |
| cls.template.download(cls.apiclient) |
| cls._cleanup.append(cls.template) |
| |
| if cls.template == FAILED: |
| assert False, "get_template() failed to return template" |
| |
| cls.services["domainid"] = cls.domain.id |
| cls.services["small"]["zoneid"] = cls.zone.id |
| cls.services["zoneid"] = cls.zone.id |
| cls.account = Account.create( |
| cls.apiclient, |
| cls.services["account"], |
| domainid=cls.domain.id |
| ) |
| cls._cleanup.append(cls.account) |
| |
| cls.service_offering = ServiceOffering.create( |
| cls.apiclient, |
| cls.services["service_offerings"]["small"] |
| ) |
| cls._cleanup.append(cls.service_offering) |
| |
| cls.sparse_disk_offering = DiskOffering.create( |
| cls.apiclient, |
| cls.services["sparse_disk_offering"] |
| ) |
| cls._cleanup.append(cls.sparse_disk_offering) |
| |
| cls.virtual_machine = VirtualMachine.create( |
| cls.apiclient, |
| cls.services["small"], |
| templateid=cls.template.id, |
| accountid=cls.account.name, |
| domainid=cls.account.domainid, |
| zoneid=cls.zone.id, |
| serviceofferingid=cls.service_offering.id, |
| diskofferingid=cls.sparse_disk_offering.id, |
| mode=cls.zone.networktype |
| ) |
| |
| hosts = Host.list(cls.apiclient, id=cls.virtual_machine.hostid) |
| if len(hosts) != 1: |
| assert False, "Could not find host with id " + cls.virtual_machine.hostid |
| |
| cls.vmhost = hosts[0] |
| |
| # Stop VM to reset password |
| cls.virtual_machine.stop(cls.apiclient) |
| |
| password = cls.virtual_machine.resetPassword(cls.apiclient) |
| cls.virtual_machine.username = "ubuntu" |
| cls.virtual_machine.password = password |
| |
| # Start VM after password reset |
| cls.virtual_machine.start(cls.apiclient) |
| cls._cleanup.append(cls.virtual_machine) |
| |
| |
| @classmethod |
| def tearDownClass(cls): |
| super(TestDeployVirtioSCSIVM, cls).tearDownClass() |
| |
| def setUp(self): |
| self.apiclient = self.testClient.getApiClient() |
| self.dbclient = self.testClient.getDbConnection() |
| self.cleanup = [] |
| |
| def tearDown(self): |
| super(TestDeployVirtioSCSIVM, self).tearDown() |
| |
| def verifyVirshState(self, diskcount): |
| host = self.vmhost.ipaddress |
| instancename = self.virtual_machine.instancename |
| virshxml = self.getVirshXML(host, instancename) |
| |
| root = ET.fromstring(virshxml) |
| |
| scsis = root.findall("./devices/controller[@type='scsi']/alias[@name='scsi0']/..") |
| |
| self.assertEqual(len(scsis), 1, "SCSI controller not found") |
| |
| scsiindex = scsis[0].get('index') |
| self.assertNotEqual(scsiindex, None, "Could not find index of SCSI controller") |
| |
| # find all scsi disks |
| disks = root.findall("./devices/disk[@device='disk']/target[@bus='scsi']/..") |
| |
| self.assertEqual(len(disks), diskcount, "Could not find expected number of disks") |
| |
| for disk in disks: |
| for child in disk: |
| if child.tag.lower() == "target": |
| dev = child.get("dev") |
| self.assertTrue(dev is not None and dev.startswith("sd"), "disk dev is invalid") |
| elif child.tag.lower() == "address": |
| con = child.get("controller") |
| self.assertEqual(con, scsiindex, "disk controller not equal to SCSI " \ |
| "controller index") |
| elif child.tag.lower() == "driver": |
| discard = child.get("discard") |
| if discard: # may not be defined by older qemu/libvirt |
| self.assertEqual(discard, "unmap", "discard settings not unmap") |
| |
| def verifyGuestState(self, diskcount): |
| ssh = self.virtual_machine.get_ssh_client(reconnect=True) |
| output = ssh.execute("lspci | grep \"Virtio SCSI\"") |
| self.assertTrue(len(output) > 0, "Could not find virtio scsi controller") |
| output = ssh.execute("lsblk -rS | grep sd") |
| for disk in output: |
| self.logger.debug("disk " + disk + " found") |
| |
| self.assertEqual(len(output), diskcount, |
| "Could not find appropriate number of scsi disks in guest") |
| |
| def getVirshXML(self, host, instancename): |
| if host is None: |
| self.logger.debug("getVirshXML: host is none") |
| return "" |
| else: |
| self.logger.debug("host is: " + host) |
| if instancename is None: |
| self.logger.debug("getVirshXML: instancename is none") |
| return "" |
| else: |
| self.logger.debug("instancename is: " + instancename) |
| sshc = SshClient( |
| host=host, |
| port=self.services['configurableData']['host']["publicport"], |
| user=self.hostConfig['username'], |
| passwd=self.hostConfig['password']) |
| |
| ssh = sshc.ssh |
| |
| chan = ssh.get_transport().open_session() |
| chan.exec_command("virsh dumpxml " + instancename) |
| stdout = "" |
| while True: |
| b = chan.recv(10000) |
| if len(b) == 0: |
| break |
| stdout += b.decode() |
| |
| stderr = "" |
| while True: |
| b = chan.recv_stderr(10000) |
| if len(b) == 0: |
| break |
| stderr += b.decode() |
| |
| xstatus = chan.recv_exit_status() |
| chan.close() |
| if xstatus != 0: |
| raise CommandNonzeroException(xstatus, stderr) |
| |
| # rely on sshClient to close ssh |
| self.logger.debug("xml is: \n\n%s\n\n" % (stdout)) |
| return stdout |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_01_verify_libvirt(self): |
| """Test that libvirt properly created domain with scsi controller |
| """ |
| |
| # Validate virsh dumpxml |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.verifyVirshState(2) |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_02_verify_libvirt_after_restart(self): |
| """ Verify that libvirt settings are as expected after a VM stop / start |
| """ |
| |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.virtual_machine.stop(self.apiclient) |
| self.virtual_machine.start(self.apiclient) |
| self.verifyVirshState(2) |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_03_verify_libvirt_attach_disk(self): |
| """ Verify that libvirt settings are expected after a disk add |
| """ |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.volume = Volume.create( |
| self.apiclient, |
| self.services, |
| zoneid=self.zone.id, |
| account=self.account.name, |
| domainid=self.account.domainid, |
| diskofferingid=self.sparse_disk_offering.id |
| ) |
| |
| self.virtual_machine.attach_volume( |
| self.apiclient, |
| self.volume |
| ) |
| |
| self.verifyVirshState(3) |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_04_verify_guest_lspci(self): |
| """ Verify that guest sees scsi controller and disks |
| """ |
| |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.verifyGuestState(3) |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_05_change_vm_ostype_restart(self): |
| """ Update os type to Ubuntu, change vm details rootdiskController |
| explicitly to scsi. |
| """ |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.virtual_machine.stop(self.apiclient) |
| ostypes = GuestOs.listMapping(self.apiclient, hypervisor="kvm") |
| self.assertTrue(len(ostypes) > 0) |
| |
| ostypeid = None |
| for ostype in ostypes: |
| if ostype.osdisplayname == "Ubuntu 16.04 (64-bit)": |
| ostypeid = ostype.ostypeid |
| break |
| |
| self.assertIsNotNone(ostypeid, |
| "Could not find ostypeid for Ubuntu 16.0.4 (64-bit) mapped to kvm") |
| |
| self.virtual_machine.update(self.apiclient, ostypeid=ostypeid, |
| details=[{"rootDiskController": "scsi"}]) |
| |
| self.virtual_machine.start(self.apiclient) |
| |
| self.verifyVirshState(3) |
| |
| @attr(tags=["advanced", "advancedns", "smoke"], required_hardware="true") |
| def test_06_verify_guest_lspci_again(self): |
| """ Verify that guest sees scsi controller and disks after switching ostype and rdc |
| """ |
| if self.hypervisorNotSupported: |
| self.skipTest("Hypervisor not supported") |
| |
| self.verifyGuestState(3) |
| |
| |
| class CommandNonzeroException(Exception): |
| def __init__(self, code, stderr): |
| self.code = code |
| self.stderr = stderr |
| |
| def __str__(self): |
| return "Status code %d: %s" % (self.code, self.stderr) |