Merge "Add sample config check to tox pep8 job"
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log.py b/tempest/api/compute/admin/test_instance_usage_audit_log.py
new file mode 100644
index 0000000..cea6e92
--- /dev/null
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log.py
@@ -0,0 +1,64 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest.test import attr
+import urllib
+
+
+class InstanceUsageAuditLogTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceUsageAuditLogTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.instance_usages_audit_log_client
+
+ @attr(type='gate')
+ def test_list_instance_usage_audit_logs(self):
+ # list instance usage audit logs
+ resp, body = self.adm_client.list_instance_usage_audit_logs()
+ self.assertEqual(200, resp.status)
+ expected_items = ['total_errors', 'total_instances', 'log',
+ 'num_hosts_running', 'num_hosts_done',
+ 'num_hosts', 'hosts_not_run', 'overall_status',
+ 'period_ending', 'period_beginning',
+ 'num_hosts_not_run']
+ for item in expected_items:
+ self.assertIn(item, body)
+
+ @attr(type='gate')
+ def test_get_instance_usage_audit_log(self):
+ # Get instance usage audit log before specified time
+ now = datetime.datetime.now()
+ resp, body = self.adm_client.get_instance_usage_audit_log(
+ urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
+
+ self.assertEqual(200, resp.status)
+ expected_items = ['total_errors', 'total_instances', 'log',
+ 'num_hosts_running', 'num_hosts_done', 'num_hosts',
+ 'hosts_not_run', 'overall_status', 'period_ending',
+ 'period_beginning', 'num_hosts_not_run']
+ for item in expected_items:
+ self.assertIn(item, body)
+
+
+class InstanceUsageAuditLogTestXML(InstanceUsageAuditLogTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
new file mode 100644
index 0000000..dcf41c5
--- /dev/null
+++ b/tempest/api/compute/admin/test_instance_usage_audit_log_negative.py
@@ -0,0 +1,56 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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.
+
+import datetime
+
+from tempest.api.compute import base
+from tempest import exceptions
+from tempest.test import attr
+import urllib
+
+
+class InstanceUsageAuditLogNegativeTestJSON(base.BaseV2ComputeAdminTest):
+
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(InstanceUsageAuditLogNegativeTestJSON, cls).setUpClass()
+ cls.adm_client = cls.os_adm.instance_usages_audit_log_client
+
+ @attr(type=['negative', 'gate'])
+ def test_instance_usage_audit_logs_with_nonadmin_user(self):
+ # the instance_usage_audit_logs API just can be accessed by admin user
+ self.assertRaises(exceptions.Unauthorized,
+ self.instance_usages_audit_log_client.
+ list_instance_usage_audit_logs)
+ now = datetime.datetime.now()
+ self.assertRaises(exceptions.Unauthorized,
+ self.instance_usages_audit_log_client.
+ get_instance_usage_audit_log,
+ urllib.quote(now.strftime("%Y-%m-%d %H:%M:%S")))
+
+ @attr(type=['negative', 'gate'])
+ def test_get_instance_usage_audit_logs_with_invalid_time(self):
+ self.assertRaises(exceptions.BadRequest,
+ self.adm_client.get_instance_usage_audit_log,
+ "invalid_time")
+
+
+class InstanceUsageAuditLogNegativeTestXML(
+ InstanceUsageAuditLogNegativeTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/base.py b/tempest/api/compute/base.py
index dac245b..4b6816b 100644
--- a/tempest/api/compute/base.py
+++ b/tempest/api/compute/base.py
@@ -155,6 +155,8 @@
cls.availability_zone_client = cls.os.availability_zone_client
cls.aggregates_client = cls.os.aggregates_client
cls.services_client = cls.os.services_client
+ cls.instance_usages_audit_log_client = \
+ cls.os.instance_usages_audit_log_client
cls.hypervisor_client = cls.os.hypervisor_client
cls.servers_client_v3_auth = cls.os.servers_client_v3_auth
cls.certificates_client = cls.os.certificates_client
diff --git a/tempest/api/compute/servers/test_list_servers_negative.py b/tempest/api/compute/servers/test_list_servers_negative.py
index a06e209..521a480 100644
--- a/tempest/api/compute/servers/test_list_servers_negative.py
+++ b/tempest/api/compute/servers/test_list_servers_negative.py
@@ -17,71 +17,19 @@
import datetime
-from tempest.api import compute
from tempest.api.compute import base
-from tempest import clients
from tempest import exceptions
from tempest.test import attr
class ListServersNegativeTestJSON(base.BaseV2ComputeTest):
_interface = 'json'
-
- @classmethod
- def _ensure_no_servers(cls, servers, username, tenant_name):
- """
- If there are servers and there is tenant isolation then a
- skipException is raised to skip the test since it requires no servers
- to already exist for the given user/tenant.
- If there are servers and there is not tenant isolation then the test
- blocks while the servers are being deleted.
- """
- if len(servers):
- if not cls.config.compute.allow_tenant_isolation:
- for srv in servers:
- cls.client.wait_for_server_termination(srv['id'],
- ignore_error=True)
- else:
- msg = ("User/tenant %(u)s/%(t)s already have "
- "existing server instances. Skipping test." %
- {'u': username, 't': tenant_name})
- raise cls.skipException(msg)
+ force_tenant_isolation = True
@classmethod
def setUpClass(cls):
super(ListServersNegativeTestJSON, cls).setUpClass()
cls.client = cls.servers_client
- cls.servers = []
-
- if compute.MULTI_USER:
- if cls.config.compute.allow_tenant_isolation:
- creds = cls.isolated_creds.get_alt_creds()
- username, tenant_name, password = creds
- cls.alt_manager = clients.Manager(username=username,
- password=password,
- tenant_name=tenant_name)
- else:
- # Use the alt_XXX credentials in the config file
- cls.alt_manager = clients.AltManager()
- cls.alt_client = cls.alt_manager.servers_client
-
- # Under circumstances when there is not a tenant/user
- # created for the test case, the test case checks
- # to see if there are existing servers for the
- # either the normal user/tenant or the alt user/tenant
- # and if so, the whole test is skipped. We do this
- # because we assume a baseline of no servers at the
- # start of the test instead of destroying any existing
- # servers.
- resp, body = cls.client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.os.username,
- cls.os.tenant_name)
-
- resp, body = cls.alt_client.list_servers()
- cls._ensure_no_servers(body['servers'],
- cls.alt_manager.username,
- cls.alt_manager.tenant_name)
# The following servers are created for use
# by the test methods in this class. These
diff --git a/tempest/api/compute/v3/servers/test_attach_interfaces.py b/tempest/api/compute/v3/servers/test_attach_interfaces.py
new file mode 100644
index 0000000..a177cea
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_attach_interfaces.py
@@ -0,0 +1,118 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+from tempest.api.compute import base
+from tempest.test import attr
+
+import time
+
+
+class AttachInterfacesTestJSON(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ if not cls.config.service_available.neutron:
+ raise cls.skipException("Neutron is required")
+ super(AttachInterfacesTestJSON, cls).setUpClass()
+ cls.client = cls.os.interfaces_client
+
+ def _check_interface(self, iface, port_id=None, network_id=None,
+ fixed_ip=None):
+ self.assertIn('port_state', iface)
+ if port_id:
+ self.assertEqual(iface['port_id'], port_id)
+ if network_id:
+ self.assertEqual(iface['net_id'], network_id)
+ if fixed_ip:
+ self.assertEqual(iface['fixed_ips'][0]['ip_address'], fixed_ip)
+
+ def _create_server_get_interfaces(self):
+ resp, server = self.create_test_server(wait_until='ACTIVE')
+ resp, ifs = self.client.list_interfaces(server['id'])
+ resp, body = self.client.wait_for_interface_status(
+ server['id'], ifs[0]['port_id'], 'ACTIVE')
+ ifs[0]['port_state'] = body['port_state']
+ return server, ifs
+
+ def _test_create_interface(self, server):
+ resp, iface = self.client.create_interface(server['id'])
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface)
+ return iface
+
+ def _test_create_interface_by_network_id(self, server, ifs):
+ network_id = ifs[0]['net_id']
+ resp, iface = self.client.create_interface(server['id'],
+ network_id=network_id)
+ resp, iface = self.client.wait_for_interface_status(
+ server['id'], iface['port_id'], 'ACTIVE')
+ self._check_interface(iface, network_id=network_id)
+ return iface
+
+ def _test_show_interface(self, server, ifs):
+ iface = ifs[0]
+ resp, _iface = self.client.show_interface(server['id'],
+ iface['port_id'])
+ self.assertEqual(iface, _iface)
+
+ def _test_delete_interface(self, server, ifs):
+ # NOTE(danms): delete not the first or last, but one in the middle
+ iface = ifs[1]
+ self.client.delete_interface(server['id'], iface['port_id'])
+ for i in range(0, 5):
+ _r, _ifs = self.client.list_interfaces(server['id'])
+ if len(ifs) != len(_ifs):
+ break
+ time.sleep(1)
+
+ self.assertEqual(len(_ifs), len(ifs) - 1)
+ for _iface in _ifs:
+ self.assertNotEqual(iface['port_id'], _iface['port_id'])
+ return _ifs
+
+ def _compare_iface_list(self, list1, list2):
+ # NOTE(danms): port_state will likely have changed, so just
+ # confirm the port_ids are the same at least
+ list1 = [x['port_id'] for x in list1]
+ list2 = [x['port_id'] for x in list2]
+
+ self.assertEqual(sorted(list1), sorted(list2))
+
+ @attr(type='gate')
+ def test_create_list_show_delete_interfaces(self):
+ server, ifs = self._create_server_get_interfaces()
+ interface_count = len(ifs)
+ self.assertTrue(interface_count > 0)
+ self._check_interface(ifs[0])
+
+ iface = self._test_create_interface(server)
+ ifs.append(iface)
+
+ iface = self._test_create_interface_by_network_id(server, ifs)
+ ifs.append(iface)
+
+ resp, _ifs = self.client.list_interfaces(server['id'])
+ self._compare_iface_list(ifs, _ifs)
+
+ self._test_show_interface(server, ifs)
+
+ _ifs = self._test_delete_interface(server, ifs)
+ self.assertEqual(len(ifs) - 1, len(_ifs))
+
+
+class AttachInterfacesTestXML(AttachInterfacesTestJSON):
+ _interface = 'xml'
diff --git a/tempest/api/compute/v3/servers/test_server_addresses.py b/tempest/api/compute/v3/servers/test_server_addresses.py
new file mode 100644
index 0000000..7ca8a52
--- /dev/null
+++ b/tempest/api/compute/v3/servers/test_server_addresses.py
@@ -0,0 +1,84 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2012 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.compute import base
+from tempest import exceptions
+from tempest.test import attr
+
+
+class ServerAddressesTest(base.BaseV2ComputeTest):
+ _interface = 'json'
+
+ @classmethod
+ def setUpClass(cls):
+ super(ServerAddressesTest, cls).setUpClass()
+ cls.client = cls.servers_client
+
+ resp, cls.server = cls.create_test_server(wait_until='ACTIVE')
+
+ @attr(type=['negative', 'gate'])
+ def test_list_server_addresses_invalid_server_id(self):
+ # List addresses request should fail if server id not in system
+ self.assertRaises(exceptions.NotFound, self.client.list_addresses,
+ '999')
+
+ @attr(type=['negative', 'gate'])
+ def test_list_server_addresses_by_network_neg(self):
+ # List addresses by network should fail if network name not valid
+ self.assertRaises(exceptions.NotFound,
+ self.client.list_addresses_by_network,
+ self.server['id'], 'invalid')
+
+ @attr(type='smoke')
+ def test_list_server_addresses(self):
+ # All public and private addresses for
+ # a server should be returned
+
+ resp, addresses = self.client.list_addresses(self.server['id'])
+ self.assertEqual('200', resp['status'])
+
+ # We do not know the exact network configuration, but an instance
+ # should at least have a single public or private address
+ self.assertTrue(len(addresses) >= 1)
+ for network_name, network_addresses in addresses.iteritems():
+ self.assertTrue(len(network_addresses) >= 1)
+ for address in network_addresses:
+ self.assertTrue(address['addr'])
+ self.assertTrue(address['version'])
+
+ @attr(type='smoke')
+ def test_list_server_addresses_by_network(self):
+ # Providing a network type should filter
+ # the addresses return by that type
+
+ resp, addresses = self.client.list_addresses(self.server['id'])
+
+ # Once again we don't know the environment's exact network config,
+ # but the response for each individual network should be the same
+ # as the partial result of the full address list
+ id = self.server['id']
+ for addr_type in addresses:
+ resp, addr = self.client.list_addresses_by_network(id, addr_type)
+ self.assertEqual('200', resp['status'])
+
+ addr = addr[addr_type]
+ for address in addresses[addr_type]:
+ self.assertTrue(any([a for a in addr if a == address]))
+
+
+class ServerAddressesTestXML(ServerAddressesTest):
+ _interface = 'xml'
diff --git a/tempest/api/volume/test_volume_transfers.py b/tempest/api/volume/test_volume_transfers.py
new file mode 100644
index 0000000..dacebf1
--- /dev/null
+++ b/tempest/api/volume/test_volume_transfers.py
@@ -0,0 +1,120 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 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.
+
+from tempest.api.volume.base import BaseVolumeTest
+from tempest import clients
+from tempest.common.utils.data_utils import rand_name
+from tempest.test import attr
+
+
+class VolumesTransfersTest(BaseVolumeTest):
+ _interface = "json"
+
+ @classmethod
+ def setUpClass(cls):
+ super(VolumesTransfersTest, cls).setUpClass()
+
+ # Add another tenant to test volume-transfer
+ if cls.config.compute.allow_tenant_isolation:
+ creds = cls.isolated_creds.get_alt_creds()
+ username, tenant_name, password = creds
+ cls.os_alt = clients.Manager(username=username,
+ password=password,
+ tenant_name=tenant_name,
+ interface=cls._interface)
+ cls.alt_tenant_id = cls.isolated_creds.get_alt_tenant()['id']
+
+ # Add admin tenant to cleanup resources
+ adm_creds = cls.isolated_creds.get_admin_creds()
+ admin_username, admin_tenant_name, admin_password = adm_creds
+ cls.os_adm = clients.Manager(username=admin_username,
+ password=admin_password,
+ tenant_name=admin_tenant_name,
+ interface=cls._interface)
+ else:
+ cls.os_alt = clients.AltManager()
+ alt_tenant_name = cls.os_alt.tenant_name
+ identity_client = cls._get_identity_admin_client()
+ _, tenants = identity_client.list_tenants()
+ cls.alt_tenant_id = [tnt['id'] for tnt in tenants
+ if tnt['name'] == alt_tenant_name][0]
+ cls.os_adm = clients.ComputeAdminManager(interface=cls._interface)
+
+ cls.client = cls.volumes_client
+ cls.alt_client = cls.os_alt.volumes_client
+ cls.adm_client = cls.os_adm.volumes_client
+
+ @attr(type='gate')
+ def test_create_get_list_accept_volume_transfer(self):
+ # Create a volume first
+ vol_name = rand_name('-Volume-')
+ _, volume = self.client.create_volume(size=1, display_name=vol_name)
+ self.addCleanup(self.adm_client.delete_volume, volume['id'])
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+ # Create a volume transfer
+ resp, transfer = self.client.create_volume_transfer(volume['id'])
+ self.assertEqual(202, resp.status)
+ transfer_id = transfer['id']
+ auth_key = transfer['auth_key']
+ self.client.wait_for_volume_status(volume['id'],
+ 'awaiting-transfer')
+
+ # Get a volume transfer
+ resp, body = self.client.get_volume_transfer(transfer_id)
+ self.assertEqual(200, resp.status)
+ self.assertEqual(volume['id'], body['volume_id'])
+
+ # List volume transfers, the result should be greater than
+ # or equal to 1
+ resp, body = self.client.list_volume_transfers()
+ self.assertEqual(200, resp.status)
+ self.assertGreaterEqual(len(body), 1)
+
+ # Accept a volume transfer by alt_tenant
+ resp, body = self.alt_client.accept_volume_transfer(transfer_id,
+ auth_key)
+ self.assertEqual(202, resp.status)
+ self.alt_client.wait_for_volume_status(volume['id'], 'available')
+
+ def test_create_list_delete_volume_transfer(self):
+ # Create a volume first
+ vol_name = rand_name('-Volume-')
+ _, volume = self.client.create_volume(size=1, display_name=vol_name)
+ self.addCleanup(self.adm_client.delete_volume, volume['id'])
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+ # Create a volume transfer
+ resp, body = self.client.create_volume_transfer(volume['id'])
+ self.assertEqual(202, resp.status)
+ transfer_id = body['id']
+ self.client.wait_for_volume_status(volume['id'],
+ 'awaiting-transfer')
+
+ # List all volume transfers, there's only one in this test
+ resp, body = self.client.list_volume_transfers()
+ self.assertEqual(200, resp.status)
+ self.assertEqual(volume['id'], body[0]['volume_id'])
+
+ # Delete a volume transfer
+ resp, body = self.client.delete_volume_transfer(transfer_id)
+ self.assertEqual(202, resp.status)
+ self.client.wait_for_volume_status(volume['id'], 'available')
+
+
+class VolumesTransfersTestXML(VolumesTransfersTest):
+ _interface = "xml"
diff --git a/tempest/clients.py b/tempest/clients.py
index a52ed5d..9029d5f 100644
--- a/tempest/clients.py
+++ b/tempest/clients.py
@@ -35,6 +35,8 @@
from tempest.services.compute.json.hypervisor_client import \
HypervisorClientJSON
from tempest.services.compute.json.images_client import ImagesClientJSON
+from tempest.services.compute.json.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClientJSON
from tempest.services.compute.json.interfaces_client import \
InterfacesClientJSON
from tempest.services.compute.json.keypairs_client import KeyPairsClientJSON
@@ -71,6 +73,8 @@
FloatingIPsClientXML
from tempest.services.compute.xml.hypervisor_client import HypervisorClientXML
from tempest.services.compute.xml.images_client import ImagesClientXML
+from tempest.services.compute.xml.instance_usage_audit_log_client import \
+ InstanceUsagesAuditLogClientXML
from tempest.services.compute.xml.interfaces_client import \
InterfacesClientXML
from tempest.services.compute.xml.keypairs_client import KeyPairsClientXML
@@ -217,6 +221,8 @@
self.token_v3_client = V3TokenClientXML(*client_args)
self.network_client = NetworkClientXML(*client_args)
self.credentials_client = CredentialsClientXML(*client_args)
+ self.instance_usages_audit_log_client = \
+ InstanceUsagesAuditLogClientXML(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientXML(
@@ -259,6 +265,8 @@
self.token_v3_client = V3TokenClientJSON(*client_args)
self.network_client = NetworkClientJSON(*client_args)
self.credentials_client = CredentialsClientJSON(*client_args)
+ self.instance_usages_audit_log_client = \
+ InstanceUsagesAuditLogClientJSON(*client_args)
if client_args_v3_auth:
self.servers_client_v3_auth = ServersClientJSON(
diff --git a/tempest/services/compute/json/instance_usage_audit_log_client.py b/tempest/services/compute/json/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..07ce1bb
--- /dev/null
+++ b/tempest/services/compute/json/instance_usage_audit_log_client.py
@@ -0,0 +1,40 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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.
+
+import json
+
+from tempest.common.rest_client import RestClient
+
+
+class InstanceUsagesAuditLogClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InstanceUsagesAuditLogClientJSON, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body["instance_usage_audit_logs"]
+
+ def get_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url)
+ body = json.loads(body)
+ return resp, body["instance_usage_audit_log"]
diff --git a/tempest/services/compute/v3/json/interfaces_client.py b/tempest/services/compute/v3/json/interfaces_client.py
new file mode 100644
index 0000000..06e6476
--- /dev/null
+++ b/tempest/services/compute/v3/json/interfaces_client.py
@@ -0,0 +1,80 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import json
+import time
+
+from tempest.common.rest_client import RestClient
+from tempest import exceptions
+
+
+class InterfacesClientJSON(RestClient):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InterfacesClientJSON, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_interfaces(self, server):
+ resp, body = self.get('servers/%s/os-interface' % server)
+ body = json.loads(body)
+ return resp, body['interfaceAttachments']
+
+ def create_interface(self, server, port_id=None, network_id=None,
+ fixed_ip=None):
+ post_body = dict(interfaceAttachment=dict())
+ if port_id:
+ post_body['port_id'] = port_id
+ if network_id:
+ post_body['net_id'] = network_id
+ if fixed_ip:
+ post_body['fixed_ips'] = [dict(ip_address=fixed_ip)]
+ post_body = json.dumps(post_body)
+ resp, body = self.post('servers/%s/os-interface' % server,
+ headers=self.headers,
+ body=post_body)
+ body = json.loads(body)
+ return resp, body['interfaceAttachment']
+
+ def show_interface(self, server, port_id):
+ resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id))
+ body = json.loads(body)
+ return resp, body['interfaceAttachment']
+
+ def delete_interface(self, server, port_id):
+ resp, body = self.delete('servers/%s/os-interface/%s' % (server,
+ port_id))
+ return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+
+ return resp, body
diff --git a/tempest/services/compute/v3/xml/interfaces_client.py b/tempest/services/compute/v3/xml/interfaces_client.py
new file mode 100644
index 0000000..a84e0bd
--- /dev/null
+++ b/tempest/services/compute/v3/xml/interfaces_client.py
@@ -0,0 +1,105 @@
+# Copyright 2013 IBM Corp.
+# All Rights Reserved.
+#
+# Licensed under the Apache License, Version 2.0 (the "License"); you may
+# not use this file except in compliance with the License. You may obtain
+# a copy of the License at
+#
+# http://www.apache.org/licenses/LICENSE-2.0
+#
+# Unless required by applicable law or agreed to in writing, software
+# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
+# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
+# License for the specific language governing permissions and limitations
+# under the License.
+
+import time
+
+from lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest import exceptions
+from tempest.services.compute.xml.common import Document
+from tempest.services.compute.xml.common import Element
+from tempest.services.compute.xml.common import Text
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class InterfacesClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InterfacesClientXML, self).__init__(config, username, password,
+ auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def _process_xml_interface(self, node):
+ iface = xml_to_json(node)
+ # NOTE(danms): if multiple addresses per interface is ever required,
+ # xml_to_json will need to be fixed or replaced in this case
+ iface['fixed_ips'] = [dict(iface['fixed_ips']['fixed_ip'].items())]
+ return iface
+
+ def list_interfaces(self, server):
+ resp, body = self.get('servers/%s/os-interface' % server, self.headers)
+ node = etree.fromstring(body)
+ interfaces = [self._process_xml_interface(x)
+ for x in node.getchildren()]
+ return resp, interfaces
+
+ def create_interface(self, server, port_id=None, network_id=None,
+ fixed_ip=None):
+ doc = Document()
+ iface = Element('interfaceAttachment')
+ if port_id:
+ _port_id = Element('port_id')
+ _port_id.append(Text(port_id))
+ iface.append(_port_id)
+ if network_id:
+ _network_id = Element('net_id')
+ _network_id.append(Text(network_id))
+ iface.append(_network_id)
+ if fixed_ip:
+ _fixed_ips = Element('fixed_ips')
+ _fixed_ip = Element('fixed_ip')
+ _ip_address = Element('ip_address')
+ _ip_address.append(Text(fixed_ip))
+ _fixed_ip.append(_ip_address)
+ _fixed_ips.append(_fixed_ip)
+ iface.append(_fixed_ips)
+ doc.append(iface)
+ resp, body = self.post('servers/%s/os-interface' % server,
+ headers=self.headers,
+ body=str(doc))
+ body = self._process_xml_interface(etree.fromstring(body))
+ return resp, body
+
+ def show_interface(self, server, port_id):
+ resp, body = self.get('servers/%s/os-interface/%s' % (server, port_id),
+ self.headers)
+ body = self._process_xml_interface(etree.fromstring(body))
+ return resp, body
+
+ def delete_interface(self, server, port_id):
+ resp, body = self.delete('servers/%s/os-interface/%s' % (server,
+ port_id))
+ return resp, body
+
+ def wait_for_interface_status(self, server, port_id, status):
+ """Waits for a interface to reach a given status."""
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+ start = int(time.time())
+
+ while(interface_status != status):
+ time.sleep(self.build_interval)
+ resp, body = self.show_interface(server, port_id)
+ interface_status = body['port_state']
+
+ timed_out = int(time.time()) - start >= self.build_timeout
+
+ if interface_status != status and timed_out:
+ message = ('Interface %s failed to reach %s status within '
+ 'the required time (%s s).' %
+ (port_id, status, self.build_timeout))
+ raise exceptions.TimeoutException(message)
+ return resp, body
diff --git a/tempest/services/compute/xml/instance_usage_audit_log_client.py b/tempest/services/compute/xml/instance_usage_audit_log_client.py
new file mode 100644
index 0000000..175997b
--- /dev/null
+++ b/tempest/services/compute/xml/instance_usage_audit_log_client.py
@@ -0,0 +1,41 @@
+# vim: tabstop=4 shiftwidth=4 softtabstop=4
+
+# Copyright 2013 IBM 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 lxml import etree
+
+from tempest.common.rest_client import RestClientXML
+from tempest.services.compute.xml.common import xml_to_json
+
+
+class InstanceUsagesAuditLogClientXML(RestClientXML):
+
+ def __init__(self, config, username, password, auth_url, tenant_name=None):
+ super(InstanceUsagesAuditLogClientXML, self).__init__(
+ config, username, password, auth_url, tenant_name)
+ self.service = self.config.compute.catalog_type
+
+ def list_instance_usage_audit_logs(self):
+ url = 'os-instance_usage_audit_log'
+ resp, body = self.get(url, self.headers)
+ instance_usage_audit_logs = xml_to_json(etree.fromstring(body))
+ return resp, instance_usage_audit_logs
+
+ def get_instance_usage_audit_log(self, time_before):
+ url = 'os-instance_usage_audit_log/%s' % time_before
+ resp, body = self.get(url, self.headers)
+ instance_usage_audit_log = xml_to_json(etree.fromstring(body))
+ return resp, instance_usage_audit_log
diff --git a/tempest/services/volume/json/volumes_client.py b/tempest/services/volume/json/volumes_client.py
index 670492a..93b28a2 100644
--- a/tempest/services/volume/json/volumes_client.py
+++ b/tempest/services/volume/json/volumes_client.py
@@ -201,3 +201,48 @@
resp, body = self.post('volumes/%s/action' % volume_id, post_body,
self.headers)
return resp, body
+
+ def create_volume_transfer(self, vol_id, display_name=None):
+ """Create a volume transfer."""
+ post_body = {
+ 'volume_id': vol_id
+ }
+ if display_name:
+ post_body['name'] = display_name
+ post_body = json.dumps({'transfer': post_body})
+ resp, body = self.post('os-volume-transfer',
+ post_body,
+ self.headers)
+ 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, self.headers)
+ 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, self.headers)
+ body = json.loads(body)
+ return resp, body['transfer']
diff --git a/tempest/services/volume/xml/volumes_client.py b/tempest/services/volume/xml/volumes_client.py
index 0edf7f3..b1e54ed 100644
--- a/tempest/services/volume/xml/volumes_client.py
+++ b/tempest/services/volume/xml/volumes_client.py
@@ -284,3 +284,56 @@
if body:
body = xml_to_json(etree.fromstring(body))
return resp, body
+
+ def create_volume_transfer(self, vol_id, display_name=None):
+ """Create a volume transfer."""
+ post_body = Element("transfer",
+ volume_id=vol_id)
+ if display_name:
+ post_body.add_attr('name', display_name)
+ resp, body = self.post('os-volume-transfer',
+ str(Document(post_body)),
+ self.headers)
+ volume = 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, self.headers)
+ volume = 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, self.headers)
+ 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] = 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 = Element("accept", auth_key=transfer_auth_key)
+ url = 'os-volume-transfer/%s/accept' % transfer_id
+ resp, body = self.post(url, str(Document(post_body)), self.headers)
+ volume = xml_to_json(etree.fromstring(body))
+ return resp, volume
diff --git a/tempest/stress/driver.py b/tempest/stress/driver.py
index a32525d..d37ab6d 100644
--- a/tempest/stress/driver.py
+++ b/tempest/stress/driver.py
@@ -25,19 +25,12 @@
from tempest.openstack.common import log as logging
from tempest.stress import cleanup
-admin_manager = clients.AdminManager()
-
LOG = logging.getLogger(__name__)
processes = []
-def do_ssh(command, host):
- username = admin_manager.config.stress.target_ssh_user
- key_filename = admin_manager.config.stress.target_private_key_path
- if not (username and key_filename):
- LOG.error('username and key_filename should not be empty')
- return None
- ssh_client = ssh.Client(host, username, key_filename=key_filename)
+def do_ssh(command, host, ssh_user, ssh_key=None):
+ ssh_client = ssh.Client(host, ssh_user, key_filename=ssh_key)
try:
return ssh_client.exec_command(command)
except exceptions.SSHExecCommandFailed:
@@ -46,14 +39,14 @@
return None
-def _get_compute_nodes(controller):
+def _get_compute_nodes(controller, ssh_user, ssh_key=None):
"""
Returns a list of active compute nodes. List is generated by running
nova-manage on the controller.
"""
nodes = []
cmd = "nova-manage service list | grep ^nova-compute"
- output = do_ssh(cmd, controller)
+ output = do_ssh(cmd, controller, ssh_user, ssh_key)
if not output:
return nodes
# For example: nova-compute xg11eth0 nova enabled :-) 2011-10-31 18:57:46
@@ -65,14 +58,15 @@
return nodes
-def _has_error_in_logs(logfiles, nodes, stop_on_error=False):
+def _has_error_in_logs(logfiles, nodes, ssh_user, ssh_key=None,
+ stop_on_error=False):
"""
Detect errors in the nova log files on the controller and compute nodes.
"""
grep = 'egrep "ERROR|TRACE" %s' % logfiles
ret = False
for node in nodes:
- errors = do_ssh(grep, node)
+ errors = do_ssh(grep, node, ssh_user, ssh_key)
if len(errors) > 0:
LOG.error('%s: %s' % (node, errors))
ret = True
@@ -88,18 +82,17 @@
terminate_all_processes()
-def terminate_all_processes():
+def terminate_all_processes(check_interval=20):
"""
Goes through the process list and terminates all child processes.
"""
- log_check_interval = int(admin_manager.config.stress.log_check_interval)
for process in processes:
if process['process'].is_alive():
try:
process['process'].terminate()
except Exception:
pass
- time.sleep(log_check_interval)
+ time.sleep(check_interval)
for process in processes:
if process['process'].is_alive():
try:
@@ -115,15 +108,19 @@
"""
Workload driver. Executes an action function against a nova-cluster.
"""
+ admin_manager = clients.AdminManager()
+
+ ssh_user = admin_manager.config.stress.target_ssh_user
+ ssh_key = admin_manager.config.stress.target_private_key_path
logfiles = admin_manager.config.stress.target_logfiles
log_check_interval = int(admin_manager.config.stress.log_check_interval)
default_thread_num = int(admin_manager.config.stress.
default_thread_number_per_action)
if logfiles:
controller = admin_manager.config.stress.target_controller
- computes = _get_compute_nodes(controller)
+ computes = _get_compute_nodes(controller, ssh_user, ssh_key)
for node in computes:
- do_ssh("rm -f %s" % logfiles, node)
+ do_ssh("rm -f %s" % logfiles, node, ssh_user, ssh_key)
for test in tests:
if test.get('use_admin', False):
manager = admin_manager
@@ -196,7 +193,8 @@
if not logfiles:
continue
- if _has_error_in_logs(logfiles, computes, stop_on_error):
+ if _has_error_in_logs(logfiles, computes, ssh_user, ssh_key,
+ stop_on_error):
had_errors = True
break
diff --git a/tools/tempest_auto_config.py b/tools/tempest_auto_config.py
index aef6a1f..fe9f5af 100644
--- a/tools/tempest_auto_config.py
+++ b/tools/tempest_auto_config.py
@@ -14,38 +14,50 @@
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
+#
+# This script aims to configure an initial Openstack environment with all the
+# necessary configurations for tempest's run using nothing but Openstack's
+# native API.
+# That includes, creating users, tenants, registering images (cirros),
+# configuring neutron and so on.
+#
+# ASSUMPTION: this script is run by an admin user as it is meant to configure
+# the Openstack environment prior to actual use.
# Config
import ConfigParser
import os
+import tarfile
+import urllib2
# Default client libs
+import glanceclient as glance_client
import keystoneclient.v2_0.client as keystone_client
# Import Openstack exceptions
+import glanceclient.exc as glance_exception
import keystoneclient.exceptions as keystone_exception
-DEFAULT_CONFIG_DIR = "%s/etc" % os.path.abspath(os.path.pardir)
-DEFAULT_CONFIG_FILE = "tempest.conf"
-DEFAULT_CONFIG_SAMPLE = "tempest.conf.sample"
+TEMPEST_TEMP_DIR = os.getenv("TEMPEST_TEMP_DIR", "/tmp").rstrip('/')
+TEMPEST_ROOT_DIR = os.getenv("TEMPEST_ROOT_DIR", os.getenv("HOME")).rstrip('/')
# Environment variables override defaults
-TEMPEST_CONFIG_DIR = os.environ.get('TEMPEST_CONFIG_DIR') or DEFAULT_CONFIG_DIR
-TEMPEST_CONFIG = os.environ.get('TEMPEST_CONFIG') or "%s/%s" % \
- (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_FILE)
-TEMPEST_CONFIG_SAMPLE = os.environ.get('TEMPEST_CONFIG_SAMPLE') or "%s/%s" % \
- (TEMPEST_CONFIG_DIR, DEFAULT_CONFIG_SAMPLE)
-
-# Admin credentials
-OS_USERNAME = os.environ.get('OS_USERNAME')
-OS_PASSWORD = os.environ.get('OS_PASSWORD')
-OS_TENANT_NAME = os.environ.get('OS_TENANT_NAME')
-OS_AUTH_URL = os.environ.get('OS_AUTH_URL')
-
+TEMPEST_CONFIG_DIR = os.getenv("TEMPEST_CONFIG_DIR",
+ "%s%s" % (TEMPEST_ROOT_DIR, "/etc")).rstrip('/')
+TEMPEST_CONFIG_FILE = os.getenv("TEMPEST_CONFIG_FILE",
+ "%s%s" % (TEMPEST_CONFIG_DIR, "/tempest.conf"))
+TEMPEST_CONFIG_SAMPLE = os.getenv("TEMPEST_CONFIG_SAMPLE",
+ "%s%s" % (TEMPEST_CONFIG_DIR,
+ "/tempest.conf.sample"))
# Image references
-IMAGE_ID = os.environ.get('IMAGE_ID')
-IMAGE_ID_ALT = os.environ.get('IMAGE_ID_ALT')
+IMAGE_DOWNLOAD_CHUNK_SIZE = 8 * 1024
+IMAGE_UEC_SOURCE_URL = os.getenv("IMAGE_UEC_SOURCE_URL",
+ "http://download.cirros-cloud.net/0.3.1/"
+ "cirros-0.3.1-x86_64-uec.tar.gz")
+TEMPEST_IMAGE_ID = os.getenv('IMAGE_ID')
+TEMPEST_IMAGE_ID_ALT = os.getenv('IMAGE_ID_ALT')
+IMAGE_STATUS_ACTIVE = 'active'
class ClientManager(object):
@@ -76,26 +88,52 @@
return self.identity_client
+ def get_image_client(self, version="1", *args, **kwargs):
+ """
+ This method returns Openstack glance python client
+ :param version: a string representing the version of the glance client
+ to use.
+ :param string endpoint: A user-supplied endpoint URL for the glance
+ service.
+ :param string token: Token for authentication.
+ :param integer timeout: Allows customization of the timeout for client
+ http requests. (optional)
+ :return: a Client object representing the glance client
+ """
+ if not self.image_client:
+ self.image_client = glance_client.Client(version, *args, **kwargs)
-def getTempestConfigSample():
+ return self.image_client
+
+
+def get_tempest_config(path_to_config):
"""
Gets the tempest configuration file as a ConfigParser object
- :return: the tempest configuration file
+ :param path_to_config: path to the config file
+ :return: a ConfigParser object representing the tempest configuration file
"""
# get the sample config file from the sample
- config_sample = ConfigParser.ConfigParser()
- config_sample.readfp(open(TEMPEST_CONFIG_SAMPLE))
+ config = ConfigParser.ConfigParser()
+ config.readfp(open(path_to_config))
- return config_sample
+ return config
def update_config_admin_credentials(config, config_section):
"""
Updates the tempest config with the admin credentials
- :param config: an object representing the tempest config file
+ :param config: a ConfigParser object representing the tempest config file
:param config_section: the section name where the admin credentials are
"""
- # Check if credentials are present
+ # Check if credentials are present, default uses the config credentials
+ OS_USERNAME = os.getenv('OS_USERNAME',
+ config.get(config_section, "admin_username"))
+ OS_PASSWORD = os.getenv('OS_PASSWORD',
+ config.get(config_section, "admin_password"))
+ OS_TENANT_NAME = os.getenv('OS_TENANT_NAME',
+ config.get(config_section, "admin_tenant_name"))
+ OS_AUTH_URL = os.getenv('OS_AUTH_URL', config.get(config_section, "uri"))
+
if not (OS_AUTH_URL and
OS_USERNAME and
OS_PASSWORD and
@@ -113,31 +151,31 @@
config_identity_params)
-def update_config_section_with_params(config, section, params):
+def update_config_section_with_params(config, config_section, params):
"""
Updates a given config object with given params
- :param config: the object representing the config file of tempest
- :param section: the section we would like to update
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section we would like to update
:param params: the parameters we wish to update for that section
"""
for option, value in params.items():
- config.set(section, option, value)
+ config.set(config_section, option, value)
-def get_identity_client_kwargs(config, section_name):
+def get_identity_client_kwargs(config, config_section):
"""
Get the required arguments for the identity python client
- :param config: the tempest configuration file
- :param section_name: the section name in the configuration where the
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name in the configuration where the
arguments can be found
:return: a dictionary representing the needed arguments for the identity
client
"""
- username = config.get(section_name, 'admin_username')
- password = config.get(section_name, 'admin_password')
- tenant_name = config.get(section_name, 'admin_tenant_name')
- auth_url = config.get(section_name, 'uri')
- dscv = config.get(section_name, 'disable_ssl_certificate_validation')
+ username = config.get(config_section, 'admin_username')
+ password = config.get(config_section, 'admin_password')
+ tenant_name = config.get(config_section, 'admin_tenant_name')
+ auth_url = config.get(config_section, 'uri')
+ dscv = config.get(config_section, 'disable_ssl_certificate_validation')
kwargs = {'username': username,
'password': password,
'tenant_name': tenant_name,
@@ -185,21 +223,21 @@
def create_users_and_tenants(identity_client,
config,
- identity_section):
+ config_section):
"""
Creates the two non admin users and tenants for tempest
:param identity_client: openstack identity python client
- :param config: tempest configuration file
- :param identity_section: the section name of identity in the config
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name of identity in the config
"""
# Get the necessary params from the config file
- tenant_name = config.get(identity_section, 'tenant_name')
- username = config.get(identity_section, 'username')
- password = config.get(identity_section, 'password')
+ tenant_name = config.get(config_section, 'tenant_name')
+ username = config.get(config_section, 'username')
+ password = config.get(config_section, 'password')
- alt_tenant_name = config.get(identity_section, 'alt_tenant_name')
- alt_username = config.get(identity_section, 'alt_username')
- alt_password = config.get(identity_section, 'alt_password')
+ alt_tenant_name = config.get(config_section, 'alt_tenant_name')
+ alt_username = config.get(config_section, 'alt_username')
+ alt_password = config.get(config_section, 'alt_password')
# Create the necessary users for the test runs
create_user_with_tenant(identity_client, username, password, tenant_name)
@@ -207,28 +245,153 @@
alt_tenant_name)
+def get_image_client_kwargs(identity_client, config, config_section):
+ """
+ Get the required arguments for the image python client
+ :param identity_client: openstack identity python client
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name of identity in the config
+ :return: a dictionary representing the needed arguments for the image
+ client
+ """
+
+ token = identity_client.auth_token
+ endpoint = identity_client.\
+ service_catalog.url_for(service_type='image', endpoint_type='publicURL'
+ )
+ dscv = config.get(config_section, 'disable_ssl_certificate_validation')
+ kwargs = {'endpoint': endpoint,
+ 'token': token,
+ 'insecure': dscv}
+
+ return kwargs
+
+
+def images_exist(image_client):
+ """
+ Checks whether the images ID's located in the environment variable are
+ indeed registered
+ :param image_client: the openstack python client representing the image
+ client
+ """
+ exist = True
+ if not TEMPEST_IMAGE_ID or not TEMPEST_IMAGE_ID_ALT:
+ exist = False
+ else:
+ try:
+ image_client.images.get(TEMPEST_IMAGE_ID)
+ image_client.images.get(TEMPEST_IMAGE_ID_ALT)
+ except glance_exception.HTTPNotFound:
+ exist = False
+
+ return exist
+
+
+def download_and_register_uec_images(image_client, download_url,
+ download_folder):
+ """
+ Downloads and registered the UEC AKI/AMI/ARI images
+ :param image_client:
+ :param download_url: the url of the uec tar file
+ :param download_folder: the destination folder we wish to save the file to
+ """
+ basename = os.path.basename(download_url)
+ path = os.path.join(download_folder, basename)
+
+ request = urllib2.urlopen(download_url)
+
+ # First, download the file
+ with open(path, "wb") as fp:
+ while True:
+ chunk = request.read(IMAGE_DOWNLOAD_CHUNK_SIZE)
+ if not chunk:
+ break
+
+ fp.write(chunk)
+
+ # Then extract and register images
+ tar = tarfile.open(path, "r")
+ for name in tar.getnames():
+ file_obj = tar.extractfile(name)
+ format = "aki"
+
+ if file_obj.name.endswith(".img"):
+ format = "ami"
+
+ if file_obj.name.endswith("initrd"):
+ format = "ari"
+
+ # Register images in image client
+ image_client.images.create(name=file_obj.name, disk_format=format,
+ container_format=format, data=file_obj,
+ is_public="true")
+
+ tar.close()
+
+
+def create_images(image_client, config, config_section,
+ download_url=IMAGE_UEC_SOURCE_URL,
+ download_folder=TEMPEST_TEMP_DIR):
+ """
+ Creates images for tempest's use and registers the environment variables
+ IMAGE_ID and IMAGE_ID_ALT with registered images
+ :param image_client: Openstack python image client
+ :param config: a ConfigParser object representing the tempest config file
+ :param config_section: the section name where the IMAGE ids are set
+ :param download_url: the URL from which we should download the UEC tar
+ :param download_folder: the place where we want to save the download file
+ """
+ if not images_exist(image_client):
+ # Falls down to the default uec images
+ download_and_register_uec_images(image_client, download_url,
+ download_folder)
+ image_ids = []
+ for image in image_client.images.list():
+ image_ids.append(image.id)
+
+ os.environ["IMAGE_ID"] = image_ids[0]
+ os.environ["IMAGE_ID_ALT"] = image_ids[1]
+
+ params = {'image_ref': os.getenv("IMAGE_ID"),
+ 'image_ref_alt': os.getenv("IMAGE_ID_ALT")}
+
+ update_config_section_with_params(config, config_section, params)
+
+
def main():
"""
Main module to control the script
"""
- # TODO(tkammer): add support for existing config file
- config_sample = getTempestConfigSample()
- update_config_admin_credentials(config_sample, 'identity')
+ # Check if config file exists or fall to the default sample otherwise
+ path_to_config = TEMPEST_CONFIG_SAMPLE
+
+ if os.path.isfile(TEMPEST_CONFIG_FILE):
+ path_to_config = TEMPEST_CONFIG_FILE
+
+ config = get_tempest_config(path_to_config)
+ update_config_admin_credentials(config, 'identity')
client_manager = ClientManager()
# Set the identity related info for tempest
- identity_client_kwargs = get_identity_client_kwargs(config_sample,
+ identity_client_kwargs = get_identity_client_kwargs(config,
'identity')
identity_client = client_manager.get_identity_client(
**identity_client_kwargs)
# Create the necessary users and tenants for tempest run
- create_users_and_tenants(identity_client,
- config_sample,
- 'identity')
+ create_users_and_tenants(identity_client, config, 'identity')
- # TODO(tkammer): add image implementation
+ # Set the image related info for tempest
+ image_client_kwargs = get_image_client_kwargs(identity_client,
+ config,
+ 'identity')
+ image_client = client_manager.get_image_client(**image_client_kwargs)
+
+ # Create the necessary users and tenants for tempest run
+ create_images(image_client, config, 'compute')
+
+ # TODO(tkammer): add network implementation
if __name__ == "__main__":
main()