Merge "Don't store duplicate policies for server_group"
diff --git a/README.rst b/README.rst
index 9daf873..ea36619 100644
--- a/README.rst
+++ b/README.rst
@@ -7,7 +7,7 @@
deployment.
Design Principles
-----------
+-----------------
Tempest Design Principles that we strive to live by.
- Tempest should be able to run against any OpenStack cloud, be it a
@@ -127,6 +127,60 @@
of tempest when running with Python 2.6. Additionally, to enable testr to work
with tempest using python 2.6 the discover module from the unittest-ext
project has to be patched to switch the unittest.TestSuite to use
-unittest2.TestSuite instead. See::
+unittest2.TestSuite instead. See:
https://code.google.com/p/unittest-ext/issues/detail?id=79
+
+Branchless Tempest Considerations
+---------------------------------
+
+Starting with the OpenStack Icehouse release Tempest no longer has any stable
+branches. This is to better ensure API consistency between releases because
+the API behavior should not change between releases. This means that the stable
+branches are also gated by the Tempest master branch, which also means that
+proposed commits to Tempest must work against both the master and all the
+currently supported stable branches of the projects. As such there are a few
+special considerations that have to be accounted for when pushing new changes
+to tempest.
+
+1. New Tests for new features
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When adding tests for new features that were not in previous releases of the
+projects the new test has to be properly skipped with a feature flag. Whether
+this is just as simple as using the @test.requires_ext() decorator to check
+if the required extension (or discoverable optional API) is enabled or adding
+a new config option to the appropriate section. If there isn't a method of
+selecting the new **feature** from the config file then there won't be a
+mechanism to disable the test with older stable releases and the new test won't
+be able to merge.
+
+2. Bug fix on core project needing Tempest changes
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+When trying to land a bug fix which changes a tested API you'll have to use the
+following procedure::
+
+ - Propose change to the project, get a +2 on the change even with failing
+ - Propose skip on Tempest which will only be approved after the
+ corresponding change in the project has a +2 on change
+ - Land project change in master and all open stable branches (if required)
+ - Land changed test in Tempest
+
+Otherwise the bug fix won't be able to land in the project.
+
+3. New Tests for existing features
+^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^^
+
+If a test is being added for a feature that exists in all the current releases
+of the projects then the only concern is that the API behavior is the same
+across all the versions of the project being tested. If the behavior is not
+consistent the test will not be able to merge.
+
+API Stability
+-------------
+
+For new tests being added to Tempest the assumption is that the API being
+tested is considered stable and adheres to the OpenStack API stability
+guidelines. If an API is still considered experimental or in development then
+it should not be tested by Tempest until it is considered stable.
diff --git a/doc/source/index.rst b/doc/source/index.rst
index 1c32b9c..c45273e 100644
--- a/doc/source/index.rst
+++ b/doc/source/index.rst
@@ -33,14 +33,6 @@
field_guide/thirdparty
field_guide/unit_tests
-------------------
-API and test cases
-------------------
-.. toctree::
- :maxdepth: 1
-
- api/modules
-
==================
Indices and tables
==================
diff --git a/etc/tempest.conf.sample b/etc/tempest.conf.sample
index 37d4d53..9051310 100644
--- a/etc/tempest.conf.sample
+++ b/etc/tempest.conf.sample
@@ -327,6 +327,11 @@
# (integer value)
#shelved_offload_time=0
+# Unallocated floating IP range, which will be used to test
+# the floating IP bulk feature for CRUD operation. (string
+# value)
+#floating_ip_range=10.0.0.0/29
+
# Allows test cases to create/destroy tenants and users. This
# option enables isolated test cases and better parallel
# execution, but also requires that OpenStack Identity API
@@ -366,17 +371,19 @@
#
# If false, skip all nova v3 tests. (boolean value)
-#api_v3=true
+#api_v3=false
# If false, skip disk config tests (boolean value)
#disk_config=true
# A list of enabled compute extensions with a special entry
-# all which indicates every extension is enabled (list value)
+# all which indicates every extension is enabled. Each
+# extension should be specified with alias name (list value)
#api_extensions=all
# A list of enabled v3 extensions with a special entry all
-# which indicates every extension is enabled (list value)
+# which indicates every extension is enabled. Each extension
+# should be specified with alias name (list value)
#api_v3_extensions=all
# Does the test environment support changing the admin
@@ -711,6 +718,10 @@
# (integer value)
#build_interval=1
+# List of dns servers whichs hould be used for subnet creation
+# (list value)
+#dns_servers=8.8.8.8,8.8.4.4
+
[network-feature-enabled]
@@ -976,6 +987,10 @@
# value)
#endpoint_type=publicURL
+# This variable is used as flag to enable notification tests
+# (boolean value)
+#too_slow_to_test=true
+
[volume]
diff --git a/openstack-common.conf b/openstack-common.conf
index 38d58ee..a9a6b0b 100644
--- a/openstack-common.conf
+++ b/openstack-common.conf
@@ -7,6 +7,7 @@
module=log
module=importlib
module=fixture
+module=versionutils
# The base module to hold the copy of openstack.common
base=tempest
diff --git a/requirements.txt b/requirements.txt
index 4bf2bcf..ab2903a 100644
--- a/requirements.txt
+++ b/requirements.txt
@@ -8,7 +8,7 @@
paramiko>=1.13.0
netaddr>=0.7.6
python-glanceclient>=0.9.0
-python-keystoneclient>=0.8.0
+python-keystoneclient>=0.9.0
python-novaclient>=2.17.0
python-neutronclient>=2.3.4,<3
python-cinderclient>=1.0.6
@@ -17,10 +17,9 @@
python-saharaclient>=0.6.0
python-swiftclient>=2.0.2
testresources>=0.2.4
-keyring>=2.1
testrepository>=0.0.18
-oslo.config>=1.2.0
-six>=1.6.0
+oslo.config>=1.2.1
+six>=1.7.0
iso8601>=0.1.9
fixtures>=0.3.14
testscenarios>=0.4
diff --git a/setup.cfg b/setup.cfg
index 14e1913..5c62710 100644
--- a/setup.cfg
+++ b/setup.cfg
@@ -20,6 +20,8 @@
[entry_points]
console_scripts =
verify-tempest-config = tempest.cmd.verify_tempest_config:main
+ javelin2 = tempest.cmd.javelin:main
+ run-tempest-stress = tempest.cmd.run_stress:main
[build_sphinx]
all_files = 1
diff --git a/tempest/api/baremetal/base.py b/tempest/api/baremetal/base.py
index 021adaf..6f7e438 100644
--- a/tempest/api/baremetal/base.py
+++ b/tempest/api/baremetal/base.py
@@ -27,13 +27,12 @@
def decorator(f):
@functools.wraps(f)
def wrapper(cls, *args, **kwargs):
- result = f(cls, *args, **kwargs)
- body = result[resource]
+ resp, body = f(cls, *args, **kwargs)
if 'uuid' in body:
cls.created_objects[resource].add(body['uuid'])
- return result
+ return resp, body
return wrapper
return decorator
@@ -51,7 +50,7 @@
mgr = clients.AdminManager()
cls.client = mgr.baremetal_client
-
+ cls.power_timeout = CONF.baremetal.power_timeout
cls.created_objects = {'chassis': set(),
'port': set(),
'node': set()}
@@ -81,8 +80,7 @@
"""
description = description or data_utils.rand_name('test-chassis-')
resp, body = cls.client.create_chassis(description=description)
-
- return {'chassis': body, 'response': resp}
+ return resp, body
@classmethod
@creates('node')
@@ -102,7 +100,7 @@
cpu_num=cpu_num, storage=storage,
memory=memory, driver=driver)
- return {'node': body, 'response': resp}
+ return resp, body
@classmethod
@creates('port')
@@ -121,7 +119,7 @@
resp, body = cls.client.create_port(address=address, node_id=node_id,
extra=extra, uuid=uuid)
- return {'port': body, 'response': resp}
+ return resp, body
@classmethod
def delete_chassis(cls, chassis_id):
diff --git a/tempest/api/baremetal/test_api_discovery.py b/tempest/api/baremetal/test_api_discovery.py
index e594b3e..bee10b9 100644
--- a/tempest/api/baremetal/test_api_discovery.py
+++ b/tempest/api/baremetal/test_api_discovery.py
@@ -20,6 +20,7 @@
@test.attr(type='smoke')
def test_api_versions(self):
resp, descr = self.client.get_api_description()
+ self.assertEqual('200', resp['status'])
expected_versions = ('v1',)
versions = [version['id'] for version in descr['versions']]
@@ -30,6 +31,7 @@
@test.attr(type='smoke')
def test_default_version(self):
resp, descr = self.client.get_api_description()
+ self.assertEqual('200', resp['status'])
default_version = descr['default_version']
self.assertEqual(default_version['id'], 'v1')
@@ -37,6 +39,7 @@
@test.attr(type='smoke')
def test_version_1_resources(self):
resp, descr = self.client.get_version_description(version='v1')
+ self.assertEqual('200', resp['status'])
expected_resources = ('nodes', 'chassis',
'ports', 'links', 'media_types')
diff --git a/tempest/api/baremetal/test_chassis.py b/tempest/api/baremetal/test_chassis.py
index 7af1336..4ab86c2 100644
--- a/tempest/api/baremetal/test_chassis.py
+++ b/tempest/api/baremetal/test_chassis.py
@@ -20,57 +20,64 @@
class TestChassis(base.BaseBaremetalTest):
"""Tests for chassis."""
+ @classmethod
+ def setUpClass(cls):
+ super(TestChassis, cls).setUpClass()
+ _, cls.chassis = cls.create_chassis()
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
+
@test.attr(type='smoke')
def test_create_chassis(self):
descr = data_utils.rand_name('test-chassis-')
- ch = self.create_chassis(description=descr)['chassis']
-
- self.assertEqual(ch['description'], descr)
+ resp, chassis = self.create_chassis(description=descr)
+ self.assertEqual('201', resp['status'])
+ self.assertEqual(chassis['description'], descr)
@test.attr(type='smoke')
def test_create_chassis_unicode_description(self):
# Use a unicode string for testing:
# 'We ♡ OpenStack in Ukraine'
descr = u'В Україні ♡ OpenStack!'
- ch = self.create_chassis(description=descr)['chassis']
-
- self.assertEqual(ch['description'], descr)
+ resp, chassis = self.create_chassis(description=descr)
+ self.assertEqual('201', resp['status'])
+ self.assertEqual(chassis['description'], descr)
@test.attr(type='smoke')
def test_show_chassis(self):
- descr = data_utils.rand_name('test-chassis-')
- uuid = self.create_chassis(description=descr)['chassis']['uuid']
-
- resp, chassis = self.client.show_chassis(uuid)
-
- self.assertEqual(chassis['uuid'], uuid)
- self.assertEqual(chassis['description'], descr)
+ resp, chassis = self.client.show_chassis(self.chassis['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(self.chassis, chassis)
@test.attr(type="smoke")
def test_list_chassis(self):
- created_ids = [self.create_chassis()['chassis']['uuid']
- for i in range(0, 5)]
-
resp, body = self.client.list_chassis()
- loaded_ids = [ch['uuid'] for ch in body['chassis']]
-
- for i in created_ids:
- self.assertIn(i, loaded_ids)
+ self.assertEqual('200', resp['status'])
+ self.assertIn(self.chassis['uuid'],
+ [i['uuid'] for i in body['chassis']])
@test.attr(type='smoke')
def test_delete_chassis(self):
- uuid = self.create_chassis()['chassis']['uuid']
+ resp, body = self.create_chassis()
+ uuid = body['uuid']
- self.delete_chassis(uuid)
-
+ resp = self.delete_chassis(uuid)
+ self.assertEqual('204', resp['status'])
self.assertRaises(exc.NotFound, self.client.show_chassis, uuid)
@test.attr(type='smoke')
def test_update_chassis(self):
- chassis_id = self.create_chassis()['chassis']['uuid']
+ resp, body = self.create_chassis()
+ uuid = body['uuid']
new_description = data_utils.rand_name('new-description-')
- self.client.update_chassis(chassis_id, description=new_description)
-
- resp, chassis = self.client.show_chassis(chassis_id)
+ resp, body = (self.client.update_chassis(uuid,
+ description=new_description))
+ self.assertEqual('200', resp['status'])
+ resp, chassis = self.client.show_chassis(uuid)
self.assertEqual(chassis['description'], new_description)
diff --git a/tempest/api/baremetal/test_nodes.py b/tempest/api/baremetal/test_nodes.py
index 0f585cb..b6432ad 100644
--- a/tempest/api/baremetal/test_nodes.py
+++ b/tempest/api/baremetal/test_nodes.py
@@ -23,7 +23,15 @@
def setUp(self):
super(TestNodes, self).setUp()
- self.chassis = self.create_chassis()['chassis']
+ _, self.chassis = self.create_chassis()
+ _, self.node = self.create_node(self.chassis['uuid'])
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in six.iteritems(expected):
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
@test.attr(type='smoke')
def test_create_node(self):
@@ -32,45 +40,32 @@
'storage': '10240',
'memory': '1024'}
- node = self.create_node(self.chassis['uuid'], **params)['node']
-
- for key in params:
- self.assertEqual(node['properties'][key], params[key])
+ resp, body = self.create_node(self.chassis['uuid'], **params)
+ self.assertEqual('201', resp['status'])
+ self._assertExpected(params, body['properties'])
@test.attr(type='smoke')
def test_delete_node(self):
- node = self.create_node(self.chassis['uuid'])['node']
- node_id = node['uuid']
+ resp, node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
- resp = self.delete_node(node_id)
+ resp = self.delete_node(node['uuid'])
self.assertEqual(resp['status'], '204')
- self.assertRaises(exc.NotFound, self.client.show_node, node_id)
+ self.assertRaises(exc.NotFound, self.client.show_node, node['uuid'])
@test.attr(type='smoke')
def test_show_node(self):
- params = {'cpu_arch': 'x86_64',
- 'cpu_num': '4',
- 'storage': '100',
- 'memory': '512'}
-
- created_node = self.create_node(self.chassis['uuid'], **params)['node']
- resp, loaded_node = self.client.show_node(created_node['uuid'])
-
- for key, val in created_node.iteritems():
- if key not in ('created_at', 'updated_at'):
- self.assertEqual(loaded_node[key], val)
+ resp, loaded_node = self.client.show_node(self.node['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(self.node, loaded_node)
@test.attr(type='smoke')
def test_list_nodes(self):
- uuids = [self.create_node(self.chassis['uuid'])['node']['uuid']
- for i in range(0, 5)]
-
resp, body = self.client.list_nodes()
- loaded_uuids = [n['uuid'] for n in body['nodes']]
-
- for u in uuids:
- self.assertIn(u, loaded_uuids)
+ self.assertEqual('200', resp['status'])
+ self.assertIn(self.node['uuid'],
+ [i['uuid'] for i in body['nodes']])
@test.attr(type='smoke')
def test_update_node(self):
@@ -79,17 +74,16 @@
'storage': '10',
'memory': '128'}
- node = self.create_node(self.chassis['uuid'], **props)['node']
- node_id = node['uuid']
+ resp, node = self.create_node(self.chassis['uuid'], **props)
+ self.assertEqual('201', resp['status'])
- new_props = {'cpu_arch': 'x86',
- 'cpu_num': '1',
- 'storage': '10000',
- 'memory': '12300'}
+ new_p = {'cpu_arch': 'x86',
+ 'cpu_num': '1',
+ 'storage': '10000',
+ 'memory': '12300'}
- self.client.update_node(node_id, properties=new_props)
- resp, node = self.client.show_node(node_id)
-
- for name, value in six.iteritems(new_props):
- if name not in ('created_at', 'updated_at'):
- self.assertEqual(node['properties'][name], value)
+ resp, body = self.client.update_node(node['uuid'], properties=new_p)
+ self.assertEqual('200', resp['status'])
+ resp, node = self.client.show_node(node['uuid'])
+ self.assertEqual('200', resp['status'])
+ self._assertExpected(new_p, node['properties'])
diff --git a/tempest/api/baremetal/test_nodestates.py b/tempest/api/baremetal/test_nodestates.py
index c658d7f..3044bc6 100644
--- a/tempest/api/baremetal/test_nodestates.py
+++ b/tempest/api/baremetal/test_nodestates.py
@@ -13,6 +13,8 @@
# under the License.
from tempest.api.baremetal import base
+from tempest import exceptions
+from tempest.openstack.common import timeutils
from tempest import test
@@ -20,10 +22,25 @@
"""Tests for baremetal NodeStates."""
@classmethod
- def setUpClass(self):
- super(TestNodeStates, self).setUpClass()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ def setUpClass(cls):
+ super(TestNodeStates, cls).setUpClass()
+ resp, cls.chassis = cls.create_chassis()
+ resp, cls.node = cls.create_node(cls.chassis['uuid'])
+
+ def _validate_power_state(self, node_uuid, power_state):
+ # Validate that power state is set within timeout
+ if power_state == 'rebooting':
+ power_state = 'power on'
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(
+ start, timeutils.utcnow()) < self.power_timeout:
+ resp, node = self.client.show_node(node_uuid)
+ self.assertEqual(200, resp.status)
+ if node['power_state'] == power_state:
+ return
+ message = ('Failed to set power state within '
+ 'the required time: %s sec.' % self.power_timeout)
+ raise exceptions.TimeoutException(message)
@test.attr(type='smoke')
def test_list_nodestates(self):
@@ -31,3 +48,16 @@
self.assertEqual('200', resp['status'])
for key in nodestates:
self.assertEqual(nodestates[key], self.node[key])
+
+ @test.attr(type='smoke')
+ def test_set_node_power_state(self):
+ resp, node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
+ states = ["power on", "rebooting", "power off"]
+ for state in states:
+ # Set power state
+ resp, _ = self.client.set_node_power_state(node['uuid'],
+ state)
+ self.assertEqual('202', resp['status'])
+ # Check power state after state is set
+ self._validate_power_state(node['uuid'], state)
diff --git a/tempest/api/baremetal/test_ports.py b/tempest/api/baremetal/test_ports.py
index 8b76811..c2af29a 100644
--- a/tempest/api/baremetal/test_ports.py
+++ b/tempest/api/baremetal/test_ports.py
@@ -22,25 +22,30 @@
def setUp(self):
super(TestPorts, self).setUp()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ _, self.chassis = self.create_chassis()
+ _, self.node = self.create_node(self.chassis['uuid'])
+ _, self.port = self.create_port(self.node['uuid'],
+ data_utils.rand_mac_address())
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ if key not in ('created_at', 'updated_at'):
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
@test.attr(type='smoke')
def test_create_port(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- result = self.create_port(node_id=node_id, address=address)
-
- port = result['port']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
- self.assertEqual(port['uuid'], body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual({}, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_create_port_specifying_uuid(self):
@@ -48,15 +53,13 @@
address = data_utils.rand_mac_address()
uuid = data_utils.rand_uuid()
- self.create_port(node_id=node_id, address=address, uuid=uuid)
+ resp, port = self.create_port(node_id=node_id,
+ address=address, uuid=uuid)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(uuid)
-
self.assertEqual(200, resp.status)
- self.assertEqual(uuid, body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual({}, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_create_port_with_extra(self):
@@ -64,76 +67,46 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- result = self.create_port(node_id=node_id, address=address,
- extra=extra)
- port = result['port']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
resp, body = self.client.show_port(port['uuid'])
-
self.assertEqual(200, resp.status)
- self.assertEqual(port['uuid'], body['uuid'])
- self.assertEqual(address, body['address'])
- self.assertEqual(extra, body['extra'])
- self.assertEqual(node_id, body['node_uuid'])
+ self._assertExpected(port, body)
@test.attr(type='smoke')
def test_delete_port(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
- resp = self.delete_port(port_id)
+ resp = self.delete_port(port['uuid'])
self.assertEqual(204, resp.status)
- self.assertRaises(exc.NotFound, self.client.show_port, port_id)
+ self.assertRaises(exc.NotFound, self.client.show_port, port['uuid'])
@test.attr(type='smoke')
def test_show_port(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
-
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
-
- resp, port = self.client.show_port(port_id)
-
+ resp, port = self.client.show_port(self.port['uuid'])
self.assertEqual(200, resp.status)
- self.assertEqual(port_id, port['uuid'])
- self.assertEqual(address, port['address'])
- self.assertEqual(extra, port['extra'])
+ self._assertExpected(self.port, port)
@test.attr(type='smoke')
def test_show_port_with_links(self):
- node_id = self.node['uuid']
- address = data_utils.rand_mac_address()
-
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
-
- resp, body = self.client.show_port(port_id)
-
+ resp, port = self.client.show_port(self.port['uuid'])
self.assertEqual(200, resp.status)
- self.assertIn('links', body.keys())
- self.assertEqual(2, len(body['links']))
- self.assertIn(port_id, body['links'][0]['href'])
+ self.assertIn('links', port.keys())
+ self.assertEqual(2, len(port['links']))
+ self.assertIn(port['uuid'], port['links'][0]['href'])
@test.attr(type='smoke')
def test_list_ports(self):
- node_id = self.node['uuid']
-
- uuids = [self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
- ['port']['uuid'] for i in xrange(5)]
-
resp, body = self.client.list_ports()
self.assertEqual(200, resp.status)
- loaded_uuids = [p['uuid'] for p in body['ports']]
-
- for uuid in uuids:
- self.assertIn(uuid, loaded_uuids)
-
+ self.assertIn(self.port['uuid'],
+ [i['uuid'] for i in body['ports']])
# Verify self links.
for port in body['ports']:
self.validate_self_link('ports', port['uuid'],
@@ -141,15 +114,8 @@
@test.attr(type='smoke')
def test_list_with_limit(self):
- node_id = self.node['uuid']
-
- for i in xrange(5):
- self.create_port(node_id=node_id,
- address=data_utils.rand_mac_address())
-
resp, body = self.client.list_ports(limit=3)
self.assertEqual(200, resp.status)
- self.assertEqual(3, len(body['ports']))
next_marker = body['ports'][-1]['uuid']
self.assertIn(next_marker, body['next'])
@@ -160,13 +126,13 @@
uuids = [
self.create_port(node_id=node_id,
address=data_utils.rand_mac_address())
- ['port']['uuid'] for i in range(0, 5)]
+ [1]['uuid'] for i in range(0, 5)]
resp, body = self.client.list_ports_detail()
self.assertEqual(200, resp.status)
- ports_dict = {port['uuid']: port for port in body['ports']
- if port['uuid'] in uuids}
+ ports_dict = dict((port['uuid'], port) for port in body['ports']
+ if port['uuid'] in uuids)
for uuid in uuids:
self.assertIn(uuid, ports_dict)
@@ -185,8 +151,9 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key2': 'new-value2',
@@ -205,9 +172,10 @@
'op': 'replace',
'value': new_extra['key3']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(new_address, body['address'])
self.assertEqual(new_extra, body['extra'])
@@ -218,23 +186,25 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2', 'key3': 'value3'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
# Removing one item from the collection
- resp, _ = self.client.update_port(port_id, [{'path': '/extra/key2',
- 'op': 'remove'}])
+ resp, _ = self.client.update_port(port['uuid'],
+ [{'path': '/extra/key2',
+ 'op': 'remove'}])
self.assertEqual(200, resp.status)
extra.pop('key2')
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(extra, body['extra'])
# Removing the collection
- resp, _ = self.client.update_port(port_id, [{'path': '/extra',
- 'op': 'remove'}])
+ resp, _ = self.client.update_port(port['uuid'], [{'path': '/extra',
+ 'op': 'remove'}])
self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual({}, body['extra'])
@@ -247,8 +217,8 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual(201, resp.status)
extra = {'key1': 'value1', 'key2': 'value2'}
@@ -259,9 +229,10 @@
'op': 'add',
'value': extra['key2']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(extra, body['extra'])
@@ -271,8 +242,9 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual(201, resp.status)
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
@@ -289,9 +261,10 @@
'op': 'add',
'value': new_extra['key3']}]
- self.client.update_port(port_id, patch)
+ resp, _ = self.client.update_port(port['uuid'], patch)
+ self.assertEqual(200, resp.status)
- resp, body = self.client.show_port(port_id)
+ resp, body = self.client.show_port(port['uuid'])
self.assertEqual(200, resp.status)
self.assertEqual(new_address, body['address'])
self.assertEqual(new_extra, body['extra'])
diff --git a/tempest/api/baremetal/test_ports_negative.py b/tempest/api/baremetal/test_ports_negative.py
index 4cbe00e..3e77a5f 100644
--- a/tempest/api/baremetal/test_ports_negative.py
+++ b/tempest/api/baremetal/test_ports_negative.py
@@ -22,8 +22,11 @@
def setUp(self):
super(TestPortsNegative, self).setUp()
- chassis = self.create_chassis()['chassis']
- self.node = self.create_node(chassis['uuid'])['node']
+ resp, self.chassis = self.create_chassis()
+ self.assertEqual('201', resp['status'])
+
+ resp, self.node = self.create_node(self.chassis['uuid'])
+ self.assertEqual('201', resp['status'])
@test.attr(type=['negative', 'smoke'])
def test_create_port_malformed_mac(self):
@@ -134,9 +137,13 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
- self.client.delete_port(port_id)
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
+ resp, body = self.client.delete_port(port_id)
+ self.assertEqual('204', resp['status'])
patch = [{'path': '/extra/key',
'op': 'replace',
@@ -162,8 +169,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/extra/key', ' op': 'add',
@@ -174,8 +182,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/extra',
@@ -187,8 +196,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/nonexistent', ' op': 'add',
'value': 'value'}])
@@ -198,8 +209,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/node_uuid',
'op': 'replace',
@@ -213,9 +225,13 @@
address1 = data_utils.rand_mac_address()
address2 = data_utils.rand_mac_address()
- self.create_port(node_id=node_id, address=address1)
- port_id = self.create_port(node_id=node_id,
- address=address2)['port']['uuid']
+ resp, port1 = self.create_port(node_id=node_id, address=address1)
+ self.assertEqual('201', resp['status'])
+
+ resp, port2 = self.create_port(node_id=node_id, address=address2)
+ self.assertEqual('201', resp['status'])
+ port_id = port2['uuid']
+
patch = [{'path': '/address',
'op': 'replace',
'value': address1}]
@@ -227,8 +243,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/node_uuid',
'op': 'replace',
@@ -241,8 +258,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/address',
'op': 'replace',
'value': 'malformed:mac'}]
@@ -256,13 +275,14 @@
address = data_utils.rand_mac_address()
extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id,
- address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/extra/key',
'op': 'replace',
'value': 0.123}]
-
self.assertRaises(exc.BadRequest,
self.client.update_port, port_id, patch)
@@ -270,11 +290,11 @@
def test_update_port_replace_whole_extra_with_malformed(self):
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- extra = {'key': 'value'}
- port_id = self.create_port(node_id=node_id,
- address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
patch = [{'path': '/extra',
'op': 'replace',
'value': [1, 2, 3, 4, 'a']}]
@@ -287,8 +307,9 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id,
- address=address)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
patch = [{'path': '/nonexistent', ' op': 'replace', 'value': 'value'}]
@@ -300,8 +321,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/address', 'op': 'remove'}])
@@ -310,8 +333,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/uuid', 'op': 'remove'}])
@@ -320,8 +345,10 @@
node_id = self.node['uuid']
address = data_utils.rand_mac_address()
- port_id = self.create_port(node_id=node_id, address=address)['port'][
- 'uuid']
+ resp, port = self.create_port(node_id=node_id, address=address)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
+
self.assertRaises(exc.BadRequest, self.client.update_port, port_id,
[{'path': '/nonexistent', 'op': 'remove'}])
@@ -339,8 +366,10 @@
address = data_utils.rand_mac_address()
extra = {'key1': 'value1', 'key2': 'value2'}
- port_id = self.create_port(node_id=node_id, address=address,
- extra=extra)['port']['uuid']
+ resp, port = self.create_port(node_id=node_id, address=address,
+ extra=extra)
+ self.assertEqual('201', resp['status'])
+ port_id = port['uuid']
new_address = data_utils.rand_mac_address()
new_extra = {'key1': 'new-value1', 'key3': 'new-value3'}
diff --git a/tempest/api/compute/admin/test_flavors.py b/tempest/api/compute/admin/test_flavors.py
index 111ac9c..18866e5 100644
--- a/tempest/api/compute/admin/test_flavors.py
+++ b/tempest/api/compute/admin/test_flavors.py
@@ -30,8 +30,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAdminTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
@@ -231,7 +231,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- # Create the flavor
+ # Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
self.ram, self.vcpus,
self.disk,
diff --git a/tempest/api/compute/admin/test_flavors_access.py b/tempest/api/compute/admin/test_flavors_access.py
index 3ba7314..f2554ea 100644
--- a/tempest/api/compute/admin/test_flavors_access.py
+++ b/tempest/api/compute/admin/test_flavors_access.py
@@ -28,8 +28,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
# Compute admin flavor client
diff --git a/tempest/api/compute/admin/test_flavors_access_negative.py b/tempest/api/compute/admin/test_flavors_access_negative.py
index 73834e9..b636ccd 100644
--- a/tempest/api/compute/admin/test_flavors_access_negative.py
+++ b/tempest/api/compute/admin/test_flavors_access_negative.py
@@ -31,8 +31,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAccessNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs.py b/tempest/api/compute/admin/test_flavors_extra_specs.py
index 91145ec..56daf96 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs.py
@@ -29,8 +29,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
index a139c2f..1e5695f 100644
--- a/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
+++ b/tempest/api/compute/admin/test_flavors_extra_specs_negative.py
@@ -30,8 +30,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsExtraSpecsNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_flavors_negative.py b/tempest/api/compute/admin/test_flavors_negative.py
index b37d32c..9e4412f 100644
--- a/tempest/api/compute/admin/test_flavors_negative.py
+++ b/tempest/api/compute/admin/test_flavors_negative.py
@@ -32,8 +32,8 @@
@classmethod
def setUpClass(cls):
super(FlavorsAdminNegativeTestJSON, cls).setUpClass()
- if not test.is_extension_enabled('FlavorExtraData', 'compute'):
- msg = "FlavorExtraData extension not enabled."
+ if not test.is_extension_enabled('OS-FLV-EXT-DATA', 'compute'):
+ msg = "OS-FLV-EXT-DATA extension not enabled."
raise cls.skipException(msg)
cls.client = cls.os_adm.flavors_client
diff --git a/tempest/api/compute/admin/test_floating_ips_bulk.py b/tempest/api/compute/admin/test_floating_ips_bulk.py
new file mode 100644
index 0000000..208b032
--- /dev/null
+++ b/tempest/api/compute/admin/test_floating_ips_bulk.py
@@ -0,0 +1,82 @@
+# Copyright 2014 NEC Technologies India Ltd.
+# 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 netaddr
+
+from tempest.api.compute import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class FloatingIPsBulkAdminTestJSON(base.BaseV2ComputeAdminTest):
+ """
+ Tests Floating IPs Bulk APIs Create, List and Delete that
+ require admin privileges.
+ API documentation - http://docs.openstack.org/api/openstack-compute/2/
+ content/ext-os-floating-ips-bulk.html
+ """
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(FloatingIPsBulkAdminTestJSON, cls).setUpClass()
+ cls.client = cls.os_adm.floating_ips_client
+ cls.ip_range = CONF.compute.floating_ip_range
+ cls.verify_unallocated_floating_ip_range(cls.ip_range)
+
+ @classmethod
+ def verify_unallocated_floating_ip_range(cls, ip_range):
+ # Verify whether configure floating IP range is not already allocated.
+ _, body = cls.client.list_floating_ips_bulk()
+ allocated_ips_list = map(lambda x: x['address'], body)
+ for ip_addr in netaddr.IPNetwork(ip_range).iter_hosts():
+ if str(ip_addr) in allocated_ips_list:
+ msg = ("Configured unallocated floating IP range is already "
+ "allocated. Configure the correct unallocated range "
+ "as 'floating_ip_range'")
+ raise cls.skipException(msg)
+ return
+
+ def _delete_floating_ips_bulk(self, ip_range):
+ try:
+ self.client.delete_floating_ips_bulk(ip_range)
+ except Exception:
+ pass
+
+ @test.attr(type='gate')
+ def test_create_list_delete_floating_ips_bulk(self):
+ # Create, List and delete the Floating IPs Bulk
+ pool = 'test_pool'
+ # NOTE(GMann): Reserving the IP range but those are not attached
+ # anywhere. Using the below mentioned interface which is not ever
+ # expected to be used. Clean Up has been done for created IP range
+ interface = 'eth0'
+ resp, body = self.client.create_floating_ips_bulk(self.ip_range,
+ pool,
+ interface)
+
+ self.assertEqual(200, resp.status)
+ self.addCleanup(self._delete_floating_ips_bulk, self.ip_range)
+ self.assertEqual(self.ip_range, body['ip_range'])
+ resp, ips_list = self.client.list_floating_ips_bulk()
+ self.assertEqual(200, resp.status)
+ self.assertNotEqual(0, len(ips_list))
+ for ip in netaddr.IPNetwork(self.ip_range).iter_hosts():
+ self.assertIn(str(ip), map(lambda x: x['address'], ips_list))
+ resp, body = self.client.delete_floating_ips_bulk(self.ip_range)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(self.ip_range, body)
diff --git a/tempest/api/compute/flavors/test_flavors.py b/tempest/api/compute/flavors/test_flavors.py
index bfebb5e..0e6b9d6 100644
--- a/tempest/api/compute/flavors/test_flavors.py
+++ b/tempest/api/compute/flavors/test_flavors.py
@@ -67,8 +67,8 @@
@test.attr(type='gate')
def test_list_flavors_using_marker(self):
# The list of flavors should start from the provided marker
- resp, flavors = self.client.list_flavors()
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
params = {'marker': flavor_id}
resp, flavors = self.client.list_flavors(params)
@@ -78,8 +78,8 @@
@test.attr(type='gate')
def test_list_flavors_detailed_using_marker(self):
# The list of flavors should start from the provided marker
- resp, flavors = self.client.list_flavors_with_detail()
- flavor_id = flavors[0]['id']
+ resp, flavor = self.client.get_flavor_details(self.flavor_ref)
+ flavor_id = flavor['id']
params = {'marker': flavor_id}
resp, flavors = self.client.list_flavors_with_detail(params)
diff --git a/tempest/api/compute/images/test_images_negative.py b/tempest/api/compute/images/test_images_negative.py
index ae00ae2..6163f4d 100644
--- a/tempest/api/compute/images/test_images_negative.py
+++ b/tempest/api/compute/images/test_images_negative.py
@@ -40,7 +40,7 @@
# 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
+ # Create a new image after server is deleted
name = data_utils.rand_name('image')
meta = {'image_type': 'test'}
self.assertRaises(exceptions.NotFound,
diff --git a/tempest/api/compute/security_groups/test_security_group_rules.py b/tempest/api/compute/security_groups/test_security_group_rules.py
index b04ab8a..35f6fc2 100644
--- a/tempest/api/compute/security_groups/test_security_group_rules.py
+++ b/tempest/api/compute/security_groups/test_security_group_rules.py
@@ -75,7 +75,6 @@
to_port,
cidr=cidr,
group_id=group_id)
- self.addCleanup(self.client.delete_security_group_rule, rule['id'])
self.assertEqual(200, resp.status)
@test.attr(type='smoke')
@@ -95,8 +94,6 @@
ip_protocol1,
from_port1, to_port1)
rule1_id = rule['id']
- # Delete the Security Group rule1 at the end of this method
- self.addCleanup(self.client.delete_security_group_rule, rule1_id)
# Add a second rule to the created Security Group
ip_protocol2 = 'icmp'
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index 768cc11..28d64fb 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -13,8 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-import datetime
-
from six import moves
from tempest.api.compute import base
@@ -37,9 +35,8 @@
# tearDownClass method of the super-class.
cls.existing_fixtures = []
cls.deleted_fixtures = []
- cls.start_time = datetime.datetime.utcnow()
for x in moves.xrange(2):
- resp, srv = cls.create_test_server()
+ resp, srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
resp, srv = cls.create_test_server()
@@ -127,19 +124,6 @@
self.assertRaises(exceptions.BadRequest, self.client.list_servers,
{'limit': -1})
- @test.attr(type='gate')
- def test_list_servers_by_changes_since(self):
- # Servers are listed by specifying changes-since date
- changes_since = {'changes-since': self.start_time.isoformat()}
- resp, body = self.client.list_servers(changes_since)
- self.assertEqual('200', resp['status'])
- # changes-since returns all instances, including deleted.
- num_expected = (len(self.existing_fixtures) +
- len(self.deleted_fixtures))
- self.assertEqual(num_expected, len(body['servers']),
- "Number of servers %d is wrong in %s" %
- (num_expected, body['servers']))
-
@test.attr(type=['negative', 'gate'])
def test_list_servers_by_changes_since_invalid_date(self):
# Return an error when invalid date format is passed
diff --git a/tempest/api/compute/servers/test_server_actions.py b/tempest/api/compute/servers/test_server_actions.py
index 80e6008..d0fd876 100644
--- a/tempest/api/compute/servers/test_server_actions.py
+++ b/tempest/api/compute/servers/test_server_actions.py
@@ -193,26 +193,46 @@
if current_flavor == self.flavor_ref else self.flavor_ref
return current_flavor, new_flavor_ref
- @testtools.skipUnless(CONF.compute_feature_enabled.resize,
- 'Resize not available.')
- @test.attr(type='smoke')
- def test_resize_server_confirm(self):
+ def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
previous_flavor_ref, new_flavor_ref = \
self._detect_server_image_flavor(self.server_id)
+ if stop:
+ resp = self.servers_client.stop(self.server_id)[0]
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'SHUTOFF')
+
resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.client.confirm_resize(self.server_id)
- self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ expected_status = 'SHUTOFF' if stop else 'ACTIVE'
+ self.client.wait_for_server_status(self.server_id, expected_status)
resp, server = self.client.get_server(self.server_id)
self.assertEqual(new_flavor_ref, server['flavor']['id'])
+ if stop:
+ # NOTE(mriedem): tearDown requires the server to be started.
+ self.client.start(self.server_id)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm(self):
+ self._test_resize_server_confirm(stop=False)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm_from_stopped(self):
+ self._test_resize_server_confirm(stop=True)
+
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@test.attr(type='gate')
diff --git a/tempest/api/compute/servers/test_servers.py b/tempest/api/compute/servers/test_servers.py
index 40b97d7..936b871 100644
--- a/tempest/api/compute/servers/test_servers.py
+++ b/tempest/api/compute/servers/test_servers.py
@@ -70,20 +70,34 @@
resp, server = self.client.get_server(server['id'])
self.assertEqual(key_name, server['key_name'])
+ def _update_server_name(self, server_id, status):
+ # The server name should be changed to the the provided value
+ new_name = data_utils.rand_name('server')
+ # Update the server with a new name
+ resp, server = self.client.update_server(server_id,
+ name=new_name)
+ self.client.wait_for_server_status(server_id, status)
+
+ # Verify the name of the server has changed
+ resp, server = self.client.get_server(server_id)
+ self.assertEqual(new_name, server['name'])
+ return server
+
@test.attr(type='gate')
def test_update_server_name(self):
# The server name should be changed to the the provided value
resp, server = self.create_test_server(wait_until='ACTIVE')
- # Update the server with a new name
- resp, server = self.client.update_server(server['id'],
- name='newname')
- self.assertEqual(200, resp.status)
- self.client.wait_for_server_status(server['id'], 'ACTIVE')
+ self._update_server_name(server['id'], 'ACTIVE')
- # Verify the name of the server has changed
- resp, server = self.client.get_server(server['id'])
- self.assertEqual('newname', server['name'])
+ @test.attr(type='gate')
+ def test_update_server_name_in_stop_state(self):
+ # The server name should be changed to the the provided value
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ self.client.stop(server['id'])
+ self.client.wait_for_server_status(server['id'], 'SHUTOFF')
+ updated_server = self._update_server_name(server['id'], 'SHUTOFF')
+ self.assertNotIn('progress', updated_server)
@test.attr(type='gate')
def test_update_access_server_address(self):
diff --git a/tempest/api/compute/servers/test_servers_negative.py b/tempest/api/compute/servers/test_servers_negative.py
index 5ac667e..6343ead 100644
--- a/tempest/api/compute/servers/test_servers_negative.py
+++ b/tempest/api/compute/servers/test_servers_negative.py
@@ -45,7 +45,10 @@
def setUpClass(cls):
super(ServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.alt_os = clients.AltManager()
+ if CONF.compute.allow_tenant_isolation:
+ cls.alt_os = clients.Manager(cls.isolated_creds.get_alt_creds())
+ else:
+ cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
diff --git a/tempest/api/compute/v3/admin/test_flavors.py b/tempest/api/compute/v3/admin/test_flavors.py
index 2a4fc02..8a4e3cf 100644
--- a/tempest/api/compute/v3/admin/test_flavors.py
+++ b/tempest/api/compute/v3/admin/test_flavors.py
@@ -229,7 +229,7 @@
flavor_name = data_utils.rand_name(self.flavor_name_prefix)
new_flavor_id = data_utils.rand_int_id(start=1000)
- # Create the flavor
+ # Create the flavor
resp, flavor = self.client.create_flavor(flavor_name,
self.ram, self.vcpus,
self.disk,
diff --git a/tempest/api/compute/v3/admin/test_servers_negative.py b/tempest/api/compute/v3/admin/test_servers_negative.py
index a971463..5eb6395 100644
--- a/tempest/api/compute/v3/admin/test_servers_negative.py
+++ b/tempest/api/compute/v3/admin/test_servers_negative.py
@@ -54,6 +54,7 @@
flavor_id = data_utils.rand_int_id(start=1000)
return flavor_id
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_ram(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -67,11 +68,12 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
+ @test.skip_because(bug="1298131")
@test.attr(type=['negative', 'gate'])
def test_resize_server_using_overlimit_vcpus(self):
flavor_name = data_utils.rand_name("flavor-")
@@ -85,7 +87,7 @@
ram, vcpus, disk,
flavor_id)
self.addCleanup(self.flavors_client.delete_flavor, flavor_id)
- self.assertRaises(exceptions.OverLimit,
+ self.assertRaises(exceptions.Unauthorized,
self.client.resize,
self.servers[0]['id'],
flavor_ref['id'])
diff --git a/tempest/api/compute/v3/servers/test_list_servers_negative.py b/tempest/api/compute/v3/servers/test_list_servers_negative.py
index 9cbc4e0..18e5c67 100644
--- a/tempest/api/compute/v3/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_list_servers_negative.py
@@ -39,7 +39,7 @@
cls.deleted_fixtures = []
cls.start_time = datetime.datetime.utcnow()
for x in moves.xrange(2):
- resp, srv = cls.create_test_server()
+ resp, srv = cls.create_test_server(wait_until='ACTIVE')
cls.existing_fixtures.append(srv)
resp, srv = cls.create_test_server()
diff --git a/tempest/api/compute/v3/servers/test_server_actions.py b/tempest/api/compute/v3/servers/test_server_actions.py
index 721fe42..e098311 100644
--- a/tempest/api/compute/v3/servers/test_server_actions.py
+++ b/tempest/api/compute/v3/servers/test_server_actions.py
@@ -186,26 +186,46 @@
if current_flavor == self.flavor_ref else self.flavor_ref
return current_flavor, new_flavor_ref
- @testtools.skipUnless(CONF.compute_feature_enabled.resize,
- 'Resize not available.')
- @test.attr(type='smoke')
- def test_resize_server_confirm(self):
+ def _test_resize_server_confirm(self, stop=False):
# The server's RAM and disk space should be modified to that of
# the provided flavor
previous_flavor_ref, new_flavor_ref = \
self._detect_server_image_flavor(self.server_id)
+ if stop:
+ resp = self.servers_client.stop(self.server_id)[0]
+ self.assertEqual(202, resp.status)
+ self.servers_client.wait_for_server_status(self.server_id,
+ 'SHUTOFF')
+
resp, server = self.client.resize(self.server_id, new_flavor_ref)
self.assertEqual(202, resp.status)
self.client.wait_for_server_status(self.server_id, 'VERIFY_RESIZE')
self.client.confirm_resize(self.server_id)
- self.client.wait_for_server_status(self.server_id, 'ACTIVE')
+ expected_status = 'SHUTOFF' if stop else 'ACTIVE'
+ self.client.wait_for_server_status(self.server_id, expected_status)
resp, server = self.client.get_server(self.server_id)
self.assertEqual(new_flavor_ref, server['flavor']['id'])
+ if stop:
+ # NOTE(mriedem): tearDown requires the server to be started.
+ self.client.start(self.server_id)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm(self):
+ self._test_resize_server_confirm(stop=False)
+
+ @testtools.skipUnless(CONF.compute_feature_enabled.resize,
+ 'Resize not available.')
+ @test.attr(type='smoke')
+ def test_resize_server_confirm_from_stopped(self):
+ self._test_resize_server_confirm(stop=True)
+
@testtools.skipUnless(CONF.compute_feature_enabled.resize,
'Resize not available.')
@test.attr(type='gate')
diff --git a/tempest/api/compute/v3/servers/test_servers_negative.py b/tempest/api/compute/v3/servers/test_servers_negative.py
index 827c4c4..90deaa9 100644
--- a/tempest/api/compute/v3/servers/test_servers_negative.py
+++ b/tempest/api/compute/v3/servers/test_servers_negative.py
@@ -45,7 +45,10 @@
def setUpClass(cls):
super(ServersNegativeV3Test, cls).setUpClass()
cls.client = cls.servers_client
- cls.alt_os = clients.AltManager()
+ if CONF.compute.allow_tenant_isolation:
+ cls.alt_os = clients.Manager(cls.isolated_creds.get_alt_creds())
+ else:
+ cls.alt_os = clients.AltManager()
cls.alt_client = cls.alt_os.servers_v3_client
resp, server = cls.create_test_server(wait_until='ACTIVE')
cls.server_id = server['id']
diff --git a/tempest/api/compute/volumes/test_attach_volume.py b/tempest/api/compute/volumes/test_attach_volume.py
index 4585912..5a64544 100644
--- a/tempest/api/compute/volumes/test_attach_volume.py
+++ b/tempest/api/compute/volumes/test_attach_volume.py
@@ -53,28 +53,28 @@
def _create_and_attach(self):
# Start a server and wait for it to become ready
admin_pass = self.image_ssh_password
- resp, server = self.create_test_server(wait_until='ACTIVE',
- adminPass=admin_pass)
- self.server = server
+ _, self.server = self.create_test_server(wait_until='ACTIVE',
+ adminPass=admin_pass)
# Record addresses so that we can ssh later
- resp, server['addresses'] = \
- self.servers_client.list_addresses(server['id'])
+ _, self.server['addresses'] = \
+ self.servers_client.list_addresses(self.server['id'])
# Create a volume and wait for it to become ready
- resp, volume = self.volumes_client.create_volume(1,
- display_name='test')
- self.volume = volume
+ _, self.volume = self.volumes_client.create_volume(
+ 1, display_name='test')
self.addCleanup(self._delete_volume)
- self.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ self.volumes_client.wait_for_volume_status(self.volume['id'],
+ 'available')
# Attach the volume to the server
- self.servers_client.attach_volume(server['id'], volume['id'],
+ self.servers_client.attach_volume(self.server['id'],
+ self.volume['id'],
device='/dev/%s' % self.device)
- self.volumes_client.wait_for_volume_status(volume['id'], 'in-use')
+ self.volumes_client.wait_for_volume_status(self.volume['id'], 'in-use')
self.attached = True
- self.addCleanup(self._detach, server['id'], volume['id'])
+ self.addCleanup(self._detach, self.server['id'], self.volume['id'])
@testtools.skipUnless(CONF.compute.run_ssh, 'SSH required for this test')
@test.attr(type='gate')
@@ -82,31 +82,33 @@
# Stop and Start a server with an attached volume, ensuring that
# the volume remains attached.
self._create_and_attach()
- server = self.server
- volume = self.volume
- self.servers_client.stop(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
+ self.servers_client.stop(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'],
+ 'SHUTOFF')
- self.servers_client.start(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.servers_client.start(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
- linux_client = remote_client.RemoteClient(server, self.image_ssh_user,
- server['adminPass'])
+ linux_client = remote_client.RemoteClient(self.server,
+ self.image_ssh_user,
+ self.server['adminPass'])
partitions = linux_client.get_partitions()
self.assertIn(self.device, partitions)
- self._detach(server['id'], volume['id'])
+ self._detach(self.server['id'], self.volume['id'])
self.attached = False
- self.servers_client.stop(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'SHUTOFF')
+ self.servers_client.stop(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'],
+ 'SHUTOFF')
- self.servers_client.start(server['id'])
- self.servers_client.wait_for_server_status(server['id'], 'ACTIVE')
+ self.servers_client.start(self.server['id'])
+ self.servers_client.wait_for_server_status(self.server['id'], 'ACTIVE')
- linux_client = remote_client.RemoteClient(server, self.image_ssh_user,
- server['adminPass'])
+ linux_client = remote_client.RemoteClient(self.server,
+ self.image_ssh_user,
+ self.server['adminPass'])
partitions = linux_client.get_partitions()
self.assertNotIn(self.device, partitions)
diff --git a/tempest/api/data_processing/base.py b/tempest/api/data_processing/base.py
index 8d5e223..0d6773c 100644
--- a/tempest/api/data_processing/base.py
+++ b/tempest/api/data_processing/base.py
@@ -38,6 +38,8 @@
cls._node_group_templates = []
cls._cluster_templates = []
cls._data_sources = []
+ cls._job_binary_internals = []
+ cls._job_binaries = []
@classmethod
def tearDownClass(cls):
@@ -47,6 +49,10 @@
cls.client.delete_node_group_template)
cls.cleanup_resources(getattr(cls, '_data_sources', []),
cls.client.delete_data_source)
+ cls.cleanup_resources(getattr(cls, '_job_binary_internals', []),
+ cls.client.delete_job_binary_internal)
+ cls.cleanup_resources(getattr(cls, '_job_binaries', []),
+ cls.client.delete_job_binary)
cls.clear_isolated_creds()
super(BaseDataProcessingTest, cls).tearDownClass()
@@ -112,3 +118,29 @@
cls._data_sources.append(body['id'])
return resp, body
+
+ @classmethod
+ def create_job_binary_internal(cls, name, data):
+ """Creates watched job binary internal with specified params.
+
+ It returns created object. All resources created in this method will
+ be automatically removed in tearDownClass method.
+ """
+ resp, body = cls.client.create_job_binary_internal(name, data)
+ # store id of created job binary internal
+ cls._job_binary_internals.append(body['id'])
+
+ return resp, body
+
+ def create_job_binary(cls, name, url, extra=None, **kwargs):
+ """Creates watched job binary with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object. All resources created in this method will be automatically
+ removed in tearDownClass method.
+ """
+ resp, body = cls.client.create_job_binary(name, url, extra, **kwargs)
+ # store id of created job binary
+ cls._job_binaries.append(body['id'])
+
+ return resp, body
diff --git a/tempest/api/data_processing/test_cluster_templates.py b/tempest/api/data_processing/test_cluster_templates.py
new file mode 100644
index 0000000..e5c6303
--- /dev/null
+++ b/tempest/api/data_processing/test_cluster_templates.py
@@ -0,0 +1,146 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class ClusterTemplateTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.0.html#cluster-templates
+ """
+ @classmethod
+ def setUpClass(cls):
+ super(ClusterTemplateTest, cls).setUpClass()
+ # create node group template
+ node_group_template = {
+ 'name': data_utils.rand_name('sahara-ng-template'),
+ 'description': 'Test node group template',
+ 'plugin_name': 'vanilla',
+ 'hadoop_version': '1.2.1',
+ 'node_processes': ['datanode'],
+ 'flavor_id': cls.flavor_ref,
+ 'node_configs': {
+ 'HDFS': {
+ 'Data Node Heap Size': 1024
+ }
+ }
+ }
+ resp_body = cls.create_node_group_template(**node_group_template)[1]
+
+ cls.full_cluster_template = {
+ 'description': 'Test cluster template',
+ 'plugin_name': 'vanilla',
+ 'hadoop_version': '1.2.1',
+ 'cluster_configs': {
+ 'HDFS': {
+ 'dfs.replication': 2
+ },
+ 'MapReduce': {
+ 'mapred.map.tasks.speculative.execution': False,
+ 'mapred.child.java.opts': '-Xmx500m'
+ },
+ 'general': {
+ 'Enable Swift': False
+ }
+ },
+ 'node_groups': [
+ {
+ 'name': 'master-node',
+ 'flavor_id': cls.flavor_ref,
+ 'node_processes': ['namenode'],
+ 'count': 1
+ },
+ {
+ 'name': 'worker-node',
+ 'node_group_template_id': resp_body['id'],
+ 'count': 3
+ }
+ ]
+ }
+ # create cls.cluster_template variable to use for comparison to cluster
+ # template response body. The 'node_groups' field in the response body
+ # has some extra info that post body does not have. The 'node_groups'
+ # field in the response body is something like this
+ #
+ # 'node_groups': [
+ # {
+ # 'count': 3,
+ # 'name': 'worker-node',
+ # 'volume_mount_prefix': '/volumes/disk',
+ # 'created_at': '2014-05-21 14:31:37',
+ # 'updated_at': None,
+ # 'floating_ip_pool': None,
+ # ...
+ # },
+ # ...
+ # ]
+ cls.cluster_template = cls.full_cluster_template.copy()
+ del cls.cluster_template['node_groups']
+
+ def _create_cluster_template(self, template_name=None):
+ """Creates Cluster Template with optional name specified.
+
+ It creates template and ensures response status, template name and
+ response body. Returns id and name of created template.
+ """
+ if not template_name:
+ # generate random name if it's not specified
+ template_name = data_utils.rand_name('sahara-cluster-template')
+
+ # create cluster template
+ resp, body = self.create_cluster_template(template_name,
+ **self.full_cluster_template)
+
+ # ensure that template created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(template_name, body['name'])
+ self.assertDictContainsSubset(self.cluster_template, body)
+
+ return body['id'], template_name
+
+ @test.attr(type='smoke')
+ def test_cluster_template_create(self):
+ self._create_cluster_template()
+
+ @test.attr(type='smoke')
+ def test_cluster_template_list(self):
+ template_info = self._create_cluster_template()
+
+ # check for cluster template in list
+ resp, templates = self.client.list_cluster_templates()
+ self.assertEqual(200, resp.status)
+ templates_info = [(template['id'], template['name'])
+ for template in templates]
+ self.assertIn(template_info, templates_info)
+
+ @test.attr(type='smoke')
+ def test_cluster_template_get(self):
+ template_id, template_name = self._create_cluster_template()
+
+ # check cluster template fetch by id
+ resp, template = self.client.get_cluster_template(template_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(template_name, template['name'])
+ self.assertDictContainsSubset(self.cluster_template, template)
+
+ @test.attr(type='smoke')
+ def test_cluster_template_delete(self):
+ template_id = self._create_cluster_template()[0]
+
+ # delete the cluster template by id
+ resp = self.client.delete_cluster_template(template_id)[0]
+ self.assertEqual(204, resp.status)
+ # TODO(ylobankov): check that cluster template is really deleted
diff --git a/tempest/api/data_processing/test_data_sources.py b/tempest/api/data_processing/test_data_sources.py
new file mode 100644
index 0000000..c72e828
--- /dev/null
+++ b/tempest/api/data_processing/test_data_sources.py
@@ -0,0 +1,154 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class DataSourceTest(dp_base.BaseDataProcessingTest):
+ @classmethod
+ def setUpClass(cls):
+ super(DataSourceTest, cls).setUpClass()
+ cls.swift_data_source_with_creds = {
+ 'url': 'swift://sahara-container.sahara/input-source',
+ 'description': 'Test data source',
+ 'credentials': {
+ 'user': CONF.identity.username,
+ 'password': CONF.identity.password
+ },
+ 'type': 'swift'
+ }
+ cls.swift_data_source = cls.swift_data_source_with_creds.copy()
+ del cls.swift_data_source['credentials']
+
+ cls.local_hdfs_data_source = {
+ 'url': 'input-source',
+ 'description': 'Test data source',
+ 'type': 'hdfs'
+ }
+
+ cls.external_hdfs_data_source = {
+ 'url': 'hdfs://172.18.168.2:8020/usr/hadoop/input-source',
+ 'description': 'Test data source',
+ 'type': 'hdfs'
+ }
+
+ def _create_data_source(self, source_body, source_name=None):
+ """Creates Data Source with optional name specified.
+
+ It creates a link to input-source file (it may not exist) and ensures
+ response status and source name. Returns id and name of created source.
+ """
+ if not source_name:
+ # generate random name if it's not specified
+ source_name = data_utils.rand_name('sahara-data-source')
+
+ # create data source
+ resp, body = self.create_data_source(source_name, **source_body)
+
+ # ensure that source created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(source_name, body['name'])
+ if source_body['type'] == 'swift':
+ source_body = self.swift_data_source
+ self.assertDictContainsSubset(source_body, body)
+
+ return body['id'], source_name
+
+ def _list_data_sources(self, source_info):
+ # check for data source in list
+ resp, sources = self.client.list_data_sources()
+ self.assertEqual(200, resp.status)
+ sources_info = [(source['id'], source['name']) for source in sources]
+ self.assertIn(source_info, sources_info)
+
+ def _get_data_source(self, source_id, source_name, source_body):
+ # check data source fetch by id
+ resp, source = self.client.get_data_source(source_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(source_name, source['name'])
+ self.assertDictContainsSubset(source_body, source)
+
+ def _delete_data_source(self, source_id):
+ # delete the data source by id
+ resp = self.client.delete_data_source(source_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_create(self):
+ self._create_data_source(self.swift_data_source_with_creds)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_list(self):
+ source_info = self._create_data_source(
+ self.swift_data_source_with_creds)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.swift_data_source_with_creds)
+ self._get_data_source(source_id, source_name, self.swift_data_source)
+
+ @test.attr(type='smoke')
+ def test_swift_data_source_delete(self):
+ source_id = self._create_data_source(
+ self.swift_data_source_with_creds)[0]
+ self._delete_data_source(source_id)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_create(self):
+ self._create_data_source(self.local_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_list(self):
+ source_info = self._create_data_source(self.local_hdfs_data_source)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.local_hdfs_data_source)
+ self._get_data_source(
+ source_id, source_name, self.local_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_local_hdfs_data_source_delete(self):
+ source_id = self._create_data_source(self.local_hdfs_data_source)[0]
+ self._delete_data_source(source_id)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_create(self):
+ self._create_data_source(self.external_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_list(self):
+ source_info = self._create_data_source(self.external_hdfs_data_source)
+ self._list_data_sources(source_info)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_get(self):
+ source_id, source_name = self._create_data_source(
+ self.external_hdfs_data_source)
+ self._get_data_source(
+ source_id, source_name, self.external_hdfs_data_source)
+
+ @test.attr(type='smoke')
+ def test_external_hdfs_data_source_delete(self):
+ source_id = self._create_data_source(self.external_hdfs_data_source)[0]
+ self._delete_data_source(source_id)
diff --git a/tempest/api/data_processing/test_job_binary_internals.py b/tempest/api/data_processing/test_job_binary_internals.py
new file mode 100644
index 0000000..6d59177
--- /dev/null
+++ b/tempest/api/data_processing/test_job_binary_internals.py
@@ -0,0 +1,88 @@
+# Copyright (c) 2014 Mirantis Inc.
+#
+# 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.data_processing import base as dp_base
+from tempest.common.utils import data_utils
+from tempest import test
+
+
+class JobBinaryInternalTest(dp_base.BaseDataProcessingTest):
+ """Link to the API documentation is http://docs.openstack.org/developer/
+ sahara/restapi/rest_api_v1.1_EDP.html#job-binary-internals
+ """
+ @classmethod
+ def setUpClass(cls):
+ super(JobBinaryInternalTest, cls).setUpClass()
+ cls.job_binary_internal_data = 'Some script may be data'
+
+ def _create_job_binary_internal(self, binary_name=None):
+ """Creates Job Binary Internal with optional name specified.
+
+ It puts data into Sahara database and ensures response status and
+ job binary internal name. Returns id and name of created job binary
+ internal.
+ """
+ if not binary_name:
+ # generate random name if it's not specified
+ binary_name = data_utils.rand_name('sahara-job-binary-internal')
+
+ # create job binary internal
+ resp, body = self.create_job_binary_internal(
+ binary_name, self.job_binary_internal_data)
+
+ # ensure that job binary internal created successfully
+ self.assertEqual(202, resp.status)
+ self.assertEqual(binary_name, body['name'])
+
+ return body['id'], binary_name
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_create(self):
+ self._create_job_binary_internal()
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_list(self):
+ binary_info = self._create_job_binary_internal()
+
+ # check for job binary internal in list
+ resp, binaries = self.client.list_job_binary_internals()
+ self.assertEqual(200, resp.status)
+ binaries_info = [(binary['id'], binary['name']) for binary in binaries]
+ self.assertIn(binary_info, binaries_info)
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_get(self):
+ binary_id, binary_name = self._create_job_binary_internal()
+
+ # check job binary internal fetch by id
+ resp, binary = self.client.get_job_binary_internal(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(binary_name, binary['name'])
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_delete(self):
+ binary_id = self._create_job_binary_internal()[0]
+
+ # delete the job binary internal by id
+ resp = self.client.delete_job_binary_internal(binary_id)[0]
+ self.assertEqual(204, resp.status)
+
+ @test.attr(type='smoke')
+ def test_job_binary_internal_get_data(self):
+ binary_id = self._create_job_binary_internal()[0]
+
+ # get data of job binary internal by id
+ resp, data = self.client.get_job_binary_internal_data(binary_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(data, self.job_binary_internal_data)
diff --git a/tempest/api/data_processing/test_node_group_templates.py b/tempest/api/data_processing/test_node_group_templates.py
index f3af4e8..04f98b4 100644
--- a/tempest/api/data_processing/test_node_group_templates.py
+++ b/tempest/api/data_processing/test_node_group_templates.py
@@ -46,7 +46,7 @@
It creates template and ensures response status and template name.
Returns id and name of created template.
"""
- if template_name is None:
+ if not template_name:
# generate random name if it's not specified
template_name = data_utils.rand_name('sahara-ng-template')
@@ -57,19 +57,13 @@
# ensure that template created successfully
self.assertEqual(202, resp.status)
self.assertEqual(template_name, body['name'])
+ self.assertDictContainsSubset(self.node_group_template, body)
return body['id'], template_name
@test.attr(type='smoke')
def test_node_group_template_create(self):
- template_name = data_utils.rand_name('sahara-ng-template')
- resp, body = self.create_node_group_template(
- template_name, **self.node_group_template)
-
- # check that template created successfully
- self.assertEqual(resp.status, 202)
- self.assertEqual(template_name, body['name'])
- self.assertDictContainsSubset(self.node_group_template, body)
+ self._create_node_group_template()
@test.attr(type='smoke')
def test_node_group_template_list(self):
@@ -77,7 +71,6 @@
# check for node group template in list
resp, templates = self.client.list_node_group_templates()
-
self.assertEqual(200, resp.status)
templates_info = [(template['id'], template['name'])
for template in templates]
@@ -89,7 +82,6 @@
# check node group template fetch by id
resp, template = self.client.get_node_group_template(template_id)
-
self.assertEqual(200, resp.status)
self.assertEqual(template_name, template['name'])
self.assertDictContainsSubset(self.node_group_template, template)
@@ -100,5 +92,4 @@
# delete the node group template by id
resp = self.client.delete_node_group_template(template_id)[0]
-
self.assertEqual(204, resp.status)
diff --git a/tempest/api/data_processing/test_plugins.py b/tempest/api/data_processing/test_plugins.py
index c6832a2..d643f23 100644
--- a/tempest/api/data_processing/test_plugins.py
+++ b/tempest/api/data_processing/test_plugins.py
@@ -23,10 +23,8 @@
It ensures response status and main plugins availability.
"""
resp, plugins = self.client.list_plugins()
-
self.assertEqual(200, resp.status)
-
- plugins_names = list([plugin['name'] for plugin in plugins])
+ plugins_names = [plugin['name'] for plugin in plugins]
self.assertIn('vanilla', plugins_names)
self.assertIn('hdp', plugins_names)
@@ -40,14 +38,12 @@
def test_plugin_get(self):
for plugin_name in self._list_all_plugin_names():
resp, plugin = self.client.get_plugin(plugin_name)
-
self.assertEqual(200, resp.status)
self.assertEqual(plugin_name, plugin['name'])
for plugin_version in plugin['versions']:
resp, detailed_plugin = self.client.get_plugin(plugin_name,
plugin_version)
-
self.assertEqual(200, resp.status)
self.assertEqual(plugin_name, detailed_plugin['name'])
diff --git a/tempest/api/database/base.py b/tempest/api/database/base.py
index cf70d11..b68c84a 100644
--- a/tempest/api/database/base.py
+++ b/tempest/api/database/base.py
@@ -41,4 +41,5 @@
os = cls.get_client_manager()
cls.os = os
cls.database_flavors_client = cls.os.database_flavors_client
+ cls.os_flavors_client = cls.os.flavors_client
cls.database_versions_client = cls.os.database_versions_client
diff --git a/tempest/api/database/flavors/test_flavors.py b/tempest/api/database/flavors/test_flavors.py
index a591e8e..64d71b9 100644
--- a/tempest/api/database/flavors/test_flavors.py
+++ b/tempest/api/database/flavors/test_flavors.py
@@ -28,6 +28,7 @@
def test_get_db_flavor(self):
# The expected flavor details should be returned
resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ self.assertEqual(200, resp.status)
self.assertEqual(self.db_flavor_ref, str(flavor['id']))
self.assertIn('ram', flavor)
self.assertIn('links', flavor)
@@ -36,6 +37,36 @@
@test.attr(type='smoke')
def test_list_db_flavors(self):
resp, flavor = self.client.get_db_flavor_details(self.db_flavor_ref)
+ self.assertEqual(200, resp.status)
# List of all flavors should contain the expected flavor
resp, flavors = self.client.list_db_flavors()
+ self.assertEqual(200, resp.status)
self.assertIn(flavor, flavors)
+
+ def _check_values(self, names, db_flavor, os_flavor, in_db=True):
+ for name in names:
+ self.assertIn(name, os_flavor)
+ if in_db:
+ self.assertIn(name, db_flavor)
+ self.assertEqual(str(db_flavor[name]), str(os_flavor[name]),
+ "DB flavor differs from OS on '%s' value"
+ % name)
+ else:
+ self.assertNotIn(name, db_flavor)
+
+ @test.attr(type='smoke')
+ def test_compare_db_flavors_with_os(self):
+ resp, db_flavors = self.client.list_db_flavors()
+ self.assertEqual(200, resp.status)
+ resp, os_flavors = self.os_flavors_client.list_flavors_with_detail()
+ self.assertEqual(200, resp.status)
+ self.assertEqual(len(os_flavors), len(db_flavors),
+ "OS flavors %s do not match DB flavors %s" %
+ (os_flavors, db_flavors))
+ for os_flavor in os_flavors:
+ resp, db_flavor =\
+ self.client.get_db_flavor_details(os_flavor['id'])
+ self.assertEqual(200, resp.status)
+ self._check_values(['id', 'name', 'ram'], db_flavor, os_flavor)
+ self._check_values(['disk', 'vcpus', 'swap'], db_flavor, os_flavor,
+ in_db=False)
diff --git a/tempest/api/identity/admin/test_services.py b/tempest/api/identity/admin/test_services.py
index 0472e07..5926488 100644
--- a/tempest/api/identity/admin/test_services.py
+++ b/tempest/api/identity/admin/test_services.py
@@ -101,7 +101,7 @@
# List and Verify Services
resp, body = self.client.list_services()
self.assertEqual(200, resp.status)
- found = [service for service in body if service['id'] in service_ids]
+ found = [serv for serv in body if serv['id'] in service_ids]
self.assertEqual(len(found), len(services), 'Services not found')
diff --git a/tempest/api/identity/admin/test_tenants.py b/tempest/api/identity/admin/test_tenants.py
index b989664..93734d2 100644
--- a/tempest/api/identity/admin/test_tenants.py
+++ b/tempest/api/identity/admin/test_tenants.py
@@ -36,7 +36,7 @@
tenant_ids = map(lambda x: x['id'], tenants)
resp, body = self.client.list_tenants()
self.assertEqual(200, resp.status)
- found = [tenant for tenant in body if tenant['id'] in tenant_ids]
+ found = [t for t in body if t['id'] in tenant_ids]
self.assertEqual(len(found), len(tenants), 'Tenants not created')
for tenant in tenants:
diff --git a/tempest/api/identity/admin/v3/test_projects.py b/tempest/api/identity/admin/v3/test_projects.py
index 31a0ddd..79717b1 100644
--- a/tempest/api/identity/admin/v3/test_projects.py
+++ b/tempest/api/identity/admin/v3/test_projects.py
@@ -171,13 +171,13 @@
@test.attr(type='gate')
def test_associate_user_to_project(self):
- #Associate a user to a project
- #Create a Project
+ # Associate a user to a project
+ # Create a Project
p_name = data_utils.rand_name('project-')
resp, project = self.client.create_project(p_name)
self.data.projects.append(project)
- #Create a User
+ # Create a User
u_name = data_utils.rand_name('user-')
u_desc = u_name + 'description'
u_email = u_name + '@testmail.tm'
@@ -191,7 +191,7 @@
# Get User To validate the user details
resp, new_user_get = self.client.get_user(user['id'])
- #Assert response body of GET
+ # Assert response body of GET
self.assertEqual(u_name, new_user_get['name'])
self.assertEqual(u_desc, new_user_get['description'])
self.assertEqual(project['id'],
diff --git a/tempest/api/identity/admin/v3/test_regions.py b/tempest/api/identity/admin/v3/test_regions.py
new file mode 100644
index 0000000..03974e4
--- /dev/null
+++ b/tempest/api/identity/admin/v3/test_regions.py
@@ -0,0 +1,102 @@
+# Copyright 2014 OpenStack Foundation
+# 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.
+
+from tempest.api.identity import base
+from tempest.common.utils import data_utils
+from tempest import exceptions
+from tempest import test
+
+
+class RegionsTestJSON(base.BaseIdentityV3AdminTest):
+ _interface = 'json'
+
+ @classmethod
+ @test.safe_setup
+ def setUpClass(cls):
+ super(RegionsTestJSON, cls).setUpClass()
+ cls.setup_regions = list()
+ cls.client = cls.region_client
+ for i in range(2):
+ r_description = data_utils.rand_name('description-')
+ _, region = cls.client.create_region(r_description)
+ cls.setup_regions.append(region)
+
+ @classmethod
+ def tearDownClass(cls):
+ for r in cls.setup_regions:
+ cls.client.delete_region(r['id'])
+ super(RegionsTestJSON, cls).tearDownClass()
+
+ def _delete_region(self, region_id):
+ resp, _ = self.client.delete_region(region_id)
+ self.assertEqual(204, resp.status)
+ self.assertRaises(exceptions.NotFound,
+ self.client.get_region, region_id)
+
+ @test.attr(type='gate')
+ def test_create_update_get_delete_region(self):
+ r_description = data_utils.rand_name('description-')
+ resp, region = self.client.create_region(
+ r_description, parent_region_id=self.setup_regions[0]['id'])
+ self.assertEqual(201, resp.status)
+ self.addCleanup(self._delete_region, region['id'])
+ self.assertEqual(r_description, region['description'])
+ self.assertEqual(self.setup_regions[0]['id'],
+ region['parent_region_id'])
+ # Update region with new description and parent ID
+ r_alt_description = data_utils.rand_name('description-')
+ resp, region = self.client.update_region(
+ region['id'],
+ description=r_alt_description,
+ parent_region_id=self.setup_regions[1]['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(r_alt_description, region['description'])
+ self.assertEqual(self.setup_regions[1]['id'],
+ region['parent_region_id'])
+ # Get the details of region
+ resp, region = self.client.get_region(region['id'])
+ self.assertEqual(200, resp.status)
+ self.assertEqual(r_alt_description, region['description'])
+ self.assertEqual(self.setup_regions[1]['id'],
+ region['parent_region_id'])
+
+ @test.attr(type='smoke')
+ def test_create_region_with_specific_id(self):
+ # Create a region with a specific id
+ r_region_id = data_utils.rand_uuid()
+ r_description = data_utils.rand_name('description-')
+ resp, region = self.client.create_region(
+ r_description, unique_region_id=r_region_id)
+ self.addCleanup(self._delete_region, region['id'])
+ # Asserting Create Region with specific id response body
+ self.assertEqual(201, resp.status)
+ self.assertEqual(r_region_id, region['id'])
+ self.assertEqual(r_description, region['description'])
+
+ @test.attr(type='gate')
+ def test_list_regions(self):
+ # Get a list of regions
+ resp, fetched_regions = self.client.list_regions()
+ self.assertEqual(200, resp.status)
+ missing_regions =\
+ [e for e in self.setup_regions if e not in fetched_regions]
+ # Asserting List Regions response
+ self.assertEqual(0, len(missing_regions),
+ "Failed to find region %s in fetched list" %
+ ', '.join(str(e) for e in missing_regions))
+
+
+class RegionsTestXML(RegionsTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/identity/base.py b/tempest/api/identity/base.py
index e4e74c1..697057f 100644
--- a/tempest/api/identity/base.py
+++ b/tempest/api/identity/base.py
@@ -96,6 +96,7 @@
cls.client = cls.os_adm.identity_v3_client
cls.token = cls.os_adm.token_v3_client
cls.endpoints_client = cls.os_adm.endpoints_client
+ cls.region_client = cls.os_adm.region_client
cls.data = DataGenerator(cls.client)
cls.non_admin_client = cls.os.identity_v3_client
cls.service_client = cls.os_adm.service_client
diff --git a/tempest/api/identity/test_extension.py b/tempest/api/identity/test_extension.py
new file mode 100644
index 0000000..67f20f4
--- /dev/null
+++ b/tempest/api/identity/test_extension.py
@@ -0,0 +1,37 @@
+# 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.
+
+from tempest.api.identity import base
+from tempest import test
+
+
+class ExtensionTestJSON(base.BaseIdentityV2AdminTest):
+ _interface = 'json'
+
+ @test.attr(type='gate')
+ def test_list_extensions(self):
+ # List all the extensions
+ resp, body = self.non_admin_client.list_extensions()
+ self.assertEqual(200, resp.status)
+ self.assertNotEmpty(body)
+ keys = ['name', 'updated', 'alias', 'links',
+ 'namespace', 'description']
+ for value in body:
+ for key in keys:
+ self.assertIn(key, value)
+
+
+class ExtensionTestXML(ExtensionTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/image/v1/test_images.py b/tempest/api/image/v1/test_images.py
index b90891b..2cc2009 100644
--- a/tempest/api/image/v1/test_images.py
+++ b/tempest/api/image/v1/test_images.py
@@ -33,12 +33,12 @@
resp, body = self.create_image(name='New Name',
container_format='bare',
disk_format='raw',
- is_public=True,
+ is_public=False,
properties=properties)
self.assertIn('id', body)
image_id = body.get('id')
self.assertEqual('New Name', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
for key, val in properties.items():
self.assertEqual(val, body.get('properties')[key])
@@ -54,14 +54,13 @@
# Register a new remote image
resp, body = self.create_image(name='New Remote Image',
container_format='bare',
- disk_format='raw', is_public=True,
- location='http://example.com'
- '/someimage.iso',
+ disk_format='raw', is_public=False,
+ location=CONF.image.http_image,
properties={'key1': 'value1',
'key2': 'value2'})
self.assertIn('id', body)
self.assertEqual('New Remote Image', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('active', body.get('status'))
properties = body.get('properties')
self.assertEqual(properties['key1'], 'value1')
@@ -71,12 +70,12 @@
def test_register_http_image(self):
resp, body = self.create_image(name='New Http Image',
container_format='bare',
- disk_format='raw', is_public=True,
+ disk_format='raw', is_public=False,
copy_from=CONF.image.http_image)
self.assertIn('id', body)
image_id = body.get('id')
self.assertEqual('New Http Image', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.client.wait_for_image_status(image_id, 'active')
resp, body = self.client.get_image(image_id)
self.assertEqual(resp['status'], '200')
@@ -88,12 +87,12 @@
resp, body = self.create_image(name='New_image_with_min_ram',
container_format='bare',
disk_format='raw',
- is_public=True,
+ is_public=False,
min_ram=40,
properties=properties)
self.assertIn('id', body)
self.assertEqual('New_image_with_min_ram', body.get('name'))
- self.assertTrue(body.get('is_public'))
+ self.assertFalse(body.get('is_public'))
self.assertEqual('queued', body.get('status'))
self.assertEqual(40, body.get('min_ram'))
for key, val in properties.items():
@@ -143,11 +142,11 @@
image
"""
name = 'New Remote Image %s' % name
- location = 'http://example.com/someimage_%s.iso' % name
+ location = CONF.image.http_image
resp, image = cls.create_image(name=name,
container_format=container_format,
disk_format=disk_format,
- is_public=True,
+ is_public=False,
location=location)
image_id = image['id']
return image_id
@@ -165,7 +164,7 @@
resp, image = cls.create_image(name=name,
container_format=container_format,
disk_format=disk_format,
- is_public=True, data=image_file)
+ is_public=False, data=image_file)
image_id = image['id']
return image_id
@@ -247,11 +246,17 @@
@classmethod
@test.safe_setup
def setUpClass(cls):
- super(ListSnapshotImagesTest, cls).setUpClass()
+ # This test class only uses nova v3 api to create snapshot
+ # as the similar test which uses nova v2 api already exists
+ # in nova v2 compute images api tests.
+ # Since nova v3 doesn't have images api proxy, this test
+ # class was added in the image api tests.
if not CONF.compute_feature_enabled.api_v3:
- cls.servers_client = cls.os.servers_client
- else:
- cls.servers_client = cls.os.servers_v3_client
+ skip_msg = ("%s skipped as nova v3 api is not available" %
+ cls.__name__)
+ raise cls.skipException(skip_msg)
+ super(ListSnapshotImagesTest, cls).setUpClass()
+ cls.servers_client = cls.os.servers_v3_client
cls.servers = []
# We add a few images here to test the listing functionality of
# the images API
@@ -264,7 +269,7 @@
resp, image = cls.create_image(name="Standard Image",
container_format='ami',
disk_format='ami',
- is_public=True, data=image_file)
+ is_public=False, data=image_file)
cls.image_id = image['id']
cls.client.wait_for_image_status(image['id'], 'active')
@@ -281,8 +286,7 @@
cls.servers.append(server)
cls.servers_client.wait_for_server_status(
server['id'], 'ACTIVE')
- resp, image = cls.servers_client.create_image(
- server['id'], name)
+ resp, _ = cls.servers_client.create_image(server['id'], name)
image_id = data_utils.parse_image_id(resp['location'])
cls.created_images.append(image_id)
cls.client.wait_for_image_status(image_id,
@@ -356,7 +360,7 @@
resp, image = cls.create_image(name=name,
container_format=container_format,
disk_format=disk_format,
- is_public=True, data=image_file,
+ is_public=False, data=image_file,
properties={'key1': 'value1'})
image_id = image['id']
return image_id
diff --git a/tempest/api/image/v2/test_images.py b/tempest/api/image/v2/test_images.py
index 2592409..37dc163 100644
--- a/tempest/api/image/v2/test_images.py
+++ b/tempest/api/image/v2/test_images.py
@@ -35,17 +35,19 @@
upload the image file, get image and get image file api's
"""
+ uuid = '00000000-1111-2222-3333-444455556666'
image_name = data_utils.rand_name('image')
resp, body = self.create_image(name=image_name,
container_format='bare',
disk_format='raw',
- visibility='public')
+ visibility='private',
+ ramdisk_id=uuid)
self.assertIn('id', body)
image_id = body.get('id')
self.assertIn('name', body)
self.assertEqual(image_name, body['name'])
self.assertIn('visibility', body)
- self.assertEqual('public', body['visibility'])
+ self.assertEqual('private', body['visibility'])
self.assertIn('status', body)
self.assertEqual('queued', body['status'])
@@ -60,6 +62,7 @@
self.assertEqual(200, resp.status)
self.assertEqual(image_id, body['id'])
self.assertEqual(image_name, body['name'])
+ self.assertEqual(uuid, body['ramdisk_id'])
self.assertIn('size', body)
self.assertEqual(1024, body.get('size'))
@@ -77,7 +80,7 @@
resp, body = self.client.create_image(name=image_name,
container_format='bare',
disk_format='raw',
- visibility='public')
+ visibility='private')
self.assertEqual(201, resp.status)
image_id = body['id']
@@ -99,7 +102,7 @@
resp, body = self.client.create_image(name=image_name,
container_format='bare',
disk_format='iso',
- visibility='public')
+ visibility='private')
self.assertEqual(201, resp.status)
self.addCleanup(self.client.delete_image, body['id'])
self.assertEqual('queued', body['status'])
@@ -113,10 +116,8 @@
# Update Image
new_image_name = data_utils.rand_name('new-image')
- new_visibility = 'private'
resp, body = self.client.update_image(image_id, [
- dict(replace='/name', value=new_image_name),
- dict(replace='/visibility', value=new_visibility)])
+ dict(replace='/name', value=new_image_name)])
self.assertEqual(200, resp.status)
@@ -126,7 +127,6 @@
self.assertEqual(200, resp.status)
self.assertEqual(image_id, body['id'])
self.assertEqual(new_image_name, body['name'])
- self.assertEqual(new_visibility, body['visibility'])
class ListImagesTest(base.BaseV2ImageTest):
@@ -160,7 +160,7 @@
resp, body = cls.create_image(name=name,
container_format=container_format,
disk_format=disk_format,
- visibility='public')
+ visibility='private')
image_id = body['id']
resp, body = cls.client.store_image(image_id, data=image_file)
@@ -202,8 +202,8 @@
@test.attr(type='gate')
def test_list_images_param_visibility(self):
- # Test to get all images with visibility = public
- params = {"visibility": "public"}
+ # Test to get all images with visibility = private
+ params = {"visibility": "private"}
self._list_by_param_value_and_assert(params)
@test.attr(type='gate')
diff --git a/tempest/api/image/v2/test_images_tags.py b/tempest/api/image/v2/test_images_tags.py
index 504c0e8..dec3353 100644
--- a/tempest/api/image/v2/test_images_tags.py
+++ b/tempest/api/image/v2/test_images_tags.py
@@ -23,7 +23,7 @@
def test_update_delete_tags_for_image(self):
resp, body = self.create_image(container_format='bare',
disk_format='raw',
- visibility='public')
+ visibility='private')
image_id = body['id']
tag = data_utils.rand_name('tag-')
self.addCleanup(self.client.delete_image, image_id)
diff --git a/tempest/api/image/v2/test_images_tags_negative.py b/tempest/api/image/v2/test_images_tags_negative.py
index 3233db7..13cfa0a 100644
--- a/tempest/api/image/v2/test_images_tags_negative.py
+++ b/tempest/api/image/v2/test_images_tags_negative.py
@@ -35,7 +35,7 @@
# Delete non existing tag.
resp, body = self.create_image(container_format='bare',
disk_format='raw',
- is_public=True,
+ visibility='private'
)
image_id = body['id']
tag = data_utils.rand_name('non-exist-tag-')
diff --git a/tempest/api/network/admin/test_l3_agent_scheduler.py b/tempest/api/network/admin/test_l3_agent_scheduler.py
index f4050c5..3b05f42 100644
--- a/tempest/api/network/admin/test_l3_agent_scheduler.py
+++ b/tempest/api/network/admin/test_l3_agent_scheduler.py
@@ -28,8 +28,9 @@
List L3 agents hosting the given router.
Add and Remove Router to L3 agent
- v2.0 of the Neutron API is assumed. It is also assumed that the following
- options are defined in the [network] section of etc/tempest.conf:
+ v2.0 of the Neutron API is assumed.
+
+ The l3_agent_scheduler extension is required for these tests.
"""
@classmethod
diff --git a/tempest/api/network/base.py b/tempest/api/network/base.py
index dcd9bff..cc768fd 100644
--- a/tempest/api/network/base.py
+++ b/tempest/api/network/base.py
@@ -81,9 +81,13 @@
cls.metering_label_rules = []
cls.fw_rules = []
cls.fw_policies = []
+ cls.ipsecpolicies = []
@classmethod
def tearDownClass(cls):
+ # Clean up ipsec policies
+ for ipsecpolicy in cls.ipsecpolicies:
+ cls.client.delete_ipsecpolicy(ipsecpolicy['id'])
# Clean up firewall policies
for fw_policy in cls.fw_policies:
cls.client.delete_firewall_policy(fw_policy['id'])
@@ -145,15 +149,16 @@
return network
@classmethod
- def create_subnet(cls, network, gateway=None):
+ def create_subnet(cls, network, gateway=None, cidr=None, mask_bits=None):
"""Wrapper utility that returns a test subnet."""
# The cidr and mask_bits depend on the ip version.
if cls._ip_version == 4:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
- mask_bits = CONF.network.tenant_network_mask_bits
+ cidr = cidr or netaddr.IPNetwork(CONF.network.tenant_network_cidr)
+ mask_bits = mask_bits or CONF.network.tenant_network_mask_bits
elif cls._ip_version == 6:
- cidr = netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr)
- mask_bits = CONF.network.tenant_network_v6_mask_bits
+ cidr = (
+ cidr or netaddr.IPNetwork(CONF.network.tenant_network_v6_cidr))
+ mask_bits = mask_bits or CONF.network.tenant_network_v6_mask_bits
# Find a cidr that is not in use yet and create a subnet with it
for subnet_cidr in cidr.subnet(mask_bits):
if not gateway:
@@ -342,6 +347,14 @@
router['id'], i['fixed_ips'][0]['subnet_id'])
cls.client.delete_router(router['id'])
+ @classmethod
+ def create_ipsecpolicy(cls, name):
+ """Wrapper utility that returns a test ipsec policy."""
+ _, body = cls.client.create_ipsecpolicy(name=name)
+ ipsecpolicy = body['ipsecpolicy']
+ cls.ipsecpolicies.append(ipsecpolicy)
+ return ipsecpolicy
+
class BaseAdminNetworkTest(BaseNetworkTest):
diff --git a/tempest/api/network/base_routers.py b/tempest/api/network/base_routers.py
index b278002..1303bcf 100644
--- a/tempest/api/network/base_routers.py
+++ b/tempest/api/network/base_routers.py
@@ -37,6 +37,15 @@
routers_list.append(router['id'])
self.assertNotIn(router_id, routers_list)
+ def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
+ resp, interface = self.client.add_router_interface_with_subnet_id(
+ router_id, subnet_id)
+ self.assertEqual('200', resp['status'])
+ self.addCleanup(self._remove_router_interface_with_subnet_id,
+ router_id, subnet_id)
+ self.assertEqual(subnet_id, interface['subnet_id'])
+ return interface
+
def _remove_router_interface_with_subnet_id(self, router_id, subnet_id):
resp, body = self.client.remove_router_interface_with_subnet_id(
router_id, subnet_id)
diff --git a/tempest/api/network/test_load_balancer.py b/tempest/api/network/test_load_balancer.py
index 673fc47..db24e0d 100644
--- a/tempest/api/network/test_load_balancer.py
+++ b/tempest/api/network/test_load_balancer.py
@@ -259,7 +259,7 @@
self.assertEqual('200', resp['status'])
member = body['member']
for key, value in member.iteritems():
- # 'status' should not be confirmed in api tests
+ # 'status' should not be confirmed in api tests
if key != 'status':
self.assertEqual(self.member[key], value)
@@ -340,7 +340,7 @@
self.assertEqual('200', resp['status'])
health_monitor = body['health_monitor']
for key, value in health_monitor.iteritems():
- # 'status' should not be confirmed in api tests
+ # 'status' should not be confirmed in api tests
if key != 'status':
self.assertEqual(self.health_monitor[key], value)
diff --git a/tempest/api/network/test_routers.py b/tempest/api/network/test_routers.py
index 7605b8a..d38633f 100644
--- a/tempest/api/network/test_routers.py
+++ b/tempest/api/network/test_routers.py
@@ -300,9 +300,13 @@
@test.attr(type='smoke')
def test_add_multiple_router_interfaces(self):
- network = self.create_network()
- subnet01 = self.create_subnet(network)
- subnet02 = self.create_subnet(network)
+ network01 = self.create_network(
+ network_name=data_utils.rand_name('router-network01-'))
+ network02 = self.create_network(
+ network_name=data_utils.rand_name('router-network02-'))
+ subnet01 = self.create_subnet(network01)
+ sub02_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr).next()
+ subnet02 = self.create_subnet(network02, cidr=sub02_cidr)
router = self._create_router(data_utils.rand_name('router-'))
interface01 = self._add_router_interface_with_subnet_id(router['id'],
subnet01['id'])
@@ -313,15 +317,6 @@
self._verify_router_interface(router['id'], subnet02['id'],
interface02['port_id'])
- def _add_router_interface_with_subnet_id(self, router_id, subnet_id):
- resp, interface = self.client.add_router_interface_with_subnet_id(
- router_id, subnet_id)
- self.assertEqual('200', resp['status'])
- self.addCleanup(self._remove_router_interface_with_subnet_id,
- router_id, subnet_id)
- self.assertEqual(subnet_id, interface['subnet_id'])
- return interface
-
def _verify_router_interface(self, router_id, subnet_id, port_id):
resp, show_port_body = self.client.show_port(port_id)
self.assertEqual('200', resp['status'])
diff --git a/tempest/api/network/test_routers_negative.py b/tempest/api/network/test_routers_negative.py
index 91ab9d6..feee51b 100644
--- a/tempest/api/network/test_routers_negative.py
+++ b/tempest/api/network/test_routers_negative.py
@@ -13,11 +13,16 @@
# License for the specific language governing permissions and limitations
# under the License.
+import netaddr
+
from tempest.api.network import base_routers as base
from tempest.common.utils import data_utils
+from tempest import config
from tempest import exceptions
from tempest import test
+CONF = config.CONF
+
class RoutersNegativeTest(base.BaseRouterTest):
_interface = 'json'
@@ -43,12 +48,30 @@
@test.attr(type=['negative', 'smoke'])
def test_router_add_gateway_net_not_external_returns_400(self):
- self.create_subnet(self.network)
+ alt_network = self.create_network(
+ network_name=data_utils.rand_name('router-negative-'))
+ sub_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr).next()
+ self.create_subnet(alt_network, cidr=sub_cidr)
self.assertRaises(exceptions.BadRequest,
self.client.update_router,
self.router['id'],
external_gateway_info={
- 'network_id': self.network['id']})
+ 'network_id': alt_network['id']})
+
+ @test.attr(type=['negative', 'smoke'])
+ def test_add_router_interfaces_on_overlapping_subnets_returns_400(self):
+ network01 = self.create_network(
+ network_name=data_utils.rand_name('router-network01-'))
+ network02 = self.create_network(
+ network_name=data_utils.rand_name('router-network02-'))
+ subnet01 = self.create_subnet(network01)
+ subnet02 = self.create_subnet(network02)
+ self._add_router_interface_with_subnet_id(self.router['id'],
+ subnet01['id'])
+ self.assertRaises(exceptions.BadRequest,
+ self._add_router_interface_with_subnet_id,
+ self.router['id'],
+ subnet02['id'])
@test.attr(type=['negative', 'smoke'])
def test_router_remove_interface_in_use_returns_409(self):
diff --git a/tempest/api/network/test_security_groups.py b/tempest/api/network/test_security_groups.py
index 97b3c4b..b98cea1 100644
--- a/tempest/api/network/test_security_groups.py
+++ b/tempest/api/network/test_security_groups.py
@@ -86,9 +86,6 @@
direction='ingress'
)
self.assertEqual('201', resp['status'])
- self.addCleanup(self._delete_security_group_rule,
- rule_create_body['security_group_rule']['id']
- )
# Show details of the created security rule
resp, show_rule_body = self.client.show_security_group_rule(
@@ -130,9 +127,6 @@
self.assertEqual('201', resp['status'])
sec_group_rule = rule_create_body['security_group_rule']
- self.addCleanup(self._delete_security_group_rule,
- sec_group_rule['id']
- )
self.assertEqual(sec_group_rule['direction'], direction)
self.assertEqual(sec_group_rule['protocol'], protocol)
diff --git a/tempest/api/network/test_security_groups_negative.py b/tempest/api/network/test_security_groups_negative.py
index 0b86398..53c9d12 100644
--- a/tempest/api/network/test_security_groups_negative.py
+++ b/tempest/api/network/test_security_groups_negative.py
@@ -55,7 +55,7 @@
def test_create_security_group_rule_with_bad_protocol(self):
group_create_body, _ = self._create_security_group()
- #Create rule with bad protocol name
+ # Create rule with bad protocol name
pname = 'bad_protocol_name'
self.assertRaises(
exceptions.BadRequest, self.client.create_security_group_rule,
@@ -66,7 +66,7 @@
def test_create_security_group_rule_with_invalid_ports(self):
group_create_body, _ = self._create_security_group()
- #Create rule with invalid ports
+ # Create rule with invalid ports
states = [(-16, 80, 'Invalid value for port -16'),
(80, 79, 'port_range_min must be <= port_range_max'),
(80, 65536, 'Invalid value for port 65536'),
diff --git a/tempest/api/network/test_vpnaas_extensions.py b/tempest/api/network/test_vpnaas_extensions.py
index a49e944..d1fe15c 100644
--- a/tempest/api/network/test_vpnaas_extensions.py
+++ b/tempest/api/network/test_vpnaas_extensions.py
@@ -16,6 +16,7 @@
from tempest.api.network import base
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest import test
CONF = config.CONF
@@ -53,6 +54,8 @@
cls.router['id'])
cls.ikepolicy = cls.create_ikepolicy(
data_utils.rand_name("ike-policy-"))
+ cls.ipsecpolicy = cls.create_ipsecpolicy(
+ data_utils.rand_name("ipsec-policy-"))
def _delete_ike_policy(self, ike_policy_id):
# Deletes a ike policy and verifies if it is deleted or not
@@ -70,6 +73,20 @@
ike_id_list.append(i['id'])
self.assertNotIn(ike_policy_id, ike_id_list)
+ def _delete_ipsec_policy(self, ipsec_policy_id):
+ # Deletes an ike policy if it exists
+ try:
+ self.client.delete_ipsecpolicy(ipsec_policy_id)
+
+ except exceptions.NotFound:
+ pass
+
+ def _assertExpected(self, expected, actual):
+ # Check if not expected keys/values exists in actual response body
+ for key, value in expected.iteritems():
+ self.assertIn(key, actual)
+ self.assertEqual(value, actual[key])
+
@test.attr(type='smoke')
def test_list_vpn_services(self):
# Verify the VPN service exists in the list of all VPN services
@@ -177,6 +194,51 @@
self.assertEqual(self.ikepolicy['ike_version'],
ikepolicy['ike_version'])
+ @test.attr(type='smoke')
+ def test_list_ipsec_policies(self):
+ # Verify the ipsec policy exists in the list of all ipsec policies
+ resp, body = self.client.list_ipsecpolicies()
+ self.assertEqual('200', resp['status'])
+ ipsecpolicies = body['ipsecpolicies']
+ self.assertIn(self.ipsecpolicy['id'], [i['id'] for i in ipsecpolicies])
+
+ @test.attr(type='smoke')
+ def test_create_update_delete_ipsec_policy(self):
+ # Creates an ipsec policy
+ ipsec_policy_body = {'name': data_utils.rand_name('ipsec-policy'),
+ 'pfs': 'group5',
+ 'encryption_algorithm': "aes-128",
+ 'auth_algorithm': 'sha1'}
+ resp, resp_body = self.client.create_ipsecpolicy(**ipsec_policy_body)
+ self.assertEqual('201', resp['status'])
+ ipsecpolicy = resp_body['ipsecpolicy']
+ self.addCleanup(self._delete_ipsec_policy, ipsecpolicy['id'])
+ self._assertExpected(ipsec_policy_body, ipsecpolicy)
+ # Verification of ipsec policy update
+ new_ipsec = {'description': 'Updated ipsec policy',
+ 'pfs': 'group2',
+ 'name': data_utils.rand_name("New-IPSec"),
+ 'encryption_algorithm': "aes-256",
+ 'lifetime': {'units': "seconds", 'value': '2000'}}
+ resp, body = self.client.update_ipsecpolicy(ipsecpolicy['id'],
+ **new_ipsec)
+ self.assertEqual('200', resp['status'])
+ updated_ipsec_policy = body['ipsecpolicy']
+ self._assertExpected(new_ipsec, updated_ipsec_policy)
+ # Verification of ipsec policy delete
+ resp, _ = self.client.delete_ipsecpolicy(ipsecpolicy['id'])
+ self.assertEqual('204', resp['status'])
+ self.assertRaises(exceptions.NotFound,
+ self.client.delete_ipsecpolicy, ipsecpolicy['id'])
+
+ @test.attr(type='smoke')
+ def test_show_ipsec_policy(self):
+ # Verifies the details of an ipsec policy
+ resp, body = self.client.show_ipsecpolicy(self.ipsecpolicy['id'])
+ self.assertEqual('200', resp['status'])
+ ipsecpolicy = body['ipsecpolicy']
+ self._assertExpected(self.ipsecpolicy, ipsecpolicy)
+
class VPNaaSTestXML(VPNaaSTestJSON):
_interface = 'xml'
diff --git a/tempest/api/object_storage/test_container_sync.py b/tempest/api/object_storage/test_container_sync.py
index 6bda83b..5f46d01 100644
--- a/tempest/api/object_storage/test_container_sync.py
+++ b/tempest/api/object_storage/test_container_sync.py
@@ -67,6 +67,7 @@
super(ContainerSyncTest, cls).tearDownClass()
@test.attr(type='slow')
+ @test.skip_because(bug='1317133')
def test_container_synchronization(self):
# container to container synchronization
# to allow/accept sync requests to/from other accounts
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
index 3e03a30..ffff580 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic.yaml
@@ -6,6 +6,7 @@
properties:
size: 1
description: a descriptive description
+ name: volume_name
outputs:
status:
@@ -20,5 +21,8 @@
description: display_description
value: { get_attr: ['volume', 'display_description'] }
+ display_name:
+ value: { get_attr: ['volume', 'display_name'] }
+
volume_id:
value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
index 08e3da4..b660c19 100644
--- a/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
+++ b/tempest/api/orchestration/stacks/templates/cinder_basic_delete_retain.yaml
@@ -7,6 +7,7 @@
properties:
size: 1
description: a descriptive description
+ name: volume_name
outputs:
status:
@@ -21,5 +22,8 @@
description: display_description
value: { get_attr: ['volume', 'display_description'] }
+ display_name:
+ value: { get_attr: ['volume', 'display_name'] }
+
volume_id:
value: { get_resource: volume }
diff --git a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
index 63b03f4..878ff68 100644
--- a/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
+++ b/tempest/api/orchestration/stacks/templates/neutron_basic.yaml
@@ -8,10 +8,12 @@
type: string
ImageId:
type: string
- ExternalRouterId:
+ SubNetCidr:
type: string
ExternalNetworkId:
type: string
+ DNSServers:
+ type: comma_delimited_list
timeout:
type: number
resources:
@@ -25,21 +27,19 @@
network_id: {Ref: Network}
name: NewSubnet
ip_version: 4
- cidr: 10.0.3.0/24
- dns_nameservers: ["8.8.8.8"]
- allocation_pools:
- - {end: 10.0.3.150, start: 10.0.3.20}
+ cidr: { get_param: SubNetCidr }
+ dns_nameservers: { get_param: DNSServers }
Router:
type: OS::Neutron::Router
properties:
name: NewRouter
- admin_state_up: false
+ admin_state_up: true
external_gateway_info:
network: {get_param: ExternalNetworkId}
RouterInterface:
type: OS::Neutron::RouterInterface
properties:
- router_id: {get_param: ExternalRouterId}
+ router_id: {get_resource: Router}
subnet_id: {get_resource: Subnet}
Server:
type: OS::Nova::Server
@@ -56,8 +56,8 @@
template: |
#!/bin/bash -v
- /opt/aws/bin/cfn-signal -e 0 -r "SmokeServerNeutron created" \
- 'wait_handle'
+ while ! /opt/aws/bin/cfn-signal -e 0 -r "SmokeServerNeutron created" \
+ 'wait_handle' ; do sleep 3; done
params:
wait_handle: {get_resource: WaitHandleNeutron}
WaitHandleNeutron:
diff --git a/tempest/api/orchestration/stacks/test_neutron_resources.py b/tempest/api/orchestration/stacks/test_neutron_resources.py
index 3086d78..e92b945 100644
--- a/tempest/api/orchestration/stacks/test_neutron_resources.py
+++ b/tempest/api/orchestration/stacks/test_neutron_resources.py
@@ -12,6 +12,7 @@
import logging
+import netaddr
from tempest.api.orchestration import base
from tempest import clients
@@ -41,9 +42,12 @@
template = cls.load_template('neutron_basic')
cls.keypair_name = (CONF.orchestration.keypair_name or
cls._create_keypair()['name'])
- cls.external_router_id = cls._get_external_router_id()
cls.external_network_id = CONF.network.public_network_id
+ tenant_cidr = netaddr.IPNetwork(CONF.network.tenant_network_cidr)
+ mask_bits = CONF.network.tenant_network_mask_bits
+ cls.subnet_cidr = tenant_cidr.subnet(mask_bits).next()
+
# create the stack
cls.stack_identifier = cls.create_stack(
cls.stack_name,
@@ -52,9 +56,10 @@
'KeyName': cls.keypair_name,
'InstanceType': CONF.orchestration.instance_type,
'ImageId': CONF.orchestration.image_ref,
- 'ExternalRouterId': cls.external_router_id,
'ExternalNetworkId': cls.external_network_id,
- 'timeout': CONF.orchestration.build_timeout
+ 'timeout': CONF.orchestration.build_timeout,
+ 'DNSServers': CONF.network.dns_servers,
+ 'SubNetCidr': str(cls.subnet_cidr)
})
cls.stack_id = cls.stack_identifier.split('/')[1]
try:
@@ -77,14 +82,6 @@
for resource in resources:
cls.test_resources[resource['logical_resource_id']] = resource
- @classmethod
- def _get_external_router_id(cls):
- resp, body = cls.network_client.list_ports()
- ports = body['ports']
- router_ports = filter(lambda port: port['device_owner'] ==
- 'network:router_interface', ports)
- return router_ports[0]['device_id']
-
@test.attr(type='slow')
def test_created_resources(self):
"""Verifies created neutron resources."""
@@ -121,11 +118,10 @@
self.assertEqual(subnet_id, subnet['id'])
self.assertEqual(network_id, subnet['network_id'])
self.assertEqual('NewSubnet', subnet['name'])
- self.assertEqual('8.8.8.8', subnet['dns_nameservers'][0])
- self.assertEqual('10.0.3.20', subnet['allocation_pools'][0]['start'])
- self.assertEqual('10.0.3.150', subnet['allocation_pools'][0]['end'])
+ self.assertEqual(sorted(CONF.network.dns_servers),
+ sorted(subnet['dns_nameservers']))
self.assertEqual(4, subnet['ip_version'])
- self.assertEqual('10.0.3.0/24', subnet['cidr'])
+ self.assertEqual(str(self.subnet_cidr), subnet['cidr'])
@test.attr(type='slow')
def test_created_router(self):
@@ -137,18 +133,19 @@
self.assertEqual('NewRouter', router['name'])
self.assertEqual(self.external_network_id,
router['external_gateway_info']['network_id'])
- self.assertEqual(False, router['admin_state_up'])
+ self.assertEqual(True, router['admin_state_up'])
@test.attr(type='slow')
def test_created_router_interface(self):
"""Verifies created router interface."""
+ router_id = self.test_resources.get('Router')['physical_resource_id']
network_id = self.test_resources.get('Network')['physical_resource_id']
subnet_id = self.test_resources.get('Subnet')['physical_resource_id']
resp, body = self.network_client.list_ports()
self.assertEqual('200', resp['status'])
ports = body['ports']
router_ports = filter(lambda port: port['device_id'] ==
- self.external_router_id, ports)
+ router_id, ports)
created_network_ports = filter(lambda port: port['network_id'] ==
network_id, router_ports)
self.assertEqual(1, len(created_network_ports))
@@ -158,7 +155,8 @@
subnet_id, fixed_ips)
self.assertEqual(1, len(subnet_fixed_ips))
router_interface_ip = subnet_fixed_ips[0]['ip_address']
- self.assertEqual('10.0.3.1', router_interface_ip)
+ self.assertEqual(str(self.subnet_cidr.iter_hosts().next()),
+ router_interface_ip)
@test.attr(type='slow')
def test_created_server(self):
@@ -170,8 +168,4 @@
self.assertEqual('ACTIVE', server['status'])
network = server['addresses']['NewNetwork'][0]
self.assertEqual(4, network['version'])
- ip_addr_prefix = network['addr'][:7]
- ip_addr_suffix = int(network['addr'].split('.')[3])
- self.assertEqual('10.0.3.', ip_addr_prefix)
- self.assertTrue(ip_addr_suffix >= 20)
- self.assertTrue(ip_addr_suffix <= 150)
+ self.assertIn(netaddr.IPAddress(network['addr']), self.subnet_cidr)
diff --git a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
index cb70d07..a81a540 100644
--- a/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
+++ b/tempest/api/orchestration/stacks/test_nova_keypair_resources.py
@@ -68,13 +68,13 @@
output_map = {}
for outputs in stack['outputs']:
output_map[outputs['output_key']] = outputs['output_value']
- #Test that first key generated public and private keys
+ # Test that first key generated public and private keys
self.assertTrue('KeyPair_PublicKey' in output_map)
self.assertTrue("Generated" in output_map['KeyPair_PublicKey'])
self.assertTrue('KeyPair_PrivateKey' in output_map)
self.assertTrue('-----BEGIN' in output_map['KeyPair_PrivateKey'])
- #Test that second key generated public key, and private key is not
- #in the output due to save_private_key = false
+ # Test that second key generated public key, and private key is not
+ # in the output due to save_private_key = false
self.assertTrue('KeyPairDontSavePrivate_PublicKey' in output_map)
self.assertTrue('Generated' in
output_map['KeyPairDontSavePrivate_PublicKey'])
diff --git a/tempest/api/orchestration/stacks/test_volumes.py b/tempest/api/orchestration/stacks/test_volumes.py
index 2544c41..5ac2a8d 100644
--- a/tempest/api/orchestration/stacks/test_volumes.py
+++ b/tempest/api/orchestration/stacks/test_volumes.py
@@ -39,6 +39,8 @@
self.assertEqual(1, volume.get('size'))
self.assertEqual('a descriptive description',
volume.get('display_description'))
+ self.assertEqual('volume_name',
+ volume.get('display_name'))
def _outputs_verify(self, stack_identifier):
self.assertEqual('available',
@@ -48,6 +50,9 @@
self.assertEqual('a descriptive description',
self.get_stack_output(stack_identifier,
'display_description'))
+ self.assertEqual('volume_name',
+ self.get_stack_output(stack_identifier,
+ 'display_name'))
@test.attr(type='gate')
def test_cinder_volume_create_delete(self):
diff --git a/tempest/api/telemetry/base.py b/tempest/api/telemetry/base.py
index c4614c6..2b422fd 100644
--- a/tempest/api/telemetry/base.py
+++ b/tempest/api/telemetry/base.py
@@ -10,9 +10,12 @@
# License for the specific language governing permissions and limitations
# under the License.
+import time
+
from tempest.common.utils import data_utils
from tempest import config
from tempest import exceptions
+from tempest.openstack.common import timeutils
import tempest.test
CONF = config.CONF
@@ -29,6 +32,12 @@
super(BaseTelemetryTest, cls).setUpClass()
os = cls.get_client_manager()
cls.telemetry_client = os.telemetry_client
+ cls.servers_client = os.servers_client
+ cls.flavors_client = os.flavors_client
+
+ cls.nova_notifications = ['memory', 'vcpus', 'disk.root.size',
+ 'disk.ephemeral.size']
+ cls.server_ids = []
cls.alarm_ids = []
@classmethod
@@ -41,11 +50,46 @@
return resp, body
@classmethod
- def tearDownClass(cls):
- for alarm_id in cls.alarm_ids:
+ def create_server(cls):
+ resp, body = cls.servers_client.create_server(
+ data_utils.rand_name('ceilometer-instance'),
+ CONF.compute.image_ref, CONF.compute.flavor_ref,
+ wait_until='ACTIVE')
+ if resp['status'] == '202':
+ cls.server_ids.append(body['id'])
+ return resp, body
+
+ @staticmethod
+ def cleanup_resources(method, list_of_ids):
+ for resource_id in list_of_ids:
try:
- cls.telemetry_client.delete_alarm(alarm_id)
+ method(resource_id)
except exceptions.NotFound:
pass
+
+ @classmethod
+ def tearDownClass(cls):
+ cls.cleanup_resources(cls.telemetry_client.delete_alarm, cls.alarm_ids)
+ cls.cleanup_resources(cls.servers_client.delete_server, cls.server_ids)
cls.clear_isolated_creds()
super(BaseTelemetryTest, cls).tearDownClass()
+
+ def await_samples(self, metric, query):
+ """
+ This method is to wait for sample to add it to database.
+ There are long time delays when using Postgresql (or Mysql)
+ database as ceilometer backend
+ """
+ timeout = CONF.compute.build_timeout
+ start = timeutils.utcnow()
+ while timeutils.delta_seconds(start, timeutils.utcnow()) < timeout:
+ resp, body = self.telemetry_client.list_samples(metric, query)
+ self.assertEqual(resp.status, 200)
+ if body:
+ return resp, body
+ time.sleep(CONF.compute.build_interval)
+
+ raise exceptions.TimeoutException(
+ 'Sample for metric:%s with query:%s has not been added to the '
+ 'database within %d seconds' % (metric, query,
+ CONF.compute.build_timeout))
diff --git a/tempest/api/telemetry/test_telemetry_notification_api.py b/tempest/api/telemetry/test_telemetry_notification_api.py
new file mode 100644
index 0000000..148f5a3
--- /dev/null
+++ b/tempest/api/telemetry/test_telemetry_notification_api.py
@@ -0,0 +1,47 @@
+# 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 testtools
+
+from tempest.api.telemetry import base
+from tempest import config
+from tempest import test
+
+CONF = config.CONF
+
+
+class TelemetryNotificationAPITestJSON(base.BaseTelemetryTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ if CONF.telemetry.too_slow_to_test:
+ raise cls.skipException("Ceilometer feature for fast work mysql "
+ "is disabled")
+ super(TelemetryNotificationAPITestJSON, cls).setUpClass()
+
+ @test.attr(type="gate")
+ @testtools.skipIf(not CONF.service_available.nova,
+ "Nova is not available.")
+ def test_check_nova_notification(self):
+
+ resp, body = self.create_server()
+ self.assertEqual(resp.status, 202)
+
+ query = ('resource', 'eq', body['id'])
+
+ for metric in self.nova_notifications:
+ self.await_samples(metric, query)
+
+
+class TelemetryNotificationAPITestXML(TelemetryNotificationAPITestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/volume/admin/test_volume_types.py b/tempest/api/volume/admin/test_volume_types.py
index ee1d09a..3b8c214 100644
--- a/tempest/api/volume/admin/test_volume_types.py
+++ b/tempest/api/volume/admin/test_volume_types.py
@@ -118,14 +118,16 @@
'from the created Volume_type')
@test.attr(type='smoke')
- def test_volume_type_encryption_create_get(self):
- # Create/get encryption type.
+ def test_volume_type_encryption_create_get_delete(self):
+ # Create/get/delete encryption type.
provider = "LuksEncryptor"
control_location = "front-end"
name = data_utils.rand_name("volume-type-")
resp, body = self.client.create_volume_type(name)
self.assertEqual(200, resp.status)
self.addCleanup(self._delete_volume_type, body['id'])
+
+ # Create encryption type
resp, encryption_type = self.client.create_encryption_type(
body['id'], provider=provider,
control_location=control_location)
@@ -137,6 +139,8 @@
self.assertEqual(control_location, encryption_type['control_location'],
"The created encryption_type control_location is not "
"equal to the requested control_location")
+
+ # Get encryption type
resp, fetched_encryption_type = self.client.get_encryption_type(
encryption_type['volume_type_id'])
self.assertEqual(200, resp.status)
@@ -148,3 +152,15 @@
fetched_encryption_type['control_location'],
'The fetched encryption_type control_location is '
'different from the created encryption_type')
+
+ # Delete encryption type
+ resp, _ = self.client.delete_encryption_type(
+ encryption_type['volume_type_id'])
+ self.assertEqual(202, resp.status)
+ resource = {"id": encryption_type['volume_type_id'],
+ "type": "encryption-type"}
+ self.client.wait_for_resource_deletion(resource)
+ resp, deleted_encryption_type = self.client.get_encryption_type(
+ encryption_type['volume_type_id'])
+ self.assertEqual(200, resp.status)
+ self.assertEmpty(deleted_encryption_type)
diff --git a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
index d3a052e..da421dc 100644
--- a/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
+++ b/tempest/api/volume/admin/test_volume_types_extra_specs_negative.py
@@ -29,9 +29,9 @@
super(ExtraSpecsNegativeTest, cls).setUpClass()
vol_type_name = data_utils.rand_name('Volume-type-')
cls.extra_specs = {"spec1": "val1"}
- resp, cls.volume_type = cls.client.create_volume_type(vol_type_name,
- extra_specs=
- cls.extra_specs)
+ resp, cls.volume_type = cls.client.create_volume_type(
+ vol_type_name,
+ extra_specs=cls.extra_specs)
@classmethod
def tearDownClass(cls):
diff --git a/tempest/api/volume/base.py b/tempest/api/volume/base.py
index 67d0203..2a9b407 100644
--- a/tempest/api/volume/base.py
+++ b/tempest/api/volume/base.py
@@ -16,6 +16,7 @@
from tempest import clients
from tempest.common.utils import data_utils
from tempest import config
+from tempest import exceptions
from tempest.openstack.common import log as logging
import tempest.test
@@ -25,9 +26,11 @@
class BaseVolumeTest(tempest.test.BaseTestCase):
-
"""Base test case class for all Cinder API tests."""
+ _api_version = 2
+ _interface = 'json'
+
@classmethod
def setUpClass(cls):
cls.set_network_resources()
@@ -47,6 +50,28 @@
cls.snapshots = []
cls.volumes = []
+ if cls._api_version == 1:
+ if not CONF.volume_feature_enabled.api_v1:
+ msg = "Volume API v1 is disabled"
+ raise cls.skipException(msg)
+ cls.snapshots_client = cls.os.snapshots_client
+ cls.volumes_client = cls.os.volumes_client
+ cls.backups_client = cls.os.backups_client
+ cls.volume_services_client = cls.os.volume_services_client
+ cls.volumes_extension_client = cls.os.volumes_extension_client
+ cls.availability_zone_client = (
+ cls.os.volume_availability_zone_client)
+
+ elif cls._api_version == 2:
+ if not CONF.volume_feature_enabled.api_v2:
+ msg = "Volume API v2 is disabled"
+ raise cls.skipException(msg)
+ cls.volumes_client = cls.os.volumes_v2_client
+
+ else:
+ msg = ("Invalid Cinder API version (%s)" % cls._api_version)
+ raise exceptions.InvalidConfiguration(message=msg)
+
@classmethod
def tearDownClass(cls):
cls.clear_snapshots()
@@ -55,6 +80,22 @@
super(BaseVolumeTest, cls).tearDownClass()
@classmethod
+ def create_volume(cls, size=1, **kwargs):
+ """Wrapper utility that returns a test volume."""
+ vol_name = data_utils.rand_name('Volume')
+ if cls._api_version == 1:
+ resp, volume = cls.volumes_client.create_volume(
+ size, display_name=vol_name, **kwargs)
+ assert 200 == resp.status
+ elif cls._api_version == 2:
+ resp, volume = cls.volumes_client.create_volume(
+ size, name=vol_name, **kwargs)
+ assert 202 == resp.status
+ cls.volumes.append(volume)
+ cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
+ return volume
+
+ @classmethod
def create_snapshot(cls, volume_id=1, **kwargs):
"""Wrapper utility that returns a test snapshot."""
resp, snapshot = cls.snapshots_client.create_snapshot(volume_id,
@@ -98,30 +139,11 @@
class BaseVolumeV1Test(BaseVolumeTest):
- @classmethod
- def setUpClass(cls):
- if not CONF.volume_feature_enabled.api_v1:
- msg = "Volume API v1 not supported"
- raise cls.skipException(msg)
- super(BaseVolumeV1Test, cls).setUpClass()
- cls.snapshots_client = cls.os.snapshots_client
- cls.volumes_client = cls.os.volumes_client
- cls.backups_client = cls.os.backups_client
- cls.volume_services_client = cls.os.volume_services_client
- cls.volumes_extension_client = cls.os.volumes_extension_client
- cls.availability_zone_client = cls.os.volume_availability_zone_client
+ _api_version = 1
- @classmethod
- def create_volume(cls, size=1, **kwargs):
- """Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- resp, volume = cls.volumes_client.create_volume(size,
- display_name=vol_name,
- **kwargs)
- assert 200 == resp.status
- cls.volumes.append(volume)
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
- return volume
+
+class BaseVolumeV2Test(BaseVolumeTest):
+ _api_version = 2
class BaseVolumeV1AdminTest(BaseVolumeV1Test):
@@ -144,25 +166,3 @@
cls.client = cls.os_adm.volume_types_client
cls.hosts_client = cls.os_adm.volume_hosts_client
cls.quotas_client = cls.os_adm.volume_quotas_client
-
-
-class BaseVolumeV2Test(BaseVolumeTest):
- @classmethod
- def setUpClass(cls):
- if not CONF.volume_feature_enabled.api_v2:
- msg = "Volume API v2 not supported"
- raise cls.skipException(msg)
- super(BaseVolumeV2Test, cls).setUpClass()
- cls.volumes_client = cls.os.volumes_v2_client
-
- @classmethod
- def create_volume(cls, size=1, **kwargs):
- """Wrapper utility that returns a test volume."""
- vol_name = data_utils.rand_name('Volume')
- resp, volume = cls.volumes_client.create_volume(size,
- name=vol_name,
- **kwargs)
- assert 202 == resp.status
- cls.volumes.append(volume)
- cls.volumes_client.wait_for_volume_status(volume['id'], 'available')
- return volume
diff --git a/tempest/api/volume/test_availability_zone.py b/tempest/api/volume/test_availability_zone.py
index 1db7b7b..fe8f96e 100644
--- a/tempest/api/volume/test_availability_zone.py
+++ b/tempest/api/volume/test_availability_zone.py
@@ -22,6 +22,7 @@
"""
Tests Availability Zone API List
"""
+ _interface = 'json'
@classmethod
def setUpClass(cls):
diff --git a/tempest/api/volume/test_volumes_get.py b/tempest/api/volume/test_volumes_get.py
index 58da440..2745b95 100644
--- a/tempest/api/volume/test_volumes_get.py
+++ b/tempest/api/volume/test_volumes_get.py
@@ -39,7 +39,7 @@
def _is_true(self, val):
# NOTE(jdg): Temporary conversion method to get cinder patch
# merged. Then we'll make this strict again and
- #specifically check "true" or "false"
+ # specifically check "true" or "false"
if val in ['true', 'True', True]:
return True
else:
@@ -121,19 +121,19 @@
new_volume = {}
new_v_desc = data_utils.rand_name('@#$%^* description')
resp, new_volume = \
- self.client.create_volume(size=1,
- display_description=new_v_desc,
- availability_zone=
- volume['availability_zone'])
+ self.client.create_volume(
+ size=1,
+ display_description=new_v_desc,
+ availability_zone=volume['availability_zone'])
self.assertEqual(200, resp.status)
self.assertIn('id', new_volume)
self.addCleanup(self._delete_volume, new_volume['id'])
self.client.wait_for_volume_status(new_volume['id'], 'available')
resp, update_volume = \
- self.client.update_volume(new_volume['id'],
- display_name=volume['display_name'],
- display_description=
- volume['display_description'])
+ self.client.update_volume(
+ new_volume['id'],
+ display_name=volume['display_name'],
+ display_description=volume['display_description'])
self.assertEqual(200, resp.status)
# NOTE(jdg): Revert back to strict true/false checking
diff --git a/tempest/api/volume/test_volumes_negative.py b/tempest/api/volume/test_volumes_negative.py
index a8b0a8d..bc5b1dc 100644
--- a/tempest/api/volume/test_volumes_negative.py
+++ b/tempest/api/volume/test_volumes_negative.py
@@ -154,6 +154,7 @@
self.assertRaises(exceptions.NotFound, self.client.delete_volume, '')
@test.attr(type=['negative', 'gate'])
+ @test.services('compute')
def test_attach_volumes_with_nonexistent_volume_id(self):
srv_name = data_utils.rand_name('Instance-')
resp, server = self.servers_client.create_server(srv_name,
diff --git a/tempest/api/volume/test_volumes_snapshots.py b/tempest/api/volume/test_volumes_snapshots.py
index 6294cd9..26316d2 100644
--- a/tempest/api/volume/test_volumes_snapshots.py
+++ b/tempest/api/volume/test_volumes_snapshots.py
@@ -63,6 +63,7 @@
self.assertEqual(params[key], snap[key], msg)
@test.attr(type='gate')
+ @test.services('compute')
def test_snapshot_create_with_volume_in_use(self):
# Create a snapshot when volume status is in-use
# Create a test instance
diff --git a/tempest/api/volume/v2/test_volumes_list.py b/tempest/api/volume/v2/test_volumes_list.py
index 41445d7..e90c957 100644
--- a/tempest/api/volume/v2/test_volumes_list.py
+++ b/tempest/api/volume/v2/test_volumes_list.py
@@ -203,7 +203,7 @@
def _list_details_with_multiple_params(limit=2,
status='available',
sort_dir='asc',
- sort_key='created_at'):
+ sort_key='id'):
params = {'limit': limit,
'status': status,
'sort_dir': sort_dir,
diff --git a/tempest/api_schema/compute/servers.py b/tempest/api_schema/compute/servers.py
index fb39087..14e9ce9 100644
--- a/tempest/api_schema/compute/servers.py
+++ b/tempest/api_schema/compute/servers.py
@@ -84,11 +84,16 @@
'links': parameter_types.links,
'addresses': parameter_types.addresses,
},
+ # NOTE(GMann): 'progress' attribute is present in the response
+ # only when server's status is one of the progress statuses
+ # ("ACTIVE","BUILD", "REBUILD", "RESIZE","VERIFY_RESIZE")
+ # So it is not defined as 'required'.
'required': ['id', 'name', 'status', 'image', 'flavor',
'user_id', 'tenant_id', 'created', 'updated',
- 'progress', 'metadata', 'links', 'addresses']
+ 'metadata', 'links', 'addresses']
}
- }
+ },
+ 'required': ['server']
}
}
@@ -114,6 +119,8 @@
list_server_metadata = copy.deepcopy(set_server_metadata)
+update_server_metadata = copy.deepcopy(set_server_metadata)
+
delete_server_metadata_item = {
'status_code': [204]
}
@@ -147,3 +154,28 @@
server_actions_delete_password = {
'status_code': [204]
}
+
+get_console_output = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'output': {'type': 'string'}
+ },
+ 'required': ['output']
+ }
+}
+
+common_instance_actions = {
+ 'type': 'object',
+ 'properties': {
+ 'action': {'type': 'string'},
+ 'request_id': {'type': 'string'},
+ 'user_id': {'type': 'string'},
+ 'project_id': {'type': 'string'},
+ 'start_time': {'type': 'string'},
+ 'message': {'type': ['string', 'null']}
+ },
+ 'required': ['action', 'request_id', 'user_id', 'project_id',
+ 'start_time', 'message']
+}
diff --git a/tempest/api_schema/compute/v2/floating_ips.py b/tempest/api_schema/compute/v2/floating_ips.py
index 3ea6320..03e6aef 100644
--- a/tempest/api_schema/compute/v2/floating_ips.py
+++ b/tempest/api_schema/compute/v2/floating_ips.py
@@ -98,3 +98,22 @@
add_remove_floating_ip = {
'status_code': [202]
}
+
+create_floating_ips_bulk = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'floating_ips_bulk_create': {
+ 'type': 'object',
+ 'properties': {
+ 'interface': {'type': ['string', 'null']},
+ 'ip_range': {'type': 'string'},
+ 'pool': {'type': ['string', 'null']},
+ },
+ 'required': ['interface', 'ip_range', 'pool']
+ }
+ },
+ 'required': ['floating_ips_bulk_create']
+ }
+}
diff --git a/tempest/api_schema/compute/v2/servers.py b/tempest/api_schema/compute/v2/servers.py
index 3da3276..fe53abd 100644
--- a/tempest/api_schema/compute/v2/servers.py
+++ b/tempest/api_schema/compute/v2/servers.py
@@ -131,3 +131,68 @@
server_actions_confirm_resize = copy.deepcopy(
servers.server_actions_delete_password)
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': parameter_types.addresses
+ },
+ 'required': ['addresses']
+ }
+}
+
+common_server_group = {
+ 'type': 'object',
+ 'properties': {
+ 'id': {'type': 'string'},
+ 'name': {'type': 'string'},
+ 'policies': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ # 'members' attribute contains the array of instance's UUID of
+ # instances present in server group
+ 'members': {
+ 'type': 'array',
+ 'items': {'type': 'string'}
+ },
+ 'metadata': {'type': 'object'}
+ },
+ 'required': ['id', 'name', 'policies', 'members', 'metadata']
+}
+
+create_get_server_group = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_group': common_server_group
+ },
+ 'required': ['server_group']
+ }
+}
+
+delete_server_group = {
+ 'status_code': [204]
+}
+
+instance_actions_object = copy.deepcopy(servers.common_instance_actions)
+instance_actions_object[
+ 'properties'].update({'instance_uuid': {'type': 'string'}})
+instance_actions_object['required'].extend(['instance_uuid'])
+
+list_instance_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'instanceActions': {
+ 'type': 'array',
+ 'items': instance_actions_object
+ }
+ },
+ 'required': ['instanceActions']
+ }
+}
diff --git a/tempest/api_schema/compute/v3/servers.py b/tempest/api_schema/compute/v3/servers.py
index 8f694f7..4fb2d87 100644
--- a/tempest/api_schema/compute/v3/servers.py
+++ b/tempest/api_schema/compute/v3/servers.py
@@ -84,3 +84,36 @@
server_actions_change_password = copy.deepcopy(
servers.server_actions_delete_password)
+
+list_addresses = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'addresses': addresses_v3
+ },
+ 'required': ['addresses']
+ }
+}
+
+update_server_metadata = copy.deepcopy(servers.update_server_metadata)
+# V3 API's response status_code is 201
+update_server_metadata['status_code'] = [201]
+
+server_actions_object = copy.deepcopy(servers.common_instance_actions)
+server_actions_object['properties'].update({'server_uuid': {'type': 'string'}})
+server_actions_object['required'].extend(['server_uuid'])
+
+list_server_actions = {
+ 'status_code': [200],
+ 'response_body': {
+ 'type': 'object',
+ 'properties': {
+ 'server_actions': {
+ 'type': 'array',
+ 'items': server_actions_object
+ }
+ },
+ 'required': ['server_actions']
+ }
+}
diff --git a/tempest/cli/simple_read_only/test_cinder.py b/tempest/cli/simple_read_only/test_cinder.py
index 723333b..e9a0cee 100644
--- a/tempest/cli/simple_read_only/test_cinder.py
+++ b/tempest/cli/simple_read_only/test_cinder.py
@@ -118,6 +118,12 @@
def test_cinder_bash_completion(self):
self.cinder('bash-completion')
+ def test_cinder_qos_list(self):
+ self.cinder('qos-list')
+
+ def test_cinder_encryption_type_list(self):
+ self.cinder('encryption-type-list')
+
def test_admin_help(self):
help_text = self.cinder('help')
lines = help_text.split('\n')
@@ -136,7 +142,7 @@
'quota-show', 'type-list', 'snapshot-list'))
self.assertFalse(wanted_commands - commands)
- # Optional arguments:
+ # Optional arguments:
def test_cinder_version(self):
self.cinder('', flags='--version')
diff --git a/tempest/cli/simple_read_only/test_heat.py b/tempest/cli/simple_read_only/test_heat.py
index cf4580c..7a952fc 100644
--- a/tempest/cli/simple_read_only/test_heat.py
+++ b/tempest/cli/simple_read_only/test_heat.py
@@ -85,6 +85,9 @@
def test_heat_help(self):
self.heat('help')
+ def test_heat_bash_completion(self):
+ self.heat('bash-completion')
+
def test_heat_help_cmd(self):
# Check requesting help for a specific command works
help_text = self.heat('help resource-template')
diff --git a/tempest/cli/simple_read_only/test_keystone.py b/tempest/cli/simple_read_only/test_keystone.py
index 1efbede..dda65c1 100644
--- a/tempest/cli/simple_read_only/test_keystone.py
+++ b/tempest/cli/simple_read_only/test_keystone.py
@@ -117,6 +117,11 @@
def test_admin_bashcompletion(self):
self.keystone('bash-completion')
+ def test_admin_ec2_credentials_list(self):
+ creds = self.keystone('ec2-credentials-list')
+ creds = self.parser.listing(creds)
+ self.assertTableStruct(creds, ['tenant', 'access', 'secret'])
+
# Optional arguments:
def test_admin_version(self):
diff --git a/tempest/cli/simple_read_only/test_nova.py b/tempest/cli/simple_read_only/test_nova.py
index a3787ab..1c1ddf1 100644
--- a/tempest/cli/simple_read_only/test_nova.py
+++ b/tempest/cli/simple_read_only/test_nova.py
@@ -145,6 +145,9 @@
def test_admin_secgroup_list_rules(self):
self.nova('secgroup-list-rules')
+ def test_admin_server_group_list(self):
+ self.nova('server-group-list')
+
def test_admin_servce_list(self):
self.nova('service-list')
diff --git a/tempest/clients.py b/tempest/clients.py
index 7745d54..4050a20 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -129,6 +129,7 @@
IdentityV3ClientJSON
from tempest.services.identity.v3.json.identity_client import V3TokenClientJSON
from tempest.services.identity.v3.json.policy_client import PolicyClientJSON
+from tempest.services.identity.v3.json.region_client import RegionClientJSON
from tempest.services.identity.v3.json.service_client import \
ServiceClientJSON
from tempest.services.identity.v3.xml.credentials_client import \
@@ -138,6 +139,7 @@
IdentityV3ClientXML
from tempest.services.identity.v3.xml.identity_client import V3TokenClientXML
from tempest.services.identity.v3.xml.policy_client import PolicyClientXML
+from tempest.services.identity.v3.xml.region_client import RegionClientXML
from tempest.services.identity.v3.xml.service_client import \
ServiceClientXML
from tempest.services.identity.xml.identity_client import IdentityClientXML
@@ -248,6 +250,7 @@
self.tenant_usages_client = TenantUsagesClientXML(
self.auth_provider)
self.policy_client = PolicyClientXML(self.auth_provider)
+ self.region_client = RegionClientXML(self.auth_provider)
self.hosts_client = HostsClientXML(self.auth_provider)
self.hypervisor_client = HypervisorClientXML(self.auth_provider)
self.network_client = NetworkClientXML(self.auth_provider)
@@ -334,6 +337,7 @@
self.migrations_v3_client = MigrationsV3ClientJSON(
self.auth_provider)
self.policy_client = PolicyClientJSON(self.auth_provider)
+ self.region_client = RegionClientJSON(self.auth_provider)
self.hosts_client = HostsClientJSON(self.auth_provider)
self.hypervisor_v3_client = HypervisorV3ClientJSON(
self.auth_provider)
@@ -452,6 +456,7 @@
HEATCLIENT_VERSION = '1'
IRONICCLIENT_VERSION = '1'
SAHARACLIENT_VERSION = '1.1'
+ CEILOMETERCLIENT_VERSION = '2'
def __init__(self, credentials):
# FIXME(andreaf) Auth provider for client_type 'official' is
@@ -472,6 +477,8 @@
credentials)
self.data_processing_client = self._get_data_processing_client(
credentials)
+ self.ceilometer_client = self._get_ceilometer_client(
+ credentials)
def _get_roles(self):
admin_credentials = auth.get_default_credentials('identity_admin')
@@ -692,3 +699,34 @@
auth_url=auth_url)
return client
+
+ def _get_ceilometer_client(self, credentials):
+ if not CONF.service_available.ceilometer:
+ return None
+
+ import ceilometerclient.client
+
+ keystone = self._get_identity_client(credentials)
+ region = CONF.identity.region
+
+ endpoint_type = CONF.telemetry.endpoint_type
+ service_type = CONF.telemetry.catalog_type
+ auth_url = CONF.identity.uri
+
+ try:
+ keystone.service_catalog.url_for(
+ attr='region',
+ filter_value=region,
+ service_type=service_type,
+ endpoint_type=endpoint_type)
+ except keystoneclient.exceptions.EndpointNotFound:
+ return None
+ else:
+ return ceilometerclient.client.get_client(
+ self.CEILOMETERCLIENT_VERSION,
+ os_username=credentials.username,
+ os_password=credentials.password,
+ os_tenant_name=credentials.tenant_name,
+ os_auth_url=auth_url,
+ os_service_type=service_type,
+ os_endpoint_type=endpoint_type)
diff --git a/tempest/cmd/javelin.py b/tempest/cmd/javelin.py
new file mode 100755
index 0000000..20ee63e
--- /dev/null
+++ b/tempest/cmd/javelin.py
@@ -0,0 +1,430 @@
+#!/usr/bin/env python
+#
+# 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.
+
+"""Javelin makes resources that should survive an upgrade.
+
+Javelin is a tool for creating, verifying, and deleting a small set of
+resources in a declarative way.
+
+"""
+
+import logging
+import os
+import sys
+import unittest
+import yaml
+
+import argparse
+
+import tempest.auth
+from tempest import exceptions
+from tempest.services.compute.json import flavors_client
+from tempest.services.compute.json import servers_client
+from tempest.services.identity.json import identity_client
+from tempest.services.image.v2.json import image_client
+from tempest.services.object_storage import container_client
+from tempest.services.object_storage import object_client
+
+OPTS = {}
+USERS = {}
+RES = {}
+
+LOG = None
+
+
+class OSClient(object):
+ _creds = None
+ identity = None
+ servers = None
+
+ def __init__(self, user, pw, tenant):
+ _creds = tempest.auth.KeystoneV2Credentials(
+ username=user,
+ password=pw,
+ tenant_name=tenant)
+ _auth = tempest.auth.KeystoneV2AuthProvider(_creds)
+ self.identity = identity_client.IdentityClientJSON(_auth)
+ self.servers = servers_client.ServersClientJSON(_auth)
+ self.objects = object_client.ObjectClient(_auth)
+ self.containers = container_client.ContainerClient(_auth)
+ self.images = image_client.ImageClientV2JSON(_auth)
+ self.flavors = flavors_client.FlavorsClientJSON(_auth)
+
+
+def load_resources(fname):
+ """Load the expected resources from a yaml flie."""
+ return yaml.load(open(fname, 'r'))
+
+
+def keystone_admin():
+ return OSClient(OPTS.os_username, OPTS.os_password, OPTS.os_tenant_name)
+
+
+def client_for_user(name):
+ LOG.debug("Entering client_for_user")
+ if name in USERS:
+ user = USERS[name]
+ LOG.debug("Created client for user %s" % user)
+ return OSClient(user['name'], user['pass'], user['tenant'])
+ else:
+ LOG.error("%s not found in USERS: %s" % (name, USERS))
+
+###################
+#
+# TENANTS
+#
+###################
+
+
+def create_tenants(tenants):
+ """Create tenants from resource definition.
+
+ Don't create the tenants if they already exist.
+ """
+ admin = keystone_admin()
+ _, body = admin.identity.list_tenants()
+ existing = [x['name'] for x in body]
+ for tenant in tenants:
+ if tenant not in existing:
+ admin.identity.create_tenant(tenant)
+ else:
+ LOG.warn("Tenant '%s' already exists in this environment" % tenant)
+
+##############
+#
+# USERS
+#
+##############
+
+
+def _users_for_tenant(users, tenant):
+ u_for_t = []
+ for user in users:
+ for n in user:
+ if user[n]['tenant'] == tenant:
+ u_for_t.append(user[n])
+ return u_for_t
+
+
+def _tenants_from_users(users):
+ tenants = set()
+ for user in users:
+ for n in user:
+ tenants.add(user[n]['tenant'])
+ return tenants
+
+
+def _assign_swift_role(user):
+ admin = keystone_admin()
+ resp, roles = admin.identity.list_roles()
+ role = next(r for r in roles if r['name'] == 'Member')
+ LOG.debug(USERS[user])
+ try:
+ admin.identity.assign_user_role(
+ USERS[user]['tenant_id'],
+ USERS[user]['id'],
+ role['id'])
+ except exceptions.Conflict:
+ # don't care if it's already assigned
+ pass
+
+
+def create_users(users):
+ """Create tenants from resource definition.
+
+ Don't create the tenants if they already exist.
+ """
+ global USERS
+ LOG.info("Creating users")
+ admin = keystone_admin()
+ for u in users:
+ try:
+ tenant = admin.identity.get_tenant_by_name(u['tenant'])
+ except exceptions.NotFound:
+ LOG.error("Tenant: %s - not found" % u['tenant'])
+ continue
+ try:
+ admin.identity.get_user_by_username(tenant['id'], u['name'])
+ LOG.warn("User '%s' already exists in this environment"
+ % u['name'])
+ except exceptions.NotFound:
+ admin.identity.create_user(
+ u['name'], u['pass'], tenant['id'],
+ "%s@%s" % (u['name'], tenant['id']),
+ enabled=True)
+
+
+def collect_users(users):
+ global USERS
+ LOG.info("Creating users")
+ admin = keystone_admin()
+ for u in users:
+ tenant = admin.identity.get_tenant_by_name(u['tenant'])
+ u['tenant_id'] = tenant['id']
+ USERS[u['name']] = u
+ body = admin.identity.get_user_by_username(tenant['id'], u['name'])
+ USERS[u['name']]['id'] = body['id']
+
+
+class JavelinCheck(unittest.TestCase):
+ def __init__(self, users, resources):
+ super(JavelinCheck, self).__init__()
+ self.users = users
+ self.res = resources
+
+ def runTest(self, *args):
+ pass
+
+ def check(self):
+ self.check_users()
+ self.check_objects()
+ self.check_servers()
+
+ def check_users(self):
+ """Check that the users we expect to exist, do.
+
+ We don't use the resource list for this because we need to validate
+ that things like tenantId didn't drift across versions.
+ """
+ for name, user in self.users.iteritems():
+ client = keystone_admin()
+ _, found = client.identity.get_user(user['id'])
+ self.assertEqual(found['name'], user['name'])
+ self.assertEqual(found['tenantId'], user['tenant_id'])
+
+ # also ensure we can auth with that user, and do something
+ # on the cloud. We don't care about the results except that it
+ # remains authorized.
+ client = client_for_user(user['name'])
+ resp, body = client.servers.list_servers()
+ self.assertEqual(resp['status'], '200')
+
+ def check_objects(self):
+ """Check that the objects created are still there."""
+ for obj in self.res['objects']:
+ client = client_for_user(obj['owner'])
+ r, contents = client.objects.get_object(
+ obj['container'], obj['name'])
+ source = _file_contents(obj['file'])
+ self.assertEqual(contents, source)
+
+ def check_servers(self):
+ """Check that the servers are still up and running."""
+ for server in self.res['servers']:
+ client = client_for_user(server['owner'])
+ found = _get_server_by_name(client, server['name'])
+ self.assertIsNotNone(
+ found,
+ "Couldn't find expected server %s" % server['name'])
+
+ r, found = client.servers.get_server(found['id'])
+ # get the ipv4 address
+ addr = found['addresses']['private'][0]['addr']
+ self.assertEqual(os.system("ping -c 1 " + addr), 0,
+ "Server %s is not pingable at %s" % (
+ server['name'], addr))
+
+
+#######################
+#
+# OBJECTS
+#
+#######################
+
+
+def _file_contents(fname):
+ with open(fname, 'r') as f:
+ return f.read()
+
+
+def create_objects(objects):
+ LOG.info("Creating objects")
+ for obj in objects:
+ LOG.debug("Object %s" % obj)
+ _assign_swift_role(obj['owner'])
+ client = client_for_user(obj['owner'])
+ client.containers.create_container(obj['container'])
+ client.objects.create_object(
+ obj['container'], obj['name'],
+ _file_contents(obj['file']))
+
+#######################
+#
+# IMAGES
+#
+#######################
+
+
+def create_images(images):
+ for image in images:
+ client = client_for_user(image['owner'])
+
+ # only upload a new image if the name isn't there
+ r, body = client.images.image_list()
+ names = [x['name'] for x in body]
+ if image['name'] in names:
+ continue
+
+ # special handling for 3 part image
+ extras = {}
+ if image['format'] == 'ami':
+ r, aki = client.images.create_image(
+ 'javelin_' + image['aki'], 'aki', 'aki')
+ client.images.store_image(aki.get('id'), open(image['aki'], 'r'))
+ extras['kernel_id'] = aki.get('id')
+
+ r, ari = client.images.create_image(
+ 'javelin_' + image['ari'], 'ari', 'ari')
+ client.images.store_image(ari.get('id'), open(image['ari'], 'r'))
+ extras['ramdisk_id'] = ari.get('id')
+
+ r, body = client.images.create_image(
+ image['name'], image['format'], image['format'], **extras)
+ image_id = body.get('id')
+ client.images.store_image(image_id, open(image['file'], 'r'))
+
+
+#######################
+#
+# SERVERS
+#
+#######################
+
+def _get_server_by_name(client, name):
+ r, body = client.servers.list_servers()
+ for server in body['servers']:
+ if name == server['name']:
+ return server
+ return None
+
+
+def _get_image_by_name(client, name):
+ r, body = client.images.image_list()
+ for image in body:
+ if name == image['name']:
+ return image
+ return None
+
+
+def _get_flavor_by_name(client, name):
+ r, body = client.flavors.list_flavors()
+ for flavor in body:
+ if name == flavor['name']:
+ return flavor
+ return None
+
+
+def create_servers(servers):
+ for server in servers:
+ client = client_for_user(server['owner'])
+
+ if _get_server_by_name(client, server['name']):
+ continue
+
+ image_id = _get_image_by_name(client, server['image'])['id']
+ flavor_id = _get_flavor_by_name(client, server['flavor'])['id']
+ client.servers.create_server(server['name'], image_id, flavor_id)
+
+
+#######################
+#
+# MAIN LOGIC
+#
+#######################
+
+def create_resources():
+ LOG.info("Creating Resources")
+ # first create keystone level resources, and we need to be admin
+ # for those.
+ create_tenants(RES['tenants'])
+ create_users(RES['users'])
+ collect_users(RES['users'])
+
+ # next create resources in a well known order
+ create_objects(RES['objects'])
+ create_images(RES['images'])
+ create_servers(RES['servers'])
+
+
+def get_options():
+ global OPTS
+ parser = argparse.ArgumentParser(
+ description='Create and validate a fixed set of OpenStack resources')
+ parser.add_argument('-m', '--mode',
+ metavar='<create|check|destroy>',
+ required=True,
+ help=('One of (create, check, destroy)'))
+ parser.add_argument('-r', '--resources',
+ required=True,
+ metavar='resourcefile.yaml',
+ help='Resources definition yaml file')
+ # auth bits, letting us also just source the devstack openrc
+ parser.add_argument('--os-username',
+ metavar='<auth-user-name>',
+ default=os.environ.get('OS_USERNAME'),
+ help=('Defaults to env[OS_USERNAME].'))
+ parser.add_argument('--os-password',
+ metavar='<auth-password>',
+ default=os.environ.get('OS_PASSWORD'),
+ help=('Defaults to env[OS_PASSWORD].'))
+ parser.add_argument('--os-tenant-name',
+ metavar='<auth-tenant-name>',
+ default=os.environ.get('OS_TENANT_NAME'),
+ help=('Defaults to env[OS_TENANT_NAME].'))
+
+ OPTS = parser.parse_args()
+ if OPTS.mode not in ('create', 'check', 'destroy'):
+ print("ERROR: Unknown mode -m %s\n" % OPTS.mode)
+ parser.print_help()
+ sys.exit(1)
+
+
+def setup_logging(debug=True):
+ global LOG
+ LOG = logging.getLogger(__name__)
+ if debug:
+ LOG.setLevel(logging.DEBUG)
+ else:
+ LOG.setLevel(logging.INFO)
+
+ ch = logging.StreamHandler(sys.stdout)
+ ch.setLevel(logging.DEBUG)
+ formatter = logging.Formatter(
+ datefmt='%Y-%m-%d %H:%M:%S',
+ fmt='%(asctime)s.%(msecs).03d - %(levelname)s - %(message)s')
+ ch.setFormatter(formatter)
+ LOG.addHandler(ch)
+
+
+def main():
+ global RES
+ get_options()
+ setup_logging()
+ RES = load_resources(OPTS.resources)
+
+ if OPTS.mode == 'create':
+ create_resources()
+ elif OPTS.mode == 'check':
+ collect_users(RES['users'])
+ checker = JavelinCheck(USERS, RES)
+ checker.check()
+ elif OPTS.mode == 'destroy':
+ LOG.warn("Destroy mode not yet implemented")
+ else:
+ LOG.error('Unknown mode %s' % OPTS.mode)
+ return 1
+ return 0
+
+if __name__ == "__main__":
+ sys.exit(main())
diff --git a/tempest/cmd/resources.yaml b/tempest/cmd/resources.yaml
new file mode 100644
index 0000000..f7cb8a9
--- /dev/null
+++ b/tempest/cmd/resources.yaml
@@ -0,0 +1,51 @@
+# This is a yaml description for the most basic definitions
+# of what should exist across the resource boundary. Perhaps
+# one day this will grow into a Heat resource template, but as
+# Heat isn't a known working element in the upgrades, we do
+# this much simpler thing for now.
+
+tenants:
+ - javelin
+ - discuss
+
+users:
+ - name: javelin
+ pass: gungnir
+ tenant: javelin
+ - name: javelin2
+ pass: gungnir2
+ tenant: discuss
+
+secgroups:
+ - angon:
+ owner: javelin
+ rules:
+ - 'icmp -1 -1 0.0.0.0/0'
+ - 'tcp 22 22 0.0.0.0/0'
+
+# resources that we want to create
+images:
+ - name: javelin_cirros
+ owner: javelin
+ file: cirros-0.3.2-x86_64-blank.img
+ format: ami
+ aki: cirros-0.3.2-x86_64-vmlinuz
+ ari: cirros-0.3.2-x86_64-initrd
+volumes:
+ - assegai:
+ - owner: javelin
+ - gb: 1
+servers:
+ - name: peltast
+ owner: javelin
+ flavor: m1.small
+ image: javelin_cirros
+ - name: hoplite
+ owner: javelin
+ flavor: m1.medium
+ image: javelin_cirros
+objects:
+ - container: jc1
+ name: javelin1
+ owner: javelin
+ file: /etc/hosts
diff --git a/tempest/stress/run_stress.py b/tempest/cmd/run_stress.py
similarity index 96%
rename from tempest/stress/run_stress.py
rename to tempest/cmd/run_stress.py
index c7c17c0..07f3f66 100755
--- a/tempest/stress/run_stress.py
+++ b/tempest/cmd/run_stress.py
@@ -51,7 +51,7 @@
except Exception:
next
if 'stress' in attrs:
- if filter_attr is not None and not filter_attr in attrs:
+ if filter_attr is not None and filter_attr not in attrs:
continue
class_setup_per = getattr(test_func, "st_class_setup_per")
@@ -70,7 +70,29 @@
return tests
-def main(ns):
+parser = argparse.ArgumentParser(description='Run stress tests')
+parser.add_argument('-d', '--duration', default=300, type=int,
+ help="Duration of test in secs")
+parser.add_argument('-s', '--serial', action='store_true',
+ help="Trigger running tests serially")
+parser.add_argument('-S', '--stop', action='store_true',
+ default=False, help="Stop on first error")
+parser.add_argument('-n', '--number', type=int,
+ help="How often an action is executed for each process")
+group = parser.add_mutually_exclusive_group(required=True)
+group.add_argument('-a', '--all', action='store_true',
+ help="Execute all stress tests")
+parser.add_argument('-T', '--type',
+ help="Filters tests of a certain type (e.g. gate)")
+parser.add_argument('-i', '--call-inherited', action='store_true',
+ default=False,
+ help="Call also inherited function with stress attribute")
+group.add_argument('-t', "--tests", nargs='?',
+ help="Name of the file with test description")
+
+
+def main():
+ ns = parser.parse_args()
result = 0
if not ns.all:
tests = json.load(open(ns.tests, 'r'))
@@ -97,29 +119,9 @@
return result
-parser = argparse.ArgumentParser(description='Run stress tests')
-parser.add_argument('-d', '--duration', default=300, type=int,
- help="Duration of test in secs")
-parser.add_argument('-s', '--serial', action='store_true',
- help="Trigger running tests serially")
-parser.add_argument('-S', '--stop', action='store_true',
- default=False, help="Stop on first error")
-parser.add_argument('-n', '--number', type=int,
- help="How often an action is executed for each process")
-group = parser.add_mutually_exclusive_group(required=True)
-group.add_argument('-a', '--all', action='store_true',
- help="Execute all stress tests")
-parser.add_argument('-T', '--type',
- help="Filters tests of a certain type (e.g. gate)")
-parser.add_argument('-i', '--call-inherited', action='store_true',
- default=False,
- help="Call also inherited function with stress attribute")
-group.add_argument('-t', "--tests", nargs='?',
- help="Name of the file with test description")
-
if __name__ == "__main__":
try:
- sys.exit(main(parser.parse_args()))
+ sys.exit(main())
except Exception:
LOG.exception("Failure in the stress test framework")
sys.exit(1)
diff --git a/tempest/cmd/verify_tempest_config.py b/tempest/cmd/verify_tempest_config.py
index 7b2e60b..0834cff 100755
--- a/tempest/cmd/verify_tempest_config.py
+++ b/tempest/cmd/verify_tempest_config.py
@@ -21,7 +21,7 @@
import urlparse
import httplib2
-from six.moves import configparser
+from six import moves
from tempest import clients
from tempest import config
@@ -46,7 +46,7 @@
def change_option(option, group, value):
- config_parse = configparser.SafeConfigParser()
+ config_parse = moves.configparser.SafeConfigParser()
config_parse.optionxform = str
config_parse.readfp(CONF_FILE)
if not config_parse.has_section(group):
@@ -160,11 +160,10 @@
extensions_client = get_extension_client(os, service)
__, resp = extensions_client.list_extensions()
if isinstance(resp, dict):
- # Neutron's extension 'name' field has is not a single word (it has
- # spaces in the string) Since that can't be used for list option the
- # api_extension option in the network-feature-enabled group uses alias
- # instead of name.
- if service == 'neutron':
+ # For both Nova and Neutron we use the alias name rather than the
+ # 'name' field because the alias is considered to be the canonical
+ # name.
+ if service in ['nova', 'nova_v3', 'neutron']:
extensions = map(lambda x: x['alias'], resp['extensions'])
elif service == 'swift':
# Remove Swift general information from extensions list
diff --git a/tempest/common/commands.py b/tempest/common/commands.py
index 6580c65..6720847 100644
--- a/tempest/common/commands.py
+++ b/tempest/common/commands.py
@@ -75,3 +75,22 @@
def ovs_db_dump():
return sudo_cmd_call("ovsdb-client dump")
+
+
+def copy_file_to_host(file_from, dest, host, username, pkey):
+ dest = "%s@%s:%s" % (username, host, dest)
+ cmd = "scp -v -o UserKnownHostsFile=/dev/null " \
+ "-o StrictHostKeyChecking=no " \
+ "-i %(pkey)s %(file1)s %(dest)s" % {'pkey': pkey,
+ 'file1': file_from,
+ 'dest': dest}
+ args = shlex.split(cmd)
+ subprocess_args = {'stdout': subprocess.PIPE,
+ 'stderr': subprocess.STDOUT}
+ proc = subprocess.Popen(args, **subprocess_args)
+ stdout, stderr = proc.communicate()
+ if proc.returncode != 0:
+ LOG.error(("Command {0} returned with exit status {1},"
+ "output {2}, error {3}").format(cmd, proc.returncode,
+ stdout, stderr))
+ return stdout
diff --git a/tempest/common/glance_http.py b/tempest/common/glance_http.py
index 9358851..55aca5a 100644
--- a/tempest/common/glance_http.py
+++ b/tempest/common/glance_http.py
@@ -160,6 +160,9 @@
def json_request(self, method, url, **kwargs):
kwargs.setdefault('headers', {})
kwargs['headers'].setdefault('Content-Type', 'application/json')
+ if kwargs['headers']['Content-Type'] != 'application/json':
+ msg = "Only application/json content-type is supported."
+ raise exc.InvalidContentType(msg)
if 'body' in kwargs:
kwargs['body'] = json.dumps(kwargs['body'])
@@ -173,7 +176,8 @@
except ValueError:
LOG.error('Could not decode response body as JSON')
else:
- body = None
+ msg = "Only json/application content-type is supported."
+ raise exc.InvalidContentType(msg)
return resp, body
diff --git a/tempest/common/rest_client.py b/tempest/common/rest_client.py
index 10223a0..33128a9 100644
--- a/tempest/common/rest_client.py
+++ b/tempest/common/rest_client.py
@@ -191,17 +191,25 @@
"""
self._skip_path = False
- def expected_success(self, expected_code, read_code):
+ @classmethod
+ def expected_success(cls, expected_code, read_code):
assert_msg = ("This function only allowed to use for HTTP status"
"codes which explicitly defined in the RFC 2616. {0}"
" is not a defined Success Code!").format(expected_code)
- assert expected_code in HTTP_SUCCESS, assert_msg
+ if isinstance(expected_code, list):
+ for code in expected_code:
+ assert code in HTTP_SUCCESS, assert_msg
+ else:
+ assert expected_code in HTTP_SUCCESS, assert_msg
# NOTE(afazekas): the http status code above 400 is processed by
# the _error_checker method
- if read_code < 400 and read_code != expected_code:
- pattern = """Unexpected http success status code {0},
- The expected status code is {1}"""
+ if read_code < 400:
+ pattern = """Unexpected http success status code {0},
+ The expected status code is {1}"""
+ if ((not isinstance(expected_code, list) and
+ (read_code != expected_code)) or (isinstance(expected_code,
+ list) and (read_code not in expected_code))):
details = pattern.format(read_code, expected_code)
raise exceptions.InvalidHttpSuccessCode(details)
@@ -238,6 +246,14 @@
return resp[i]
return ""
+ def _log_request_start(self, method, req_url, req_headers={},
+ req_body=None):
+ caller_name = misc_utils.find_test_caller()
+ trace_regex = CONF.debug.trace_requests
+ if trace_regex and re.search(trace_regex, caller_name):
+ self.LOG.debug('Starting Request (%s): %s %s' %
+ (caller_name, method, req_url))
+
def _log_request(self, method, req_url, resp,
secs="", req_headers={},
req_body=None, resp_body=None):
@@ -364,6 +380,7 @@
# Do the actual request, and time it
start = time.time()
+ self._log_request_start(method, req_url)
resp, resp_body = self.http_obj.request(
req_url, method, headers=req_headers, body=req_body)
end = time.time()
@@ -546,11 +563,7 @@
# declared in the V3 API and so we should be able to export this in
# the response schema. For now we'll ignore it.
if resp.status in HTTP_SUCCESS:
- response_code = schema['status_code']
- if resp.status not in response_code:
- msg = ("The status code(%s) is different than the expected "
- "one(%s)") % (resp.status, response_code)
- raise exceptions.InvalidHttpSuccessCode(msg)
+ cls.expected_success(schema['status_code'], resp.status)
# Check the body of a response
body_schema = schema.get('response_body')
diff --git a/tempest/common/utils/linux/remote_client.py b/tempest/common/utils/linux/remote_client.py
index 95b6833..57a14a2 100644
--- a/tempest/common/utils/linux/remote_client.py
+++ b/tempest/common/utils/linux/remote_client.py
@@ -57,11 +57,6 @@
actual_hostname = self.exec_command("hostname").rstrip()
return expected_hostname == actual_hostname
- def get_files(self, path):
- # Return a list of comma separated files
- command = "ls -m " + path
- return self.exec_command(command).rstrip('\n').split(', ')
-
def get_ram_size_in_mb(self):
output = self.exec_command('free -m | grep Mem')
if output:
diff --git a/tempest/common/utils/misc.py b/tempest/common/utils/misc.py
index b9f411b..0d78273 100644
--- a/tempest/common/utils/misc.py
+++ b/tempest/common/utils/misc.py
@@ -60,6 +60,9 @@
break
elif re.search("^_run_cleanup", name):
is_cleanup = True
+ elif name == 'main':
+ caller_name = 'main'
+ break
else:
cname = ""
if 'self' in frame.f_locals:
diff --git a/tempest/common/waiters.py b/tempest/common/waiters.py
index d52ed7c..d8474a0 100644
--- a/tempest/common/waiters.py
+++ b/tempest/common/waiters.py
@@ -22,6 +22,16 @@
LOG = logging.getLogger(__name__)
+def _console_dump(client, server_id):
+ try:
+ resp, output = client.get_console_output(server_id, None)
+ LOG.debug("Console Output for Server %s:\n%s" % (
+ server_id, output))
+ except exceptions.NotFound:
+ LOG.debug("Server %s: doesn't have a console" % server_id)
+ pass
+
+
# NOTE(afazekas): This function needs to know a token and a subject.
def wait_for_server_status(client, server_id, status, ready_wait=True,
extra_timeout=0, raise_on_error=True):
@@ -71,7 +81,9 @@
'/'.join((old_status, str(old_task_state))),
'/'.join((server_status, str(task_state))),
time.time() - start_time)
+
if (server_status == 'ERROR') and raise_on_error:
+ _console_dump(client, server_id)
raise exceptions.BuildErrorException(server_id=server_id)
timed_out = int(time.time()) - start_time >= timeout
@@ -87,9 +99,11 @@
'timeout': timeout})
message += ' Current status: %s.' % server_status
message += ' Current task state: %s.' % task_state
+
caller = misc_utils.find_test_caller()
if caller:
message = '(%s) %s' % (caller, message)
+ _console_dump(client, server_id)
raise exceptions.TimeoutException(message)
old_status = server_status
old_task_state = task_state
diff --git a/tempest/config.py b/tempest/config.py
index 1049f67..e3f0f2a 100644
--- a/tempest/config.py
+++ b/tempest/config.py
@@ -247,7 +247,11 @@
'for removing from a host. -1 never offload, 0 offload '
'when shelved. This time should be the same as the time '
'of nova.conf, and some tests will run for as long as the '
- 'time.')
+ 'time.'),
+ cfg.StrOpt('floating_ip_range',
+ default='10.0.0.0/29',
+ help='Unallocated floating IP range, which will be used to '
+ 'test the floating IP bulk feature for CRUD operation.')
]
compute_features_group = cfg.OptGroup(name='compute-feature-enabled',
@@ -255,7 +259,7 @@
ComputeFeaturesGroup = [
cfg.BoolOpt('api_v3',
- default=True,
+ default=False,
help="If false, skip all nova v3 tests."),
cfg.BoolOpt('disk_config',
default=True,
@@ -263,11 +267,13 @@
cfg.ListOpt('api_extensions',
default=['all'],
help='A list of enabled compute extensions with a special '
- 'entry all which indicates every extension is enabled'),
+ 'entry all which indicates every extension is enabled. '
+ 'Each extension should be specified with alias name'),
cfg.ListOpt('api_v3_extensions',
default=['all'],
help='A list of enabled v3 extensions with a special entry all'
- ' which indicates every extension is enabled'),
+ ' which indicates every extension is enabled. '
+ 'Each extension should be specified with alias name'),
cfg.BoolOpt('change_password',
default=False,
help="Does the test environment support changing the admin "
@@ -415,6 +421,10 @@
default=1,
help="Time in seconds between network operation status "
"checks."),
+ cfg.ListOpt('dns_servers',
+ default=["8.8.8.8", "8.8.4.4"],
+ help="List of dns servers whichs hould be used"
+ " for subnet creation")
]
network_feature_group = cfg.OptGroup(name='network-feature-enabled',
@@ -638,6 +648,10 @@
choices=['public', 'admin', 'internal',
'publicURL', 'adminURL', 'internalURL'],
help="The endpoint type to use for the telemetry service."),
+ cfg.BoolOpt('too_slow_to_test',
+ default=True,
+ help="This variable is used as flag to enable "
+ "notification tests")
]
@@ -1070,8 +1084,21 @@
class TempestConfigProxy(object):
_config = None
+ _extra_log_defaults = [
+ 'keystoneclient.session=INFO',
+ 'paramiko.transport=INFO',
+ 'requests.packages.urllib3.connectionpool=WARN'
+ ]
+
+ def _fix_log_levels(self):
+ """Tweak the oslo log defaults."""
+ for opt in logging.log_opts:
+ if opt.dest == 'default_log_levels':
+ opt.default.extend(self._extra_log_defaults)
+
def __getattr__(self, attr):
if not self._config:
+ self._fix_log_levels()
self._config = TempestConfigPrivate()
return getattr(self._config, attr)
diff --git a/tempest/openstack/common/versionutils.py b/tempest/openstack/common/versionutils.py
new file mode 100644
index 0000000..131046e
--- /dev/null
+++ b/tempest/openstack/common/versionutils.py
@@ -0,0 +1,148 @@
+# Copyright (c) 2013 OpenStack Foundation
+# 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.
+
+"""
+Helpers for comparing version strings.
+"""
+
+import functools
+import pkg_resources
+
+from tempest.openstack.common.gettextutils import _
+from tempest.openstack.common import log as logging
+
+
+LOG = logging.getLogger(__name__)
+
+
+class deprecated(object):
+ """A decorator to mark callables as deprecated.
+
+ This decorator logs a deprecation message when the callable it decorates is
+ used. The message will include the release where the callable was
+ deprecated, the release where it may be removed and possibly an optional
+ replacement.
+
+ Examples:
+
+ 1. Specifying the required deprecated release
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE)
+ ... def a(): pass
+
+ 2. Specifying a replacement:
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE, in_favor_of='f()')
+ ... def b(): pass
+
+ 3. Specifying the release where the functionality may be removed:
+
+ >>> @deprecated(as_of=deprecated.ICEHOUSE, remove_in=+1)
+ ... def c(): pass
+
+ """
+
+ FOLSOM = 'F'
+ GRIZZLY = 'G'
+ HAVANA = 'H'
+ ICEHOUSE = 'I'
+
+ _RELEASES = {
+ 'F': 'Folsom',
+ 'G': 'Grizzly',
+ 'H': 'Havana',
+ 'I': 'Icehouse',
+ }
+
+ _deprecated_msg_with_alternative = _(
+ '%(what)s is deprecated as of %(as_of)s in favor of '
+ '%(in_favor_of)s and may be removed in %(remove_in)s.')
+
+ _deprecated_msg_no_alternative = _(
+ '%(what)s is deprecated as of %(as_of)s and may be '
+ 'removed in %(remove_in)s. It will not be superseded.')
+
+ def __init__(self, as_of, in_favor_of=None, remove_in=2, what=None):
+ """Initialize decorator
+
+ :param as_of: the release deprecating the callable. Constants
+ are define in this class for convenience.
+ :param in_favor_of: the replacement for the callable (optional)
+ :param remove_in: an integer specifying how many releases to wait
+ before removing (default: 2)
+ :param what: name of the thing being deprecated (default: the
+ callable's name)
+
+ """
+ self.as_of = as_of
+ self.in_favor_of = in_favor_of
+ self.remove_in = remove_in
+ self.what = what
+
+ def __call__(self, func):
+ if not self.what:
+ self.what = func.__name__ + '()'
+
+ @functools.wraps(func)
+ def wrapped(*args, **kwargs):
+ msg, details = self._build_message()
+ LOG.deprecated(msg, details)
+ return func(*args, **kwargs)
+ return wrapped
+
+ def _get_safe_to_remove_release(self, release):
+ # TODO(dstanek): this method will have to be reimplemented once
+ # when we get to the X release because once we get to the Y
+ # release, what is Y+2?
+ new_release = chr(ord(release) + self.remove_in)
+ if new_release in self._RELEASES:
+ return self._RELEASES[new_release]
+ else:
+ return new_release
+
+ def _build_message(self):
+ details = dict(what=self.what,
+ as_of=self._RELEASES[self.as_of],
+ remove_in=self._get_safe_to_remove_release(self.as_of))
+
+ if self.in_favor_of:
+ details['in_favor_of'] = self.in_favor_of
+ msg = self._deprecated_msg_with_alternative
+ else:
+ msg = self._deprecated_msg_no_alternative
+ return msg, details
+
+
+def is_compatible(requested_version, current_version, same_major=True):
+ """Determine whether `requested_version` is satisfied by
+ `current_version`; in other words, `current_version` is >=
+ `requested_version`.
+
+ :param requested_version: version to check for compatibility
+ :param current_version: version to check against
+ :param same_major: if True, the major version must be identical between
+ `requested_version` and `current_version`. This is used when a
+ major-version difference indicates incompatibility between the two
+ versions. Since this is the common-case in practice, the default is
+ True.
+ :returns: True if compatible, False if not
+ """
+ requested_parts = pkg_resources.parse_version(requested_version)
+ current_parts = pkg_resources.parse_version(current_version)
+
+ if same_major and (requested_parts[0] != current_parts[0]):
+ return False
+
+ return current_parts >= requested_parts
diff --git a/tempest/scenario/manager.py b/tempest/scenario/manager.py
index f83a4b8..07d8828 100644
--- a/tempest/scenario/manager.py
+++ b/tempest/scenario/manager.py
@@ -29,6 +29,7 @@
from tempest.api.network import common as net_common
from tempest import auth
from tempest import clients
+from tempest.common import debug
from tempest.common import isolated_creds
from tempest.common.utils import data_utils
from tempest.common.utils.linux import remote_client
@@ -81,6 +82,7 @@
cls.object_storage_client = cls.manager.object_storage_client
cls.orchestration_client = cls.manager.orchestration_client
cls.data_processing_client = cls.manager.data_processing_client
+ cls.ceilometer_client = cls.manager.ceilometer_client
cls.resource_keys = {}
cls.os_resources = []
@@ -285,10 +287,26 @@
for ruleset in rulesets:
sg_rule = client.security_group_rules.create(secgroup_id,
**ruleset)
- self.set_resource(sg_rule.id, sg_rule)
rules.append(sg_rule)
return rules
+ def _create_security_group_nova(self, client=None,
+ namestart='secgroup-smoke-'):
+ if client is None:
+ client = self.compute_client
+ # Create security group
+ sg_name = data_utils.rand_name(namestart)
+ sg_desc = sg_name + " description"
+ secgroup = client.security_groups.create(sg_name, sg_desc)
+ self.assertEqual(secgroup.name, sg_name)
+ self.assertEqual(secgroup.description, sg_desc)
+ self.set_resource(sg_name, secgroup)
+
+ # Add rules to the security group
+ self._create_loginable_secgroup_rule_nova(client, secgroup.id)
+
+ return secgroup
+
def create_server(self, client=None, name=None, image=None, flavor=None,
wait=True, create_kwargs={}):
if client is None:
@@ -389,7 +407,16 @@
username = CONF.scenario.ssh_user
if private_key is None:
private_key = self.keypair.private_key
- return remote_client.RemoteClient(ip, username, pkey=private_key)
+ linux_client = remote_client.RemoteClient(ip, username,
+ pkey=private_key)
+ try:
+ linux_client.validate_authentication()
+ except exceptions.SSHTimeout:
+ LOG.exception('ssh connection to %s failed' % ip)
+ debug.log_net_debug()
+ raise
+
+ return linux_client
def _log_console_output(self, servers=None):
if not servers:
@@ -411,7 +438,7 @@
'name': name,
'container_format': fmt,
'disk_format': fmt,
- 'is_public': 'True',
+ 'is_public': 'False',
}
params.update(properties)
image = self.image_client.images.create(**params)
@@ -447,6 +474,30 @@
LOG.debug("image:%s" % self.image)
+# power/provision states as of icehouse
+class BaremetalPowerStates(object):
+ """Possible power states of an Ironic node."""
+ POWER_ON = 'power on'
+ POWER_OFF = 'power off'
+ REBOOT = 'rebooting'
+ SUSPEND = 'suspended'
+
+
+class BaremetalProvisionStates(object):
+ """Possible provision states of an Ironic node."""
+ NOSTATE = None
+ INIT = 'initializing'
+ ACTIVE = 'active'
+ BUILDING = 'building'
+ DEPLOYWAIT = 'wait call-back'
+ DEPLOYING = 'deploying'
+ DEPLOYFAIL = 'deploy failed'
+ DEPLOYDONE = 'deploy complete'
+ DELETING = 'deleting'
+ DELETED = 'deleted'
+ ERROR = 'error'
+
+
class BaremetalScenarioTest(OfficialClientTest):
@classmethod
def setUpClass(cls):
@@ -522,6 +573,55 @@
ports.append(self.baremetal_client.port.get(port.uuid))
return ports
+ def add_keypair(self):
+ self.keypair = self.create_keypair()
+
+ def verify_connectivity(self, ip=None):
+ if ip:
+ dest = self.get_remote_client(ip)
+ else:
+ dest = self.get_remote_client(self.instance)
+ dest.validate_authentication()
+
+ def boot_instance(self):
+ create_kwargs = {
+ 'key_name': self.keypair.id
+ }
+ self.instance = self.create_server(
+ wait=False, create_kwargs=create_kwargs)
+
+ self.set_resource('instance', self.instance)
+
+ self.wait_node(self.instance.id)
+ self.node = self.get_node(instance_id=self.instance.id)
+
+ self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_ON)
+
+ self.wait_provisioning_state(
+ self.node.uuid,
+ [BaremetalProvisionStates.DEPLOYWAIT,
+ BaremetalProvisionStates.ACTIVE],
+ timeout=15)
+
+ self.wait_provisioning_state(self.node.uuid,
+ BaremetalProvisionStates.ACTIVE,
+ timeout=CONF.baremetal.active_timeout)
+
+ self.status_timeout(
+ self.compute_client.servers, self.instance.id, 'ACTIVE')
+
+ self.node = self.get_node(instance_id=self.instance.id)
+ self.instance = self.compute_client.servers.get(self.instance.id)
+
+ def terminate_instance(self):
+ self.instance.delete()
+ self.remove_resource('instance')
+ self.wait_power_state(self.node.uuid, BaremetalPowerStates.POWER_OFF)
+ self.wait_provisioning_state(
+ self.node.uuid,
+ BaremetalProvisionStates.NOSTATE,
+ timeout=CONF.baremetal.unprovision_timeout)
+
class NetworkScenarioTest(OfficialClientTest):
"""
@@ -778,9 +878,56 @@
msg=msg)
if should_connect:
# no need to check ssh for negative connectivity
- linux_client = self.get_remote_client(ip_address, username,
- private_key)
- linux_client.validate_authentication()
+ self.get_remote_client(ip_address, username, private_key)
+
+ def _check_public_network_connectivity(self, ip_address, username,
+ private_key, should_connect=True,
+ msg=None, servers=None):
+ # The target login is assumed to have been configured for
+ # key-based authentication by cloud-init.
+ LOG.debug('checking network connections to IP %s with user: %s' %
+ (ip_address, username))
+ try:
+ self._check_vm_connectivity(ip_address,
+ username,
+ private_key,
+ should_connect=should_connect)
+ except Exception as e:
+ ex_msg = 'Public network connectivity check failed'
+ if msg:
+ ex_msg += ": " + msg
+ LOG.exception(ex_msg)
+ self._log_console_output(servers)
+ # network debug is called as part of ssh init
+ if not isinstance(e, exceptions.SSHTimeout):
+ debug.log_net_debug()
+ raise
+
+ def _check_tenant_network_connectivity(self, server,
+ username,
+ private_key,
+ should_connect=True,
+ servers_for_debug=None):
+ if not CONF.network.tenant_networks_reachable:
+ msg = 'Tenant networks not configured to be reachable.'
+ LOG.info(msg)
+ return
+ # The target login is assumed to have been configured for
+ # key-based authentication by cloud-init.
+ try:
+ for net_name, ip_addresses in server.networks.iteritems():
+ for ip_address in ip_addresses:
+ self._check_vm_connectivity(ip_address,
+ username,
+ private_key,
+ should_connect=should_connect)
+ except Exception as e:
+ LOG.exception('Tenant network connectivity check failed')
+ self._log_console_output(servers_for_debug)
+ # network debug is called as part of ssh init
+ if not isinstance(e, exceptions.SSHTimeout):
+ debug.log_net_debug()
+ raise
def _check_remote_connectivity(self, source, dest, should_succeed=True):
"""
@@ -804,24 +951,6 @@
CONF.compute.ping_timeout,
1)
- def _create_security_group_nova(self, client=None,
- namestart='secgroup-smoke-',
- tenant_id=None):
- if client is None:
- client = self.compute_client
- # Create security group
- sg_name = data_utils.rand_name(namestart)
- sg_desc = sg_name + " description"
- secgroup = client.security_groups.create(sg_name, sg_desc)
- self.assertEqual(secgroup.name, sg_name)
- self.assertEqual(secgroup.description, sg_desc)
- self.set_resource(sg_name, secgroup)
-
- # Add rules to the security group
- self._create_loginable_secgroup_rule_nova(client, secgroup.id)
-
- return secgroup
-
def _create_security_group_neutron(self, tenant_id, client=None,
namestart='secgroup-smoke-'):
if client is None:
@@ -923,7 +1052,6 @@
client=client,
**sg_rule['security_group_rule']
)
- self.set_resource(sg_rule.id, sg_rule)
self.assertEqual(secgroup.tenant_id, sg_rule.tenant_id)
self.assertEqual(secgroup.id, sg_rule.security_group_id)
diff --git a/tempest/scenario/test_aggregates_basic_ops.py b/tempest/scenario/test_aggregates_basic_ops.py
index 8e34c16..6817c48 100644
--- a/tempest/scenario/test_aggregates_basic_ops.py
+++ b/tempest/scenario/test_aggregates_basic_ops.py
@@ -54,8 +54,8 @@
def _get_host_name(self):
hosts = self.compute_client.hosts.list()
self.assertTrue(len(hosts) >= 1)
- hostname = hosts[0].host_name
- return hostname
+ computes = [x for x in hosts if x.service == 'compute']
+ return computes[0].host_name
def _add_host(self, aggregate_name, host):
aggregate = self.compute_client.aggregates.add_host(aggregate_name,
diff --git a/tempest/scenario/test_baremetal_basic_ops.py b/tempest/scenario/test_baremetal_basic_ops.py
index c53aa83..f197c15 100644
--- a/tempest/scenario/test_baremetal_basic_ops.py
+++ b/tempest/scenario/test_baremetal_basic_ops.py
@@ -23,31 +23,7 @@
LOG = logging.getLogger(__name__)
-# power/provision states as of icehouse
-class PowerStates(object):
- """Possible power states of an Ironic node."""
- POWER_ON = 'power on'
- POWER_OFF = 'power off'
- REBOOT = 'rebooting'
- SUSPEND = 'suspended'
-
-
-class ProvisionStates(object):
- """Possible provision states of an Ironic node."""
- NOSTATE = None
- INIT = 'initializing'
- ACTIVE = 'active'
- BUILDING = 'building'
- DEPLOYWAIT = 'wait call-back'
- DEPLOYING = 'deploying'
- DEPLOYFAIL = 'deploy failed'
- DEPLOYDONE = 'deploy complete'
- DELETING = 'deleting'
- DELETED = 'deleted'
- ERROR = 'error'
-
-
-class BaremetalBasicOptsPXESSH(manager.BaremetalScenarioTest):
+class BaremetalBasicOpsPXESSH(manager.BaremetalScenarioTest):
"""
This smoke test tests the pxe_ssh Ironic driver. It follows this basic
set of operations:
@@ -55,8 +31,6 @@
* Boots an instance using the keypair
* Monitors the associated Ironic node for power and
expected state transitions
- * Validates Ironic node's driver_info has been properly
- updated
* Validates Ironic node's port data has been properly updated
* Verifies SSH connectivity using created keypair via fixed IP
* Associates a floating ip
@@ -65,32 +39,11 @@
* Monitors the associated Ironic node for power and
expected state transitions
"""
- def add_keypair(self):
- self.keypair = self.create_keypair()
-
def add_floating_ip(self):
floating_ip = self.compute_client.floating_ips.create()
self.instance.add_floating_ip(floating_ip)
return floating_ip.ip
- def verify_connectivity(self, ip=None):
- if ip:
- dest = self.get_remote_client(ip)
- else:
- dest = self.get_remote_client(self.instance)
- dest.validate_authentication()
-
- def validate_driver_info(self):
- f_id = self.instance.flavor['id']
- flavor_extra = self.compute_client.flavors.get(f_id).get_keys()
- driver_info = self.node.driver_info
- self.assertEqual(driver_info['pxe_deploy_kernel'],
- flavor_extra['baremetal:deploy_kernel_id'])
- self.assertEqual(driver_info['pxe_deploy_ramdisk'],
- flavor_extra['baremetal:deploy_ramdisk_id'])
- self.assertEqual(driver_info['pxe_image_source'],
- self.instance.image['id'])
-
def validate_ports(self):
for port in self.get_ports(self.node.uuid):
n_port_id = port.extra['vif_port_id']
@@ -98,48 +51,10 @@
self.assertEqual(n_port['device_id'], self.instance.id)
self.assertEqual(n_port['mac_address'], port.address)
- def boot_instance(self):
- create_kwargs = {
- 'key_name': self.keypair.id
- }
- self.instance = self.create_server(
- wait=False, create_kwargs=create_kwargs)
-
- self.set_resource('instance', self.instance)
-
- self.wait_node(self.instance.id)
- self.node = self.get_node(instance_id=self.instance.id)
-
- self.wait_power_state(self.node.uuid, PowerStates.POWER_ON)
-
- self.wait_provisioning_state(
- self.node.uuid,
- [ProvisionStates.DEPLOYWAIT, ProvisionStates.ACTIVE],
- timeout=15)
-
- self.wait_provisioning_state(self.node.uuid, ProvisionStates.ACTIVE,
- timeout=CONF.baremetal.active_timeout)
-
- self.status_timeout(
- self.compute_client.servers, self.instance.id, 'ACTIVE')
-
- self.node = self.get_node(instance_id=self.instance.id)
- self.instance = self.compute_client.servers.get(self.instance.id)
-
- def terminate_instance(self):
- self.instance.delete()
- self.remove_resource('instance')
- self.wait_power_state(self.node.uuid, PowerStates.POWER_OFF)
- self.wait_provisioning_state(
- self.node.uuid,
- ProvisionStates.NOSTATE,
- timeout=CONF.baremetal.unprovision_timeout)
-
@test.services('baremetal', 'compute', 'image', 'network')
def test_baremetal_server_ops(self):
self.add_keypair()
self.boot_instance()
- self.validate_driver_info()
self.validate_ports()
self.verify_connectivity()
floating_ip = self.add_floating_ip()
diff --git a/tempest/scenario/test_load_balancer_basic.py b/tempest/scenario/test_load_balancer_basic.py
index 9152220..03cfef5 100644
--- a/tempest/scenario/test_load_balancer_basic.py
+++ b/tempest/scenario/test_load_balancer_basic.py
@@ -13,10 +13,14 @@
# License for the specific language governing permissions and limitations
# under the License.
+
+import httplib
+import tempfile
import time
-import urllib
+import urllib2
from tempest.api.network import common as net_common
+from tempest.common import commands
from tempest import config
from tempest import exceptions
from tempest.scenario import manager
@@ -135,71 +139,52 @@
1. SSH to the instance
2. Start two http backends listening on ports 80 and 88 respectively
- In case there are two instances, each backend is created on a separate
- instance.
-
- The backends are the inetd services. To start them we need to edit
- /etc/inetd.conf in the following way:
- www stream tcp nowait root /bin/sh sh /home/cirros/script_name
-
- Where /home/cirros/script_name is a path to a script which
- echoes the responses:
- echo -e 'HTTP/1.0 200 OK\r\n\r\nserver_name
-
- If we want the server to listen on port 88, then we use
- "kerberos" instead of "www".
"""
for server_id, ip in self.server_ips.iteritems():
private_key = self.servers_keypairs[server_id].private_key
server_name = self.compute_client.servers.get(server_id).name
+ username = config.scenario.ssh_user
ssh_client = self.get_remote_client(
server_or_ip=ip,
private_key=private_key)
- ssh_client.validate_authentication()
- # Create service for inetd
- create_script = """sudo sh -c "echo -e \\"echo -e 'HTTP/1.0 """ \
- """200 OK\\\\\\r\\\\\\n\\\\\\r\\\\\\n""" \
- """%(server)s'\\" >>/home/cirros/%(script)s\""""
- cmd = create_script % {
- 'server': server_name,
- 'script': 'script1'}
+ # Write a backend's responce into a file
+ resp = """HTTP/1.0 200 OK\r\nContent-Length: 8\r\n\r\n%s"""
+ with tempfile.NamedTemporaryFile() as script:
+ script.write(resp % server_name)
+ script.flush()
+ with tempfile.NamedTemporaryFile() as key:
+ key.write(private_key)
+ key.flush()
+ commands.copy_file_to_host(script.name,
+ "~/script1",
+ ip,
+ username, key.name)
+ # Start netcat
+ start_server = """sudo nc -ll -p %(port)s -e cat """ \
+ """~/%(script)s &"""
+ cmd = start_server % {'port': self.port1,
+ 'script': 'script1'}
ssh_client.exec_command(cmd)
- # Configure inetd
- configure_inetd = """sudo sh -c "echo -e \\"%(service)s """ \
- """stream tcp nowait root /bin/sh sh """ \
- """/home/cirros/%(script)s\\" >> """ \
- """/etc/inetd.conf\""""
- # "www" stands for port 80
- cmd = configure_inetd % {'service': 'www',
- 'script': 'script1'}
- ssh_client.exec_command(cmd)
-
if len(self.server_ips) == 1:
- cmd = create_script % {'server': 'server2',
- 'script': 'script2'}
+ with tempfile.NamedTemporaryFile() as script:
+ script.write(resp % 'server2')
+ script.flush()
+ with tempfile.NamedTemporaryFile() as key:
+ key.write(private_key)
+ key.flush()
+ commands.copy_file_to_host(script.name,
+ "~/script2", ip,
+ username, key.name)
+ cmd = start_server % {'port': self.port2,
+ 'script': 'script2'}
ssh_client.exec_command(cmd)
- # "kerberos" stands for port 88
- cmd = configure_inetd % {'service': 'kerberos',
- 'script': 'script2'}
- ssh_client.exec_command(cmd)
-
- # Get PIDs of inetd
- pids = ssh_client.get_pids('inetd')
- if pids != ['']:
- # If there are any inetd processes, reload them
- kill_cmd = "sudo kill -HUP %s" % ' '.join(pids)
- ssh_client.exec_command(kill_cmd)
- else:
- # In other case start inetd
- start_inetd = "sudo /usr/sbin/inetd /etc/inetd.conf"
- ssh_client.exec_command(start_inetd)
def _check_connection(self, check_ip, port=80):
def try_connect(ip, port):
try:
- resp = urllib.urlopen("http://{0}:{1}/".format(ip, port))
+ resp = urllib2.urlopen("http://{0}:{1}/".format(ip, port))
if resp.getcode() == 200:
return True
return False
@@ -284,27 +269,32 @@
def _check_load_balancing(self):
"""
- 1. Send 100 requests on the floating ip associated with the VIP
+ 1. Send 10 requests on the floating ip associated with the VIP
2. Check that the requests are shared between
the two servers and that both of them get equal portions
of the requests
"""
self._check_connection(self.vip_ip)
- resp = self._send_requests(self.vip_ip)
- self.assertEqual(set(["server1\n", "server2\n"]), set(resp))
- self.assertEqual(50, resp.count("server1\n"))
- self.assertEqual(50, resp.count("server2\n"))
+ self._send_requests(self.vip_ip, set(["server1", "server2"]))
- def _send_requests(self, vip_ip):
- resp = []
- for count in range(100):
- resp.append(
- urllib.urlopen(
- "http://{0}/".format(vip_ip)).read())
- return resp
+ def _send_requests(self, vip_ip, expected, num_req=10):
+ count = 0
+ while count < num_req:
+ try:
+ resp = []
+ for i in range(len(self.members)):
+ resp.append(
+ urllib2.urlopen(
+ "http://{0}/".format(vip_ip)).read())
+ count += 1
+ self.assertEqual(expected,
+ set(resp))
+ # NOTE: There always is a slim chance of getting this exception
+ # due to special aspects of haproxy internal behavior.
+ except httplib.BadStatusLine:
+ pass
- @test.skip_because(bug='1295165')
@test.attr(type='smoke')
@test.services('compute', 'network')
def test_load_balancer_basic(self):
diff --git a/tempest/scenario/test_minimum_basic.py b/tempest/scenario/test_minimum_basic.py
index 24d2677..0406217 100644
--- a/tempest/scenario/test_minimum_basic.py
+++ b/tempest/scenario/test_minimum_basic.py
@@ -93,11 +93,12 @@
def ssh_to_server(self):
try:
self.linux_client = self.get_remote_client(self.floating_ip.ip)
- self.linux_client.validate_authentication()
- except Exception:
+ except Exception as e:
LOG.exception('ssh to server failed')
self._log_console_output()
- debug.log_net_debug()
+ # network debug is called as part of ssh init
+ if not isinstance(e, test.exceptions.SSHTimeout):
+ debug.log_net_debug()
raise
def check_partitions(self):
@@ -112,6 +113,11 @@
volume = self.volume_client.volumes.get(self.volume.id)
self.assertEqual('available', volume.status)
+ def create_and_add_security_group(self):
+ secgroup = self._create_security_group_nova()
+ self.server.add_security_group(secgroup.name)
+ self.addCleanup(self.server.remove_security_group, secgroup.name)
+
@test.services('compute', 'volume', 'image', 'network')
def test_minimum_basic_scenario(self):
self.glance_image_create()
@@ -128,7 +134,7 @@
self.nova_floating_ip_create()
self.nova_floating_ip_add()
- self._create_loginable_secgroup_rule_nova()
+ self.create_and_add_security_group()
self.ssh_to_server()
self.nova_reboot()
self.ssh_to_server()
diff --git a/tempest/scenario/test_network_advanced_server_ops.py b/tempest/scenario/test_network_advanced_server_ops.py
index e0d5779..f1cd320 100644
--- a/tempest/scenario/test_network_advanced_server_ops.py
+++ b/tempest/scenario/test_network_advanced_server_ops.py
@@ -15,7 +15,6 @@
import testtools
-from tempest.common import debug
from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
@@ -85,55 +84,16 @@
public_network_id)
self.addCleanup(self.cleanup_wrapper, self.floating_ip)
- def _check_tenant_network_connectivity(self, server,
- username,
- private_key,
- should_connect=True):
- if not CONF.network.tenant_networks_reachable:
- msg = 'Tenant networks not configured to be reachable.'
- LOG.info(msg)
- return
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
- try:
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address,
- username,
- private_key,
- should_connect=should_connect)
- except Exception:
- LOG.exception('Tenant network connectivity check failed')
- self._log_console_output(servers=[server])
- debug.log_ip_ns()
- raise
-
- def _check_public_network_connectivity(self, floating_ip,
- username,
- private_key,
- should_connect=True):
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
- try:
- self._check_vm_connectivity(floating_ip, username, private_key,
- should_connect=should_connect)
- except Exception:
- LOG.exception("Public network connectivity check failed")
- debug.log_ip_ns()
- raise
-
def _check_network_connectivity(self, should_connect=True):
username = CONF.compute.image_ssh_user
private_key = self.keypair.private_key
- self._check_tenant_network_connectivity(self.server,
- username,
- private_key,
- should_connect=should_connect)
+ self._check_tenant_network_connectivity(
+ self.server, username, private_key, should_connect=should_connect,
+ servers_for_debug=[self.server])
floating_ip = self.floating_ip.floating_ip_address
- self._check_public_network_connectivity(floating_ip,
- username,
- private_key,
- should_connect=should_connect)
+ self._check_public_network_connectivity(floating_ip, username,
+ private_key, should_connect,
+ servers=[self.server])
def _wait_server_status_and_check_network_connectivity(self):
self.status_timeout(self.compute_client.servers, self.server.id,
diff --git a/tempest/scenario/test_network_basic_ops.py b/tempest/scenario/test_network_basic_ops.py
index d5ab3d3..c84d4b9 100644
--- a/tempest/scenario/test_network_basic_ops.py
+++ b/tempest/scenario/test_network_basic_ops.py
@@ -87,6 +87,8 @@
@classmethod
def setUpClass(cls):
+ # Create no network resources for these tests.
+ cls.set_network_resources()
super(TestNetworkBasicOps, cls).setUpClass()
for ext in ['router', 'security-group']:
if not test.is_extension_enabled(ext, 'network'):
@@ -156,24 +158,13 @@
return dict(server=server, keypair=keypair)
def _check_tenant_network_connectivity(self):
- if not CONF.network.tenant_networks_reachable:
- msg = 'Tenant networks not configured to be reachable.'
- LOG.info(msg)
- return
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
ssh_login = CONF.compute.image_ssh_user
- try:
- for server, key in self.servers.iteritems():
- for net_name, ip_addresses in server.networks.iteritems():
- for ip_address in ip_addresses:
- self._check_vm_connectivity(ip_address, ssh_login,
- key.private_key)
- except Exception:
- LOG.exception('Tenant connectivity check failed')
- self._log_console_output(servers=self.servers.keys())
- debug.log_net_debug()
- raise
+ for server, key in self.servers.iteritems():
+ # call the common method in the parent class
+ super(TestNetworkBasicOps, self).\
+ _check_tenant_network_connectivity(
+ server, ssh_login, key.private_key,
+ servers_for_debug=self.servers.keys())
def _create_and_associate_floating_ips(self):
public_network_id = CONF.network.public_network_id
@@ -184,28 +175,16 @@
def _check_public_network_connectivity(self, should_connect=True,
msg=None):
- # The target login is assumed to have been configured for
- # key-based authentication by cloud-init.
ssh_login = CONF.compute.image_ssh_user
- LOG.debug('checking network connections')
floating_ip, server = self.floating_ip_tuple
ip_address = floating_ip.floating_ip_address
private_key = None
if should_connect:
private_key = self.servers[server].private_key
- try:
- self._check_vm_connectivity(ip_address,
- ssh_login,
- private_key,
- should_connect=should_connect)
- except Exception:
- ex_msg = 'Public network connectivity check failed'
- if msg:
- ex_msg += ": " + msg
- LOG.exception(ex_msg)
- self._log_console_output(servers=self.servers.keys())
- debug.log_net_debug()
- raise
+ # call the common method in the parent class
+ super(TestNetworkBasicOps, self)._check_public_network_connectivity(
+ ip_address, ssh_login, private_key, should_connect, msg,
+ self.servers.keys())
def _disassociate_floating_ips(self):
floating_ip, server = self.floating_ip_tuple
diff --git a/tempest/scenario/test_security_groups_basic_ops.py b/tempest/scenario/test_security_groups_basic_ops.py
index 4616b82..dd89dc0 100644
--- a/tempest/scenario/test_security_groups_basic_ops.py
+++ b/tempest/scenario/test_security_groups_basic_ops.py
@@ -134,6 +134,8 @@
@classmethod
def setUpClass(cls):
+ # Create no network resources for these tests.
+ cls.set_network_resources()
super(TestSecurityGroupsBasicOps, cls).setUpClass()
cls.alt_creds = cls.alt_credentials()
cls.alt_manager = clients.OfficialClientManager(cls.alt_creds)
@@ -334,6 +336,8 @@
self.assertTrue(self._check_remote_connectivity(access_point, ip,
should_succeed),
msg)
+ except test.exceptions.SSHTimeout:
+ raise
except Exception:
debug.log_net_debug()
raise
diff --git a/tempest/scenario/test_server_basic_ops.py b/tempest/scenario/test_server_basic_ops.py
index 13e00a5..54f1d9e 100644
--- a/tempest/scenario/test_server_basic_ops.py
+++ b/tempest/scenario/test_server_basic_ops.py
@@ -13,7 +13,6 @@
# License for the specific language governing permissions and limitations
# under the License.
-from tempest.common.utils import data_utils
from tempest import config
from tempest.openstack.common import log as logging
from tempest.scenario import manager
@@ -68,22 +67,12 @@
def add_keypair(self):
self.keypair = self.create_keypair()
- def create_security_group(self):
- sg_name = data_utils.rand_name('secgroup-smoke')
- sg_desc = sg_name + " description"
- self.secgroup = self.compute_client.security_groups.create(sg_name,
- sg_desc)
- self.assertEqual(self.secgroup.name, sg_name)
- self.assertEqual(self.secgroup.description, sg_desc)
- self.set_resource('secgroup', self.secgroup)
-
- # Add rules to the security group
- self._create_loginable_secgroup_rule_nova(secgroup_id=self.secgroup.id)
-
def boot_instance(self):
# Create server with image and flavor from input scenario
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.id
+ 'key_name': self.keypair.id,
+ 'security_groups': security_groups
}
instance = self.create_server(image=self.image_ref,
flavor=self.flavor_ref,
@@ -104,11 +93,10 @@
instance.add_floating_ip(floating_ip)
# Check ssh
try:
- linux_client = self.get_remote_client(
+ self.get_remote_client(
server_or_ip=floating_ip.ip,
username=self.image_utils.ssh_user(self.image_ref),
private_key=self.keypair.private_key)
- linux_client.validate_authentication()
except Exception:
LOG.exception('ssh to server failed')
self._log_console_output()
@@ -117,7 +105,7 @@
@test.services('compute', 'network')
def test_server_basicops(self):
self.add_keypair()
- self.create_security_group()
+ self.security_group = self._create_security_group_nova()
self.boot_instance()
self.verify_ssh()
self.terminate_instance()
diff --git a/tempest/scenario/test_snapshot_pattern.py b/tempest/scenario/test_snapshot_pattern.py
index 562020a..d41490a 100644
--- a/tempest/scenario/test_snapshot_pattern.py
+++ b/tempest/scenario/test_snapshot_pattern.py
@@ -35,8 +35,10 @@
"""
def _boot_image(self, image_id):
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.name
+ 'key_name': self.keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image=image_id, create_kwargs=create_kwargs)
@@ -47,8 +49,9 @@
try:
return self.get_remote_client(server_or_ip)
except Exception:
- LOG.exception()
+ LOG.exception('Initializing SSH connection failed')
self._log_console_output()
+ raise
def _write_timestamp(self, server_or_ip):
ssh_client = self._ssh_to_server(server_or_ip)
@@ -72,7 +75,7 @@
def test_snapshot_pattern(self):
# prepare for booting a instance
self._add_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# boot a instance and create a timestamp file in it
server = self._boot_image(CONF.compute.image_ref)
diff --git a/tempest/scenario/test_stamp_pattern.py b/tempest/scenario/test_stamp_pattern.py
index 5235871..20561ae 100644
--- a/tempest/scenario/test_stamp_pattern.py
+++ b/tempest/scenario/test_stamp_pattern.py
@@ -62,8 +62,10 @@
volume_snapshot.id, status)
def _boot_image(self, image_id):
+ security_groups = [self.security_group.name]
create_kwargs = {
- 'key_name': self.keypair.name
+ 'key_name': self.keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image=image_id, create_kwargs=create_kwargs)
@@ -152,7 +154,7 @@
def test_stamp_pattern(self):
# prepare for booting a instance
self._add_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# boot an instance and create a timestamp file in it
volume = self._create_volume()
diff --git a/tempest/scenario/test_volume_boot_pattern.py b/tempest/scenario/test_volume_boot_pattern.py
index faca31f..4905dbf 100644
--- a/tempest/scenario/test_volume_boot_pattern.py
+++ b/tempest/scenario/test_volume_boot_pattern.py
@@ -55,9 +55,11 @@
bd_map = {
'vda': vol_id + ':::0'
}
+ security_groups = [self.security_group.name]
create_kwargs = {
'block_device_mapping': bd_map,
- 'key_name': keypair.name
+ 'key_name': keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image='', create_kwargs=create_kwargs)
@@ -135,7 +137,7 @@
@test.services('compute', 'volume', 'image')
def test_volume_boot_pattern(self):
keypair = self.create_keypair()
- self._create_loginable_secgroup_rule_nova()
+ self.security_group = self._create_security_group_nova()
# create an instance from volume
volume_origin = self._create_volume_from_image()
@@ -182,8 +184,10 @@
bdms = [{'uuid': vol_id, 'source_type': 'volume',
'destination_type': 'volume', 'boot_index': 0,
'delete_on_termination': False}]
+ security_groups = [self.security_group.name]
create_kwargs = {
'block_device_mapping_v2': bdms,
- 'key_name': keypair.name
+ 'key_name': keypair.name,
+ 'security_groups': security_groups
}
return self.create_server(image='', create_kwargs=create_kwargs)
diff --git a/tempest/services/baremetal/base.py b/tempest/services/baremetal/base.py
index 2af287f..321b08b 100644
--- a/tempest/services/baremetal/base.py
+++ b/tempest/services/baremetal/base.py
@@ -199,3 +199,14 @@
"""
return self._list_request(version, permanent=True)
+
+ def _put_request(self, resource, put_object):
+ """
+ Update specified object with JSON-patch.
+
+ """
+ uri = self._get_uri(resource)
+ put_body = json.dumps(put_object)
+
+ resp, body = self.put(uri, body=put_body)
+ return resp, body
diff --git a/tempest/services/baremetal/v1/base_v1.py b/tempest/services/baremetal/v1/base_v1.py
index 296a199..52479b5 100644
--- a/tempest/services/baremetal/v1/base_v1.py
+++ b/tempest/services/baremetal/v1/base_v1.py
@@ -226,3 +226,16 @@
"""
return self._patch_request('ports', uuid, patch)
+
+ @base.handle_errors
+ def set_node_power_state(self, node_uuid, state):
+ """
+ Set power state of the specified node.
+
+ :param node_uuid: The unique identifier of the node.
+ :state: desired state to set (on/off/reboot).
+
+ """
+ target = {'target': state}
+ return self._put_request('nodes/%s/states/power' % node_uuid,
+ target)
diff --git a/tempest/services/compute/json/floating_ips_client.py b/tempest/services/compute/json/floating_ips_client.py
index e2e12d5..92b4ddf 100644
--- a/tempest/services/compute/json/floating_ips_client.py
+++ b/tempest/services/compute/json/floating_ips_client.py
@@ -112,3 +112,29 @@
body = json.loads(body)
self.validate_response(schema.floating_ip_pools, resp, body)
return resp, body['floating_ip_pools']
+
+ def create_floating_ips_bulk(self, ip_range, pool, interface):
+ """Allocate floating IPs in bulk."""
+ post_body = {
+ 'ip_range': ip_range,
+ 'pool': pool,
+ 'interface': interface
+ }
+ post_body = json.dumps({'floating_ips_bulk_create': post_body})
+ resp, body = self.post('os-floating-ips-bulk', post_body)
+ body = json.loads(body)
+ self.validate_response(schema.create_floating_ips_bulk, resp, body)
+ return resp, body['floating_ips_bulk_create']
+
+ def list_floating_ips_bulk(self):
+ """Returns a list of all floating IPs bulk."""
+ resp, body = self.get('os-floating-ips-bulk')
+ body = json.loads(body)
+ return resp, body['floating_ip_info']
+
+ def delete_floating_ips_bulk(self, ip_range):
+ """Deletes the provided floating IPs bulk."""
+ post_body = json.dumps({'ip_range': ip_range})
+ resp, body = self.put('os-floating-ips-bulk/delete', post_body)
+ body = json.loads(body)
+ return resp, body['floating_ips_bulk_delete']
diff --git a/tempest/services/compute/json/servers_client.py b/tempest/services/compute/json/servers_client.py
index 9c7eec6..23c1e64 100644
--- a/tempest/services/compute/json/servers_client.py
+++ b/tempest/services/compute/json/servers_client.py
@@ -57,6 +57,7 @@
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
return_reservation_id: Enable/Disable the return of reservation id
+ block_device_mapping: Block device mapping for the server.
"""
post_body = {
'name': name,
@@ -69,7 +70,7 @@
'availability_zone', 'accessIPv4', 'accessIPv6',
'min_count', 'max_count', ('metadata', 'meta'),
('OS-DCF:diskConfig', 'disk_config'),
- 'return_reservation_id']:
+ 'return_reservation_id', 'block_device_mapping']:
if isinstance(option, tuple):
post_param = option[0]
key = option[1]
@@ -193,6 +194,7 @@
"""Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
return resp, body['addresses']
def list_addresses_by_network(self, server_id, network_id):
@@ -210,11 +212,13 @@
post_body)
if response_key is not None:
body = json.loads(body)
- # Check for Schema as 'None' because if we donot have any server
+ # Check for Schema as 'None' because if we do not have any server
# action schema implemented yet then they can pass 'None' to skip
# the validation.Once all server action has their schema
# implemented then, this check can be removed if every actions are
# supposed to validate their response.
+ # TODO(GMann): Remove the below 'if' check once all server actions
+ # schema are implemented.
if schema is not None:
self.validate_response(schema, resp, body)
body = body[response_key]
@@ -305,6 +309,8 @@
resp, body = self.post('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(common_schema.update_server_metadata,
+ resp, body)
return resp, body['metadata']
def get_server_metadata_item(self, server_id, key):
@@ -427,7 +433,7 @@
def get_console_output(self, server_id, length):
return self.action(server_id, 'os-getConsoleOutput', 'output',
- None, length=length)
+ common_schema.get_console_output, length=length)
def list_virtual_interfaces(self, server_id):
"""
@@ -457,6 +463,7 @@
resp, body = self.get("servers/%s/os-instance-actions" %
str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_instance_actions, resp, body)
return resp, body['instanceActions']
def get_instance_action(self, server_id, request_id):
@@ -503,11 +510,14 @@
resp, body = self.post('os-server-groups', post_body)
body = json.loads(body)
+ self.validate_response(schema.create_get_server_group, resp, body)
return resp, body['server_group']
def delete_server_group(self, server_group_id):
"""Delete the given server-group."""
- return self.delete("os-server-groups/%s" % str(server_group_id))
+ resp, body = self.delete("os-server-groups/%s" % str(server_group_id))
+ self.validate_response(schema.delete_server_group, resp, body)
+ return resp, body
def list_server_groups(self):
"""List the server-groups."""
@@ -519,4 +529,5 @@
"""Get the details of given server_group."""
resp, body = self.get("os-server-groups/%s" % str(server_group_id))
body = json.loads(body)
+ self.validate_response(schema.create_get_server_group, resp, body)
return resp, body['server_group']
diff --git a/tempest/services/compute/v3/json/servers_client.py b/tempest/services/compute/v3/json/servers_client.py
index caa0a1d..11258a6 100644
--- a/tempest/services/compute/v3/json/servers_client.py
+++ b/tempest/services/compute/v3/json/servers_client.py
@@ -55,6 +55,7 @@
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
return_reservation_id: Enable/Disable the return of reservation id
+ block_device_mapping: Block device mapping for the server.
"""
post_body = {
'name': name,
@@ -75,7 +76,9 @@
('metadata', 'meta'),
('os-disk-config:disk_config', 'disk_config'),
('os-multiple-create:return_reservation_id',
- 'return_reservation_id')]:
+ 'return_reservation_id'),
+ ('os-block-device-mapping:block_device_mapping',
+ 'block_device_mapping')]:
if isinstance(option, tuple):
post_param = option[0]
key = option[1]
@@ -193,6 +196,7 @@
"""Lists all addresses for a server."""
resp, body = self.get("servers/%s/ips" % str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_addresses, resp, body)
return resp, body['addresses']
def list_addresses_by_network(self, server_id, network_id):
@@ -209,7 +213,17 @@
resp, body = self.post('servers/%s/action' % str(server_id),
post_body)
if response_key is not None:
- body = json.loads(body)[response_key]
+ body = json.loads(body)
+ # Check for Schema as 'None' because if we do not have any server
+ # action schema implemented yet then they can pass 'None' to skip
+ # the validation.Once all server action has their schema
+ # implemented then, this check can be removed if every actions are
+ # supposed to validate their response.
+ # TODO(GMann): Remove the below 'if' check once all server actions
+ # schema are implemented.
+ if schema is not None:
+ self.validate_response(schema, resp, body)
+ body = body[response_key]
else:
self.validate_response(schema, resp, body)
return resp, body
@@ -256,7 +270,7 @@
if 'disk_config' in kwargs:
kwargs['os-disk-config:disk_config'] = kwargs['disk_config']
del kwargs['disk_config']
- return self.action(server_id, 'rebuild', 'server', **kwargs)
+ return self.action(server_id, 'rebuild', 'server', None, **kwargs)
def resize(self, server_id, flavor_ref, **kwargs):
"""Changes the flavor of a server."""
@@ -313,6 +327,7 @@
resp, body = self.post('servers/%s/metadata' % str(server_id),
post_body)
body = json.loads(body)
+ self.validate_response(schema.update_server_metadata, resp, body)
return resp, body['metadata']
def get_server_metadata_item(self, server_id, key):
@@ -420,12 +435,16 @@
return self.action(server_id, 'shelve_offload', None, **kwargs)
def get_console_output(self, server_id, length):
+ if length is None:
+ # NOTE(mriedem): -1 means optional/unlimited in the nova v3 API.
+ length = -1
return self.action(server_id, 'get_console_output', 'output',
- length=length)
+ common_schema.get_console_output, length=length)
def rescue_server(self, server_id, **kwargs):
"""Rescue the provided server."""
- return self.action(server_id, 'rescue', 'admin_password', **kwargs)
+ return self.action(server_id, 'rescue', 'admin_password',
+ None, **kwargs)
def unrescue_server(self, server_id):
"""Unrescue the provided server."""
@@ -442,6 +461,7 @@
resp, body = self.get("servers/%s/os-server-actions" %
str(server_id))
body = json.loads(body)
+ self.validate_response(schema.list_server_actions, resp, body)
return resp, body['server_actions']
def get_server_action(self, server_id, request_id):
@@ -483,9 +503,9 @@
def get_spice_console(self, server_id, console_type):
"""Get URL of Spice console."""
return self.action(server_id, "get_spice_console"
- "console", type=console_type)
+ "console", None, type=console_type)
def get_rdp_console(self, server_id, console_type):
"""Get URL of RDP console."""
return self.action(server_id, "get_rdp_console"
- "console", type=console_type)
+ "console", None, type=console_type)
diff --git a/tempest/services/compute/xml/servers_client.py b/tempest/services/compute/xml/servers_client.py
index c1105f9..626e655 100644
--- a/tempest/services/compute/xml/servers_client.py
+++ b/tempest/services/compute/xml/servers_client.py
@@ -318,6 +318,7 @@
min_count: Count of minimum number of instances to launch.
max_count: Count of maximum number of instances to launch.
disk_config: Determines if user or admin controls disk configuration.
+ block_device_mapping: Block device mapping for the server.
"""
server = xml_utils.Element("server",
xmlns=xml_utils.XMLNS_11,
@@ -327,7 +328,8 @@
for attr in ["adminPass", "accessIPv4", "accessIPv6", "key_name",
"user_data", "availability_zone", "min_count",
- "max_count", "return_reservation_id"]:
+ "max_count", "return_reservation_id",
+ "block_device_mapping"]:
if attr in kwargs:
server.add_attr(attr, kwargs[attr])
diff --git a/tempest/services/data_processing/v1_1/client.py b/tempest/services/data_processing/v1_1/client.py
index b07e663..c2c7fd1 100644
--- a/tempest/services/data_processing/v1_1/client.py
+++ b/tempest/services/data_processing/v1_1/client.py
@@ -161,3 +161,74 @@
uri = 'data-sources/%s' % source_id
return self.delete(uri)
+
+ def list_job_binary_internals(self):
+ """List all job binary internals for a user."""
+
+ uri = 'job-binary-internals'
+ return self._request_and_parse(self.get, uri, 'binaries')
+
+ def get_job_binary_internal(self, job_binary_id):
+ """Returns the details of a single job binary internal."""
+
+ uri = 'job-binary-internals/%s' % job_binary_id
+ return self._request_and_parse(self.get, uri, 'job_binary_internal')
+
+ def create_job_binary_internal(self, name, data):
+ """Creates job binary internal with specified params."""
+
+ uri = 'job-binary-internals/%s' % name
+ return self._request_and_parse(self.put, uri, 'job_binary_internal',
+ data)
+
+ def delete_job_binary_internal(self, job_binary_id):
+ """Deletes the specified job binary internal by id."""
+
+ uri = 'job-binary-internals/%s' % job_binary_id
+ return self.delete(uri)
+
+ def get_job_binary_internal_data(self, job_binary_id):
+ """Returns data of a single job binary internal."""
+
+ uri = 'job-binary-internals/%s/data' % job_binary_id
+ return self.get(uri)
+
+ def list_job_binaries(self):
+ """List all job binaries for a user."""
+
+ uri = 'job-binaries'
+ return self._request_and_parse(self.get, uri, 'binaries')
+
+ def get_job_binary(self, job_binary_id):
+ """Returns the details of a single job binary."""
+
+ uri = 'job-binaries/%s' % job_binary_id
+ return self._request_and_parse(self.get, uri, 'job_binary')
+
+ def create_job_binary(self, name, url, extra=None, **kwargs):
+ """Creates job binary with specified params.
+
+ It supports passing additional params using kwargs and returns created
+ object.
+ """
+ uri = 'job-binaries'
+ body = kwargs.copy()
+ body.update({
+ 'name': name,
+ 'url': url,
+ 'extra': extra or dict(),
+ })
+ return self._request_and_parse(self.post, uri, 'job_binary',
+ body=json.dumps(body))
+
+ def delete_job_binary(self, job_binary_id):
+ """Deletes the specified job binary by id."""
+
+ uri = 'job-binaries/%s' % job_binary_id
+ return self.delete(uri)
+
+ def get_job_binary_data(self, job_binary_id):
+ """Returns data of a single job binary."""
+
+ uri = 'job-binaries/%s/data' % job_binary_id
+ return self.get(uri)
diff --git a/tempest/services/identity/json/identity_client.py b/tempest/services/identity/json/identity_client.py
index 479a289..b0cab8e 100644
--- a/tempest/services/identity/json/identity_client.py
+++ b/tempest/services/identity/json/identity_client.py
@@ -27,7 +27,8 @@
self.endpoint_url = 'adminURL'
# Needed for xml service client
- self.list_tags = ["roles", "tenants", "users", "services"]
+ self.list_tags = ["roles", "tenants", "users", "services",
+ "extensions"]
def has_admin_extensions(self):
"""
@@ -237,6 +238,12 @@
resp, body = self.put('users/%s/OS-KSADM/password' % user_id, put_body)
return resp, self._parse_resp(body)
+ def list_extensions(self):
+ """List all the extensions."""
+ resp, body = self.get('/extensions')
+ body = json.loads(body)
+ return resp, body['extensions']['values']
+
class TokenClientJSON(IdentityClientJSON):
diff --git a/tempest/services/identity/v3/json/region_client.py b/tempest/services/identity/v3/json/region_client.py
new file mode 100644
index 0000000..f95d00f
--- /dev/null
+++ b/tempest/services/identity/v3/json/region_client.py
@@ -0,0 +1,80 @@
+# Copyright 2014 OpenStack Foundation
+# 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 json
+import urllib
+
+from tempest.common import rest_client
+from tempest import config
+
+CONF = config.CONF
+
+
+class RegionClientJSON(rest_client.RestClient):
+
+ def __init__(self, auth_provider):
+ super(RegionClientJSON, self).__init__(auth_provider)
+ self.service = CONF.identity.catalog_type
+ self.endpoint_url = 'adminURL'
+ self.api_version = "v3"
+
+ def create_region(self, description, **kwargs):
+ """Create region."""
+ req_body = {
+ 'description': description,
+ }
+ if kwargs.get('parent_region_id'):
+ req_body['parent_region_id'] = kwargs.get('parent_region_id')
+ req_body = json.dumps({'region': req_body})
+ if kwargs.get('unique_region_id'):
+ resp, body = self.put(
+ 'regions/%s' % kwargs.get('unique_region_id'), req_body)
+ else:
+ resp, body = self.post('regions', req_body)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def update_region(self, region_id, **kwargs):
+ """Updates a region."""
+ post_body = {}
+ if 'description' in kwargs:
+ post_body['description'] = kwargs.get('description')
+ if 'parent_region_id' in kwargs:
+ post_body['parent_region_id'] = kwargs.get('parent_region_id')
+ post_body = json.dumps({'region': post_body})
+ resp, body = self.patch('regions/%s' % region_id, post_body)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def get_region(self, region_id):
+ """Get region."""
+ url = 'regions/%s' % region_id
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['region']
+
+ def list_regions(self, params=None):
+ """List regions."""
+ url = 'regions'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body['regions']
+
+ def delete_region(self, region_id):
+ """Delete region."""
+ resp, body = self.delete('regions/%s' % region_id)
+ return resp, body
diff --git a/tempest/services/identity/v3/xml/region_client.py b/tempest/services/identity/v3/xml/region_client.py
new file mode 100644
index 0000000..9f9161d
--- /dev/null
+++ b/tempest/services/identity/v3/xml/region_client.py
@@ -0,0 +1,120 @@
+# Copyright 2014 OpenStack Foundation
+# 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 urllib
+
+from lxml import etree
+
+from tempest.common import http
+from tempest.common import rest_client
+from tempest.common import xml_utils as common
+from tempest import config
+
+CONF = config.CONF
+
+XMLNS = "http://docs.openstack.org/identity/api/v3"
+
+
+class RegionClientXML(rest_client.RestClient):
+ TYPE = "xml"
+
+ def __init__(self, auth_provider):
+ super(RegionClientXML, self).__init__(auth_provider)
+ self.service = CONF.identity.catalog_type
+ self.region_url = 'adminURL'
+ self.api_version = "v3"
+
+ def _parse_array(self, node):
+ array = []
+ for child in node.getchildren():
+ tag_list = child.tag.split('}', 1)
+ if tag_list[1] == "region":
+ array.append(common.xml_to_json(child))
+ return array
+
+ def _parse_body(self, body):
+ json = common.xml_to_json(body)
+ return json
+
+ def request(self, method, url, extra_headers=False, headers=None,
+ body=None, wait=None):
+ """Overriding the existing HTTP request in super class RestClient."""
+ if extra_headers:
+ try:
+ headers.update(self.get_headers())
+ except (ValueError, TypeError):
+ headers = self.get_headers()
+ dscv = CONF.identity.disable_ssl_certificate_validation
+ self.http_obj = http.ClosingHttp(
+ disable_ssl_certificate_validation=dscv)
+ return super(RegionClientXML, self).request(method, url,
+ extra_headers,
+ headers=headers,
+ body=body)
+
+ def create_region(self, description, **kwargs):
+ """Create region."""
+ create_region = common.Element("region",
+ xmlns=XMLNS,
+ description=description)
+ if 'parent_region_id' in kwargs:
+ create_region.append(common.Element(
+ 'parent_region_id', kwargs.get('parent_region_id')))
+ if 'unique_region_id' in kwargs:
+ resp, body = self.put(
+ 'regions/%s' % kwargs.get('unique_region_id'),
+ str(common.Document(create_region)))
+ else:
+ resp, body = self.post('regions',
+ str(common.Document(create_region)))
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def update_region(self, region_id, **kwargs):
+ """Updates an region with given parameters.
+ """
+ description = kwargs.get('description', None)
+ update_region = common.Element("region",
+ xmlns=XMLNS,
+ description=description)
+ if 'parent_region_id' in kwargs:
+ update_region.append(common.Element('parent_region_id',
+ kwargs.get('parent_region_id')))
+
+ resp, body = self.patch('regions/%s' % str(region_id),
+ str(common.Document(update_region)))
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def get_region(self, region_id):
+ """Get Region."""
+ url = 'regions/%s' % region_id
+ resp, body = self.get(url)
+ body = self._parse_body(etree.fromstring(body))
+ return resp, body
+
+ def list_regions(self, params=None):
+ """Get the list of regions."""
+ url = 'regions'
+ if params:
+ url += '?%s' % urllib.urlencode(params)
+ resp, body = self.get(url)
+ body = self._parse_array(etree.fromstring(body))
+ return resp, body
+
+ def delete_region(self, region_id):
+ """Delete region."""
+ resp, body = self.delete('regions/%s' % region_id)
+ return resp, body
diff --git a/tempest/services/identity/xml/identity_client.py b/tempest/services/identity/xml/identity_client.py
index b213c1a..886ce7b 100644
--- a/tempest/services/identity/xml/identity_client.py
+++ b/tempest/services/identity/xml/identity_client.py
@@ -127,6 +127,11 @@
str(xml.Document(put_body)))
return resp, self._parse_resp(body)
+ def list_extensions(self):
+ """List all the extensions."""
+ resp, body = self.get('/extensions')
+ return resp, self._parse_resp(body)
+
class TokenClientXML(identity_client.TokenClientJSON):
TYPE = "xml"
diff --git a/tempest/services/image/v2/json/image_client.py b/tempest/services/image/v2/json/image_client.py
index b3014fc..201869e 100644
--- a/tempest/services/image/v2/json/image_client.py
+++ b/tempest/services/image/v2/json/image_client.py
@@ -70,13 +70,12 @@
"disk_format": disk_format,
}
- for option in ['visibility']:
- if option in kwargs:
- value = kwargs.get(option)
- if isinstance(value, dict) or isinstance(value, tuple):
- params.update(value)
- else:
- params[option] = value
+ for option in kwargs:
+ value = kwargs.get(option)
+ if isinstance(value, dict) or isinstance(value, tuple):
+ params.update(value)
+ else:
+ params[option] = value
data = json.dumps(params)
self._validate_schema(data)
diff --git a/tempest/services/network/network_client_base.py b/tempest/services/network/network_client_base.py
index 2a797b2..81792c4 100644
--- a/tempest/services/network/network_client_base.py
+++ b/tempest/services/network/network_client_base.py
@@ -30,6 +30,7 @@
'members': 'lb',
'vpnservices': 'vpn',
'ikepolicies': 'vpn',
+ 'ipsecpolicies': 'vpn',
'metering_labels': 'metering',
'metering_label_rules': 'metering',
'firewall_rules': 'fw',
@@ -47,6 +48,7 @@
'security_groups': 'security_groups',
'security_group_rules': 'security_group_rules',
'ikepolicy': 'ikepolicies',
+ 'ipsecpolicy': 'ipsecpolicies',
'quotas': 'quotas',
'firewall_policy': 'firewall_policies'
}
diff --git a/tempest/services/network/xml/network_client.py b/tempest/services/network/xml/network_client.py
index a7a6b2c..22cc948 100644
--- a/tempest/services/network/xml/network_client.py
+++ b/tempest/services/network/xml/network_client.py
@@ -38,7 +38,7 @@
return _root_tag_fetcher_and_xml_to_json_parse(body)
def serialize(self, body):
- #TODO(enikanorov): implement better json to xml conversion
+ # TODO(enikanorov): implement better json to xml conversion
# expecting the dict with single key
root = body.keys()[0]
post_body = common.Element(root)
diff --git a/tempest/services/volume/json/admin/volume_types_client.py b/tempest/services/volume/json/admin/volume_types_client.py
index c9c0582..65ecc67 100644
--- a/tempest/services/volume/json/admin/volume_types_client.py
+++ b/tempest/services/volume/json/admin/volume_types_client.py
@@ -18,6 +18,7 @@
from tempest.common import rest_client
from tempest import config
+from tempest import exceptions
CONF = config.CONF
@@ -34,6 +35,26 @@
self.build_interval = CONF.volume.build_interval
self.build_timeout = CONF.volume.build_timeout
+ def is_resource_deleted(self, resource):
+ # to use this method self.resource must be defined to respective value
+ # Resource is a dictionary containing resource id and type
+ # Resource : {"id" : resource_id
+ # "type": resource_type}
+ try:
+ if resource['type'] == "volume-type":
+ self.get_volume_type(resource['id'])
+ elif resource['type'] == "encryption-type":
+ resp, body = self.get_encryption_type(resource['id'])
+ assert 200 == resp.status
+ if not body:
+ return True
+ else:
+ msg = (" resource value is either not defined or incorrect.")
+ raise exceptions.UnprocessableEntity(msg)
+ except exceptions.NotFound:
+ return True
+ return False
+
def list_volume_types(self, params=None):
"""List all the volume_types created."""
url = 'types'
@@ -150,3 +171,7 @@
resp, body = self.post(url, post_body)
body = json.loads(body)
return resp, body['encryption']
+
+ def delete_encryption_type(self, vol_type_id):
+ """Delete the encryption type for the specified volume-type."""
+ return self.delete("/types/%s/encryption/provider" % str(vol_type_id))
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index b55a037..6c97497 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -24,13 +24,13 @@
CONF = config.CONF
-class VolumesClientJSON(rest_client.RestClient):
+class BaseVolumesClientJSON(rest_client.RestClient):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Base client class to send CRUD Volume API requests to a Cinder endpoint
"""
def __init__(self, auth_provider):
- super(VolumesClientJSON, self).__init__(auth_provider)
+ super(BaseVolumesClientJSON, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
self.build_interval = CONF.volume.build_interval
@@ -72,7 +72,8 @@
Creates a new Volume.
size: Size of volume in GB.
Following optional keyword arguments are accepted:
- display_name: Optional Volume Name.
+ display_name: Optional Volume Name(only for V1).
+ name: Optional Volume Name(only for V2).
metadata: A dictionary of values to be used as metadata.
volume_type: Optional Name of volume_type for the volume
snapshot_id: When specified the volume is created from this snapshot
@@ -150,7 +151,6 @@
def wait_for_volume_status(self, volume_id, status):
"""Waits for a Volume to reach a given status."""
resp, body = self.get_volume(volume_id)
- volume_name = body['display_name']
volume_status = body['status']
start = int(time.time())
@@ -162,9 +162,10 @@
raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
if int(time.time()) - start >= self.build_timeout:
- message = ('Volume %s failed to reach %s status within '
- 'the required time (%s s).' %
- (volume_name, status, self.build_timeout))
+ message = 'Volume %s failed to reach %s status within '\
+ 'the required time (%s s).' % (volume_id,
+ status,
+ self.build_timeout)
raise exceptions.TimeoutException(message)
def is_resource_deleted(self, id):
@@ -297,3 +298,9 @@
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
resp, body = self.delete(url)
return resp, body
+
+
+class VolumesClientJSON(BaseVolumesClientJSON):
+ """
+ Client class to send CRUD Volume V1 API requests to a Cinder endpoint
+ """
diff --git a/tempest/services/volume/v2/json/volumes_client.py b/tempest/services/volume/v2/json/volumes_client.py
index df20a2a..1f16ead 100644
--- a/tempest/services/volume/v2/json/volumes_client.py
+++ b/tempest/services/volume/v2/json/volumes_client.py
@@ -13,18 +13,10 @@
# License for the specific language governing permissions and limitations
# under the License.
-import json
-import time
-import urllib
-
-from tempest.common import rest_client
-from tempest import config
-from tempest import exceptions
-
-CONF = config.CONF
+from tempest.services.volume.json import volumes_client
-class VolumesV2ClientJSON(rest_client.RestClient):
+class VolumesV2ClientJSON(volumes_client.BaseVolumesClientJSON):
"""
Client class to send CRUD Volume V2 API requests to a Cinder endpoint
"""
@@ -33,268 +25,3 @@
super(VolumesV2ClientJSON, self).__init__(auth_provider)
self.api_version = "v2"
- self.service = CONF.volume.catalog_type
- self.build_interval = CONF.volume.build_interval
- self.build_timeout = CONF.volume.build_timeout
-
- def get_attachment_from_volume(self, volume):
- """Return the element 'attachment' from input volumes."""
- return volume['attachments'][0]
-
- def list_volumes(self, params=None):
- """List all the volumes created."""
- url = 'volumes'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volumes']
-
- def list_volumes_with_detail(self, params=None):
- """List the details of all volumes."""
- url = 'volumes/detail'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volumes']
-
- def get_volume(self, volume_id):
- """Returns the details of a single volume."""
- url = "volumes/%s" % str(volume_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['volume']
-
- def create_volume(self, size=None, **kwargs):
- """
- Creates a new Volume.
- size: Size of volume in GB.
- Following optional keyword arguments are accepted:
- name: Optional Volume Name.
- metadata: A dictionary of values to be used as metadata.
- volume_type: Optional Name of volume_type for the volume
- snapshot_id: When specified the volume is created from this snapshot
- imageRef: When specified the volume is created from this image
- """
- # for bug #1293885:
- # If no size specified, read volume size from CONF
- if size is None:
- size = CONF.volume.volume_size
- post_body = {'size': size}
- post_body.update(kwargs)
- post_body = json.dumps({'volume': post_body})
- resp, body = self.post('volumes', post_body)
- body = json.loads(body)
- return resp, body['volume']
-
- def update_volume(self, volume_id, **kwargs):
- """Updates the Specified Volume."""
- put_body = json.dumps({'volume': kwargs})
- resp, body = self.put('volumes/%s' % volume_id, put_body)
- body = json.loads(body)
- return resp, body['volume']
-
- def delete_volume(self, volume_id):
- """Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
-
- def upload_volume(self, volume_id, image_name, disk_format):
- """Uploads a volume in Glance."""
- post_body = {
- 'image_name': image_name,
- 'disk_format': disk_format
- }
- post_body = json.dumps({'os-volume_upload_image': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- return resp, body['os-volume_upload_image']
-
- def attach_volume(self, volume_id, instance_uuid, mountpoint):
- """Attaches a volume to a given instance on a given mountpoint."""
- post_body = {
- 'instance_uuid': instance_uuid,
- 'mountpoint': mountpoint,
- }
- post_body = json.dumps({'os-attach': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def detach_volume(self, volume_id):
- """Detaches a volume from an instance."""
- post_body = {}
- post_body = json.dumps({'os-detach': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def reserve_volume(self, volume_id):
- """Reserves a volume."""
- post_body = {}
- post_body = json.dumps({'os-reserve': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def unreserve_volume(self, volume_id):
- """Restore a reserved volume ."""
- post_body = {}
- post_body = json.dumps({'os-unreserve': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status."""
- resp, body = self.get_volume(volume_id)
- volume_name = body['name']
- volume_status = body['status']
- start = int(time.time())
-
- while volume_status != status:
- time.sleep(self.build_interval)
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- if volume_status == 'error':
- raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = ('Volume %s failed to reach %s status within '
- 'the required time (%s s).' %
- (volume_name, status, self.build_timeout))
- raise exceptions.TimeoutException(message)
-
- def is_resource_deleted(self, id):
- try:
- self.get_volume(id)
- except exceptions.NotFound:
- return True
- return False
-
- def extend_volume(self, volume_id, extend_size):
- """Extend a volume."""
- post_body = {
- 'new_size': extend_size
- }
- post_body = json.dumps({'os-extend': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def reset_volume_status(self, volume_id, status):
- """Reset the Specified Volume's Status."""
- post_body = json.dumps({'os-reset_status': {"status": status}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def volume_begin_detaching(self, volume_id):
- """Volume Begin Detaching."""
- post_body = json.dumps({'os-begin_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def volume_roll_detaching(self, volume_id):
- """Volume Roll Detaching."""
- post_body = json.dumps({'os-roll_detaching': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def create_volume_transfer(self, vol_id, name=None):
- """Create a volume transfer."""
- post_body = {
- 'volume_id': vol_id
- }
- if name:
- post_body['name'] = name
- post_body = json.dumps({'transfer': post_body})
- resp, body = self.post('os-volume-transfer', post_body)
- body = json.loads(body)
- return resp, body['transfer']
-
- def get_volume_transfer(self, transfer_id):
- """Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % str(transfer_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['transfer']
-
- def list_volume_transfers(self, params=None):
- """List all the volume transfers created."""
- url = 'os-volume-transfer'
- if params:
- url += '?%s' % urllib.urlencode(params)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['transfers']
-
- def delete_volume_transfer(self, transfer_id):
- """Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
-
- def accept_volume_transfer(self, transfer_id, transfer_auth_key):
- """Accept a volume transfer."""
- post_body = {
- 'auth_key': transfer_auth_key,
- }
- url = 'os-volume-transfer/%s/accept' % transfer_id
- post_body = json.dumps({'accept': post_body})
- resp, body = self.post(url, post_body)
- body = json.loads(body)
- return resp, body['transfer']
-
- def update_volume_readonly(self, volume_id, readonly):
- """Update the Specified Volume readonly."""
- post_body = {
- 'readonly': readonly
- }
- post_body = json.dumps({'os-update_readonly_flag': post_body})
- url = 'volumes/%s/action' % (volume_id)
- resp, body = self.post(url, post_body)
- return resp, body
-
- def force_delete_volume(self, volume_id):
- """Force Delete Volume."""
- post_body = json.dumps({'os-force_delete': {}})
- resp, body = self.post('volumes/%s/action' % volume_id, post_body)
- return resp, body
-
- def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.post(url, put_body)
- body = json.loads(body)
- return resp, body['metadata']
-
- def get_volume_metadata(self, volume_id):
- """Get metadata of the volume."""
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.get(url)
- body = json.loads(body)
- return resp, body['metadata']
-
- def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
- put_body = json.dumps({'metadata': metadata})
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- return resp, body['metadata']
-
- def update_volume_metadata_item(self, volume_id, id, meta_item):
- """Update metadata item for the volume."""
- put_body = json.dumps({'meta': meta_item})
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, put_body)
- body = json.loads(body)
- return resp, body['meta']
-
- def delete_volume_metadata_item(self, volume_id, id):
- """Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.delete(url)
- return resp, body
diff --git a/tempest/services/volume/v2/xml/volumes_client.py b/tempest/services/volume/v2/xml/volumes_client.py
index 1fdaf19..c1bcf6e 100644
--- a/tempest/services/volume/v2/xml/volumes_client.py
+++ b/tempest/services/volume/v2/xml/volumes_client.py
@@ -13,32 +13,23 @@
# License for the specific language governing permissions and limitations
# under the License.
-import time
import urllib
from lxml import etree
-from tempest.common import rest_client
from tempest.common import xml_utils as common
-from tempest import config
-from tempest import exceptions
-
-CONF = config.CONF
+from tempest.services.volume.xml import volumes_client
-class VolumesV2ClientXML(rest_client.RestClient):
+class VolumesV2ClientXML(volumes_client.BaseVolumesClientXML):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Client class to send CRUD Volume API V2 requests to a Cinder endpoint
"""
- TYPE = "xml"
def __init__(self, auth_provider):
super(VolumesV2ClientXML, self).__init__(auth_provider)
self.api_version = "v2"
- self.service = CONF.volume.catalog_type
- self.build_interval = CONF.compute.build_interval
- self.build_timeout = CONF.compute.build_timeout
def _parse_volume(self, body):
vol = dict((attr, body.get(attr)) for attr in body.keys())
@@ -53,46 +44,9 @@
child.getchildren())
else:
vol[tag] = common.xml_to_json(child)
+ self._translate_attributes_to_json(vol)
return vol
- def get_attachment_from_volume(self, volume):
- """Return the element 'attachment' from input volumes."""
- return volume['attachments']['attachment']
-
- def _check_if_bootable(self, volume):
- """
- Check if the volume is bootable, also change the value
- of 'bootable' from string to boolean.
- """
-
- # NOTE(jdg): Version 1 of Cinder API uses lc strings
- # We should consider being explicit in this check to
- # avoid introducing bugs like: LP #1227837
-
- if volume['bootable'].lower() == 'true':
- volume['bootable'] = True
- elif volume['bootable'].lower() == 'false':
- volume['bootable'] = False
- else:
- raise ValueError(
- 'bootable flag is supposed to be either True or False,'
- 'it is %s' % volume['bootable'])
- return volume
-
- def list_volumes(self, params=None):
- """List all the volumes created."""
- url = 'volumes'
-
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = etree.fromstring(body)
- volumes = []
- if body is not None:
- volumes += [self._parse_volume(vol) for vol in list(body)]
- return resp, volumes
-
def list_volumes_with_detail(self, params=None):
"""List all the details of volumes."""
url = 'volumes/detail'
@@ -116,293 +70,3 @@
body = self._parse_volume(etree.fromstring(body))
body = self._check_if_bootable(body)
return resp, body
-
- def create_volume(self, size=None, **kwargs):
- """Creates a new Volume.
-
- :param size: Size of volume in GB.
- :param name: Optional Volume Name.
- :param metadata: An optional dictionary of values for metadata.
- :param volume_type: Optional Name of volume_type for the volume
- :param snapshot_id: When specified the volume is created from
- this snapshot
- :param imageRef: When specified the volume is created from this
- image
- """
- # for bug #1293885:
- # If no size specified, read volume size from CONF
- if size is None:
- size = CONF.volume.volume_size
- # NOTE(afazekas): it should use a volume namespace
- volume = common.Element("volume", xmlns=common.XMLNS_11, size=size)
-
- if 'metadata' in kwargs:
- _metadata = common.Element('metadata')
- volume.append(_metadata)
- for key, value in kwargs['metadata'].items():
- meta = common.Element('meta')
- meta.add_attr('key', key)
- meta.append(common.Text(value))
- _metadata.append(meta)
- attr_to_add = kwargs.copy()
- del attr_to_add['metadata']
- else:
- attr_to_add = kwargs
-
- for key, value in attr_to_add.items():
- volume.add_attr(key, value)
-
- resp, body = self.post('volumes', str(common.Document(volume)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def update_volume(self, volume_id, **kwargs):
- """Updates the Specified Volume."""
- put_body = common.Element("volume", xmlns=common.XMLNS_11, **kwargs)
-
- resp, body = self.put('volumes/%s' % volume_id,
- str(common.Document(put_body)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def delete_volume(self, volume_id):
- """Deletes the Specified Volume."""
- return self.delete("volumes/%s" % str(volume_id))
-
- def wait_for_volume_status(self, volume_id, status):
- """Waits for a Volume to reach a given status."""
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- start = int(time.time())
-
- while volume_status != status:
- time.sleep(self.build_interval)
- resp, body = self.get_volume(volume_id)
- volume_status = body['status']
- if volume_status == 'error':
- raise exceptions.VolumeBuildErrorException(volume_id=volume_id)
-
- if int(time.time()) - start >= self.build_timeout:
- message = 'Volume %s failed to reach %s status within '\
- 'the required time (%s s).' % (volume_id,
- status,
- self.build_timeout)
- raise exceptions.TimeoutException(message)
-
- def is_resource_deleted(self, id):
- try:
- self.get_volume(id)
- except exceptions.NotFound:
- return True
- return False
-
- def attach_volume(self, volume_id, instance_uuid, mountpoint):
- """Attaches a volume to a given instance on a given mountpoint."""
- post_body = common.Element("os-attach",
- instance_uuid=instance_uuid,
- mountpoint=mountpoint
- )
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def detach_volume(self, volume_id):
- """Detaches a volume from an instance."""
- post_body = common.Element("os-detach")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def upload_volume(self, volume_id, image_name, disk_format):
- """Uploads a volume in Glance."""
- post_body = common.Element("os-volume_upload_image",
- image_name=image_name,
- disk_format=disk_format)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def extend_volume(self, volume_id, extend_size):
- """Extend a volume."""
- post_body = common.Element("os-extend",
- new_size=extend_size)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def reset_volume_status(self, volume_id, status):
- """Reset the Specified Volume's Status."""
- post_body = common.Element("os-reset_status",
- status=status
- )
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def volume_begin_detaching(self, volume_id):
- """Volume Begin Detaching."""
- post_body = common.Element("os-begin_detaching")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def volume_roll_detaching(self, volume_id):
- """Volume Roll Detaching."""
- post_body = common.Element("os-roll_detaching")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def reserve_volume(self, volume_id):
- """Reserves a volume."""
- post_body = common.Element("os-reserve")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def unreserve_volume(self, volume_id):
- """Restore a reserved volume ."""
- post_body = common.Element("os-unreserve")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def create_volume_transfer(self, vol_id, name=None):
- """Create a volume transfer."""
- post_body = common.Element("transfer", volume_id=vol_id)
- if name:
- post_body.add_attr('name', name)
- resp, body = self.post('os-volume-transfer',
- str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def get_volume_transfer(self, transfer_id):
- """Returns the details of a volume transfer."""
- url = "os-volume-transfer/%s" % str(transfer_id)
- resp, body = self.get(url)
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def list_volume_transfers(self, params=None):
- """List all the volume transfers created."""
- url = 'os-volume-transfer'
- if params:
- url += '?%s' % urllib.urlencode(params)
-
- resp, body = self.get(url)
- body = etree.fromstring(body)
- volumes = []
- if body is not None:
- volumes += [self._parse_volume_transfer(vol) for vol in list(body)]
- return resp, volumes
-
- def _parse_volume_transfer(self, body):
- vol = dict((attr, body.get(attr)) for attr in body.keys())
- for child in body.getchildren():
- tag = child.tag
- if tag.startswith("{"):
- tag = tag.split("}", 1)
- vol[tag] = common.xml_to_json(child)
- return vol
-
- def delete_volume_transfer(self, transfer_id):
- """Delete a volume transfer."""
- return self.delete("os-volume-transfer/%s" % str(transfer_id))
-
- def accept_volume_transfer(self, transfer_id, transfer_auth_key):
- """Accept a volume transfer."""
- post_body = common.Element("accept", auth_key=transfer_auth_key)
- url = 'os-volume-transfer/%s/accept' % transfer_id
- resp, body = self.post(url, str(common.Document(post_body)))
- volume = common.xml_to_json(etree.fromstring(body))
- return resp, volume
-
- def update_volume_readonly(self, volume_id, readonly):
- """Update the Specified Volume readonly."""
- post_body = common.Element("os-update_readonly_flag",
- readonly=readonly)
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def force_delete_volume(self, volume_id):
- """Force Delete Volume."""
- post_body = common.Element("os-force_delete")
- url = 'volumes/%s/action' % str(volume_id)
- resp, body = self.post(url, str(common.Document(post_body)))
- if body:
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def _metadata_body(self, meta):
- post_body = common.Element('metadata')
- for k, v in meta.items():
- data = common.Element('meta', key=k)
- data.append(common.Text(v))
- post_body.append(data)
- return post_body
-
- def _parse_key_value(self, node):
- """Parse <foo key='key'>value</foo> data into {'key': 'value'}."""
- data = {}
- for node in node.getchildren():
- data[node.get('key')] = node.text
- return data
-
- def create_volume_metadata(self, volume_id, metadata):
- """Create metadata for the volume."""
- post_body = self._metadata_body(metadata)
- resp, body = self.post('volumes/%s/metadata' % volume_id,
- str(common.Document(post_body)))
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def get_volume_metadata(self, volume_id):
- """Get metadata of the volume."""
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.get(url)
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def update_volume_metadata(self, volume_id, metadata):
- """Update metadata for the volume."""
- put_body = self._metadata_body(metadata)
- url = "volumes/%s/metadata" % str(volume_id)
- resp, body = self.put(url, str(common.Document(put_body)))
- body = self._parse_key_value(etree.fromstring(body))
- return resp, body
-
- def update_volume_metadata_item(self, volume_id, id, meta_item):
- """Update metadata item for the volume."""
- for k, v in meta_item.items():
- put_body = common.Element('meta', key=k)
- put_body.append(common.Text(v))
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- resp, body = self.put(url, str(common.Document(put_body)))
- body = common.xml_to_json(etree.fromstring(body))
- return resp, body
-
- def delete_volume_metadata_item(self, volume_id, id):
- """Delete metadata item for the volume."""
- url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
- return self.delete(url)
diff --git a/tempest/services/volume/xml/backups_client.py b/tempest/services/volume/xml/backups_client.py
index 81caaee..a691a25 100644
--- a/tempest/services/volume/xml/backups_client.py
+++ b/tempest/services/volume/xml/backups_client.py
@@ -22,5 +22,5 @@
"""
TYPE = "xml"
- #TODO(gfidente): XML client isn't yet implemented because of bug 1270589
+ # TODO(gfidente): XML client isn't yet implemented because of bug 1270589
pass
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 9799e55..2d4a9e9 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -32,14 +32,14 @@
VOLUMES_TENANT_NS = VOLUME_NS_BASE + 'volume_tenant_attribute/api/v1'
-class VolumesClientXML(rest_client.RestClient):
+class BaseVolumesClientXML(rest_client.RestClient):
"""
- Client class to send CRUD Volume API requests to a Cinder endpoint
+ Base client class to send CRUD Volume API requests to a Cinder endpoint
"""
TYPE = "xml"
def __init__(self, auth_provider):
- super(VolumesClientXML, self).__init__(auth_provider)
+ super(BaseVolumesClientXML, self).__init__(auth_provider)
self.service = CONF.volume.catalog_type
self.build_interval = CONF.compute.build_interval
self.build_timeout = CONF.compute.build_timeout
@@ -141,6 +141,8 @@
"""Creates a new Volume.
:param size: Size of volume in GB.
+ :param display_name: Optional Volume Name(only for V1).
+ :param name: Optional Volume Name(only for V2).
:param display_name: Optional Volume Name.
:param metadata: An optional dictionary of values for metadata.
:param volume_type: Optional Name of volume_type for the volume
@@ -428,3 +430,9 @@
"""Delete metadata item for the volume."""
url = "volumes/%s/metadata/%s" % (str(volume_id), str(id))
return self.delete(url)
+
+
+class VolumesClientXML(BaseVolumesClientXML):
+ """
+ Client class to send CRUD Volume API V1 requests to a Cinder endpoint
+ """
diff --git a/tempest/stress/README.rst b/tempest/stress/README.rst
index b56f96b..0a63679 100644
--- a/tempest/stress/README.rst
+++ b/tempest/stress/README.rst
@@ -34,14 +34,14 @@
In order to use this discovery you have to be in the tempest root directory
and execute the following:
- tempest/stress/run_stress.py -a -d 30
+ run-tempest-stress -a -d 30
Running the sample test
-----------------------
-To test installation, do the following (from the tempest/stress directory):
+To test installation, do the following:
- ./run_stress.py -t etc/server-create-destroy-test.json -d 30
+ run-tempest-stress -t tempest/stress/etc/server-create-destroy-test.json -d 30
This sample test tries to create a few VMs and kill a few VMs.
diff --git a/tempest/stress/actions/volume_attach_delete.py b/tempest/stress/actions/volume_attach_delete.py
index c2e6072..b438f52 100644
--- a/tempest/stress/actions/volume_attach_delete.py
+++ b/tempest/stress/actions/volume_attach_delete.py
@@ -28,9 +28,9 @@
# Step 1: create volume
name = data_utils.rand_name("volume")
self.logger.info("creating volume: %s" % name)
- resp, volume = self.manager.volumes_client.create_volume(size=1,
- display_name=
- name)
+ resp, volume = self.manager.volumes_client.create_volume(
+ size=1,
+ display_name=name)
assert(resp.status == 200)
self.manager.volumes_client.wait_for_volume_status(volume['id'],
'available')
diff --git a/tempest/stress/actions/volume_attach_verify.py b/tempest/stress/actions/volume_attach_verify.py
index 1bc3b06..a3ca0b7 100644
--- a/tempest/stress/actions/volume_attach_verify.py
+++ b/tempest/stress/actions/volume_attach_verify.py
@@ -81,9 +81,9 @@
name = data_utils.rand_name("volume")
self.logger.info("creating volume: %s" % name)
volumes_client = self.manager.volumes_client
- resp, self.volume = volumes_client.create_volume(size=1,
- display_name=
- name)
+ resp, self.volume = volumes_client.create_volume(
+ size=1,
+ display_name=name)
assert(resp.status == 200)
volumes_client.wait_for_volume_status(self.volume['id'],
'available')
diff --git a/tempest/test.py b/tempest/test.py
index 748a98c..650fad7 100644
--- a/tempest/test.py
+++ b/tempest/test.py
@@ -107,6 +107,7 @@
'identity': True,
'object_storage': CONF.service_available.swift,
'dashboard': CONF.service_available.horizon,
+ 'ceilometer': CONF.service_available.ceilometer,
}
def decorator(f):
@@ -256,6 +257,12 @@
network_resources = {}
+ # NOTE(sdague): log_format is defined inline here instead of using the oslo
+ # default because going through the config path recouples config to the
+ # stress tests too early, and depending on testr order will fail unit tests
+ log_format = ('%(asctime)s %(process)d %(levelname)-8s '
+ '[%(name)s] %(message)s')
+
@classmethod
def setUpClass(cls):
if hasattr(super(BaseTestCase, cls), 'setUpClass'):
@@ -293,9 +300,8 @@
self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
if (os.environ.get('OS_LOG_CAPTURE') != 'False' and
os.environ.get('OS_LOG_CAPTURE') != '0'):
- log_format = '%(asctime)-15s %(message)s'
self.useFixture(fixtures.LoggerFixture(nuke_handlers=False,
- format=log_format,
+ format=self.log_format,
level=None))
@classmethod
@@ -396,7 +402,7 @@
:param file: the file name
"""
- #NOTE(mkoderer): must be extended for xml support
+ # NOTE(mkoderer): must be extended for xml support
fn = os.path.join(
os.path.abspath(os.path.dirname(os.path.dirname(__file__))),
"etc", "schemas", file)
diff --git a/tempest/tests/base.py b/tempest/tests/base.py
index 15e4311..f4df3b9 100644
--- a/tempest/tests/base.py
+++ b/tempest/tests/base.py
@@ -12,28 +12,16 @@
# License for the specific language governing permissions and limitations
# under the License.
-import os
-
-import fixtures
import mock
-import testtools
-from tempest.openstack.common.fixture import moxstubout
+from oslotest import base
+from oslotest import moxstubout
-class TestCase(testtools.TestCase):
+class TestCase(base.BaseTestCase):
def setUp(self):
super(TestCase, self).setUp()
- if (os.environ.get('OS_STDOUT_CAPTURE') == 'True' or
- os.environ.get('OS_STDOUT_CAPTURE') == '1'):
- stdout = self.useFixture(fixtures.StringStream('stdout')).stream
- self.useFixture(fixtures.MonkeyPatch('sys.stdout', stdout))
- if (os.environ.get('OS_STDERR_CAPTURE') == 'True' or
- os.environ.get('OS_STDERR_CAPTURE') == '1'):
- stderr = self.useFixture(fixtures.StringStream('stderr')).stream
- self.useFixture(fixtures.MonkeyPatch('sys.stderr', stderr))
-
mox_fixture = self.useFixture(moxstubout.MoxStubout())
self.mox = mox_fixture.mox
self.stubs = mox_fixture.stubs
diff --git a/tempest/tests/cmd/test_verify_tempest_config.py b/tempest/tests/cmd/test_verify_tempest_config.py
index 40caf30..d0140dd 100644
--- a/tempest/tests/cmd/test_verify_tempest_config.py
+++ b/tempest/tests/cmd/test_verify_tempest_config.py
@@ -15,6 +15,7 @@
import json
import mock
+from oslo.config import cfg
from tempest.cmd import verify_tempest_config
from tempest import config
@@ -152,6 +153,7 @@
False, True)
def test_verify_nova_versions(self):
+ cfg.CONF.set_default('api_v3', True, 'compute-feature-enabled')
self.useFixture(mockpatch.PatchObject(
verify_tempest_config, '_get_unversioned_endpoint',
return_value='http://fake_endpoint:5000'))
@@ -280,9 +282,9 @@
def test_verify_extensions_nova(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -302,9 +304,9 @@
def test_verify_extensions_nova_all(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -319,9 +321,9 @@
def test_verify_extensions_nova_v3(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_v3_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
@@ -341,9 +343,9 @@
def test_verify_extensions_nova_v3_all(self):
def fake_list_extensions():
- return (None, {'extensions': [{'name': 'fake1'},
- {'name': 'fake2'},
- {'name': 'not_fake'}]})
+ return (None, {'extensions': [{'alias': 'fake1'},
+ {'alias': 'fake2'},
+ {'alias': 'not_fake'}]})
fake_os = mock.MagicMock()
fake_os.extensions_v3_client.list_extensions = fake_list_extensions
self.useFixture(mockpatch.PatchObject(
diff --git a/tempest/tests/common/utils/linux/__init__.py b/tempest/tests/common/utils/linux/__init__.py
new file mode 100644
index 0000000..e69de29
--- /dev/null
+++ b/tempest/tests/common/utils/linux/__init__.py
diff --git a/tempest/tests/common/utils/linux/test_remote_client.py b/tempest/tests/common/utils/linux/test_remote_client.py
new file mode 100644
index 0000000..0db4cfa
--- /dev/null
+++ b/tempest/tests/common/utils/linux/test_remote_client.py
@@ -0,0 +1,150 @@
+# Copyright 2014 IBM Corp.
+#
+# 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 time
+
+from oslo.config import cfg
+
+from tempest.common.utils.linux import remote_client
+from tempest import config
+from tempest.openstack.common.fixture import mockpatch
+from tempest.tests import base
+from tempest.tests import fake_config
+
+
+class TestRemoteClient(base.TestCase):
+ def setUp(self):
+ super(TestRemoteClient, self).setUp()
+ self.useFixture(fake_config.ConfigFixture())
+ self.stubs.Set(config, 'TempestConfigPrivate', fake_config.FakePrivate)
+ cfg.CONF.set_default('ip_version_for_ssh', 4, group='compute')
+ cfg.CONF.set_default('network_for_ssh', 'public', group='compute')
+ cfg.CONF.set_default('ssh_channel_timeout', 1, group='compute')
+
+ self.conn = remote_client.RemoteClient('127.0.0.1', 'user', 'pass')
+ self.ssh_mock = self.useFixture(mockpatch.PatchObject(self.conn,
+ 'ssh_client'))
+
+ def test_hostname_equals_servername_for_expected_names(self):
+ self.ssh_mock.mock.exec_command.return_value = 'fake_hostname'
+ self.assertTrue(self.conn.hostname_equals_servername('fake_hostname'))
+
+ def test_hostname_equals_servername_for_unexpected_names(self):
+ self.ssh_mock.mock.exec_command.return_value = 'fake_hostname'
+ self.assertFalse(
+ self.conn.hostname_equals_servername('unexpected_hostname'))
+
+ def test_get_ram_size(self):
+ free_output = "Mem: 48294 45738 2555 0" \
+ "402 40346"
+ self.ssh_mock.mock.exec_command.return_value = free_output
+ self.assertEqual(self.conn.get_ram_size_in_mb(), '48294')
+
+ def test_write_to_console_regular_str(self):
+ self.conn.write_to_console('test')
+ self._assert_exec_called_with(
+ 'sudo sh -c "echo \\"test\\" >/dev/console"')
+
+ def _test_write_to_console_helper(self, message, expected_call):
+ self.conn.write_to_console(message)
+ self._assert_exec_called_with(expected_call)
+
+ def test_write_to_console_special_chars(self):
+ self._test_write_to_console_helper(
+ '\`',
+ 'sudo sh -c "echo \\"\\\\\\`\\" >/dev/console"')
+ self.conn.write_to_console('$')
+ self._assert_exec_called_with(
+ 'sudo sh -c "echo \\"\\\\$\\" >/dev/console"')
+
+ # NOTE(maurosr): The tests below end up closer to an output format
+ # assurance than a test since it's basically using comand_exec to format
+ # the information using gnu/linux tools.
+
+ def _assert_exec_called_with(self, cmd):
+ self.ssh_mock.mock.exec_command.assert_called_with(cmd)
+
+ def test_get_number_of_vcpus(self):
+ self.ssh_mock.mock.exec_command.return_value = '16'
+ self.assertEqual(self.conn.get_number_of_vcpus(), 16)
+ self._assert_exec_called_with(
+ 'cat /proc/cpuinfo | grep processor | wc -l')
+
+ def test_get_partitions(self):
+ proc_partitions = """major minor #blocks name
+
+8 0 1048576 vda"""
+ self.ssh_mock.mock.exec_command.return_value = proc_partitions
+ self.assertEqual(self.conn.get_partitions(), proc_partitions)
+ self._assert_exec_called_with('cat /proc/partitions')
+
+ def test_get_boot_time(self):
+ booted_at = 10000
+ uptime_sec = 5000.02
+ self.ssh_mock.mock.exec_command.return_value = uptime_sec
+ self.useFixture(mockpatch.PatchObject(
+ time, 'time', return_value=booted_at + uptime_sec))
+ self.assertEqual(self.conn.get_boot_time(),
+ time.localtime(booted_at))
+ self._assert_exec_called_with('cut -f1 -d. /proc/uptime')
+
+ def test_ping_host(self):
+ ping_response = """PING localhost (127.0.0.1) 56(84) bytes of data.
+64 bytes from localhost (127.0.0.1): icmp_req=1 ttl=64 time=0.048 ms
+
+--- localhost ping statistics ---
+1 packets transmitted, 1 received, 0% packet loss, time 0ms
+rtt min/avg/max/mdev = 0.048/0.048/0.048/0.000 ms"""
+ self.ssh_mock.mock.exec_command.return_value = ping_response
+ self.assertEqual(self.conn.ping_host('127.0.0.1'), ping_response)
+ self._assert_exec_called_with('ping -c1 -w1 127.0.0.1')
+
+ def test_get_mac_address(self):
+ macs = """0a:0b:0c:0d:0e:0f
+a0:b0:c0:d0:e0:f0"""
+ self.ssh_mock.mock.exec_command.return_value = macs
+
+ self.assertEqual(self.conn.get_mac_address(), macs)
+ self._assert_exec_called_with(
+ "/sbin/ifconfig | awk '/HWaddr/ {print $5}'")
+
+ def test_get_ip_list(self):
+ ips = """1: lo: <LOOPBACK,UP,LOWER_UP> mtu 16436 qdisc noqueue
+ link/loopback 00:00:00:00:00:00 brd 00:00:00:00:00:00
+ inet 127.0.0.1/8 scope host lo
+ inet6 ::1/128 scope host
+ valid_lft forever preferred_lft forever
+2: eth0: <BROADCAST,MULTICAST,UP,LOWER_UP> mtu 1500 qdisc pfifo_fast qlen 1000
+ link/ether fa:16:3e:6e:26:3b brd ff:ff:ff:ff:ff:ff
+ inet 10.0.0.4/24 brd 10.0.0.255 scope global eth0
+ inet6 fd55:faaf:e1ab:3d9:f816:3eff:fe6e:263b/64 scope global dynamic
+ valid_lft 2591936sec preferred_lft 604736sec
+ inet6 fe80::f816:3eff:fe6e:263b/64 scope link
+ valid_lft forever preferred_lft forever"""
+ self.ssh_mock.mock.exec_command.return_value = ips
+ self.assertEqual(self.conn.get_ip_list(), ips)
+ self._assert_exec_called_with('/bin/ip address')
+
+ def test_assign_static_ip(self):
+ self.ssh_mock.mock.exec_command.return_value = ''
+ ip = '10.0.0.2'
+ nic = 'eth0'
+ self.assertEqual(self.conn.assign_static_ip(nic, ip), '')
+ self._assert_exec_called_with(
+ "sudo /bin/ip addr add %s/%s dev %s" % (ip, '28', nic))
+
+ def test_turn_nic_on(self):
+ nic = 'eth0'
+ self.conn.turn_nic_on(nic)
+ self._assert_exec_called_with('sudo /bin/ip link set %s up' % nic)
diff --git a/tempest/tests/fake_http.py b/tempest/tests/fake_http.py
index 7b878af..ce2b2c0 100644
--- a/tempest/tests/fake_http.py
+++ b/tempest/tests/fake_http.py
@@ -32,7 +32,6 @@
'headers': headers
}
return (fake_headers, return_obj)
- # return (headers, return_obj)
elif isinstance(self.return_type, int):
body = "fake_body"
header_info = {
diff --git a/tempest/tests/stress/test_stress.py b/tempest/tests/stress/test_stress.py
index c76abde..5a334c5 100644
--- a/tempest/tests/stress/test_stress.py
+++ b/tempest/tests/stress/test_stress.py
@@ -18,12 +18,12 @@
import tempest.cli as cli
from tempest.openstack.common import log as logging
-import tempest.test
+from tempest.tests import base
LOG = logging.getLogger(__name__)
-class StressFrameworkTest(tempest.test.BaseTestCase):
+class StressFrameworkTest(base.TestCase):
"""Basic test for the stress test framework.
"""
@@ -51,5 +51,5 @@
return proc.returncode
def test_help_function(self):
- result = self._cmd("python", "-m tempest.stress.run_stress -h")
+ result = self._cmd("python", "-m tempest.cmd.run_stress -h")
self.assertEqual(0, result)
diff --git a/tempest/tests/test_auth.py b/tempest/tests/test_auth.py
index 03333be..1dcddad 100644
--- a/tempest/tests/test_auth.py
+++ b/tempest/tests/test_auth.py
@@ -16,11 +16,12 @@
import copy
import datetime
+from oslotest import mockpatch
+
from tempest import auth
from tempest.common import http
from tempest import config
from tempest import exceptions
-from tempest.openstack.common.fixture import mockpatch
from tempest.tests import base
from tempest.tests import fake_auth_provider
from tempest.tests import fake_config
diff --git a/tempest/tests/test_decorators.py b/tempest/tests/test_decorators.py
index 804204a..6b678f7 100644
--- a/tempest/tests/test_decorators.py
+++ b/tempest/tests/test_decorators.py
@@ -14,13 +14,12 @@
import mock
-import testtools
-
from oslo.config import cfg
+from oslotest import mockpatch
+import testtools
from tempest import config
from tempest import exceptions
-from tempest.openstack.common.fixture import mockpatch
from tempest import test
from tempest.tests import base
from tempest.tests import fake_config
diff --git a/tempest/tests/test_glance_http.py b/tempest/tests/test_glance_http.py
index bb2df43..88b8129 100644
--- a/tempest/tests/test_glance_http.py
+++ b/tempest/tests/test_glance_http.py
@@ -54,26 +54,37 @@
return_value=resp))
return resp
- def test_json_request_without_content_type_header(self):
+ def test_json_request_without_content_type_header_in_response(self):
self._set_response_fixture({}, 200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertIsNone(body)
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images')
- def test_json_request_with_xml_content_type_header(self):
+ def test_json_request_with_xml_content_type_header_in_request(self):
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images',
+ headers={'Content-Type': 'application/xml'})
+
+ def test_json_request_with_xml_content_type_header_in_response(self):
self._set_response_fixture({'content-type': 'application/xml'},
200, 'fake_response_body')
- resp, body = self.client.json_request('GET', '/images')
- self.assertEqual(200, resp.status)
- self.assertIsNone(body)
+ self.assertRaises(exceptions.InvalidContentType,
+ self.client.json_request, 'GET', '/images')
- def test_json_request_with_content_type_header(self):
+ def test_json_request_with_json_content_type_header_only_in_resp(self):
self._set_response_fixture({'content-type': 'application/json'},
200, 'fake_response_body')
resp, body = self.client.json_request('GET', '/images')
self.assertEqual(200, resp.status)
self.assertEqual('fake_response_body', body)
+ def test_json_request_with_json_content_type_header_in_req_and_resp(self):
+ self._set_response_fixture({'content-type': 'application/json'},
+ 200, 'fake_response_body')
+ resp, body = self.client.json_request('GET', '/images', headers={
+ 'Content-Type': 'application/json'})
+ self.assertEqual(200, resp.status)
+ self.assertEqual('fake_response_body', body)
+
def test_json_request_fails_to_json_loads(self):
self._set_response_fixture({'content-type': 'application/json'},
200, 'fake_response_body')
diff --git a/tempest/tests/test_rest_client.py b/tempest/tests/test_rest_client.py
index 64ad3bc..a351bd5 100644
--- a/tempest/tests/test_rest_client.py
+++ b/tempest/tests/test_rest_client.py
@@ -15,11 +15,12 @@
import httplib2
import json
+from oslotest import mockpatch
+
from tempest.common import rest_client
from tempest.common import xml_utils as xml
from tempest import config
from tempest import exceptions
-from tempest.openstack.common.fixture import mockpatch
from tempest.tests import base
from tempest.tests import fake_auth_provider
from tempest.tests import fake_config
@@ -540,3 +541,50 @@
self.assertRaises(AssertionError,
self.negative_rest_client.send_request,
'OTHER', self.url, [])
+
+
+class TestExpectedSuccess(BaseRestClientTestClass):
+
+ def setUp(self):
+ self.fake_http = fake_http.fake_httplib2()
+ super(TestExpectedSuccess, self).setUp()
+
+ def test_expected_succes_int_match(self):
+ expected_code = 202
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_int_no_match(self):
+ expected_code = 204
+ read_code = 202
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_expected_succes_list_match(self):
+ expected_code = [202, 204]
+ read_code = 202
+ resp = self.rest_client.expected_success(expected_code, read_code)
+ # Assert None resp on success
+ self.assertFalse(resp)
+
+ def test_expected_succes_list_no_match(self):
+ expected_code = [202, 204]
+ read_code = 200
+ self.assertRaises(exceptions.InvalidHttpSuccessCode,
+ self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_int(self):
+ expected_code = 404
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
+
+ def test_non_success_expected_list(self):
+ expected_code = [404, 202]
+ read_code = 202
+ self.assertRaises(AssertionError, self.rest_client.expected_success,
+ expected_code, read_code)
diff --git a/tempest/tests/test_ssh.py b/tempest/tests/test_ssh.py
index a6eedc4..0da52dc 100644
--- a/tempest/tests/test_ssh.py
+++ b/tempest/tests/test_ssh.py
@@ -14,6 +14,7 @@
import contextlib
import socket
+import time
import mock
import testtools
@@ -43,25 +44,21 @@
rsa_mock.assert_not_called()
cs_mock.assert_not_called()
- def test_get_ssh_connection(self):
- c_mock = self.patch('paramiko.SSHClient')
- aa_mock = self.patch('paramiko.AutoAddPolicy')
- s_mock = self.patch('time.sleep')
- t_mock = self.patch('time.time')
+ def _set_ssh_connection_mocks(self):
+ client_mock = mock.MagicMock()
+ client_mock.connect.return_value = True
+ return (self.patch('paramiko.SSHClient'),
+ self.patch('paramiko.AutoAddPolicy'),
+ client_mock)
+ def test_get_ssh_connection(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
+ s_mock = self.patch('time.sleep')
+
+ c_mock.return_value = client_mock
aa_mock.return_value = mock.sentinel.aa
- def reset_mocks():
- aa_mock.reset_mock()
- c_mock.reset_mock()
- s_mock.reset_mock()
- t_mock.reset_mock()
-
# Test normal case for successful connection on first try
- client_mock = mock.MagicMock()
- c_mock.return_value = client_mock
- client_mock.connect.return_value = True
-
client = ssh.Client('localhost', 'root', timeout=2)
client._get_ssh_connection(sleep=1)
@@ -79,50 +76,40 @@
)]
self.assertEqual(expected_connect, client_mock.connect.mock_calls)
s_mock.assert_not_called()
- t_mock.assert_called_once_with()
- reset_mocks()
+ def test_get_ssh_connection_two_attemps(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
- # Test case when connection fails on first two tries and
- # succeeds on third try (this validates retry logic)
- client_mock.connect.side_effect = [socket.error, socket.error, True]
- t_mock.side_effect = [
- 1000, # Start time
- 1000, # LOG.warning() calls time.time() loop 1
- 1001, # Sleep loop 1
- 1001, # LOG.warning() calls time.time() loop 2
- 1002 # Sleep loop 2
+ c_mock.return_value = client_mock
+ client_mock.connect.side_effect = [
+ socket.error,
+ mock.MagicMock()
]
+ client = ssh.Client('localhost', 'root', timeout=1)
+ start_time = int(time.time())
client._get_ssh_connection(sleep=1)
+ end_time = int(time.time())
+ self.assertTrue((end_time - start_time) < 3)
+ self.assertTrue((end_time - start_time) > 1)
- expected_sleeps = [
- mock.call(2),
- mock.call(3)
- ]
- self.assertEqual(expected_sleeps, s_mock.mock_calls)
+ def test_get_ssh_connection_timeout(self):
+ c_mock, aa_mock, client_mock = self._set_ssh_connection_mocks()
- reset_mocks()
-
- # Test case when connection fails on first three tries and
- # exceeds the timeout, so expect to raise a Timeout exception
+ c_mock.return_value = client_mock
client_mock.connect.side_effect = [
socket.error,
socket.error,
- socket.error
- ]
- t_mock.side_effect = [
- 1000, # Start time
- 1000, # LOG.warning() calls time.time() loop 1
- 1001, # Sleep loop 1
- 1001, # LOG.warning() calls time.time() loop 2
- 1002, # Sleep loop 2
- 1003, # Sleep loop 3
- 1004 # LOG.error() calls time.time()
+ socket.error,
]
+ client = ssh.Client('localhost', 'root', timeout=2)
+ start_time = int(time.time())
with testtools.ExpectedException(exceptions.SSHTimeout):
client._get_ssh_connection()
+ end_time = int(time.time())
+ self.assertTrue((end_time - start_time) < 4)
+ self.assertTrue((end_time - start_time) >= 2)
def test_exec_command(self):
gsc_mock = self.patch('tempest.common.ssh.Client._get_ssh_connection')
diff --git a/tempest/tests/test_tenant_isolation.py b/tempest/tests/test_tenant_isolation.py
index 7a9b6be..485beff 100644
--- a/tempest/tests/test_tenant_isolation.py
+++ b/tempest/tests/test_tenant_isolation.py
@@ -59,6 +59,8 @@
'_get_object_storage_client'))
self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager,
'_get_orchestration_client'))
+ self.useFixture(mockpatch.PatchObject(clients.OfficialClientManager,
+ '_get_ceilometer_client'))
iso_creds = isolated_creds.IsolatedCreds('test class',
tempest_client=False)
self.assertTrue(isinstance(iso_creds.identity_admin_client,
diff --git a/tempest/thirdparty/boto/test.py b/tempest/thirdparty/boto/test.py
index 4c39f78..d3cbc4b 100644
--- a/tempest/thirdparty/boto/test.py
+++ b/tempest/thirdparty/boto/test.py
@@ -56,8 +56,8 @@
A_I_IMAGES_READY = all_read(ami_path, aki_path, ari_path)
boto_logger = logging.getLogger('boto')
level = boto_logger.logger.level
- boto_logger.logger.setLevel(orig_logging.CRITICAL) # suppress logging
- # for these
+ # suppress logging for boto
+ boto_logger.logger.setLevel(orig_logging.CRITICAL)
def _cred_sub_check(connection_data):
if not id_matcher.match(connection_data["aws_access_key_id"]):
diff --git a/tempest/thirdparty/boto/test_ec2_instance_run.py b/tempest/thirdparty/boto/test_ec2_instance_run.py
index b2eb18d..7713931 100644
--- a/tempest/thirdparty/boto/test_ec2_instance_run.py
+++ b/tempest/thirdparty/boto/test_ec2_instance_run.py
@@ -20,7 +20,6 @@
from tempest import config
from tempest import exceptions
from tempest.openstack.common import log as logging
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
from tempest.thirdparty.boto.utils import s3
from tempest.thirdparty.boto.utils import wait
@@ -80,10 +79,9 @@
if state != "available":
for _image in cls.images.itervalues():
cls.ec2_client.deregister_image(_image["image_id"])
- raise exceptions.EC2RegisterImageException(image_id=
- image["image_id"])
+ raise exceptions.EC2RegisterImageException(
+ image_id=image["image_id"])
- @test.attr(type='smoke')
def test_run_idempotent_instances(self):
# EC2 run instances idempotently
@@ -121,7 +119,6 @@
_terminate_reservation(reservation_1, rcuk_1)
_terminate_reservation(reservation_2, rcuk_2)
- @test.attr(type='smoke')
def test_run_stop_terminate_instance(self):
# EC2 run, stop and terminate instance
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -146,7 +143,6 @@
instance.terminate()
self.cancelResourceCleanUp(rcuk)
- @test.attr(type='smoke')
def test_run_stop_terminate_instance_with_tags(self):
# EC2 run, stop and terminate instance with tags
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -193,7 +189,6 @@
instance.terminate()
self.cancelResourceCleanUp(rcuk)
- @test.attr(type='smoke')
def test_run_terminate_instance(self):
# EC2 run, terminate immediately
image_ami = self.ec2_client.get_image(self.images["ami"]
@@ -217,7 +212,6 @@
else:
self.assertNotEqual(instance.state, "running")
- @test.attr(type='smoke')
def test_compute_with_volumes(self):
# EC2 1. integration test (not strict)
image_ami = self.ec2_client.get_image(self.images["ami"]["image_id"])
diff --git a/tempest/thirdparty/boto/test_ec2_keys.py b/tempest/thirdparty/boto/test_ec2_keys.py
index dec0170..698e3e1 100644
--- a/tempest/thirdparty/boto/test_ec2_keys.py
+++ b/tempest/thirdparty/boto/test_ec2_keys.py
@@ -32,7 +32,6 @@
cls.ec = cls.ec2_error_code
# TODO(afazekas): merge create, delete, get test cases
- @test.attr(type='smoke')
def test_create_ec2_keypair(self):
# EC2 create KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -42,7 +41,6 @@
self.client.get_key_pair(key_name)))
@test.skip_because(bug="1072318")
- @test.attr(type='smoke')
def test_delete_ec2_keypair(self):
# EC2 delete KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -50,7 +48,6 @@
self.client.delete_key_pair(key_name)
self.assertIsNone(self.client.get_key_pair(key_name))
- @test.attr(type='smoke')
def test_get_ec2_keypair(self):
# EC2 get KeyPair
key_name = data_utils.rand_name("keypair-")
@@ -59,7 +56,6 @@
self.assertTrue(compare_key_pairs(keypair,
self.client.get_key_pair(key_name)))
- @test.attr(type='smoke')
def test_duplicate_ec2_keypair(self):
# EC2 duplicate KeyPair
key_name = data_utils.rand_name("keypair-")
diff --git a/tempest/thirdparty/boto/test_ec2_network.py b/tempest/thirdparty/boto/test_ec2_network.py
index d508c07..792dde3 100644
--- a/tempest/thirdparty/boto/test_ec2_network.py
+++ b/tempest/thirdparty/boto/test_ec2_network.py
@@ -26,7 +26,6 @@
# Note(afazekas): these tests for things duable without an instance
@test.skip_because(bug="1080406")
- @test.attr(type='smoke')
def test_disassociate_not_associated_floating_ip(self):
# EC2 disassociate not associated floating ip
ec2_codes = self.ec2_error_code
diff --git a/tempest/thirdparty/boto/test_ec2_security_groups.py b/tempest/thirdparty/boto/test_ec2_security_groups.py
index 86140ec..7d9bdab 100644
--- a/tempest/thirdparty/boto/test_ec2_security_groups.py
+++ b/tempest/thirdparty/boto/test_ec2_security_groups.py
@@ -14,7 +14,6 @@
# under the License.
from tempest.common.utils import data_utils
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
@@ -25,7 +24,6 @@
super(EC2SecurityGroupTest, cls).setUpClass()
cls.client = cls.os.ec2api_client
- @test.attr(type='smoke')
def test_create_authorize_security_group(self):
# EC2 Create, authorize/revoke security group
group_name = data_utils.rand_name("securty_group-")
diff --git a/tempest/thirdparty/boto/test_ec2_volumes.py b/tempest/thirdparty/boto/test_ec2_volumes.py
index 12dea18..b50c6b0 100644
--- a/tempest/thirdparty/boto/test_ec2_volumes.py
+++ b/tempest/thirdparty/boto/test_ec2_volumes.py
@@ -15,7 +15,6 @@
from tempest import config
from tempest.openstack.common import log as logging
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
CONF = config.CONF
@@ -40,7 +39,6 @@
cls.client = cls.os.ec2api_client
cls.zone = CONF.boto.aws_zone
- @test.attr(type='smoke')
def test_create_get_delete(self):
# EC2 Create, get, delete Volume
volume = self.client.create_volume(1, self.zone)
@@ -53,7 +51,6 @@
self.client.delete_volume(volume.id)
self.cancelResourceCleanUp(cuk)
- @test.attr(type='smoke')
def test_create_volume_from_snapshot(self):
# EC2 Create volume from snapshot
volume = self.client.create_volume(1, self.zone)
diff --git a/tempest/thirdparty/boto/test_s3_buckets.py b/tempest/thirdparty/boto/test_s3_buckets.py
index af6aa8b..3a8dc89 100644
--- a/tempest/thirdparty/boto/test_s3_buckets.py
+++ b/tempest/thirdparty/boto/test_s3_buckets.py
@@ -26,7 +26,6 @@
cls.client = cls.os.s3_client
@test.skip_because(bug="1076965")
- @test.attr(type='smoke')
def test_create_and_get_delete_bucket(self):
# S3 Create, get and delete bucket
bucket_name = data_utils.rand_name("s3bucket-")
diff --git a/tempest/thirdparty/boto/test_s3_ec2_images.py b/tempest/thirdparty/boto/test_s3_ec2_images.py
index d2300ee..389e25c 100644
--- a/tempest/thirdparty/boto/test_s3_ec2_images.py
+++ b/tempest/thirdparty/boto/test_s3_ec2_images.py
@@ -17,7 +17,6 @@
from tempest.common.utils import data_utils
from tempest import config
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
from tempest.thirdparty.boto.utils import s3
@@ -48,7 +47,6 @@
cls.bucket_name)
s3.s3_upload_dir(bucket, cls.materials_path)
- @test.attr(type='smoke')
def test_register_get_deregister_ami_image(self):
# Register and deregister ami image
image = {"name": data_utils.rand_name("ami-name-"),
diff --git a/tempest/thirdparty/boto/test_s3_objects.py b/tempest/thirdparty/boto/test_s3_objects.py
index 1ae46de..db3c1cf 100644
--- a/tempest/thirdparty/boto/test_s3_objects.py
+++ b/tempest/thirdparty/boto/test_s3_objects.py
@@ -18,7 +18,6 @@
import boto.s3.key
from tempest.common.utils import data_utils
-from tempest import test
from tempest.thirdparty.boto import test as boto_test
@@ -29,7 +28,6 @@
super(S3BucketsTest, cls).setUpClass()
cls.client = cls.os.s3_client
- @test.attr(type='smoke')
def test_create_get_delete_object(self):
# S3 Create, get and delete object
bucket_name = data_utils.rand_name("s3bucket-")
diff --git a/test-requirements.txt b/test-requirements.txt
index 942a7c3..215f28b 100644
--- a/test-requirements.txt
+++ b/test-requirements.txt
@@ -1,9 +1,10 @@
-hacking>=0.8.0,<0.9
+hacking>=0.9.2,<0.10
# needed for doc build
docutils==0.9.1
-sphinx>=1.2.1,<1.3
+sphinx>=1.1.2,!=1.2.0,<1.3
python-subunit>=0.0.18
oslosphinx
mox>=0.5.3
mock>=1.0
coverage>=3.6
+oslotest
diff --git a/tools/pretty_tox.sh b/tools/pretty_tox.sh
index f3c88f3..0a04ce6 100755
--- a/tools/pretty_tox.sh
+++ b/tools/pretty_tox.sh
@@ -3,4 +3,4 @@
set -o pipefail
TESTRARGS=$1
-python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py
+python setup.py testr --slowest --testr-args="--subunit $TESTRARGS" | $(dirname $0)/subunit-trace.py --no-failure-debug -f
diff --git a/tools/pretty_tox_serial.sh b/tools/pretty_tox_serial.sh
index 1634b8e..db70890 100755
--- a/tools/pretty_tox_serial.sh
+++ b/tools/pretty_tox_serial.sh
@@ -7,7 +7,8 @@
if [ ! -d .testrepository ]; then
testr init
fi
-testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py
+testr run --subunit $TESTRARGS | $(dirname $0)/subunit-trace.py -f -n
retval=$?
testr slowest
+
exit $retval
diff --git a/tools/subunit-trace.py b/tools/subunit-trace.py
index 7bb88a4..c6f8eab 100755
--- a/tools/subunit-trace.py
+++ b/tools/subunit-trace.py
@@ -18,6 +18,7 @@
"""Trace a subunit stream in reasonable detail and high accuracy."""
+import argparse
import functools
import re
import sys
@@ -151,7 +152,7 @@
stream.write(" %s\n" % line)
-def show_outcome(stream, test):
+def show_outcome(stream, test, print_failures=False):
global RESULTS
status = test['status']
# TODO(sdague): ask lifeless why on this?
@@ -178,14 +179,16 @@
FAILS.append(test)
stream.write('{%s} %s [%s] ... FAILED\n' % (
worker, name, duration))
- print_attachments(stream, test, all_channels=True)
+ if not print_failures:
+ print_attachments(stream, test, all_channels=True)
elif status == 'skip':
stream.write('{%s} %s ... SKIPPED: %s\n' % (
worker, name, test['details']['reason'].as_text()))
else:
stream.write('{%s} %s [%s] ... %s\n' % (
worker, name, duration, test['status']))
- print_attachments(stream, test, all_channels=True)
+ if not print_failures:
+ print_attachments(stream, test, all_channels=True)
stream.flush()
@@ -218,6 +221,14 @@
return count
+def run_time():
+ runtime = 0.0
+ for k, v in RESULTS.items():
+ for test in v:
+ runtime += float(get_duration(test['timestamps']).strip('s'))
+ return runtime
+
+
def worker_stats(worker):
tests = RESULTS[worker]
num_tests = len(tests)
@@ -227,7 +238,8 @@
def print_summary(stream):
stream.write("\n======\nTotals\n======\n")
- stream.write("Run: %s\n" % count_tests('status', '.*'))
+ stream.write("Run: %s in %s sec.\n" % (count_tests('status', '.*'),
+ run_time()))
stream.write(" - Passed: %s\n" % count_tests('status', 'success'))
stream.write(" - Skipped: %s\n" % count_tests('status', 'skip'))
stream.write(" - Failed: %s\n" % count_tests('status', 'fail'))
@@ -247,12 +259,25 @@
(w, num, time))
+def parse_args():
+ parser = argparse.ArgumentParser()
+ parser.add_argument('--no-failure-debug', '-n', action='store_true',
+ dest='print_failures', help='Disable printing failure '
+ 'debug infomation in realtime')
+ parser.add_argument('--fails', '-f', action='store_true',
+ dest='post_fails', help='Print failure debug '
+ 'information after the stream is proccesed')
+ return parser.parse_args()
+
+
def main():
+ args = parse_args()
stream = subunit.ByteStreamToStreamResult(
sys.stdin, non_subunit_name='stdout')
starts = Starts(sys.stdout)
outcomes = testtools.StreamToDict(
- functools.partial(show_outcome, sys.stdout))
+ functools.partial(show_outcome, sys.stdout,
+ print_failures=args.print_failures))
summary = testtools.StreamSummary()
result = testtools.CopyStreamResult([starts, outcomes, summary])
result.startTestRun()
@@ -260,6 +285,8 @@
stream.run(result)
finally:
result.stopTestRun()
+ if args.post_fails:
+ print_fails(sys.stdout)
print_summary(sys.stdout)
return (0 if summary.wasSuccessful() else 1)
diff --git a/tox.ini b/tox.ini
index 2110362..c1acde9 100644
--- a/tox.ini
+++ b/tox.ini
@@ -77,7 +77,7 @@
[testenv:stress]
sitepackages = True
commands =
- python -m tempest/stress/run_stress -a -d 3600 -S
+ run-tempest-stress -a -d 3600 -S
[testenv:venv]
commands = {posargs}
@@ -99,6 +99,8 @@
[flake8]
# E125 is a won't fix until https://github.com/jcrocholl/pep8/issues/126 is resolved. For further detail see https://review.openstack.org/#/c/36788/
-ignore = E125,H404
+# H402 skipped because some docstrings aren't sentences
+# Skipped because of new hacking 0.9: H407,H405,H904,H305,E123,H307,E122,E129,E128
+ignore = E125,H402,H404,H407,H405,H904,H305,E123,H307,E122,E129,E128
show-source = True
exclude = .git,.venv,.tox,dist,doc,openstack,*egg