Merge "API test for 'create server with scheduler hints'"
diff --git a/tempest/api/compute/images/test_images.py b/tempest/api/compute/images/test_images.py
index 5de2436..29df2b0 100644
--- a/tempest/api/compute/images/test_images.py
+++ b/tempest/api/compute/images/test_images.py
@@ -15,7 +15,6 @@
from tempest.api.compute import base
from tempest.common.utils import data_utils
from tempest import config
-from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -32,48 +31,6 @@
cls.client = cls.images_client
cls.servers_client = cls.servers_client
- @test.attr(type=['negative', 'gate'])
- def test_create_image_from_deleted_server(self):
- # An image should not be created if the server instance is removed
- resp, server = self.create_test_server(wait_until='ACTIVE')
-
- # Delete server before trying to create server
- self.servers_client.delete_server(server['id'])
- self.servers_client.wait_for_server_termination(server['id'])
- # Create a new image after server is deleted
- name = data_utils.rand_name('image')
- meta = {'image_type': 'test'}
- self.assertRaises(exceptions.NotFound,
- self.create_image_from_server,
- server['id'], name=name, meta=meta)
-
- @test.attr(type=['negative', 'gate'])
- def test_create_image_from_invalid_server(self):
- # An image should not be created with invalid server id
- # Create a new image with invalid server id
- name = data_utils.rand_name('image')
- meta = {'image_type': 'test'}
- resp = {}
- resp['status'] = None
- self.assertRaises(exceptions.NotFound,
- self.create_image_from_server,
- '!@#$%^&*()', name=name, meta=meta)
-
- @test.attr(type=['negative', 'gate'])
- def test_create_image_from_stopped_server(self):
- resp, server = self.create_test_server(wait_until='ACTIVE')
- self.servers_client.stop(server['id'])
- self.servers_client.wait_for_server_status(server['id'],
- 'SHUTOFF')
- self.addCleanup(self.servers_client.delete_server, server['id'])
- snapshot_name = data_utils.rand_name('test-snap-')
- resp, image = self.create_image_from_server(server['id'],
- name=snapshot_name,
- wait_until='ACTIVE',
- wait_for_server=False)
- self.addCleanup(self.client.delete_image, image['id'])
- self.assertEqual(snapshot_name, image['name'])
-
@test.attr(type='gate')
def test_delete_saving_image(self):
snapshot_name = data_utils.rand_name('test-snap-')
@@ -85,59 +42,6 @@
resp, body = self.client.delete_image(image['id'])
self.assertEqual('204', resp['status'])
- @test.attr(type=['negative', 'gate'])
- def test_create_image_specify_uuid_35_characters_or_less(self):
- # Return an error if Image ID passed is 35 characters or less
- snapshot_name = data_utils.rand_name('test-snap-')
- test_uuid = ('a' * 35)
- self.assertRaises(exceptions.NotFound, self.client.create_image,
- test_uuid, snapshot_name)
-
- @test.attr(type=['negative', 'gate'])
- def test_create_image_specify_uuid_37_characters_or_more(self):
- # Return an error if Image ID passed is 37 characters or more
- snapshot_name = data_utils.rand_name('test-snap-')
- test_uuid = ('a' * 37)
- self.assertRaises(exceptions.NotFound, self.client.create_image,
- test_uuid, snapshot_name)
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_image_with_invalid_image_id(self):
- # An image should not be deleted with invalid image id
- self.assertRaises(exceptions.NotFound, self.client.delete_image,
- '!@$%^&*()')
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_non_existent_image(self):
- # Return an error while trying to delete a non-existent image
-
- non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
- self.assertRaises(exceptions.NotFound, self.client.delete_image,
- non_existent_image_id)
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_image_blank_id(self):
- # Return an error while trying to delete an image with blank Id
- self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_image_non_hex_string_id(self):
- # Return an error while trying to delete an image with non hex id
- image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
- self.assertRaises(exceptions.NotFound, self.client.delete_image,
- image_id)
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_image_negative_image_id(self):
- # Return an error while trying to delete an image with negative id
- self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
-
- @test.attr(type=['negative', 'gate'])
- def test_delete_image_id_is_over_35_character_limit(self):
- # Return an error while trying to delete image with id over limit
- self.assertRaises(exceptions.NotFound, self.client.delete_image,
- '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
-
class ImagesTestXML(ImagesTestJSON):
_interface = 'xml'
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
new file mode 100644
index 0000000..ae00ae2
--- /dev/null
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -0,0 +1,131 @@
+# Copyright 2012 OpenStack Foundation
+#
+# Licensed 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 tempest.api.compute import base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import exceptions
+from tempest import test
+
+CONF = config.CONF
+
+
+class ImagesNegativeTestJSON(base.BaseV2ComputeTest):
+
+ @classmethod
+ def setUpClass(cls):
+ super(ImagesNegativeTestJSON, cls).setUpClass()
+ if not CONF.service_available.glance:
+ skip_msg = ("%s skipped as glance is not available" % cls.__name__)
+ raise cls.skipException(skip_msg)
+ cls.client = cls.images_client
+ cls.servers_client = cls.servers_client
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_image_from_deleted_server(self):
+ # An image should not be created if the server instance is removed
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+
+ # Delete server before trying to create server
+ self.servers_client.delete_server(server['id'])
+ self.servers_client.wait_for_server_termination(server['id'])
+ # Create a new image after server is deleted
+ name = data_utils.rand_name('image')
+ meta = {'image_type': 'test'}
+ self.assertRaises(exceptions.NotFound,
+ self.create_image_from_server,
+ server['id'], name=name, meta=meta)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_image_from_invalid_server(self):
+ # An image should not be created with invalid server id
+ # Create a new image with invalid server id
+ name = data_utils.rand_name('image')
+ meta = {'image_type': 'test'}
+ resp = {}
+ resp['status'] = None
+ self.assertRaises(exceptions.NotFound, self.create_image_from_server,
+ '!@#$%^&*()', name=name, meta=meta)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_image_from_stopped_server(self):
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.servers_client.stop(server['id'])
+ self.servers_client.wait_for_server_status(server['id'],
+ 'SHUTOFF')
+ self.addCleanup(self.servers_client.delete_server, server['id'])
+ snapshot_name = data_utils.rand_name('test-snap-')
+ resp, image = self.create_image_from_server(server['id'],
+ name=snapshot_name,
+ wait_until='ACTIVE',
+ wait_for_server=False)
+ self.addCleanup(self.client.delete_image, image['id'])
+ self.assertEqual(snapshot_name, image['name'])
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_35_characters_or_less(self):
+ # Return an error if Image ID passed is 35 characters or less
+ snapshot_name = data_utils.rand_name('test-snap-')
+ test_uuid = ('a' * 35)
+ self.assertRaises(exceptions.NotFound, self.client.create_image,
+ test_uuid, snapshot_name)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_create_image_specify_uuid_37_characters_or_more(self):
+ # Return an error if Image ID passed is 37 characters or more
+ snapshot_name = data_utils.rand_name('test-snap-')
+ test_uuid = ('a' * 37)
+ self.assertRaises(exceptions.NotFound, self.client.create_image,
+ test_uuid, snapshot_name)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_image_with_invalid_image_id(self):
+ # An image should not be deleted with invalid image id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '!@$%^&*()')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_non_existent_image(self):
+ # Return an error while trying to delete a non-existent image
+
+ non_existent_image_id = '11a22b9-12a9-5555-cc11-00ab112223fa'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ non_existent_image_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_image_blank_id(self):
+ # Return an error while trying to delete an image with blank Id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, '')
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_image_non_hex_string_id(self):
+ # Return an error while trying to delete an image with non hex id
+ image_id = '11a22b9-120q-5555-cc11-00ab112223gj'
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ image_id)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_image_negative_image_id(self):
+ # Return an error while trying to delete an image with negative id
+ self.assertRaises(exceptions.NotFound, self.client.delete_image, -1)
+
+ @test.attr(type=['negative', 'gate'])
+ def test_delete_image_id_is_over_35_character_limit(self):
+ # Return an error while trying to delete image with id over limit
+ self.assertRaises(exceptions.NotFound, self.client.delete_image,
+ '11a22b9-12a9-5555-cc11-00ab112223fa-3fac')
+
+
+class ImagesNegativeTestXML(ImagesNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/network/admin/test_dhcp_agent_scheduler.py b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
index ecd992a..0e601d1 100644
--- a/tempest/api/network/admin/test_dhcp_agent_scheduler.py
+++ b/tempest/api/network/admin/test_dhcp_agent_scheduler.py
@@ -26,7 +26,7 @@
msg = "dhcp_agent_scheduler extension not enabled."
raise cls.skipException(msg)
# Create a network and make sure it will be hosted by a
- # dhcp agent.
+ # dhcp agent: this is done by creating a regular port
cls.network = cls.create_network()
cls.subnet = cls.create_subnet(cls.network)
cls.cidr = cls.subnet['cidr']
@@ -60,6 +60,9 @@
@test.attr(type='smoke')
def test_remove_network_from_dhcp_agent(self):
+ # The agent is now bound to the network, we can free the port
+ self.client.delete_port(self.port['id'])
+ self.ports.remove(self.port)
resp, body = self.admin_client.list_dhcp_agent_hosting_network(
self.network['id'])
agents = body['agents']
diff --git a/tempest/api/network/test_networks.py b/tempest/api/network/test_networks.py
index 70fb00a..b9041ee 100644
--- a/tempest/api/network/test_networks.py
+++ b/tempest/api/network/test_networks.py
@@ -18,6 +18,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.test import attr
CONF = config.CONF
@@ -43,6 +44,8 @@
port update
network update
subnet update
+ delete a network also deletes its subnets
+ create a port with no IP address associated with it
All subnet tests are run once with ipv4 and once with ipv6.
@@ -183,6 +186,67 @@
self.assertEqual(len(subnet), 1)
self.assertIn('id', subnet)
+ def _try_delete_network(self, net_id):
+ # delete network, if it exists
+ try:
+ self.client.delete_network(net_id)
+ # if network is not found, this means it was deleted in the test
+ except exceptions.NotFound:
+ pass
+
+ @attr(type='smoke')
+ def test_delete_network_with_subnet(self):
+ # Creates a network
+ name = data_utils.rand_name('network-')
+ resp, body = self.client.create_network(name=name)
+ self.assertEqual('201', resp['status'])
+ network = body['network']
+ net_id = network['id']
+ self.addCleanup(self._try_delete_network, net_id)
+
+ # Find a cidr that is not in use yet and create a subnet with it
+ subnet = self.create_subnet(network)
+ subnet_id = subnet['id']
+
+ # Delete network while the subnet still exists
+ resp, body = self.client.delete_network(net_id)
+ self.assertEqual('204', resp['status'])
+
+ # Verify that the subnet got automatically deleted.
+ self.assertRaises(exceptions.NotFound, self.client.show_subnet,
+ subnet_id)
+
+ # Since create_subnet adds the subnet to the delete list, and it is
+ # is actually deleted here - this will create and issue, hence remove
+ # it from the list.
+ self.subnets.pop()
+
+ @attr(type='smoke')
+ def test_create_port_with_no_ip(self):
+ # For this test create a new network - do not use any previously
+ # created networks.
+ name = data_utils.rand_name('network-nosubnet-')
+ resp, body = self.client.create_network(name=name)
+ self.assertEqual('201', resp['status'])
+ network = body['network']
+ net_id = network['id']
+ self.networks.append(network)
+
+ # Now create a port for this network - without creating any
+ # subnets for this network - this ensures no IP for the port
+ resp, body = self.client.create_port(network_id=net_id)
+ self.assertEqual('201', resp['status'])
+ port = body['port']
+ port_id = port['id']
+ self.addCleanup(self.client.delete_port, port_id)
+
+ # Verify that the port does not have any IP address
+ resp, body = self.client.show_port(port_id)
+ self.assertEqual('200', resp['status'])
+ port_resp = body['port']
+ self.assertEqual(port_id, port_resp['id'])
+ self.assertEqual(port_resp['fixed_ips'], [])
+
class NetworksTestXML(NetworksTestJSON):
_interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volume_quotas.py b/tempest/api/volume/admin/test_volume_quotas.py
index cf308f5..2949d56 100644
--- a/tempest/api/volume/admin/test_volume_quotas.py
+++ b/tempest/api/volume/admin/test_volume_quotas.py
@@ -61,9 +61,11 @@
self.demo_tenant_id,
**new_quota_set)
- default_quota_set.pop('id')
+ cleanup_quota_set = dict(
+ (k, v) for k, v in default_quota_set.iteritems()
+ if k in QUOTA_KEYS)
self.addCleanup(self.quotas_client.update_quota_set,
- self.demo_tenant_id, **default_quota_set)
+ self.demo_tenant_id, **cleanup_quota_set)
self.assertEqual(200, resp.status)
# test that the specific values we set are actually in
# the final result. There is nothing here that ensures there
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 2701e84..84c9501 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -71,8 +71,8 @@
resp, server = self.servers_client.create_server(server_name,
self.image_ref,
self.flavor_ref)
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
self.addCleanup(self.servers_client.delete_server, server['id'])
+ self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
mountpoint = '/dev/%s' % CONF.compute.volume_device_name
resp, body = self.volumes_client.attach_volume(
self.volume_origin['id'], server['id'], mountpoint)
diff --git a/tempest/api_schema/compute/hosts.py b/tempest/api_schema/compute/hosts.py
new file mode 100644
index 0000000..b9a3db9
--- /dev/null
+++ b/tempest/api_schema/compute/hosts.py
@@ -0,0 +1,35 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed 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.
+
+list_hosts = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'hosts': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'host_name': {'type': 'string'},
+ 'service': {'type': 'string'},
+ 'zone': {'type': 'string'}
+ },
+ 'required': ['host_name', 'service', 'zone']
+ }
+ }
+ },
+ 'required': ['hosts']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/fixed_ips.py b/tempest/api_schema/compute/v2/fixed_ips.py
new file mode 100644
index 0000000..a6add04
--- /dev/null
+++ b/tempest/api_schema/compute/v2/fixed_ips.py
@@ -0,0 +1,36 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed 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.
+
+fixed_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'fixed_ip': {
+ 'type': 'object',
+ 'properties': {
+ 'address': {
+ 'type': 'string',
+ 'format': 'ip-address'
+ },
+ 'cidr': {'type': 'string'},
+ 'host': {'type': 'string'},
+ 'hostname': {'type': 'string'}
+ },
+ 'required': ['address', 'cidr', 'host', 'hostname']
+ }
+ },
+ 'required': ['fixed_ip']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
new file mode 100644
index 0000000..61582ec
--- /dev/null
+++ b/tempest/api_schema/compute/v2/floating_ips.py
@@ -0,0 +1,46 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed 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.
+
+list_floating_ips = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is integer, but
+ # here allows 'string' also because we will be
+ # able to change it to 'uuid' in the future.
+ 'id': {'type': ['integer', 'string']},
+ 'pool': {'type': ['string', 'null']},
+ 'instance_id': {'type': ['integer', 'string', 'null']},
+ 'ip': {
+ 'type': 'string',
+ 'format': 'ip-address'
+ },
+ 'fixed_ip': {
+ 'type': ['string', 'null'],
+ 'format': 'ip-address'
+ }
+ },
+ 'required': ['id', 'pool', 'instance_id', 'ip', 'fixed_ip']
+ }
+ }
+ },
+ 'required': ['floating_ips']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
new file mode 100644
index 0000000..7f06ca6
--- /dev/null
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -0,0 +1,53 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed 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.
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is uuid, but here allows
+ # 'integer' also because old OpenStack uses 'integer'
+ # as a server id.
+ 'id': {'type': ['integer', 'string']},
+ 'security_groups': {'type': 'array'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ },
+ 'adminPass': {'type': 'string'},
+ 'OS-DCF:diskConfig': {'type': 'string'}
+ },
+ # NOTE: OS-DCF:diskConfig is API extension, and some
+ # environments return a response without the attribute.
+ # So it is not 'required'.
+ 'required': ['id', 'security_groups', 'links', 'adminPass']
+ }
+ },
+ 'required': ['server']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/__init__.py b/tempest/api_schema/compute/v3/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/api_schema/compute/v3/__init__.py
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
new file mode 100644
index 0000000..e69b25f
--- /dev/null
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -0,0 +1,55 @@
+# Copyright 2014 NEC Corporation. All rights reserved.
+#
+# Licensed 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.
+
+create_server = {
+ 'status_code': [202],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server': {
+ 'type': 'object',
+ 'properties': {
+ # NOTE: Now the type of 'id' is uuid, but here allows
+ # 'integer' also because old OpenStack uses 'integer'
+ # as a server id.
+ 'id': {'type': ['integer', 'string']},
+ 'os-security-groups:security_groups': {'type': 'array'},
+ 'links': {
+ 'type': 'array',
+ 'items': {
+ 'type': 'object',
+ 'properties': {
+ 'href': {
+ 'type': 'string',
+ 'format': 'uri'
+ },
+ 'rel': {'type': 'string'}
+ },
+ 'required': ['href', 'rel']
+ }
+ },
+ 'admin_password': {'type': 'string'},
+ 'os-access-ips:access_ip_v4': {'type': 'string'},
+ 'os-access-ips:access_ip_v6': {'type': 'string'}
+ },
+ # NOTE: os-access-ips:access_ip_v4/v6 are API extension,
+ # and some environments return a response without these
+ # attributes. So they are not 'required'.
+ 'required': ['id', 'os-security-groups:security_groups',
+ 'links', 'admin_password']
+ }
+ },
+ 'required': ['server']
+ }
+}
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index d0b6028..a3787ab 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -182,6 +182,10 @@
self.nova('agent-list')
self.nova('agent-list', flags='--debug')
+ def test_migration_list(self):
+ self.nova('migration-list')
+ self.nova('migration-list', flags='--debug')
+
# Optional arguments:
def test_admin_version(self):
diff --git a/tempest/services/compute/json/fixed_ips_client.py b/tempest/services/compute/json/fixed_ips_client.py
index 8b2c6c9..5fdd564 100644
--- a/tempest/services/compute/json/fixed_ips_client.py
+++ b/tempest/services/compute/json/fixed_ips_client.py
@@ -15,6 +15,7 @@
import json
+from tempest.api_schema.compute.v2 import fixed_ips as schema
from tempest.common import rest_client
from tempest import config
@@ -31,6 +32,7 @@
url = "os-fixed-ips/%s" % (fixed_ip)
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.fixed_ips, resp, body)
return resp, body['fixed_ip']
def reserve_fixed_ip(self, ip, body):
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index 42487c3..2a7e25a 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -16,6 +16,7 @@
import json
import urllib
+from tempest.api_schema.compute.v2 import floating_ips as schema
from tempest.common import rest_client
from tempest import config
from tempest import exceptions
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_floating_ips, resp, body)
return resp, body['floating_ips']
def get_floating_ip_details(self, floating_ip_id):
diff --git a/tempest/services/compute/json/hosts_client.py b/tempest/services/compute/json/hosts_client.py
index fb45997..0130f27 100644
--- a/tempest/services/compute/json/hosts_client.py
+++ b/tempest/services/compute/json/hosts_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute import hosts as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
return resp, body['hosts']
def show_host_detail(self, hostname):
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 58f1e84..d6705db 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -18,6 +18,7 @@
import time
import urllib
+from tempest.api_schema.compute.v2 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -90,6 +91,7 @@
# with return reservation id set True
if 'reservation_id' in body:
return resp, body
+ self.validate_response(schema.create_server, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, accessIPv4=None,
diff --git a/tempest/services/compute/v3/json/hosts_client.py b/tempest/services/compute/v3/json/hosts_client.py
index e27c7c6..bcb9d36 100644
--- a/tempest/services/compute/v3/json/hosts_client.py
+++ b/tempest/services/compute/v3/json/hosts_client.py
@@ -15,6 +15,7 @@
import json
import urllib
+from tempest.api_schema.compute import hosts as schema
from tempest.common import rest_client
from tempest import config
@@ -36,6 +37,7 @@
resp, body = self.get(url)
body = json.loads(body)
+ self.validate_response(schema.list_hosts, resp, body)
return resp, body['hosts']
def show_host_detail(self, hostname):
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index 92eb09b..6f492d0 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -19,6 +19,7 @@
import time
import urllib
+from tempest.api_schema.compute.v3 import servers as schema
from tempest.common import rest_client
from tempest.common import waiters
from tempest import config
@@ -91,6 +92,7 @@
# with return reservation id set True
if 'servers_reservation' in body:
return resp, body['servers_reservation']
+ self.validate_response(schema.create_server, resp, body)
return resp, body['server']
def update_server(self, server_id, name=None, meta=None, access_ip_v4=None,
diff --git a/tempest/tests/common/utils/test_file_utils.py b/tempest/tests/common/utils/test_file_utils.py
new file mode 100644
index 0000000..99ae033
--- /dev/null
+++ b/tempest/tests/common/utils/test_file_utils.py
@@ -0,0 +1,32 @@
+# Copyright 2014 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed 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 mock
+from mock import patch
+
+from tempest.common.utils import file_utils
+from tempest.tests import base
+
+
+class TestFileUtils(base.TestCase):
+
+ def test_have_effective_read_path(self):
+ with patch('__builtin__.open', mock.mock_open(), create=True):
+ result = file_utils.have_effective_read_access('fake_path')
+ self.assertTrue(result)
+
+ def test_not_effective_read_path(self):
+ result = file_utils.have_effective_read_access('fake_path')
+ self.assertFalse(result)