| # 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. |
| |
| """ Component tests for Base Image Updation functionality |
| |
| Feature Specifications: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Base+Image+Update+facility |
| |
| Test Plan: https://cwiki.apache.org/confluence/display/CLOUDSTACK/Base+Image+Updation+facility+TestPlan |
| |
| Issue Link: https://issues.apache.org/jira/browse/CLOUDSTACK-2243 |
| """ |
| |
| #Import Local Modules |
| from marvin.codes import (PASS, |
| RECURRING) |
| from nose.plugins.attrib import attr |
| from marvin.cloudstackTestCase import cloudstackTestCase |
| import unittest |
| |
| from marvin.lib.base import (ServiceOffering, |
| Account, |
| VirtualMachine, |
| Volume, |
| Host, |
| Snapshot, |
| SnapshotPolicy, |
| Template |
| ) |
| |
| from marvin.lib.common import (get_domain, |
| get_zone, |
| get_template, |
| list_templates |
| ) |
| |
| from marvin.lib.utils import (validateList, |
| cleanup_resources) |
| |
| import time |
| |
| class Services: |
| """Test Base Image Updation |
| """ |
| |
| def __init__(self): |
| self.services = { |
| "account": { |
| "email": "test@test.com", |
| "firstname": "Test", |
| "lastname": "User", |
| "username": "test", |
| # Random characters are appended for unique |
| # username |
| "password": "password", |
| }, |
| "service_offering_with_reset": { |
| "name": "Tiny Instance With Reset", |
| "displaytext": "Tiny Instance With Reset", |
| "cpunumber": 1, |
| "cpuspeed": 100, |
| "memory": 128, |
| "isvolatile": True, |
| }, |
| "service_offering_without_reset": { |
| "name": "Tiny Instance Without Reset", |
| "displaytext": "Tiny Instance Without Reset", |
| "cpunumber": 1, |
| "cpuspeed": 100, |
| "memory": 128, |
| "isvolatile": False, |
| }, |
| "recurring_snapshot": { |
| "intervaltype": 'HOURLY', |
| # Frequency of snapshots |
| "maxsnaps": 2, # Should be min 2 |
| "schedule": 1, |
| "timezone": 'America/New_York', |
| # Timezone Formats - http://cloudstack.apache.org/docs/en-US/Apache_CloudStack/4.0.0-incubating/html-single/API_Developers_Guide/#time-zones |
| }, |
| "templates": { |
| # Configs for different Template formats |
| # For Eg. raw image, zip etc |
| "XenServer": { |
| "displaytext": "Public Template - Xen", |
| "name": "Public template - Xen", |
| "ostype": "CentOS 5.3 (64-bit)", |
| "url": "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.vhd.bz2", |
| "hypervisor": "xenserver", |
| "format": "VHD", |
| "isfeatured": True, |
| "ispublic": True, |
| "isextractable": True, |
| }, |
| "KVM": { |
| "displaytext": "Public Template - KVM", |
| "name": "Public template -KVM", |
| "ostype": "CentOS 5.3 (64-bit)", |
| "url": "http://download.cloudstack.org/releases/2.0.0/UbuntuServer-10-04-64bit.qcow2.bz2", |
| "hypervisor": "kvm", |
| "format": "qcow2", |
| "isfeatured": True, |
| "ispublic": True, |
| "isextractable": True, |
| |
| }, |
| "VMware": { |
| "displaytext": "Public Template - VMware", |
| "name": "Public template -VMware", |
| "ostype": "CentOS 5.3 (64-bit)", |
| "url": "http://download.cloudstack.org/releases/2.2.0/CentOS5.3-x86_64.ova", |
| "hypervisor": "vmware", |
| "format": "ova", |
| "isfeatured": True, |
| "ispublic": True, |
| "isextractable": True, |
| |
| } |
| }, |
| "template": { |
| "displaytext": "Cent OS Template 2", |
| "name": "Cent OS Template 2", |
| "ostype": "CentOS 5.3 (64-bit)", |
| "templatefilter": "self", |
| }, |
| "virtual_machine": { |
| "displayname": "Test VM", |
| "username": "root", |
| "password": "password", |
| "ssh_port": 22, |
| "hypervisor": "XenServer", |
| # Hypervisor type should be same as |
| # hypervisor type of cluster |
| "privateport": 22, |
| "publicport": 22, |
| "protocol": "TCP", |
| "userdata": "This is sample data", |
| }, |
| "ostype": "CentOS 5.3 (64-bit)", |
| # Cent OS 5.3 (64 bit) |
| "sleep": 60, |
| "retriesCount": 10, |
| "mode": "advanced" |
| } |
| |
| |
| class TestBaseImageUpdate(cloudstackTestCase): |
| |
| @classmethod |
| def setUpClass(cls): |
| cls.testClient = super(TestBaseImageUpdate, cls).getClsTestClient() |
| cls.api_client = cls.testClient.getApiClient() |
| |
| cls.services = Services().services |
| # Get Zone, Domain and templates |
| cls.domain = get_domain(cls.api_client) |
| cls.zone = get_zone(cls.api_client, cls.testClient.getZoneForTests()) |
| cls._cleanup = [] |
| cls.template = get_template( |
| cls.api_client, |
| cls.zone.id, |
| cls.services["ostype"] |
| ) |
| cls.services["virtual_machine"]["zoneid"] = cls.zone.id |
| cls.services["virtual_machine"]["template"] = cls.template.id |
| |
| cls.service_offering_with_reset = ServiceOffering.create( |
| cls.api_client, |
| cls.services["service_offering_with_reset"] |
| ) |
| |
| cls.service_offering_without_reset = ServiceOffering.create( |
| cls.api_client, |
| cls.services["service_offering_without_reset"] |
| ) |
| |
| cls.account = Account.create( |
| cls.api_client, |
| cls.services["account"], |
| admin=True, |
| domainid=cls.domain.id |
| ) |
| cls._cleanup.append(cls.account) |
| |
| cls.vm_with_reset = VirtualMachine.create( |
| cls.api_client, |
| cls.services["virtual_machine"], |
| accountid=cls.account.name, |
| domainid=cls.account.domainid, |
| serviceofferingid=cls.service_offering_with_reset.id, |
| ) |
| cls._cleanup.append(cls.vm_with_reset) |
| cls.vm_with_reset_root_disk_id = cls.get_root_device_uuid_for_vm(cls.vm_with_reset.id, |
| cls.vm_with_reset.rootdeviceid) |
| |
| |
| cls.vm_without_reset = VirtualMachine.create( |
| cls.api_client, |
| cls.services["virtual_machine"], |
| accountid=cls.account.name, |
| domainid=cls.account.domainid, |
| serviceofferingid=cls.service_offering_without_reset.id, |
| ) |
| cls._cleanup.append(cls.vm_without_reset) |
| cls.vm_without_reset_root_disk_id = cls.get_root_device_uuid_for_vm(cls.vm_without_reset.id, |
| cls.vm_without_reset.rootdeviceid) |
| |
| return |
| |
| @classmethod |
| def tearDownClass(cls): |
| super(TestBaseImageUpdate, cls).tearDownClass() |
| |
| @classmethod |
| def get_root_device_uuid_for_vm(cls, vm_id, root_device_id): |
| volumes = Volume.list(cls.api_client, virtualmachineid=vm_id, listall=True) |
| return volumes[root_device_id].id |
| |
| def setUp(self): |
| self.apiclient = self.testClient.getApiClient() |
| self.hypervisor = self.testClient.getHypervisorInfo() |
| self.dbclient = self.testClient.getDbConnection() |
| self.cleanup = [] |
| |
| return |
| |
| def tearDown(self): |
| super(TestBaseImageUpdate, self).tearDown() |
| |
| def verify_template_listing(self, template): |
| |
| retriesCount = int(self.services["retriesCount"]) |
| |
| template_list_validation_result = None |
| |
| while True: |
| list_template_response = list_templates( |
| self.apiclient, |
| templatefilter=\ |
| self.services["template"]["templatefilter"], |
| id=template.id, |
| zoneid=self.zone.id, |
| account=self.account.name, |
| domainid=self.account.domainid |
| ) |
| |
| template_list_validation_result = validateList(list_template_response) |
| |
| if template_list_validation_result[0] == PASS: |
| break |
| |
| elif retriesCount == 0: |
| self.fail("Failed to get the template list") |
| |
| # Sleep for 5 seconds and again continue the loop if retriesCount has not reached zero |
| time.sleep(5) |
| |
| #Reduce the retriesCount until it becomes zero, when it reaches zero, exception is raised |
| retriesCount = retriesCount - 1 |
| |
| template_response = template_list_validation_result[1] |
| |
| self.assertEqual( |
| template_response.isready, |
| True, |
| "Check isready of newly created template Expected :True Got:%s" %template_response.isready |
| ) |
| |
| return |
| |
| @attr(tags=["advanced", "basic"], required_hardware="false") |
| def test_01_deploy_instance_with_is_volatile_offering(self): |
| """ Test deploy an instance with service offerings with IsVolatile set. |
| """ |
| |
| # Validate the following |
| # 1. Service offerings were created successfully |
| # 2. Vms were successfully deployed with the service offerings. |
| |
| self.debug("Checking if deployed VMs are in running state...") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| account=self.account.name, |
| domainid=self.account.domainid, |
| listall=True |
| ) |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "VM list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| for vm in vms: |
| self.debug("VM name: %s, VM state: %s" % (vm.name, vm.state)) |
| self.debug("%s" %vm) |
| self.assertEqual( |
| vm.state, |
| "Running", |
| "Vm state should be running for each VM deployed" |
| ) |
| return |
| |
| @attr(tags=["advanced", "basic"], required_hardware="false") |
| def test_02_reboot_instance_with_is_volatile_offering(self): |
| """ Test rebooting instances created with isVolatile service offerings |
| """ |
| |
| # Validate the following |
| # 1. Reboot the virtual machines. |
| # 2. Validate the following |
| # a. VM with created with isVolatile=True should have new Root disk but same IP |
| # b. VM with created with isVolatile=False should have same Root disk and IP as before reboot |
| |
| self.debug("Rebooting the virtual machines in account: %s" % |
| self.account.name) |
| try: |
| self.vm_with_reset.reboot(self.apiclient) |
| self.vm_without_reset.reboot(self.apiclient) |
| except Exception as e: |
| self.fail("Failed to reboot the virtual machines, %s" % e) |
| |
| # Check if the the root disk was destroyed and recreated for isVolatile=True |
| self.debug("Checking root disk of VM with isVolatile=True") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_with_reset.id, |
| listall=True |
| ) |
| |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "VM list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_with_reset = vm_list_validation_result[1] |
| vm_with_reset_root_disk_id = self.get_root_device_uuid_for_vm(vm_with_reset.id, vm_with_reset.rootdeviceid) |
| |
| self.assertNotEqual(self.vm_with_reset_root_disk_id, |
| vm_with_reset_root_disk_id, |
| "VM created with IsVolatile=True has same rootdeviceid : %s after reboot" %vm_with_reset_root_disk_id |
| ) |
| # Make sure it has the same IP after reboot |
| self.assertEqual(self.vm_with_reset.nic[0].ipaddress, |
| vm_with_reset.nic[0].ipaddress, |
| "VM created with IsVolatile=True doesn't have same ip after reboot. Got : %s Expected : %s" |
| %(vm_with_reset.nic[0].ipaddress, self.vm_with_reset.nic[0].ipaddress) |
| ) |
| |
| # Check if the the root disk was not destroyed for isVolatile=False |
| self.debug("Checking root disk of VM with isVolatile=False") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_without_reset.id, |
| listall=True |
| ) |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_without_reset = vm_list_validation_result[1] |
| |
| vm_without_reset_root_disk_id = self.get_root_device_uuid_for_vm(vm_without_reset.id, |
| vm_without_reset.rootdeviceid) |
| |
| self.assertEqual(self.vm_without_reset_root_disk_id, |
| vm_without_reset_root_disk_id, |
| "VM created with IsVolatile=False has different rootdeviceid after reboot Got: %s Expected : %s" |
| %(vm_without_reset_root_disk_id, self.vm_without_reset_root_disk_id) |
| ) |
| |
| # Make sure it has the same IP after reboot |
| self.assertEqual(self.vm_without_reset.nic[0].ipaddress, |
| vm_without_reset.nic[0].ipaddress, |
| "VM created with IsVolatile=True doesn't have same ip after reboot. Got : %s Expected : %s" |
| %(vm_without_reset.nic[0].ipaddress, self.vm_without_reset.nic[0].ipaddress) |
| ) |
| |
| return |
| |
| @attr(tags=["advanced", "basic"], required_hardware="false") |
| def test_03_restore_vm_with_new_template(self): |
| """ Test restoring a vm with different template than the one it was created with |
| """ |
| |
| hosts = Host.list( |
| self.apiclient, |
| type="Routing", |
| listall=True |
| ) |
| |
| host_list_validation_result = validateList(hosts) |
| |
| self.assertEqual(host_list_validation_result[0], PASS, "host list validation failed due to %s" % |
| host_list_validation_result[2]) |
| |
| hypervisor = host_list_validation_result[1].hypervisor |
| |
| for k, v in list(self.services["templates"].items()): |
| if k.lower() == hypervisor.lower(): |
| # Register new template |
| template = Template.register( |
| self.apiclient, |
| v, |
| zoneid=self.zone.id, |
| account=self.account.name, |
| domainid=self.account.domainid, |
| hypervisor=self.hypervisor |
| ) |
| self.debug( |
| "Registered a template of format: %s with ID: %s" % ( |
| v["format"], |
| template.id |
| )) |
| self.debug( |
| "Downloading template with ID: %s" % ( |
| template.id |
| )) |
| template.download(self.apiclient) |
| self._cleanup.insert(1, template) |
| |
| # Wait for template status to be changed across |
| time.sleep(self.services["sleep"]) |
| |
| self.verify_template_listing(template) |
| |
| # Restore a vm with the new template. |
| self.vm_with_reset.restore(self.apiclient, templateid=template.id) |
| self.vm_without_reset.restore(self.apiclient, templateid=template.id) |
| |
| # Make sure the VMs now have the new template ID |
| # Make sure the Ip address of the VMs haven't changed |
| self.debug("Checking template id of VM with isVolatile=True") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_with_reset.id, |
| listall=True |
| ) |
| |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "VM list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_with_reset = vm_list_validation_result[1] |
| |
| self.assertNotEqual(self.vm_with_reset.templateid, |
| vm_with_reset.templateid, |
| "VM created with IsVolatile=True has same templateid : %s after restore" %vm_with_reset.templateid |
| ) |
| |
| self.assertNotEqual(self.vm_with_reset.templateid, |
| template.id, |
| "VM created with IsVolatile=True has wrong templateid after restore Got:%s Expected: %s" |
| %(self.vm_with_reset.templateid, template.id) |
| ) |
| # Make sure it has the same IP after reboot |
| self.assertEqual(self.vm_with_reset.nic[0].ipaddress, |
| vm_with_reset.nic[0].ipaddress, |
| "VM created with IsVolatile=True doesn't have same ip after restore. Got : %s Expected : %s" |
| %(vm_with_reset.nic[0].ipaddress, self.vm_with_reset.nic[0].ipaddress) |
| ) |
| |
| # Check if the the root disk was not destroyed for isVolatile=False |
| self.debug("Checking template id of VM with isVolatile=False") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_without_reset.id, |
| listall=True |
| ) |
| |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "VM list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_without_reset = vm_list_validation_result[1] |
| |
| self.assertNotEqual(self.vm_without_reset.templateid, |
| vm_without_reset.templateid, |
| "VM created with IsVolatile=False has same templateid : %s after restore" %vm_with_reset.templateid |
| ) |
| |
| self.assertNotEqual(self.vm_without_reset.templateid, |
| template.id, |
| "VM created with IsVolatile=False has wrong templateid after restore Got:%s Expected: %s" |
| %(self.vm_without_reset.templateid, template.id) |
| ) |
| # Make sure it has the same IP after reboot |
| self.assertEqual(self.vm_without_reset.nic[0].ipaddress, |
| vm_without_reset.nic[0].ipaddress, |
| "VM created with IsVolatile=False doesn't have same ip after restore. Got : %s Expected : %s" |
| %(vm_without_reset.nic[0].ipaddress, self.vm_without_reset.nic[0].ipaddress) |
| ) |
| return |
| |
| @attr(tags=["advanced", "basic"], required_hardware="false") |
| def test_04_reoccuring_snapshot_rules(self): |
| """ |
| 1) Create a VM using the Service offering IsVolatile enabled |
| 2) Apply a recurring snapshot rule on the Volume. |
| 3) After a couple of snapshots are taken reboot the VM. |
| |
| Verify the following conditions |
| 1) New root disk should be formed |
| 2) The recurring snapshot rule should be deleted |
| """ |
| self.hypervisor = self.testClient.getHypervisorInfo() |
| if self.hypervisor.lower() in ['lxc']: |
| self.skipTest("snapshot creation is not supported in LXC") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_with_reset.id, |
| listall=True |
| ) |
| |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "vm list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_with_reset = vm_list_validation_result[1] |
| vm_with_reset_root_disk_id = self.get_root_device_uuid_for_vm( |
| vm_with_reset.id, |
| vm_with_reset.rootdeviceid |
| ) |
| |
| self.debug("Creating recurring snapshot policy for root disk on vm created with IsVolatile=True") |
| self.debug("Snapshot Policy - Type : %s Scheduled Hours : %s" %( |
| self.services["recurring_snapshot"]["intervaltype"], |
| self.services["recurring_snapshot"]["schedule"])) |
| |
| recurring_snapshot = SnapshotPolicy.create( |
| self.apiclient, |
| vm_with_reset_root_disk_id, |
| self.services["recurring_snapshot"] |
| ) |
| self.cleanup.append(recurring_snapshot) |
| |
| #ListSnapshotPolicy should return newly created policy |
| list_snapshots_policy = SnapshotPolicy.list( |
| self.apiclient, |
| id=recurring_snapshot.id, |
| volumeid=vm_with_reset_root_disk_id |
| ) |
| |
| snapshot_list_validation_result = validateList(list_snapshots_policy) |
| |
| self.assertEqual(snapshot_list_validation_result[0], PASS, "snapshot list validation failed due to %s" % |
| snapshot_list_validation_result[2]) |
| |
| snapshots_policy = snapshot_list_validation_result[1] |
| |
| self.assertEqual( |
| snapshots_policy.id, |
| recurring_snapshot.id, |
| "Check recurring snapshot id in list resources call" |
| ) |
| self.assertEqual( |
| snapshots_policy.maxsnaps, |
| self.services["recurring_snapshot"]["maxsnaps"], |
| "Check interval type in list resources call" |
| ) |
| sleep_seconds = (self.services["recurring_snapshot"]["schedule"]) * 3600 + 600 |
| sleep_minutes = sleep_seconds/60 |
| self.debug("Sleeping for %s minutes till the volume is snapshoted" %sleep_minutes) |
| time.sleep(sleep_seconds) |
| |
| retriesCount = self.services["retriesCount"] |
| while True: |
| snapshots = Snapshot.list( |
| self.apiclient, |
| volumeid=vm_with_reset_root_disk_id, |
| intervaltype=\ |
| self.services["recurring_snapshot"]["intervaltype"], |
| snapshottype=RECURRING, |
| listall=True |
| ) |
| |
| snapshot_list_validation_result = validateList(snapshots) |
| |
| if snapshot_list_validation_result[0] == PASS: |
| break |
| |
| elif retriesCount == 0: |
| self.fail("Failed to get snapshots list") |
| |
| time.sleep(60) |
| retriesCount = retriesCount - 1 |
| |
| # rebooting the vm with isVolatile = True |
| try: |
| self.vm_with_reset.reboot(self.apiclient) |
| except Exception as e: |
| self.fail("Failed to reboot the virtual machine. Error: %s" % e) |
| |
| # Check if the the root disk was destroyed and recreated for isVolatile=True |
| self.debug("Checking whether root disk of VM with isVolatile=True was destroyed") |
| vms = VirtualMachine.list( |
| self.apiclient, |
| id=self.vm_with_reset.id, |
| listall=True |
| ) |
| |
| vm_list_validation_result = validateList(vms) |
| |
| self.assertEqual(vm_list_validation_result[0], PASS, "list validation failed due to %s" % |
| vm_list_validation_result[2]) |
| |
| vm_with_reset_after_reboot = vm_list_validation_result[1] |
| |
| vm_with_reset_root_disk_id_after_reboot = self.get_root_device_uuid_for_vm( |
| vm_with_reset_after_reboot.id, |
| vm_with_reset_after_reboot.rootdeviceid |
| ) |
| |
| self.assertNotEqual(vm_with_reset_root_disk_id, |
| vm_with_reset_root_disk_id_after_reboot, |
| "VM created with IsVolatile=True has same rootdeviceid : %s after reboot" %vm_with_reset_root_disk_id_after_reboot |
| ) |
| # Make sure it has the same IP after reboot |
| self.assertEqual(vm_with_reset.nic[0].ipaddress, |
| vm_with_reset_after_reboot.nic[0].ipaddress, |
| "VM created with IsVolatile=True doesn't have same ip after reboot. Got : %s Expected : %s" |
| %(vm_with_reset_after_reboot.nic[0].ipaddress, vm_with_reset.nic[0].ipaddress) |
| ) |
| |
| # Check whether the recurring policy has been deleted from the database |
| self.debug("Checking whether snapshot rule for VM with isVolatile=True was destroyed \ |
| Here we are passing root disk id of vm before reboot which does not exist hence\ |
| listing should fail") |
| |
| with self.assertRaises(Exception): |
| listSnapshotPolicies = SnapshotPolicy.list( |
| self.apiclient, |
| volumeid=vm_with_reset_root_disk_id) |
| self.assertEqual( |
| validateList(listSnapshotPolicies)[0], |
| PASS, |
| "snapshot policies list validation failed" |
| ) |
| return |