Merge "port some flavor tests into nova v3 part2"
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 8cd6c11..602bd5b 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -123,6 +123,7 @@
metadata=meta,
personality=personality,
admin_password=password)
+ self.addCleanup(self.client.rebuild, self.server_id, self.image_ref)
# Verify the properties in the initial response are correct
self.assertEqual(self.server_id, rebuilt_server['id'])
@@ -142,6 +143,35 @@
linux_client = RemoteClient(server, self.ssh_user, password)
linux_client.validate_authentication()
+ @attr(type='gate')
+ def test_rebuild_server_in_stop_state(self):
+ # The server in stop state should be rebuilt using the provided
+ # image and remain in SHUTOFF state
+ resp, server = self.client.get_server(self.server_id)
+ old_image = server['image']['id']
+ new_image = self.image_ref_alt \
+ if old_image == self.image_ref else self.image_ref
+ resp, server = self.client.stop(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'SHUTOFF')
+ self.addCleanup(self.client.start, self.server_id)
+ resp, rebuilt_server = self.client.rebuild(self.server_id, new_image)
+ self.addCleanup(self.client.wait_for_server_status, self.server_id,
+ 'SHUTOFF')
+ self.addCleanup(self.client.rebuild, self.server_id, old_image)
+
+ # Verify the properties in the initial response are correct
+ self.assertEqual(self.server_id, rebuilt_server['id'])
+ rebuilt_image_id = rebuilt_server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+ self.assertEqual(self.flavor_ref, rebuilt_server['flavor']['id'])
+
+ # Verify the server properties after the rebuild completes
+ self.client.wait_for_server_status(rebuilt_server['id'], 'SHUTOFF')
+ resp, server = self.client.get_server(rebuilt_server['id'])
+ rebuilt_image_id = server['image']['id']
+ self.assertEqual(new_image, rebuilt_image_id)
+
def _detect_server_image_flavor(self, server_id):
# Detects the current server image flavor ref.
resp, server = self.client.get_server(self.server_id)
@@ -199,32 +229,115 @@
raise exceptions.TimeoutException(message)
@attr(type='gate')
+ def test_create_backup(self):
+ # Positive test:create backup successfully and rotate backups correctly
+ # create the first and the second backup
+ backup1 = data_utils.rand_name('backup')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup1)
+ oldest_backup_exist = True
+
+ # the oldest one should be deleted automatically in this test
+ def _clean_oldest_backup(oldest_backup):
+ if oldest_backup_exist:
+ self.images_client.delete_image(oldest_backup)
+
+ image1_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(_clean_oldest_backup, image1_id)
+ self.assertEqual(202, resp.status)
+ self.images_client.wait_for_image_status(image1_id, 'active')
+
+ backup2 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup2)
+ image2_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.images_client.delete_image, image2_id)
+ self.assertEqual(202, resp.status)
+ self.images_client.wait_for_image_status(image2_id, 'active')
+
+ # verify they have been created
+ properties = {
+ 'image_type': 'backup',
+ 'backup_type': "daily",
+ 'instance_uuid': self.server_id,
+ }
+ resp, image_list = self.images_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup1, backup2),
+ (image_list[0]['name'], image_list[1]['name']))
+
+ # create the third one, due to the rotation is 2,
+ # the first one will be deleted
+ backup3 = data_utils.rand_name('backup')
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+ resp, _ = self.servers_client.create_backup(self.server_id,
+ 'daily',
+ 2,
+ backup3)
+ image3_id = data_utils.parse_image_id(resp['location'])
+ self.addCleanup(self.images_client.delete_image, image3_id)
+ self.assertEqual(202, resp.status)
+ # the first back up should be deleted
+ self.images_client.wait_for_resource_deletion(image1_id)
+ oldest_backup_exist = False
+ resp, image_list = self.images_client.image_list_detail(
+ properties,
+ sort_key='created_at',
+ sort_dir='asc')
+ self.assertEqual(200, resp.status)
+ self.assertEqual(2, len(image_list))
+ self.assertEqual((backup2, backup3),
+ (image_list[0]['name'], image_list[1]['name']))
+
+ def _get_output(self):
+ resp, output = self.servers_client.get_console_output(
+ self.server_id, 10)
+ self.assertEqual(200, resp.status)
+ self.assertTrue(output, "Console output was empty.")
+ lines = len(output.split('\n'))
+ self.assertEqual(lines, 10)
+
+ @attr(type='gate')
def test_get_console_output(self):
# Positive test:Should be able to GET the console output
# for a given server_id and number of lines
- def get_output():
- resp, output = self.servers_client.get_console_output(
- self.server_id, 10)
- self.assertEqual(200, resp.status)
- self.assertTrue(output, "Console output was empty.")
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
- self.wait_for(get_output)
- @skip_because(bug="1014683")
+ # This reboot is necessary for outputting some console log after
+ # creating a instance backup. If a instance backup, the console
+ # log file is truncated and we cannot get any console log through
+ # "console-log" API.
+ # The detail is https://bugs.launchpad.net/nova/+bug/1251920
+ resp, body = self.servers_client.reboot(self.server_id, 'HARD')
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ self.wait_for(self._get_output)
+
@attr(type='gate')
- def test_get_console_output_server_id_in_reboot_status(self):
+ def test_get_console_output_server_id_in_shutoff_status(self):
# Positive test:Should be able to GET the console output
- # for a given server_id in reboot status
- resp, output = self.servers_client.reboot(self.server_id, 'SOFT')
- self.servers_client.wait_for_server_status(self.server_id,
- 'REBOOT')
- resp, output = self.servers_client.get_console_output(self.server_id,
- 10)
- self.assertEqual(200, resp.status)
- self.assertIsNotNone(output)
- lines = len(output.split('\n'))
- self.assertEqual(lines, 10)
+ # for a given server_id in SHUTOFF status
+
+ # NOTE: SHUTOFF is irregular status. To avoid test instability,
+ # one server is created only for this test without using
+ # the server that was created in setupClass.
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ temp_server_id = server['id']
+
+ resp, server = self.servers_client.stop(temp_server_id)
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(temp_server_id, 'SHUTOFF')
+
+ self.wait_for(self._get_output)
@attr(type='gate')
def test_pause_unpause_server(self):
@@ -245,6 +358,30 @@
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
@attr(type='gate')
+ def test_shelve_unshelve_server(self):
+ resp, server = self.client.shelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+
+ offload_time = self.config.compute.shelved_offload_time
+ if offload_time >= 0:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED_OFFLOADED',
+ extra_timeout=offload_time)
+ else:
+ self.client.wait_for_server_status(self.server_id,
+ 'SHELVED')
+
+ resp, server = self.client.get_server(self.server_id)
+ image_name = server['name'] + '-shelved'
+ resp, images = self.images_client.image_list(name=image_name)
+ self.assertEqual(1, len(images))
+ self.assertEqual(image_name, images[0]['name'])
+
+ resp, server = self.client.unshelve_server(self.server_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+
+ @attr(type='gate')
def test_stop_start_server(self):
resp, server = self.servers_client.stop(self.server_id)
self.assertEqual(202, resp.status)
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 6532032..85fe47c 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -25,11 +25,11 @@
from tempest import test
-class ServersNegativeTestJSON(base.BaseV2ComputeTest):
+class ServersNegativeV3TestJSON(base.BaseV3ComputeTest):
_interface = 'json'
def setUp(self):
- super(ServersNegativeTestJSON, self).setUp()
+ super(ServersNegativeV3TestJSON, self).setUp()
try:
self.client.wait_for_server_status(self.server_id, 'ACTIVE')
except Exception:
@@ -37,10 +37,10 @@
@classmethod
def setUpClass(cls):
- super(ServersNegativeTestJSON, cls).setUpClass()
+ super(ServersNegativeV3TestJSON, cls).setUpClass()
cls.client = cls.servers_client
cls.alt_os = clients.AltManager()
- cls.alt_client = cls.alt_os.servers_client
+ cls.alt_client = cls.alt_os.servers_v3_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
@@ -53,18 +53,6 @@
name='')
@test.attr(type=['negative', 'gate'])
- def test_personality_file_contents_not_encoded(self):
- # Use an unencoded file when creating a server with personality
-
- file_contents = 'This is a test file.'
- person = [{'path': '/etc/testfile.txt',
- 'contents': file_contents}]
-
- self.assertRaises(exceptions.BadRequest,
- self.create_test_server,
- personality=person)
-
- @test.attr(type=['negative', 'gate'])
def test_create_with_invalid_image(self):
# Create a server with an unknown image
@@ -86,7 +74,7 @@
IPv4 = '1.1.1.1.1.1'
self.assertRaises(exceptions.BadRequest,
- self.create_test_server, accessIPv4=IPv4)
+ self.create_test_server, access_ip_v4=IPv4)
@test.attr(type=['negative', 'gate'])
def test_invalid_ip_v6_address(self):
@@ -95,7 +83,7 @@
IPv6 = 'notvalid'
self.assertRaises(exceptions.BadRequest,
- self.create_test_server, accessIPv6=IPv6)
+ self.create_test_server, access_ip_v6=IPv6)
@test.attr(type=['negative', 'gate'])
def test_resize_nonexistent_server(self):
@@ -186,12 +174,12 @@
self.create_test_server,
name=server_name)
+ @test.skip_because(bug="1208743")
@test.attr(type=['negative', 'gate'])
def test_create_with_invalid_network_uuid(self):
# Pass invalid network uuid while creating a server
networks = [{'fixed_ip': '10.0.1.1', 'uuid': 'a-b-c-d-e-f-g-h-i-j'}]
-
self.assertRaises(exceptions.BadRequest,
self.create_test_server,
networks=networks)
@@ -421,8 +409,7 @@
resp, server = self.client.get_server(self.server_id)
image_name = server['name'] + '-shelved'
- params = {'name': image_name}
- resp, images = self.images_client.list_images(params)
+ resp, images = self.images_client.image_list(name=image_name)
self.assertEqual(1, len(images))
self.assertEqual(image_name, images[0]['name'])
@@ -445,5 +432,5 @@
self.server_id)
-class ServersNegativeTestXML(ServersNegativeTestJSON):
+class ServersNegativeV3TestXML(ServersNegativeV3TestJSON):
_interface = 'xml'
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index aedba15..ce620a8 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -29,7 +29,10 @@
"""Waits for a server to reach a given status."""
def _get_task_state(body):
- task_state = body.get('OS-EXT-STS:task_state', None)
+ if client.service == CONF.compute.catalog_v3_type:
+ task_state = body.get("os-extended-status:task_state", None)
+ else:
+ task_state = body.get('OS-EXT-STS:task_state', None)
return task_state
# NOTE(afazekas): UNKNOWN status possible on ERROR
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index a7fcc6d..a486801 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -64,12 +64,14 @@
'flavor_ref': flavor_ref
}
- for option in ['personality', 'admin_password', 'key_name',
- 'security_groups', 'networks',
+ for option in ['personality', 'admin_password', 'key_name', 'networks',
+ ('os-security-groups:security_groups',
+ 'security_groups'),
('os-user-data:user_data', 'user_data'),
('os-availability-zone:availability_zone',
'availability_zone'),
- 'access_ip_v4', 'access_ip_v6',
+ ('os-access-ips:access_ip_v4', 'access_ip_v4'),
+ ('os-access-ips:access_ip_v6', 'access_ip_v6'),
('os-multiple-create:min_count', 'min_count'),
('os-multiple-create:max_count', 'max_count'),
('metadata', 'meta'),
@@ -205,6 +207,13 @@
body = json.loads(body)[response_key]
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "create_backup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, admin_password):
"""Changes the root password for the server."""
return self.action(server_id, 'change_password', None,
@@ -356,6 +365,14 @@
"""Resets the state of a server to active/error."""
return self.action(server_id, 'reset_state', None, state=state)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def get_console_output(self, server_id, length):
return self.action(server_id, 'get_console_output', 'output',
length=length)
@@ -388,3 +405,11 @@
(str(server_id), str(request_id)))
body = json.loads(body)
return resp, body['instance_action']
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'force_delete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)
diff --git a/tempest/services/compute/v3/xml/servers_client.py b/tempest/services/compute/v3/xml/servers_client.py
index 7af4161..c3381a3 100644
--- a/tempest/services/compute/v3/xml/servers_client.py
+++ b/tempest/services/compute/v3/xml/servers_client.py
@@ -214,6 +214,14 @@
"""Resets the state of a server to active/error."""
return self.action(server_id, 'reset_state', None, state=state)
+ def shelve_server(self, server_id, **kwargs):
+ """Shelves the provided server."""
+ return self.action(server_id, 'shelve', None, **kwargs)
+
+ def unshelve_server(self, server_id, **kwargs):
+ """Un-shelves the provided server."""
+ return self.action(server_id, 'unshelve', None, **kwargs)
+
def delete_server(self, server_id):
"""Deletes the given server."""
return self.delete("servers/%s" % str(server_id))
@@ -299,7 +307,17 @@
flavor_ref=flavor_ref,
image_ref=image_ref,
name=name)
- attrs = ["admin_password", "access_ip_v4", "access_ip_v6", "key_name",
+ attrs = ["admin_password", "key_name",
+ ('os-access-ips:access_ip_v4',
+ 'access_ip_v4',
+ 'xmlns:os-access-ips',
+ "http://docs.openstack.org/compute/ext/"
+ "os-access-ips/api/v3"),
+ ('os-access-ips:access_ip_v6',
+ 'access_ip_v6',
+ 'xmlns:os-access-ips',
+ "http://docs.openstack.org/compute/ext/"
+ "os-access-ips/api/v3"),
("os-user-data:user_data",
'user_data',
'xmlns:os-user-data',
@@ -346,7 +364,10 @@
server.add_attr(post_param, value)
if 'security_groups' in kwargs:
- secgroups = Element("security_groups")
+ server.add_attr("xmlns:os-security-groups",
+ "http://docs.openstack.org/compute/ext/"
+ "securitygroups/api/v3")
+ secgroups = Element("os-security-groups:security_groups")
server.append(secgroups)
for secgroup in kwargs['security_groups']:
s = Element("security_group", name=secgroup['name'])
@@ -441,6 +462,13 @@
body = xml_to_json(etree.fromstring(body))
return resp, body
+ def create_backup(self, server_id, backup_type, rotation, name):
+ """Backup a server instance."""
+ return self.action(server_id, "create_backup", None,
+ backup_type=backup_type,
+ rotation=rotation,
+ name=name)
+
def change_password(self, server_id, password):
return self.action(server_id, "change_password", None,
admin_password=password)
@@ -621,3 +649,11 @@
(server_id, request_id), self.headers)
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def force_delete_server(self, server_id, **kwargs):
+ """Force delete a server."""
+ return self.action(server_id, 'force_delete', None, **kwargs)
+
+ def restore_soft_deleted_server(self, server_id, **kwargs):
+ """Restore a soft-deleted server."""
+ return self.action(server_id, 'restore', None, **kwargs)