diff --git a/test/integration/component/test_recurring_snapshots.py b/test/integration/component/test_recurring_snapshots.py new file mode 100644 index 00000000000..06df1708d94 --- /dev/null +++ b/test/integration/component/test_recurring_snapshots.py @@ -0,0 +1,405 @@ +# 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. + +from nose.plugins.attrib import attr +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 * + +class Services: + """Test Snapshots Services + """ + + 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": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "disk_offering": { + "displaytext": "Small Disk", + "name": "Small Disk", + "disksize": 1 + }, + "server_with_disk": + { + "displayname": "Test VM -With Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + + "server_without_disk": + { + "displayname": "Test VM-No Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + # For NAT rule creation + "publicport": 22, + "protocol": 'TCP', + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '192.168.100.21', + "username": "root", + "password": "password", + "port": 22, + }, + "recurring_snapshot": { + "intervaltype": 'HOURLY', + # Frequency of snapshots + "maxsnaps": 1, # Should be min 2 + "schedule": 1, + "timezone": 'US/Arizona', + # Timezone Formats - http://cloud.mindtouch.us/CloudStack_Documentation/Developer's_Guide%3A_CloudStack + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "volume": { + "diskname": "APP Data Volume", + "size": 1, # in GBs + "diskdevice": ['/dev/xvdb', '/dev/sdb', '/dev/hdb', '/dev/vdb' ], # Data Disk + }, + "paths": { + "mount_dir": "/mnt/tmp", + "sub_dir": "test", + "sub_lvl_dir1": "test1", + "sub_lvl_dir2": "test2", + "random_data": "random.data", + }, + "ostype": "CentOS 5.3 (64-bit)", + # Cent OS 5.3 (64 bit) + "sleep": 60, + "timeout": 10, + } + + +class TestRecurringSnapshots(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestRecurringSnapshots, 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.services['mode'] = cls.zone.networktype + cls.disk_offering = DiskOffering.create( + cls.api_client, + cls.services["disk_offering"] + ) + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + + cls.services["domainid"] = cls.domain.id + cls.services["server_with_disk"]["zoneid"] = cls.zone.id + cls.services["server_with_disk"]["diskoffering"] = cls.disk_offering.id + + cls.services["server_without_disk"]["zoneid"] = cls.zone.id + + cls.services["templates"]["ostypeid"] = template.ostypeid + cls.services["zoneid"] = cls.zone.id + cls.services["diskoffering"] = cls.disk_offering.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = cls.virtual_machine_with_disk = \ + VirtualMachine.create( + cls.api_client, + cls.services["server_with_disk"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls.virtual_machine_without_disk = \ + VirtualMachine.create( + cls.api_client, + cls.services["server_without_disk"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup = [ + cls.service_offering, + cls.disk_offering, + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(speed = "slow") + @attr(tags = ["advanced", "advancedns", "smoke"]) + def test_recurring_snapshot_root_disk(self): + """Test Recurring Snapshot Root Disk + """ + #1. Create snapshot policy for root disk + #2. ListSnapshot policy should return newly created policy + #3. Verify only most recent number (maxsnaps) snapshots retailed + + volume = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine_with_disk.id, + type='ROOT', + listall=True + ) + self.assertEqual( + isinstance(volume, list), + True, + "Check list response returns a valid list" + ) + recurring_snapshot = SnapshotPolicy.create( + self.apiclient, + volume[0].id, + self.services["recurring_snapshot"] + ) + self.cleanup.append(recurring_snapshot) + + #ListSnapshotPolicy should return newly created policy + list_snapshots_policy = list_snapshot_policy( + self.apiclient, + id=recurring_snapshot.id, + volumeid=volume[0].id + ) + self.assertEqual( + isinstance(list_snapshots_policy, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + list_snapshots_policy, + None, + "Check if result exists in list item call" + ) + snapshots_policy = list_snapshots_policy[0] + 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 for (maxsnaps+1) hours to verify + # only maxsnaps snapshots are retained + time.sleep( + (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 + ) + + timeout = self.services["timeout"] + while True: + snapshots = list_snapshots( + self.apiclient, + volumeid=volume[0].id, + intervaltype=\ + self.services["recurring_snapshot"]["intervaltype"], + snapshottype='RECURRING', + listall=True + ) + + if isinstance(snapshots, list): + break + + elif timeout == 0: + raise Exception("List snapshots API call failed.") + + time.sleep(1) + timeout = timeout - 1 + + self.assertEqual( + isinstance(snapshots, list), + True, + "Check list response returns a valid list" + ) + + self.assertEqual( + len(snapshots), + self.services["recurring_snapshot"]["maxsnaps"], + "Check maximum number of recurring snapshots retained" + ) + return + + @attr(speed = "slow") + @attr(tags = ["advanced", "advancedns", "smoke"]) + def test_recurring_snapshot_data_disk(self): + """Test Recurring Snapshot data Disk + """ + #1. Create snapshot policy for data disk + #2. ListSnapshot policy should return newly created policy + #3. Verify only most recent number (maxsnaps) snapshots retailed + + volume = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine_with_disk.id, + type='DATADISK', + listall=True + ) + + self.assertEqual( + isinstance(volume, list), + True, + "Check list response returns a valid list" + ) + + recurring_snapshot = SnapshotPolicy.create( + self.apiclient, + volume[0].id, + self.services["recurring_snapshot"] + ) + self.cleanup.append(recurring_snapshot) + #ListSnapshotPolicy should return newly created policy + list_snapshots_policy = list_snapshot_policy( + self.apiclient, + id=recurring_snapshot.id, + volumeid=volume[0].id + ) + + self.assertEqual( + isinstance(list_snapshots_policy, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + list_snapshots_policy, + None, + "Check if result exists in list item call" + ) + snapshots_policy = list_snapshots_policy[0] + 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 for (maxsnaps) hours to verify only maxsnaps snapshots are + # retained + time.sleep( + (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 + ) + + timeout = self.services["timeout"] + while True: + snapshots = list_snapshots( + self.apiclient, + volumeid=volume[0].id, + intervaltype=\ + self.services["recurring_snapshot"]["intervaltype"], + snapshottype='RECURRING', + listall=True + ) + + if isinstance(snapshots, list): + break + + elif timeout == 0: + raise Exception("List snapshots API call failed.") + + time.sleep(1) + timeout = timeout - 1 + + self.assertEqual( + isinstance(snapshots, list), + True, + "Check list response returns a valid list" + ) + self.assertEqual( + len(snapshots), + self.services["recurring_snapshot"]["maxsnaps"], + "Check maximum number of recurring snapshots retained" + ) + return \ No newline at end of file diff --git a/test/integration/component/test_snapshot_gc.py b/test/integration/component/test_snapshot_gc.py new file mode 100644 index 00000000000..369543d0bc7 --- /dev/null +++ b/test/integration/component/test_snapshot_gc.py @@ -0,0 +1,474 @@ +# 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. + +import marvin +from nose.plugins.attrib import attr +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.remoteSSHClient import remoteSSHClient + + +class Services: + """Test Snapshots Services + """ + + 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": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "disk_offering": { + "displaytext": "Small Disk", + "name": "Small Disk", + "disksize": 1 + }, + "server_with_disk": + { + "displayname": "Test VM -With Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + + "server_without_disk": + { + "displayname": "Test VM-No Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + # For NAT rule creation + "publicport": 22, + "protocol": 'TCP', + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '192.168.100.21', + "username": "root", + "password": "password", + "port": 22, + }, + "recurring_snapshot": { + "intervaltype": 'HOURLY', + # Frequency of snapshots + "maxsnaps": 1, # Should be min 2 + "schedule": 1, + "timezone": 'US/Arizona', + # Timezone Formats - http://cloud.mindtouch.us/CloudStack_Documentation/Developer's_Guide%3A_CloudStack + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "volume": { + "diskname": "APP Data Volume", + "size": 1, # in GBs + "diskdevice": ['/dev/xvdb', '/dev/sdb', '/dev/hdb', '/dev/vdb' ], # Data Disk + }, + "paths": { + "mount_dir": "/mnt/tmp", + "sub_dir": "test", + "sub_lvl_dir1": "test1", + "sub_lvl_dir2": "test2", + "random_data": "random.data", + }, + "ostype": "CentOS 5.3 (64-bit)", + # Cent OS 5.3 (64 bit) + "sleep": 60, + "timeout": 10, + } + + +class TestAccountSnapshotClean(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestAccountSnapshotClean, 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.services['mode'] = cls.zone.networktype + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["server"]["zoneid"] = cls.zone.id + + cls.services["template"] = template.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["server"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + # Get the Root disk of VM + volumes = list_volumes( + cls.api_client, + virtualmachineid=cls.virtual_machine.id, + type='ROOT', + listall=True + ) + volume = volumes[0] + + # Create a snapshot from the ROOTDISK + cls.snapshot = Snapshot.create(cls.api_client, volumes[0].id) + + cls._cleanup = [ + cls.service_offering, + ] + return + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(speed = "slow") + @attr(tags = ["advanced", "advancedns", "basic", "sg"]) + def test_02_accountSnapshotClean(self): + """Test snapshot cleanup after account deletion + """ + # Validate the following + # 1. listAccounts API should list out the newly created account + # 2. listVirtualMachines() command should return the deployed VM. + # State of this VM should be "Running" + # 3. a)listSnapshots should list the snapshot that was created. + # b)verify that secondary storage NFS share contains the reqd volume + # under /secondary/snapshots/$accountid/$volumeid/$snapshot_uuid + # 4. a)listAccounts should not list account that is deleted + # b) snapshot image($snapshot_uuid) should be deleted from the + # /secondary/snapshots/$accountid/$volumeid/ + + accounts = list_accounts( + self.apiclient, + id=self.account.id + ) + self.assertEqual( + isinstance(accounts, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(accounts), + 0, + "Check list Accounts response" + ) + + # VM should be in 'Running' state + virtual_machines = list_virtual_machines( + self.apiclient, + id=self.virtual_machine.id + ) + self.assertEqual( + isinstance(virtual_machines, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + len(virtual_machines), + 0, + "Check list virtual machines response" + ) + for virtual_machine in virtual_machines: + self.debug("VM ID: %s, VM state: %s" % ( + virtual_machine.id, + virtual_machine.state + )) + self.assertEqual( + virtual_machine.state, + 'Running', + "Check list VM response for Running state" + ) + + # Verify the snapshot was created or not + snapshots = list_snapshots( + self.apiclient, + id=self.snapshot.id + ) + self.assertEqual( + isinstance(snapshots, list), + True, + "Check list response returns a valid list" + ) + self.assertNotEqual( + snapshots, + None, + "Check if result exists in list snapshots call" + ) + self.assertEqual( + snapshots[0].id, + self.snapshot.id, + "Check snapshot id in list resources call" + ) + + # Fetch values from database + qresultset = self.dbclient.execute( + "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ + % self.snapshot.id + ) + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DB response returns a valid list" + ) + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + + qresult = qresultset[0] + snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID + account_id = qresult[1] + volume_id = qresult[2] + + # Get the Secondary Storage details from list Hosts + hosts = list_hosts( + self.apiclient, + type='SecondaryStorage', + zoneid=self.zone.id + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list response returns a valid list" + ) + uuids = [] + for host in hosts: + # hosts[0].name = "nfs://192.168.100.21/export/test" + parse_url = (host.name).split('/') + # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] + + # Stripping end ':' from storage type + storage_type = parse_url[0][:-1] + # Split IP address and export path from name + sec_storage_ip = parse_url[2] + # Sec Storage IP: 192.168.100.21 + + if sec_storage_ip[-1] != ":": + sec_storage_ip = sec_storage_ip + ":" + + export_path = '/'.join(parse_url[3:]) + # Export path: export/test + + # Sleep to ensure that snapshot is reflected in sec storage + time.sleep(self.services["sleep"]) + try: + # Login to Secondary storage VM to check snapshot present on sec disk + ssh_client = remoteSSHClient( + self.services["mgmt_server"]["ipaddress"], + self.services["mgmt_server"]["port"], + self.services["mgmt_server"]["username"], + self.services["mgmt_server"]["password"], + ) + + cmds = [ + "mkdir -p %s" % self.services["paths"]["mount_dir"], + "mount -t %s %s/%s %s" % ( + storage_type, + sec_storage_ip, + export_path, + self.services["paths"]["mount_dir"] + ), + "ls %s/snapshots/%s/%s" % ( + self.services["paths"]["mount_dir"], + account_id, + volume_id + ), + ] + + for c in cmds: + self.debug("command: %s" % c) + result = ssh_client.execute(c) + self.debug("Result: %s" % result) + + uuids.append(result) + + # Unmount the Sec Storage + cmds = [ + "umount %s" % (self.services["mount_dir"]), + ] + for c in cmds: + result = ssh_client.execute(c) + except Exception as e: + self.fail("SSH failed for management server: %s - %s" % + (self.services["mgmt_server"]["ipaddress"], e)) + + res = str(uuids) + self.assertEqual( + res.count(snapshot_uuid), + 1, + "Check snapshot UUID in secondary storage and database" + ) + + self.debug("Deleting account: %s" % self.account.name) + # Delete account + self.account.delete(self.apiclient) + + interval = list_configurations( + self.apiclient, + name='account.cleanup.interval' + ) + self.assertEqual( + isinstance(interval, list), + True, + "Check list response returns a valid list" + ) + self.debug("account.cleanup.interval: %s" % interval[0].value) + + # Wait for account cleanup interval + time.sleep(int(interval[0].value) * 2) + + accounts = list_accounts( + self.apiclient, + id=self.account.id + ) + + self.assertEqual( + accounts, + None, + "List accounts should return empty list after account deletion" + ) + + uuids = [] + for host in hosts: + # hosts[0].name = "nfs://192.168.100.21/export/test" + parse_url = (host.name).split('/') + # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] + + # Stripping end ':' from storage type + storage_type = parse_url[0][:-1] + # Split IP address and export path from name + sec_storage_ip = parse_url[2] + # Sec Storage IP: 192.168.100.21 + + if sec_storage_ip[-1] != ":": + sec_storage_ip = sec_storage_ip + ":" + + export_path = '/'.join(parse_url[3:]) + # Export path: export/test + + try: + cmds = [ + "mount -t %s %s/%s %s" % ( + storage_type, + sec_storage_ip, + export_path, + self.services["paths"]["mount_dir"] + ), + "ls %s/snapshots/%s/%s" % ( + self.services["paths"]["mount_dir"], + account_id, + volume_id + ), + ] + + for c in cmds: + self.debug("command: %s" % c) + result = ssh_client.execute(c) + self.debug("Result: %s" % result) + + uuids.append(result) + # Unmount the Sec Storage + cmds = [ + "umount %s" % (self.services["paths"]["mount_dir"]), + ] + for c in cmds: + self.debug("command: %s" % c) + result = ssh_client.execute(c) + self.debug("Result: %s" % result) + + except Exception as e: + self.fail("SSH failed for management server: %s - %s" % + (self.services["mgmt_server"]["ipaddress"], e)) + + res = str(uuids) + self.assertNotEqual( + res.count(snapshot_uuid), + 1, + "Check snapshot UUID in secondary storage and database" + ) + return \ No newline at end of file diff --git a/test/integration/component/test_snapshot_limits.py b/test/integration/component/test_snapshot_limits.py new file mode 100644 index 00000000000..5acd066a478 --- /dev/null +++ b/test/integration/component/test_snapshot_limits.py @@ -0,0 +1,384 @@ +# 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. + +import marvin +from nose.plugins.attrib import attr +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.remoteSSHClient import remoteSSHClient + + +class Services: + """Test Snapshots Services + """ + + 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": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "disk_offering": { + "displaytext": "Small Disk", + "name": "Small Disk", + "disksize": 1 + }, + "server_with_disk": + { + "displayname": "Test VM -With Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + + "server_without_disk": + { + "displayname": "Test VM-No Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + # For NAT rule creation + "publicport": 22, + "protocol": 'TCP', + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '192.168.100.21', + "username": "root", + "password": "password", + "port": 22, + }, + "recurring_snapshot": { + "intervaltype": 'HOURLY', + # Frequency of snapshots + "maxsnaps": 1, # Should be min 2 + "schedule": 1, + "timezone": 'US/Arizona', + # Timezone Formats - http://cloud.mindtouch.us/CloudStack_Documentation/Developer's_Guide%3A_CloudStack + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "volume": { + "diskname": "APP Data Volume", + "size": 1, # in GBs + "diskdevice": ['/dev/xvdb', '/dev/sdb', '/dev/hdb', '/dev/vdb' ], # Data Disk + }, + "paths": { + "mount_dir": "/mnt/tmp", + "sub_dir": "test", + "sub_lvl_dir1": "test1", + "sub_lvl_dir2": "test2", + "random_data": "random.data", + }, + "ostype": "CentOS 5.3 (64-bit)", + # Cent OS 5.3 (64 bit) + "sleep": 60, + "timeout": 10, + } + + +class TestSnapshotLimit(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestSnapshotLimit, 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.services['mode'] = cls.zone.networktype + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["server"]["zoneid"] = cls.zone.id + + cls.services["template"] = template.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = VirtualMachine.create( + cls.api_client, + cls.services["server"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id + ) + cls._cleanup = [ + cls.service_offering, + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(speed = "slow") + @attr(tags = ["advanced", "advancedns"]) + def test_04_snapshot_limit(self): + """Test snapshot limit in snapshot policies + """ + # Validate the following + # 1. Perform hourly recurring snapshot on the root disk of VM and keep + # the maxsnapshots as 1 + # 2. listSnapshots should list the snapshot that was created + # snapshot folder in secondary storage should contain only one + # snapshot image(/secondary/snapshots/$accountid/$volumeid/) + + # Get the Root disk of VM + volumes = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='ROOT', + listall=True + ) + self.assertEqual( + isinstance(volumes, list), + True, + "Check list response returns a valid list" + ) + volume = volumes[0] + + # Create a snapshot policy + recurring_snapshot = SnapshotPolicy.create( + self.apiclient, + volume.id, + self.services["recurring_snapshot"] + ) + self.cleanup.append(recurring_snapshot) + + snapshot_policy = list_snapshot_policy( + self.apiclient, + id=recurring_snapshot.id, + volumeid=volume.id + ) + self.assertEqual( + isinstance(snapshot_policy, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + snapshot_policy, + None, + "Check if result exists in list item call" + ) + + self.assertEqual( + snapshot_policy[0].id, + recurring_snapshot.id, + "Check recurring snapshot id in list resources call" + ) + self.assertEqual( + snapshot_policy[0].maxsnaps, + self.services["recurring_snapshot"]["maxsnaps"], + "Check interval type in list resources call" + ) + # Sleep for (maxsnaps+1) hours to verify + # only maxsnaps snapshots are retained + time.sleep( + (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 + ) + + # Verify the snapshot was created or not + snapshots = list_snapshots( + self.apiclient, + volumeid=volume.id, + intervaltype=\ + self.services["recurring_snapshot"]["intervaltype"], + snapshottype='RECURRING', + listall=True + ) + + self.assertEqual( + isinstance(snapshots, list), + True, + "Check list response returns a valid list" + ) + self.assertEqual( + len(snapshots), + self.services["recurring_snapshot"]["maxsnaps"], + "Check maximum number of recurring snapshots retained" + ) + snapshot = snapshots[0] + # Sleep to ensure that snapshot is reflected in sec storage + time.sleep(self.services["sleep"]) + + # Fetch values from database + qresultset = self.dbclient.execute( + "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ + % snapshot.id + ) + self.assertEqual( + isinstance(qresultset, list), + True, + "Check DBQuery returns a valid list" + ) + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + + qresult = qresultset[0] + snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID + account_id = qresult[1] + volume_id = qresult[2] + + # Get the Secondary Storage details from list Hosts + hosts = list_hosts( + self.apiclient, + type='SecondaryStorage', + zoneid=self.zone.id + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list response returns a valid list" + ) + uuids = [] + for host in hosts: + # hosts[0].name = "nfs://192.168.100.21/export/test" + parse_url = (host.name).split('/') + # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] + + # Stripping end ':' from storage type + storage_type = parse_url[0][:-1] + # Split IP address and export path from name + sec_storage_ip = parse_url[2] + # Sec Storage IP: 192.168.100.21 + + if sec_storage_ip[-1] != ":": + sec_storage_ip = sec_storage_ip + ":" + + export_path = '/'.join(parse_url[3:]) + # Export path: export/test + try: + # Login to VM to check snapshot present on sec disk + ssh_client = remoteSSHClient( + self.services["mgmt_server"]["ipaddress"], + self.services["mgmt_server"]["port"], + self.services["mgmt_server"]["username"], + self.services["mgmt_server"]["password"], + ) + + cmds = [ + "mkdir -p %s" % self.services["paths"]["mount_dir"], + "mount -t %s %s/%s %s" % ( + storage_type, + sec_storage_ip, + export_path, + self.services["paths"]["mount_dir"] + ), + "ls %s/snapshots/%s/%s" % ( + self.services["paths"]["mount_dir"], + account_id, + volume_id + ), + ] + + for c in cmds: + result = ssh_client.execute(c) + + uuids.append(result) + + # Unmount the Sec Storage + cmds = [ + "umount %s" % (self.services["paths"]["mount_dir"]), + ] + for c in cmds: + result = ssh_client.execute(c) + except Exception as e: + raise Exception( + "SSH access failed for management server: %s - %s" % + (self.services["mgmt_server"]["ipaddress"], e)) + + res = str(uuids) + self.assertEqual( + res.count(snapshot_uuid), + 1, + "Check snapshot UUID in secondary storage and database" + ) + return \ No newline at end of file diff --git a/test/integration/component/test_snapshots.py b/test/integration/component/test_snapshots.py index 708a8b29996..18dcefd7672 100644 --- a/test/integration/component/test_snapshots.py +++ b/test/integration/component/test_snapshots.py @@ -17,7 +17,6 @@ """ P1 tests for Snapshots """ #Import Local Modules -import marvin from nose.plugins.attrib import attr from marvin.cloudstackTestCase import * from marvin.cloudstackAPI import * @@ -111,7 +110,7 @@ class Services: "volume": { "diskname": "APP Data Volume", "size": 1, # in GBs - "diskdevice": "/dev/xvdb", # Data Disk + "diskdevice": ['/dev/xvdb', '/dev/sdb', '/dev/hdb', '/dev/vdb' ], # Data Disk }, "paths": { "mount_dir": "/mnt/tmp", @@ -120,15 +119,6 @@ class Services: "sub_lvl_dir2": "test2", "random_data": "random.data", }, - "diskdevice": "/dev/xvda", - "diskname": "TestDiskServ", - "size": 1, # GBs - - "mount_dir": "/mnt/tmp", - "sub_dir": "test", - "sub_lvl_dir1": "test1", - "sub_lvl_dir2": "test2", - "random_data": "random.data", "ostype": "CentOS 5.3 (64-bit)", # Cent OS 5.3 (64 bit) "sleep": 60, @@ -136,229 +126,6 @@ class Services: } -class TestSnapshotRootDisk(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - cls.api_client = super(TestSnapshotRootDisk, 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.services['mode'] = cls.zone.networktype - - template = get_template( - cls.api_client, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["domainid"] = cls.domain.id - cls.services["server_without_disk"]["zoneid"] = cls.zone.id - cls.services["templates"]["ostypeid"] = template.ostypeid - cls.services["zoneid"] = cls.zone.id - - # Create VMs, NAT Rules etc - cls.account = Account.create( - cls.api_client, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.services["account"] = cls.account.name - - cls.service_offering = ServiceOffering.create( - cls.api_client, - cls.services["service_offering"] - ) - cls.virtual_machine = cls.virtual_machine_with_disk = \ - VirtualMachine.create( - cls.api_client, - cls.services["server_without_disk"], - templateid=template.id, - accountid=cls.account.name, - domainid=cls.account.domainid, - serviceofferingid=cls.service_offering.id, - mode=cls.services["mode"] - ) - cls._cleanup = [ - cls.service_offering, - cls.account, - ] - return - - @classmethod - def tearDownClass(cls): - try: - #Cleanup resources used - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns", "smoke"]) - def test_01_snapshot_root_disk(self): - """Test Snapshot Root Disk - """ - - # Validate the following - # 1. listSnapshots should list the snapshot that was created. - # 2. verify that secondary storage NFS share contains - # the reqd volume under - # /secondary/snapshots//$account_id/$volumeid/$snapshot_uuid - # 3. verify backup_snap_id was non null in the `snapshots` table - - volumes = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine_with_disk.id, - type='ROOT', - listall=True - ) - - snapshot = Snapshot.create( - self.apiclient, - volumes[0].id, - account=self.account.name, - domainid=self.account.domainid - ) - self.debug("Snapshot created: ID - %s" % snapshot.id) - - snapshots = list_snapshots( - self.apiclient, - id=snapshot.id - ) - self.assertEqual( - isinstance(snapshots, list), - True, - "Check list response returns a valid list" - ) - - self.assertNotEqual( - snapshots, - None, - "Check if result exists in list item call" - ) - self.assertEqual( - snapshots[0].id, - snapshot.id, - "Check resource id in list resources call" - ) - self.debug( - "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ - % str(snapshot.id) - ) - qresultset = self.dbclient.execute( - "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ - % str(snapshot.id) - ) - self.assertNotEqual( - len(qresultset), - 0, - "Check DB Query result set" - ) - - qresult = qresultset[0] - - snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID - account_id = qresult[1] - volume_id = qresult[2] - - self.assertNotEqual( - str(snapshot_uuid), - 'NULL', - "Check if backup_snap_id is not null" - ) - - # Get the Secondary Storage details from list Hosts - hosts = list_hosts( - self.apiclient, - type='SecondaryStorage', - zoneid=self.zone.id - ) - self.assertEqual( - isinstance(hosts, list), - True, - "Check list response returns a valid list" - ) - uuids = [] - for host in hosts: - # hosts[0].name = "nfs://192.168.100.21/export/test" - parse_url = (host.name).split('/') - # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] - - # Split IP address and export path from name - sec_storage_ip = parse_url[2] - # Sec Storage IP: 192.168.100.21 - - export_path = '/'.join(parse_url[3:]) - # Export path: export/test - - try: - # Login to VM to check snapshot present on sec disk - ssh_client = self.virtual_machine_with_disk.get_ssh_client() - - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount %s/%s %s" % ( - sec_storage_ip, - export_path, - self.services["mount_dir"] - ), - "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], - account_id, - volume_id - ), - ] - - for c in cmds: - self.debug(c) - result = ssh_client.execute(c) - self.debug(result) - - except Exception: - self.fail("SSH failed for Virtual machine: %s" % - self.virtual_machine_with_disk.ipaddress) - - uuids.append(result) - # Unmount the Sec Storage - cmds = [ - "umount %s" % (self.services["mount_dir"]), - ] - try: - for c in cmds: - self.debug(c) - result = ssh_client.execute(c) - self.debug(result) - - except Exception as e: - self.fail("SSH failed for Virtual machine: %s" % - self.virtual_machine_with_disk.ipaddress) - - res = str(uuids) - # Check snapshot UUID in secondary storage and database - self.assertEqual( - res.count(snapshot_uuid), - 1, - "Check snapshot UUID in secondary storage and database" - ) - return - - class TestSnapshots(cloudstackTestCase): @classmethod @@ -389,11 +156,6 @@ class TestSnapshots(cloudstackTestCase): cls.services["zoneid"] = cls.zone.id cls.services["diskoffering"] = cls.disk_offering.id - #determine device type from hypervisor - hosts = Host.list(cls.api_client, id=cls.virtual_machine.hostid) - if len(hosts) > 0 and hosts[0].hypervisor.lower() == "kvm": - cls.service["volume"]["diskdevice"] = "/dev/vdb" - # Create VMs, NAT Rules etc cls.account = Account.create( cls.api_client, @@ -555,14 +317,14 @@ class TestSnapshots(cloudstackTestCase): ssh_client = self.virtual_machine_with_disk.get_ssh_client() cmds = [ - "mkdir -p %s" % self.services["mount_dir"], + "mkdir -p %s" % self.services["paths"]["mount_dir"], "mount %s/%s %s" % ( sec_storage_ip, export_path, - self.services["mount_dir"] + self.services["paths"]["mount_dir"] ), "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], + self.services["paths"]["mount_dir"], account_id, volume_id ), @@ -579,7 +341,7 @@ class TestSnapshots(cloudstackTestCase): uuids.append(result) # Unmount the Sec Storage cmds = [ - "umount %s" % (self.services["mount_dir"]), + "umount %s" % (self.services["paths"]["mount_dir"]), ] try: for c in cmds: @@ -854,192 +616,213 @@ class TestSnapshots(cloudstackTestCase): return @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns", "smoke"]) - def test_05_recurring_snapshot_root_disk(self): - """Test Recurring Snapshot Root Disk + @attr(tags = ["advanced", "advancedns", "basic", "sg"]) + def test_03_snapshot_detachedDisk(self): + """Test snapshot from detached disk """ - #1. Create snapshot policy for root disk - #2. ListSnapshot policy should return newly created policy - #3. Verify only most recent number (maxsnaps) snapshots retailed + # Validate the following + # 1. login in VM and write some data on data disk(use fdisk to + # partition datadisk,fdisk /dev/sdb, and make filesystem using + # mkfs.ext3) + # 2. Detach the data disk and write some data on data disk + # 3. perform the snapshot on the detached volume + # 4. listvolumes with VM id shouldn't show the detached volume + # 5. listSnapshots should list the snapshot that was created + # 6. verify that secondary storage NFS share contains the reqd volume + # under /secondary/snapshots/$accountid/$volumeid/$snapshot_uuid + # 7. verify backup_snap_id was non null in the `snapshots` table - volume = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine_with_disk.id, - type='ROOT', - listall=True - ) + volumes = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='DATADISK', + listall=True + ) self.assertEqual( - isinstance(volume, list), + isinstance(volumes, list), True, "Check list response returns a valid list" ) - recurring_snapshot = SnapshotPolicy.create( - self.apiclient, - volume[0].id, - self.services["recurring_snapshot"] - ) - self.cleanup.append(recurring_snapshot) + volume = volumes[0] + random_data_0 = random_gen(100) + random_data_1 = random_gen(100) + try: + ssh_client = self.virtual_machine.get_ssh_client() - #ListSnapshotPolicy should return newly created policy - list_snapshots_policy = list_snapshot_policy( - self.apiclient, - id=recurring_snapshot.id, - volumeid=volume[0].id - ) - self.assertEqual( - isinstance(list_snapshots_policy, list), - True, - "Check list response returns a valid list" + #Format partition using ext3 + format_volume_to_ext3( + ssh_client, + self.services["volume"]["diskdevice"] + ) + cmds = [ + "mkdir -p %s" % self.services["paths"]["mount_dir"], + "mount %s1 %s" % ( + self.services["volume"]["diskdevice"], + self.services["paths"]["mount_dir"] + ), + "pushd %s" % self.services["mount_dir"], + "mkdir -p %s/{%s,%s} " % ( + self.services["sub_dir"], + self.services["sub_lvl_dir1"], + self.services["sub_lvl_dir2"] + ), + "echo %s > %s/%s/%s" % ( + random_data_0, + self.services["sub_dir"], + self.services["sub_lvl_dir1"], + self.services["random_data"] + ), + "echo %s > %s/%s/%s" % ( + random_data_1, + self.services["sub_dir"], + self.services["sub_lvl_dir2"], + self.services["random_data"] + ), + "sync", + ] + for c in cmds: + self.debug(ssh_client.execute(c)) + + #detach volume from VM + cmd = detachVolume.detachVolumeCmd() + cmd.id = volume.id + self.apiclient.detachVolume(cmd) + + #Create snapshot from detached volume + snapshot = Snapshot.create(self.apiclient, volume.id) + self.cleanup.append(snapshot) + + volumes = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine.id, + type='DATADISK', + listall=True + ) + + self.assertEqual( + volumes, + None, + "Check Volume is detached" + ) + + # Verify the snapshot was created or not + snapshots = list_snapshots( + self.apiclient, + id=snapshot.id + ) + self.assertNotEqual( + snapshots, + None, + "Check if result exists in list snapshots call" + ) + self.assertEqual( + snapshots[0].id, + snapshot.id, + "Check snapshot id in list resources call" + ) + except Exception as e: + self.fail("SSH failed for VM with IP: %s - %s" % + (self.virtual_machine.ipaddress, e)) + + # Fetch values from database + qresultset = self.dbclient.execute( + "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ + % snapshot.id ) self.assertNotEqual( - list_snapshots_policy, - None, - "Check if result exists in list item call" + len(qresultset), + 0, + "Check DB Query result set" ) - snapshots_policy = list_snapshots_policy[0] - 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 for (maxsnaps+1) hours to verify - # only maxsnaps snapshots are retained - time.sleep( - (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 - ) - timeout = self.services["timeout"] - while True: - snapshots = list_snapshots( - self.apiclient, - volumeid=volume[0].id, - intervaltype=\ - self.services["recurring_snapshot"]["intervaltype"], - snapshottype='RECURRING', - listall=True - ) - - if isinstance(snapshots, list): - break - - elif timeout == 0: - raise Exception("List snapshots API call failed.") - - time.sleep(1) - timeout = timeout - 1 - - self.assertEqual( - isinstance(snapshots, list), - True, - "Check list response returns a valid list" - ) - - self.assertEqual( - len(snapshots), - self.services["recurring_snapshot"]["maxsnaps"], - "Check maximum number of recurring snapshots retained" - ) - return - - @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns", "smoke"]) - def test_06_recurring_snapshot_data_disk(self): - """Test Recurring Snapshot data Disk - """ - #1. Create snapshot policy for data disk - #2. ListSnapshot policy should return newly created policy - #3. Verify only most recent number (maxsnaps) snapshots retailed - - volume = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine_with_disk.id, - type='DATADISK', - listall=True - ) - - self.assertEqual( - isinstance(volume, list), - True, - "Check list response returns a valid list" - ) - - recurring_snapshot = SnapshotPolicy.create( - self.apiclient, - volume[0].id, - self.services["recurring_snapshot"] - ) - self.cleanup.append(recurring_snapshot) - #ListSnapshotPolicy should return newly created policy - list_snapshots_policy = list_snapshot_policy( - self.apiclient, - id=recurring_snapshot.id, - volumeid=volume[0].id - ) - - self.assertEqual( - isinstance(list_snapshots_policy, list), - True, - "Check list response returns a valid list" - ) + qresult = qresultset[0] + snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID + account_id = qresult[1] + volume_id = qresult[2] self.assertNotEqual( - list_snapshots_policy, - None, - "Check if result exists in list item call" - ) - snapshots_policy = list_snapshots_policy[0] - 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" + str(qresult[0]), + 'NULL', + "Check if backup_snap_id is not null" ) - # Sleep for (maxsnaps) hours to verify only maxsnaps snapshots are - # retained - time.sleep( - (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 - ) - - timeout = self.services["timeout"] - while True: - snapshots = list_snapshots( - self.apiclient, - volumeid=volume[0].id, - intervaltype=\ - self.services["recurring_snapshot"]["intervaltype"], - snapshottype='RECURRING', - listall=True - ) - - if isinstance(snapshots, list): - break - - elif timeout == 0: - raise Exception("List snapshots API call failed.") - - time.sleep(1) - timeout = timeout - 1 - + # Get the Secondary Storage details from list Hosts + hosts = list_hosts( + self.apiclient, + type='SecondaryStorage', + zoneid=self.zone.id + ) self.assertEqual( - isinstance(snapshots, list), + isinstance(hosts, list), True, "Check list response returns a valid list" ) + uuids = [] + for host in hosts: + # hosts[0].name = "nfs://192.168.100.21/export/test" + parse_url = (host.name).split('/') + # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] + + # Stripping end ':' from storage type + storage_type = parse_url[0][:-1] + + # Split IP address and export path from name + sec_storage_ip = parse_url[2] + # Sec Storage IP: 192.168.100.21 + + if sec_storage_ip[-1] != ":": + sec_storage_ip = sec_storage_ip + ":" + + export_path = '/'.join(parse_url[3:]) + # Export path: export/test + + # Sleep to ensure that snapshot is reflected in sec storage + time.sleep(self.services["sleep"]) + try: + # Login to Management server to check snapshot present on + # sec disk + ssh_client = remoteSSHClient( + self.services["mgmt_server"]["ipaddress"], + self.services["mgmt_server"]["port"], + self.services["mgmt_server"]["username"], + self.services["mgmt_server"]["password"], + ) + + cmds = [ + "mkdir -p %s" % self.services["mount_dir"], + "mount -t %s %s/%s %s" % ( + storage_type, + sec_storage_ip, + export_path, + self.services["mount_dir"] + ), + "ls %s/snapshots/%s/%s" % ( + self.services["mount_dir"], + account_id, + volume_id + ), + ] + + for c in cmds: + result = ssh_client.execute(c) + + uuids.append(result) + # Unmount the Sec Storage + cmds = [ + "umount %s" % (self.services["mount_dir"]), + ] + for c in cmds: + result = ssh_client.execute(c) + except Exception as e: + self.fail("SSH failed for management server: %s - %s" % + (self.services["mgmt_server"]["ipaddress"], e)) + + res = str(uuids) self.assertEqual( - len(snapshots), - self.services["recurring_snapshot"]["maxsnaps"], - "Check maximum number of recurring snapshots retained" - ) + res.count(snapshot_uuid), + 1, + "Check snapshot UUID in secondary storage and database" + ) return @attr(speed = "slow") @@ -1063,30 +846,30 @@ class TestSnapshots(cloudstackTestCase): ssh_client = self.virtual_machine.get_ssh_client() cmds = [ - "mkdir -p %s" % self.services["mount_dir"], + "mkdir -p %s" % self.services["paths"]["mount_dir"], "mount %s1 %s" % ( self.services["rootdisk"], - self.services["mount_dir"] + self.services["paths"]["mount_dir"] ), "mkdir -p %s/%s/{%s,%s} " % ( - self.services["mount_dir"], - self.services["sub_dir"], - self.services["sub_lvl_dir1"], - self.services["sub_lvl_dir2"] + self.services["paths"]["mount_dir"], + self.services["paths"]["sub_dir"], + self.services["paths"]["sub_lvl_dir1"], + self.services["paths"]["sub_lvl_dir2"] ), "echo %s > %s/%s/%s/%s" % ( random_data_0, - self.services["mount_dir"], - self.services["sub_dir"], - self.services["sub_lvl_dir1"], - self.services["random_data"] + self.services["paths"]["mount_dir"], + self.services["paths"]["sub_dir"], + self.services["paths"]["sub_lvl_dir1"], + self.services["paths"]["random_data"] ), "echo %s > %s/%s/%s/%s" % ( random_data_1, - self.services["mount_dir"], - self.services["sub_dir"], - self.services["sub_lvl_dir2"], - self.services["random_data"] + self.services["paths"]["mount_dir"], + self.services["paths"]["sub_dir"], + self.services["paths"]["sub_lvl_dir2"], + self.services["paths"]["random_data"] ), "sync", ] @@ -1102,7 +885,7 @@ class TestSnapshots(cloudstackTestCase): # Unmount the Volume cmds = [ - "umount %s" % (self.services["mount_dir"]), + "umount %s" % (self.services["paths"]["mount_dir"]), ] for c in cmds: self.debug(c) @@ -1177,10 +960,10 @@ class TestSnapshots(cloudstackTestCase): ssh = new_virtual_machine.get_ssh_client() cmds = [ - "mkdir -p %s" % self.services["mount_dir"], + "mkdir -p %s" % self.services["paths"]["mount_dir"], "mount %s1 %s" % ( self.services["rootdisk"], - self.services["mount_dir"] + self.services["paths"]["mount_dir"] ) ] @@ -1188,17 +971,17 @@ class TestSnapshots(cloudstackTestCase): ssh.execute(c) returned_data_0 = ssh.execute("cat %s/%s/%s/%s" % ( - self.services["mount_dir"], - self.services["sub_dir"], - self.services["sub_lvl_dir1"], - self.services["random_data"] + self.services["paths"]["mount_dir"], + self.services["paths"]["sub_dir"], + self.services["paths"]["sub_lvl_dir1"], + self.services["paths"]["random_data"] )) self.debug(returned_data_0) returned_data_1 = ssh.execute("cat %s/%s/%s/%s" % ( - self.services["mount_dir"], - self.services["sub_dir"], - self.services["sub_lvl_dir2"], - self.services["random_data"] + self.services["paths"]["mount_dir"], + self.services["paths"]["sub_dir"], + self.services["paths"]["sub_lvl_dir2"], + self.services["paths"]["random_data"] )) self.debug(returned_data_1) except Exception as e: @@ -1230,11 +1013,11 @@ class TestSnapshots(cloudstackTestCase): return -class TestCreateVMsnapshotTemplate(cloudstackTestCase): +class TestCreateVMSnapshotTemplate(cloudstackTestCase): @classmethod def setUpClass(cls): - cls.api_client = super(TestCreateVMsnapshotTemplate, cls).getClsTestClient().getApiClient() + cls.api_client = super(TestCreateVMSnapshotTemplate, cls).getClsTestClient().getApiClient() cls.services = Services().services # Get Zone, Domain and templates cls.domain = get_domain(cls.api_client, cls.services) @@ -1480,15 +1263,15 @@ class TestCreateVMsnapshotTemplate(cloudstackTestCase): ) cmds = [ - "mkdir -p %s" % self.services["mount_dir"], + "mkdir -p %s" % self.services["paths"]["mount_dir"], "mount -t %s %s/%s %s" % ( storage_type, sec_storage_ip, export_path, - self.services["mount_dir"] + self.services["paths"]["mount_dir"] ), "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], + self.services["paths"]["mount_dir"], account_id, volume_id ), @@ -1526,907 +1309,6 @@ class TestCreateVMsnapshotTemplate(cloudstackTestCase): return -class TestAccountSnapshotClean(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - cls.api_client = super(TestAccountSnapshotClean, 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.services['mode'] = cls.zone.networktype - - template = get_template( - cls.api_client, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["server"]["zoneid"] = cls.zone.id - - cls.services["template"] = template.id - - # Create VMs, NAT Rules etc - cls.account = Account.create( - cls.api_client, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.services["account"] = cls.account.name - - cls.service_offering = ServiceOffering.create( - cls.api_client, - cls.services["service_offering"] - ) - cls.virtual_machine = VirtualMachine.create( - cls.api_client, - cls.services["server"], - templateid=template.id, - accountid=cls.account.name, - domainid=cls.account.domainid, - serviceofferingid=cls.service_offering.id - ) - # Get the Root disk of VM - volumes = list_volumes( - cls.api_client, - virtualmachineid=cls.virtual_machine.id, - type='ROOT', - listall=True - ) - volume = volumes[0] - - # Create a snapshot from the ROOTDISK - cls.snapshot = Snapshot.create(cls.api_client, volumes[0].id) - - cls._cleanup = [ - cls.service_offering, - ] - return - - @classmethod - def tearDownClass(cls): - try: - #Cleanup resources used - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns", "basic", "sg"]) - def test_02_accountSnapshotClean(self): - """Test snapshot cleanup after account deletion - """ - # Validate the following - # 1. listAccounts API should list out the newly created account - # 2. listVirtualMachines() command should return the deployed VM. - # State of this VM should be "Running" - # 3. a)listSnapshots should list the snapshot that was created. - # b)verify that secondary storage NFS share contains the reqd volume - # under /secondary/snapshots/$accountid/$volumeid/$snapshot_uuid - # 4. a)listAccounts should not list account that is deleted - # b) snapshot image($snapshot_uuid) should be deleted from the - # /secondary/snapshots/$accountid/$volumeid/ - - accounts = list_accounts( - self.apiclient, - id=self.account.id - ) - self.assertEqual( - isinstance(accounts, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(accounts), - 0, - "Check list Accounts response" - ) - - # VM should be in 'Running' state - virtual_machines = list_virtual_machines( - self.apiclient, - id=self.virtual_machine.id - ) - self.assertEqual( - isinstance(virtual_machines, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - len(virtual_machines), - 0, - "Check list virtual machines response" - ) - for virtual_machine in virtual_machines: - self.debug("VM ID: %s, VM state: %s" % ( - virtual_machine.id, - virtual_machine.state - )) - self.assertEqual( - virtual_machine.state, - 'Running', - "Check list VM response for Running state" - ) - - # Verify the snapshot was created or not - snapshots = list_snapshots( - self.apiclient, - id=self.snapshot.id - ) - self.assertEqual( - isinstance(snapshots, list), - True, - "Check list response returns a valid list" - ) - self.assertNotEqual( - snapshots, - None, - "Check if result exists in list snapshots call" - ) - self.assertEqual( - snapshots[0].id, - self.snapshot.id, - "Check snapshot id in list resources call" - ) - - # Fetch values from database - qresultset = self.dbclient.execute( - "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ - % self.snapshot.id - ) - self.assertEqual( - isinstance(qresultset, list), - True, - "Check DB response returns a valid list" - ) - self.assertNotEqual( - len(qresultset), - 0, - "Check DB Query result set" - ) - - qresult = qresultset[0] - snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID - account_id = qresult[1] - volume_id = qresult[2] - - # Get the Secondary Storage details from list Hosts - hosts = list_hosts( - self.apiclient, - type='SecondaryStorage', - zoneid=self.zone.id - ) - self.assertEqual( - isinstance(hosts, list), - True, - "Check list response returns a valid list" - ) - uuids = [] - for host in hosts: - # hosts[0].name = "nfs://192.168.100.21/export/test" - parse_url = (host.name).split('/') - # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] - - # Stripping end ':' from storage type - storage_type = parse_url[0][:-1] - # Split IP address and export path from name - sec_storage_ip = parse_url[2] - # Sec Storage IP: 192.168.100.21 - - if sec_storage_ip[-1] != ":": - sec_storage_ip = sec_storage_ip + ":" - - export_path = '/'.join(parse_url[3:]) - # Export path: export/test - - # Sleep to ensure that snapshot is reflected in sec storage - time.sleep(self.services["sleep"]) - try: - # Login to Secondary storage VM to check snapshot present on sec disk - ssh_client = remoteSSHClient( - self.services["mgmt_server"]["ipaddress"], - self.services["mgmt_server"]["port"], - self.services["mgmt_server"]["username"], - self.services["mgmt_server"]["password"], - ) - - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount -t %s %s/%s %s" % ( - storage_type, - sec_storage_ip, - export_path, - self.services["mount_dir"] - ), - "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], - account_id, - volume_id - ), - ] - - for c in cmds: - self.debug("command: %s" % c) - result = ssh_client.execute(c) - self.debug("Result: %s" % result) - - uuids.append(result) - - # Unmount the Sec Storage - cmds = [ - "umount %s" % (self.services["mount_dir"]), - ] - for c in cmds: - result = ssh_client.execute(c) - except Exception as e: - self.fail("SSH failed for management server: %s - %s" % - (self.services["mgmt_server"]["ipaddress"], e)) - - res = str(uuids) - self.assertEqual( - res.count(snapshot_uuid), - 1, - "Check snapshot UUID in secondary storage and database" - ) - - self.debug("Deleting account: %s" % self.account.name) - # Delete account - self.account.delete(self.apiclient) - - interval = list_configurations( - self.apiclient, - name='account.cleanup.interval' - ) - self.assertEqual( - isinstance(interval, list), - True, - "Check list response returns a valid list" - ) - self.debug("account.cleanup.interval: %s" % interval[0].value) - - # Wait for account cleanup interval - time.sleep(int(interval[0].value) * 2) - - accounts = list_accounts( - self.apiclient, - id=self.account.id - ) - - self.assertEqual( - accounts, - None, - "List accounts should return empty list after account deletion" - ) - - uuids = [] - for host in hosts: - # hosts[0].name = "nfs://192.168.100.21/export/test" - parse_url = (host.name).split('/') - # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] - - # Stripping end ':' from storage type - storage_type = parse_url[0][:-1] - # Split IP address and export path from name - sec_storage_ip = parse_url[2] - # Sec Storage IP: 192.168.100.21 - - if sec_storage_ip[-1] != ":": - sec_storage_ip = sec_storage_ip + ":" - - export_path = '/'.join(parse_url[3:]) - # Export path: export/test - - try: - cmds = [ - "mount -t %s %s/%s %s" % ( - storage_type, - sec_storage_ip, - export_path, - self.services["mount_dir"] - ), - "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], - account_id, - volume_id - ), - ] - - for c in cmds: - self.debug("command: %s" % c) - result = ssh_client.execute(c) - self.debug("Result: %s" % result) - - uuids.append(result) - # Unmount the Sec Storage - cmds = [ - "umount %s" % (self.services["mount_dir"]), - ] - for c in cmds: - self.debug("command: %s" % c) - result = ssh_client.execute(c) - self.debug("Result: %s" % result) - - except Exception as e: - self.fail("SSH failed for management server: %s - %s" % - (self.services["mgmt_server"]["ipaddress"], e)) - - res = str(uuids) - self.assertNotEqual( - res.count(snapshot_uuid), - 1, - "Check snapshot UUID in secondary storage and database" - ) - return - - -class TestSnapshotDetachedDisk(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - cls.api_client = super(TestSnapshotDetachedDisk, 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.services['mode'] = cls.zone.networktype - - cls.disk_offering = DiskOffering.create( - cls.api_client, - cls.services["disk_offering"] - ) - template = get_template( - cls.api_client, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["server"]["zoneid"] = cls.zone.id - cls.services["server"]["diskoffering"] = cls.disk_offering.id - - cls.services["template"] = template.id - - #determine device type from hypervisor - hosts = Host.list(cls.api_client, id=cls.virtual_machine.hostid) - if len(hosts) > 0 and hosts[0].hypervisor.lower() == "kvm": - cls.service["volume"]["diskdevice"] = "/dev/vdb" - - # Create VMs, NAT Rules etc - cls.account = Account.create( - cls.api_client, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.services["account"] = cls.account.name - - cls.service_offering = ServiceOffering.create( - cls.api_client, - cls.services["service_offering"] - ) - cls.virtual_machine = VirtualMachine.create( - cls.api_client, - cls.services["server"], - templateid=template.id, - accountid=cls.account.name, - domainid=cls.account.domainid, - serviceofferingid=cls.service_offering.id, - mode=cls.services["mode"] - ) - cls._cleanup = [ - cls.service_offering, - cls.disk_offering, - cls.account, - ] - return - - @classmethod - def tearDownClass(cls): - try: - #Cleanup resources used - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns", "basic", "sg"]) - def test_03_snapshot_detachedDisk(self): - """Test snapshot from detached disk - """ - # Validate the following - # 1. login in VM and write some data on data disk(use fdisk to - # partition datadisk,fdisk /dev/sdb, and make filesystem using - # mkfs.ext3) - # 2. Detach the data disk and write some data on data disk - # 3. perform the snapshot on the detached volume - # 4. listvolumes with VM id shouldn't show the detached volume - # 5. listSnapshots should list the snapshot that was created - # 6. verify that secondary storage NFS share contains the reqd volume - # under /secondary/snapshots/$accountid/$volumeid/$snapshot_uuid - # 7. verify backup_snap_id was non null in the `snapshots` table - - volumes = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine.id, - type='DATADISK', - listall=True - ) - self.assertEqual( - isinstance(volumes, list), - True, - "Check list response returns a valid list" - ) - volume = volumes[0] - random_data_0 = random_gen(100) - random_data_1 = random_gen(100) - try: - ssh_client = self.virtual_machine.get_ssh_client() - - #Format partition using ext3 - format_volume_to_ext3( - ssh_client, - self.services["volume"]["diskdevice"] - ) - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount %s1 %s" % ( - self.services["diskdevice"], - self.services["mount_dir"] - ), - "pushd %s" % self.services["mount_dir"], - "mkdir -p %s/{%s,%s} " % ( - self.services["sub_dir"], - self.services["sub_lvl_dir1"], - self.services["sub_lvl_dir2"] - ), - "echo %s > %s/%s/%s" % ( - random_data_0, - self.services["sub_dir"], - self.services["sub_lvl_dir1"], - self.services["random_data"] - ), - "echo %s > %s/%s/%s" % ( - random_data_1, - self.services["sub_dir"], - self.services["sub_lvl_dir2"], - self.services["random_data"] - ), - "sync", - ] - for c in cmds: - self.debug(ssh_client.execute(c)) - - #detach volume from VM - cmd = detachVolume.detachVolumeCmd() - cmd.id = volume.id - self.apiclient.detachVolume(cmd) - - #Create snapshot from detached volume - snapshot = Snapshot.create(self.apiclient, volume.id) - self.cleanup.append(snapshot) - - volumes = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine.id, - type='DATADISK', - listall=True - ) - - self.assertEqual( - volumes, - None, - "Check Volume is detached" - ) - - # Verify the snapshot was created or not - snapshots = list_snapshots( - self.apiclient, - id=snapshot.id - ) - self.assertNotEqual( - snapshots, - None, - "Check if result exists in list snapshots call" - ) - self.assertEqual( - snapshots[0].id, - snapshot.id, - "Check snapshot id in list resources call" - ) - except Exception as e: - self.fail("SSH failed for VM with IP: %s - %s" % - (self.virtual_machine.ipaddress, e)) - - # Fetch values from database - qresultset = self.dbclient.execute( - "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ - % snapshot.id - ) - self.assertNotEqual( - len(qresultset), - 0, - "Check DB Query result set" - ) - - qresult = qresultset[0] - snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID - account_id = qresult[1] - volume_id = qresult[2] - - self.assertNotEqual( - str(qresult[0]), - 'NULL', - "Check if backup_snap_id is not null" - ) - - # Get the Secondary Storage details from list Hosts - hosts = list_hosts( - self.apiclient, - type='SecondaryStorage', - zoneid=self.zone.id - ) - self.assertEqual( - isinstance(hosts, list), - True, - "Check list response returns a valid list" - ) - uuids = [] - for host in hosts: - # hosts[0].name = "nfs://192.168.100.21/export/test" - parse_url = (host.name).split('/') - # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] - - # Stripping end ':' from storage type - storage_type = parse_url[0][:-1] - - # Split IP address and export path from name - sec_storage_ip = parse_url[2] - # Sec Storage IP: 192.168.100.21 - - if sec_storage_ip[-1] != ":": - sec_storage_ip = sec_storage_ip + ":" - - export_path = '/'.join(parse_url[3:]) - # Export path: export/test - - # Sleep to ensure that snapshot is reflected in sec storage - time.sleep(self.services["sleep"]) - try: - # Login to Management server to check snapshot present on - # sec disk - ssh_client = remoteSSHClient( - self.services["mgmt_server"]["ipaddress"], - self.services["mgmt_server"]["port"], - self.services["mgmt_server"]["username"], - self.services["mgmt_server"]["password"], - ) - - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount -t %s %s/%s %s" % ( - storage_type, - sec_storage_ip, - export_path, - self.services["mount_dir"] - ), - "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], - account_id, - volume_id - ), - ] - - for c in cmds: - result = ssh_client.execute(c) - - uuids.append(result) - # Unmount the Sec Storage - cmds = [ - "umount %s" % (self.services["mount_dir"]), - ] - for c in cmds: - result = ssh_client.execute(c) - except Exception as e: - self.fail("SSH failed for management server: %s - %s" % - (self.services["mgmt_server"]["ipaddress"], e)) - - res = str(uuids) - self.assertEqual( - res.count(snapshot_uuid), - 1, - "Check snapshot UUID in secondary storage and database" - ) - return - - -class TestSnapshotLimit(cloudstackTestCase): - - @classmethod - def setUpClass(cls): - cls.api_client = super(TestSnapshotLimit, 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.services['mode'] = cls.zone.networktype - - template = get_template( - cls.api_client, - cls.zone.id, - cls.services["ostype"] - ) - cls.services["server"]["zoneid"] = cls.zone.id - - cls.services["template"] = template.id - - # Create VMs, NAT Rules etc - cls.account = Account.create( - cls.api_client, - cls.services["account"], - domainid=cls.domain.id - ) - - cls.services["account"] = cls.account.name - - cls.service_offering = ServiceOffering.create( - cls.api_client, - cls.services["service_offering"] - ) - cls.virtual_machine = VirtualMachine.create( - cls.api_client, - cls.services["server"], - templateid=template.id, - accountid=cls.account.name, - domainid=cls.account.domainid, - serviceofferingid=cls.service_offering.id - ) - cls._cleanup = [ - cls.service_offering, - cls.account, - ] - return - - @classmethod - def tearDownClass(cls): - try: - #Cleanup resources used - cleanup_resources(cls.api_client, cls._cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - def setUp(self): - self.apiclient = self.testClient.getApiClient() - self.dbclient = self.testClient.getDbConnection() - self.cleanup = [] - return - - def tearDown(self): - try: - #Clean up, terminate the created instance, volumes and snapshots - cleanup_resources(self.apiclient, self.cleanup) - except Exception as e: - raise Exception("Warning: Exception during cleanup : %s" % e) - return - - @attr(speed = "slow") - @attr(tags = ["advanced", "advancedns"]) - def test_04_snapshot_limit(self): - """Test snapshot limit in snapshot policies - """ - # Validate the following - # 1. Perform hourly recurring snapshot on the root disk of VM and keep - # the maxsnapshots as 1 - # 2. listSnapshots should list the snapshot that was created - # snapshot folder in secondary storage should contain only one - # snapshot image(/secondary/snapshots/$accountid/$volumeid/) - - # Get the Root disk of VM - volumes = list_volumes( - self.apiclient, - virtualmachineid=self.virtual_machine.id, - type='ROOT', - listall=True - ) - self.assertEqual( - isinstance(volumes, list), - True, - "Check list response returns a valid list" - ) - volume = volumes[0] - - # Create a snapshot policy - recurring_snapshot = SnapshotPolicy.create( - self.apiclient, - volume.id, - self.services["recurring_snapshot"] - ) - self.cleanup.append(recurring_snapshot) - - snapshot_policy = list_snapshot_policy( - self.apiclient, - id=recurring_snapshot.id, - volumeid=volume.id - ) - self.assertEqual( - isinstance(snapshot_policy, list), - True, - "Check list response returns a valid list" - ) - - self.assertNotEqual( - snapshot_policy, - None, - "Check if result exists in list item call" - ) - - self.assertEqual( - snapshot_policy[0].id, - recurring_snapshot.id, - "Check recurring snapshot id in list resources call" - ) - self.assertEqual( - snapshot_policy[0].maxsnaps, - self.services["recurring_snapshot"]["maxsnaps"], - "Check interval type in list resources call" - ) - # Sleep for (maxsnaps+1) hours to verify - # only maxsnaps snapshots are retained - time.sleep( - (self.services["recurring_snapshot"]["maxsnaps"]) * 3600 - ) - - # Verify the snapshot was created or not - snapshots = list_snapshots( - self.apiclient, - volumeid=volume.id, - intervaltype=\ - self.services["recurring_snapshot"]["intervaltype"], - snapshottype='RECURRING', - listall=True - ) - - self.assertEqual( - isinstance(snapshots, list), - True, - "Check list response returns a valid list" - ) - self.assertEqual( - len(snapshots), - self.services["recurring_snapshot"]["maxsnaps"], - "Check maximum number of recurring snapshots retained" - ) - snapshot = snapshots[0] - # Sleep to ensure that snapshot is reflected in sec storage - time.sleep(self.services["sleep"]) - - # Fetch values from database - qresultset = self.dbclient.execute( - "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ - % snapshot.id - ) - self.assertEqual( - isinstance(qresultset, list), - True, - "Check DBQuery returns a valid list" - ) - self.assertNotEqual( - len(qresultset), - 0, - "Check DB Query result set" - ) - - qresult = qresultset[0] - snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID - account_id = qresult[1] - volume_id = qresult[2] - - # Get the Secondary Storage details from list Hosts - hosts = list_hosts( - self.apiclient, - type='SecondaryStorage', - zoneid=self.zone.id - ) - self.assertEqual( - isinstance(hosts, list), - True, - "Check list response returns a valid list" - ) - uuids = [] - for host in hosts: - # hosts[0].name = "nfs://192.168.100.21/export/test" - parse_url = (host.name).split('/') - # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] - - # Stripping end ':' from storage type - storage_type = parse_url[0][:-1] - # Split IP address and export path from name - sec_storage_ip = parse_url[2] - # Sec Storage IP: 192.168.100.21 - - if sec_storage_ip[-1] != ":": - sec_storage_ip = sec_storage_ip + ":" - - export_path = '/'.join(parse_url[3:]) - # Export path: export/test - try: - # Login to VM to check snapshot present on sec disk - ssh_client = remoteSSHClient( - self.services["mgmt_server"]["ipaddress"], - self.services["mgmt_server"]["port"], - self.services["mgmt_server"]["username"], - self.services["mgmt_server"]["password"], - ) - - cmds = [ - "mkdir -p %s" % self.services["mount_dir"], - "mount -t %s %s/%s %s" % ( - storage_type, - sec_storage_ip, - export_path, - self.services["mount_dir"] - ), - "ls %s/snapshots/%s/%s" % ( - self.services["mount_dir"], - account_id, - volume_id - ), - ] - - for c in cmds: - result = ssh_client.execute(c) - - uuids.append(result) - - # Unmount the Sec Storage - cmds = [ - "umount %s" % (self.services["mount_dir"]), - ] - for c in cmds: - result = ssh_client.execute(c) - except Exception as e: - raise Exception( - "SSH access failed for management server: %s - %s" % - (self.services["mgmt_server"]["ipaddress"], e)) - - res = str(uuids) - self.assertEqual( - res.count(snapshot_uuid), - 1, - "Check snapshot UUID in secondary storage and database" - ) - return - - class TestSnapshotEvents(cloudstackTestCase): @classmethod diff --git a/test/integration/smoke/test_snapshots.py b/test/integration/smoke/test_snapshots.py new file mode 100644 index 00000000000..06777e4eb60 --- /dev/null +++ b/test/integration/smoke/test_snapshots.py @@ -0,0 +1,345 @@ +# 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. + +from nose.plugins.attrib import attr +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 * + +class Services: + """Test Snapshots Services + """ + + 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": { + "name": "Tiny Instance", + "displaytext": "Tiny Instance", + "cpunumber": 1, + "cpuspeed": 200, # in MHz + "memory": 256, # In MBs + }, + "disk_offering": { + "displaytext": "Small Disk", + "name": "Small Disk", + "disksize": 1 + }, + "server_with_disk": + { + "displayname": "Test VM -With Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + + "server_without_disk": + { + "displayname": "Test VM-No Disk", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + # For NAT rule creation + "publicport": 22, + "protocol": 'TCP', + }, + "server": { + "displayname": "TestVM", + "username": "root", + "password": "password", + "ssh_port": 22, + "hypervisor": 'XenServer', + "privateport": 22, + "publicport": 22, + "protocol": 'TCP', + }, + "mgmt_server": { + "ipaddress": '192.168.100.21', + "username": "root", + "password": "password", + "port": 22, + }, + "recurring_snapshot": { + "intervaltype": 'HOURLY', + # Frequency of snapshots + "maxsnaps": 1, # Should be min 2 + "schedule": 1, + "timezone": 'US/Arizona', + # Timezone Formats - http://cloud.mindtouch.us/CloudStack_Documentation/Developer's_Guide%3A_CloudStack + }, + "templates": { + "displaytext": 'Template', + "name": 'Template', + "ostype": "CentOS 5.3 (64-bit)", + "templatefilter": 'self', + }, + "volume": { + "diskname": "APP Data Volume", + "size": 1, # in GBs + "diskdevice": ['/dev/xvdb', '/dev/sdb', '/dev/hdb', '/dev/vdb' ], # Data Disk + }, + "paths": { + "mount_dir": "/mnt/tmp", + "sub_dir": "test", + "sub_lvl_dir1": "test1", + "sub_lvl_dir2": "test2", + "random_data": "random.data", + }, + "ostype": "CentOS 5.3 (64-bit)", + # Cent OS 5.3 (64 bit) + "sleep": 60, + "timeout": 10, + } + + +class TestSnapshotRootDisk(cloudstackTestCase): + + @classmethod + def setUpClass(cls): + cls.api_client = super(TestSnapshotRootDisk, 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.services['mode'] = cls.zone.networktype + + template = get_template( + cls.api_client, + cls.zone.id, + cls.services["ostype"] + ) + cls.services["domainid"] = cls.domain.id + cls.services["server_without_disk"]["zoneid"] = cls.zone.id + cls.services["templates"]["ostypeid"] = template.ostypeid + cls.services["zoneid"] = cls.zone.id + + # Create VMs, NAT Rules etc + cls.account = Account.create( + cls.api_client, + cls.services["account"], + domainid=cls.domain.id + ) + + cls.services["account"] = cls.account.name + + cls.service_offering = ServiceOffering.create( + cls.api_client, + cls.services["service_offering"] + ) + cls.virtual_machine = cls.virtual_machine_with_disk = \ + VirtualMachine.create( + cls.api_client, + cls.services["server_without_disk"], + templateid=template.id, + accountid=cls.account.name, + domainid=cls.account.domainid, + serviceofferingid=cls.service_offering.id, + mode=cls.services["mode"] + ) + cls._cleanup = [ + cls.service_offering, + cls.account, + ] + return + + @classmethod + def tearDownClass(cls): + try: + #Cleanup resources used + cleanup_resources(cls.api_client, cls._cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + def setUp(self): + self.apiclient = self.testClient.getApiClient() + self.dbclient = self.testClient.getDbConnection() + self.cleanup = [] + return + + def tearDown(self): + try: + #Clean up, terminate the created instance, volumes and snapshots + cleanup_resources(self.apiclient, self.cleanup) + except Exception as e: + raise Exception("Warning: Exception during cleanup : %s" % e) + return + + @attr(speed = "slow") + @attr(tags = ["advanced", "advancedns", "smoke"]) + def test_01_snapshot_root_disk(self): + """Test Snapshot Root Disk + """ + + # Validate the following + # 1. listSnapshots should list the snapshot that was created. + # 2. verify that secondary storage NFS share contains + # the reqd volume under + # /secondary/snapshots//$account_id/$volumeid/$snapshot_uuid + # 3. verify backup_snap_id was non null in the `snapshots` table + + volumes = list_volumes( + self.apiclient, + virtualmachineid=self.virtual_machine_with_disk.id, + type='ROOT', + listall=True + ) + + snapshot = Snapshot.create( + self.apiclient, + volumes[0].id, + account=self.account.name, + domainid=self.account.domainid + ) + self.debug("Snapshot created: ID - %s" % snapshot.id) + + snapshots = list_snapshots( + self.apiclient, + id=snapshot.id + ) + self.assertEqual( + isinstance(snapshots, list), + True, + "Check list response returns a valid list" + ) + + self.assertNotEqual( + snapshots, + None, + "Check if result exists in list item call" + ) + self.assertEqual( + snapshots[0].id, + snapshot.id, + "Check resource id in list resources call" + ) + self.debug( + "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ + % str(snapshot.id) + ) + qresultset = self.dbclient.execute( + "select backup_snap_id, account_id, volume_id from snapshots where uuid = '%s';" \ + % str(snapshot.id) + ) + self.assertNotEqual( + len(qresultset), + 0, + "Check DB Query result set" + ) + + qresult = qresultset[0] + + snapshot_uuid = qresult[0] # backup_snap_id = snapshot UUID + account_id = qresult[1] + volume_id = qresult[2] + + self.assertNotEqual( + str(snapshot_uuid), + 'NULL', + "Check if backup_snap_id is not null" + ) + + # Get the Secondary Storage details from list Hosts + hosts = list_hosts( + self.apiclient, + type='SecondaryStorage', + zoneid=self.zone.id + ) + self.assertEqual( + isinstance(hosts, list), + True, + "Check list response returns a valid list" + ) + uuids = [] + for host in hosts: + # hosts[0].name = "nfs://192.168.100.21/export/test" + parse_url = (host.name).split('/') + # parse_url = ['nfs:', '', '192.168.100.21', 'export', 'test'] + + # Split IP address and export path from name + sec_storage_ip = parse_url[2] + # Sec Storage IP: 192.168.100.21 + + export_path = '/'.join(parse_url[3:]) + # Export path: export/test + + try: + # Login to VM to check snapshot present on sec disk + ssh_client = self.virtual_machine_with_disk.get_ssh_client() + + cmds = [ + "mkdir -p %s" % self.services["paths"]["mount_dir"], + "mount %s/%s %s" % ( + sec_storage_ip, + export_path, + self.services["paths"]["mount_dir"] + ), + "ls %s/snapshots/%s/%s" % ( + self.services["paths"]["mount_dir"], + account_id, + volume_id + ), + ] + + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine_with_disk.ipaddress) + + uuids.append(result) + # Unmount the Sec Storage + cmds = [ + "umount %s" % (self.services["mount_dir"]), + ] + try: + for c in cmds: + self.debug(c) + result = ssh_client.execute(c) + self.debug(result) + + except Exception as e: + self.fail("SSH failed for Virtual machine: %s" % + self.virtual_machine_with_disk.ipaddress) + + res = str(uuids) + # Check snapshot UUID in secondary storage and database + self.assertEqual( + res.count(snapshot_uuid), + 1, + "Check snapshot UUID in secondary storage and database" + ) + return \ No newline at end of file