diff --git a/.testr.conf b/.testr.conf index fc01c03f..62cd09f7 100644 --- a/.testr.conf +++ b/.testr.conf @@ -4,6 +4,6 @@ test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \ OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \ OS_DEBUG=${OS_DEBUG:-1} \ OS_TEST_TIMEOUT=60 \ - ${PYTHON:-python} -m subunit.run discover . $LISTOPT $IDOPTION + ${PYTHON:-python} -m subunit.run discover ./cue/tests $LISTOPT $IDOPTION test_id_option=--load-list $IDFILE test_list_option=--list diff --git a/test-requirements.txt b/test-requirements.txt index 34af9984..f9316bd0 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -17,3 +17,4 @@ sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 sphinxcontrib-pecanwsme>=0.8 oslosphinx>=2.2.0 # Apache-2.0 zake>=0.1.6 +tempest-lib>=0.4.0 diff --git a/tests/__init__.py b/tests/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/.testr.conf b/tests/integration/.testr.conf new file mode 100644 index 00000000..c31512a3 --- /dev/null +++ b/tests/integration/.testr.conf @@ -0,0 +1,4 @@ +[DEFAULT] +test_command=python -m subunit.run discover ./api $LISTOPT $IDOPTION +test_id_option=--load-list $IDFILE +test_list_option=--list diff --git a/tests/integration/__init__.py b/tests/integration/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/api/__init__.py b/tests/integration/api/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/api/v1/__init__.py b/tests/integration/api/v1/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/api/v1/clients/__init__.py b/tests/integration/api/v1/clients/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/api/v1/clients/clusters_client.py b/tests/integration/api/v1/clients/clusters_client.py new file mode 100644 index 00000000..1d8e1db6 --- /dev/null +++ b/tests/integration/api/v1/clients/clusters_client.py @@ -0,0 +1,83 @@ +# 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 logging +import urllib + +from tempest_lib.common import rest_client + +from tests.integration.common.client import BaseMessageQueueClient + +LOG = logging.getLogger(__name__) + + +class MessageQueueClustersClient(BaseMessageQueueClient): + """This class is used for creating a Cue Cluster client. + + It contains all the CRUD requests for Cue Clusters. + """ + + def list_clusters(self, params=None): + """List all clusters + + :param params: Optional parameters for listing cluster + """ + url = 'clusters' + if params: + url += '?%s' % urllib.urlencode(params) + + resp, body = self.get(url) + self.expected_success(200, resp.status) + return rest_client.ResponseBodyData(resp, body) + + def get_cluster_details(self, cluster_id): + """Get a cluster + + :param cluster_id: The ID of the cluster to get + """ + resp, body = self.get("clusters/%s" % str(cluster_id)) + self.expected_success(200, resp.status) + return rest_client.ResponseBody(resp, self._parse_resp(body)) + + def create_cluster(self, name, flavor, network_id): + """Create a new cluster with one node + + :param name: The name of the cluster + :param flavor: The flavor of the cluster + :param network_id: The network_id to associate the cluster + """ + post_body = { + 'name': name, + 'size': 1, + "flavor": flavor, + 'volume_size': 100, + "network_id": network_id, + } + + post_body = post_body + post_body = json.dumps(post_body) + + resp, body = self.post('clusters', post_body) + return rest_client.ResponseBody(resp, self._parse_resp(body)) + + def delete_cluster(self, cluster_id): + """Delete a cluster + + :param cluster_id: The ID of the cluster to delete + """ + resp, body = self.delete("clusters/%s" % str(cluster_id)) + self.expected_success(202, resp.status) + return rest_client.ResponseBody(resp, body) \ No newline at end of file diff --git a/tests/integration/api/v1/clusters/__init__.py b/tests/integration/api/v1/clusters/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/api/v1/clusters/test_clusters.py b/tests/integration/api/v1/clusters/test_clusters.py new file mode 100644 index 00000000..bfffa97b --- /dev/null +++ b/tests/integration/api/v1/clusters/test_clusters.py @@ -0,0 +1,63 @@ +# 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. + +""" +Tests for the API /cluster/ controller methods. +""" + +import logging + +import tempest_lib.base +from tempest_lib.common.utils import data_utils + +from tests.integration.api.v1.clients import clusters_client +from tests.integration.common import config + + +CONF = config.get_config() +LOG = logging.getLogger(__name__) + + +class ClusterTest(tempest_lib.base.BaseTestCase): + """Cluster integration tests for Cue.""" + + @classmethod + def setUpClass(cls): + super(ClusterTest, cls).setUpClass() + cls.client = clusters_client.MessageQueueClustersClient() + + def setUp(self): + super(ClusterTest, self).setUp() + self.cluster = self._create_cluster() + + def tearDown(self): + super(ClusterTest, self).tearDown() + self.client.delete_cluster(self.cluster['id']) + + def _create_cluster(self): + name = data_utils.rand_name(ClusterTest.__name__ + "-cluster") + network_id = [self.client.private_network['id']] + flavor = CONF.message_queue.flavor + return self.client.create_cluster(name, flavor, network_id) + + def test_list_clusters(self): + clusters = self.client.list_clusters() + self.assertIn('id', clusters.data) + self.assertIn('status', clusters.data) + + def test_get_cluster(self): + cluster_resp = self.client.get_cluster_details(self.cluster['id']) + self.assertEqual(self.cluster['id'], cluster_resp['id']) + self.assertEqual(self.cluster['name'], cluster_resp['name']) diff --git a/tests/integration/common/__init__.py b/tests/integration/common/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/tests/integration/common/client.py b/tests/integration/common/client.py new file mode 100644 index 00000000..aa3092be --- /dev/null +++ b/tests/integration/common/client.py @@ -0,0 +1,63 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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.services.compute.json import tenant_networks_client +from tempest_lib import auth +from tempest_lib.common import rest_client + +from tests.integration.common import config + + +CONF = config.get_config() + + +class BaseMessageQueueClient(rest_client.RestClient): + """This class is used for building Cue api clients. + + It extends the Openstack RestClient class, which provides a base layer for + wrapping outgoing http requests in keystone auth as well as providing + response code checking and error handling. It obtains the keystone + credentials from the configuration. + """ + + def __init__(self): + + auth_provider = self._get_keystone_auth_provider() + super(BaseMessageQueueClient, self).__init__( + auth_provider=auth_provider, + service='message_queue', + region='RegionOne', + ) + self.private_network = self._get_network('private') + + def _get_network(self, label): + network_client = tenant_networks_client.TenantNetworksClientJSON( + self._get_keystone_auth_provider(), + 'compute', + 'RegionOne') + networks = network_client.list_tenant_networks() + return [network for network in networks + if network['label'] == label][0] + + def _get_keystone_auth_provider(self): + creds = auth.KeystoneV2Credentials( + username=CONF.identity.username, + password=CONF.identity.password, + tenant_name=CONF.identity.tenant_name, + ) + auth_provider = auth.KeystoneV2AuthProvider(creds, + CONF.identity.uri) + auth_provider.fill_credentials() + return auth_provider diff --git a/tests/integration/common/config.py b/tests/integration/common/config.py new file mode 100644 index 00000000..076333ea --- /dev/null +++ b/tests/integration/common/config.py @@ -0,0 +1,64 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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 os + +from oslo_config import cfg + +TEST_CONF = None + + +def setup_config(config_file=''): + global TEST_CONF + TEST_CONF = cfg.ConfigOpts() + + identity_group = cfg.OptGroup(name='identity') + identity_options = [ + cfg.StrOpt('uri', default='http://localhost:5000/v2.0'), + cfg.StrOpt('username', default='demo'), + cfg.StrOpt('password', default='secret'), + cfg.StrOpt('tenant_name', default='demo'), + ] + TEST_CONF.register_group(identity_group) + TEST_CONF.register_opts(identity_options, group=identity_group) + + message_queue_group = cfg.OptGroup(name='message_queue') + message_queue_options = [ + cfg.StrOpt('flavor', default='8795'), + ] + TEST_CONF.register_group(message_queue_group) + TEST_CONF.register_opts(message_queue_options, group=message_queue_group) + + # Figure out which config to load + config_to_load = [] + local_config = 'cue-integration.conf' + if os.path.isfile(config_file): + config_to_load.append(config_file) + elif os.path.isfile(local_config): + config_to_load.append(local_config) + else: + config_to_load.append('/etc/cue/cue-integration.conf') + + # Actually parse config + TEST_CONF( + (), # Required to load a anonymous config + default_config_files=config_to_load + ) + + +def get_config(): + if not TEST_CONF: + setup_config() + return TEST_CONF \ No newline at end of file diff --git a/tests/integration/cue-integration.conf b/tests/integration/cue-integration.conf new file mode 100644 index 00000000..1c6a4263 --- /dev/null +++ b/tests/integration/cue-integration.conf @@ -0,0 +1,11 @@ +[DEFAULT] + +[identity] +# Replace these with values that represent your identity configuration +uri=http://localhost:5000/v2.0 +username=demo +tenant_name=demo +password=password + +[message_queue] +flavor=8795 diff --git a/tests/integration/run_tests.sh b/tests/integration/run_tests.sh new file mode 100755 index 00000000..0448360e --- /dev/null +++ b/tests/integration/run_tests.sh @@ -0,0 +1,39 @@ +# -*- coding: utf-8 -*- +# Copyright 2015 Hewlett-Packard Development Company, L.P. +# +# 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. + +# How many seconds to wait for the API to be responding before giving up +API_RESPONDING_TIMEOUT=20 + +if ! timeout ${API_RESPONDING_TIMEOUT} sh -c "while ! curl -s http://127.0.0.1:8795/ 2>/dev/null | grep -q 'v1' ; do sleep 1; done"; then + echo "The Cue API failed to respond within ${API_RESPONDING_TIMEOUT} seconds" + exit 1 +fi + +echo "Successfully contacted the Cue API" + +# Where Cue and Tempest code lives +CUE_DIR=${CUE_DIR:-/opt/stack/cue} +TEMPEST_DIR=${TEMPEST_DIR:-/opt/stack/tempest} + +# Install tempest +pip freeze | grep tempest 2>&1 1>/dev/null || pip install -e $TEMPEST_DIR + +# run the tests in parallel +test -d .testrepository || testr init +testr run --parallel --subunit | subunit-trace --no-failure-debug -f +retval=$? +testr slowest + +exit $retval