From 13463268dbc4bb93a6d606a52e89ff3e23af1de7 Mon Sep 17 00:00:00 2001 From: Andrey Kurilin Date: Tue, 21 Apr 2015 09:19:13 -0400 Subject: [PATCH] Fix unit and functional tests stable/kilo branch is broken due to next reasons: 1) unittests requires capped oslo libraries and mock 2) functional tests cannot access to credentials via environment variables Both issues block each other, so we need to squach several commits. Fix for functional tests is done via backporting patches: * refactor functional test base class to no inherit from tempest_lib Conflicts: None Change-Id: I716be51d7d1825a757934298f06b2f04d64cf0dd (cherry picked from commit 420dc2884ac369924e8e65eba5cf81935b6ffc9d) * pass credentials via config file instead of magic Conflicts: README.rst Change-Id: Ifdab38a03c94f51d30449149c0dbd9c6265460a5 (cherry picked from commit 6379287480bf6bc0a8f80bc93f01005a31882aec) Unittest doesn't require full sync with global-requrements, so only oslo.i18n, oslo.serialization and mock are updated. Change-Id: I5ecd52abcaf0da1b067e70d7b19297305af19174 --- README.rst | 13 ++ functional_creds.conf.sample | 8 ++ novaclient/tests/functional/base.py | 126 ++++++++++++++++-- .../tests/functional/hooks/post_test_hook.sh | 17 ++- novaclient/tests/functional/test_instances.py | 42 ------ .../tests/functional/test_volumes_api.py | 11 -- requirements.txt | 4 +- test-requirements.txt | 2 +- 8 files changed, 155 insertions(+), 68 deletions(-) create mode 100644 functional_creds.conf.sample diff --git a/README.rst b/README.rst index a7edf8707..b28112c9a 100644 --- a/README.rst +++ b/README.rst @@ -77,3 +77,16 @@ To use with nova, with keystone as the authentication system:: [...] >>> nt.keypairs.list() [...] + +Testing +------- + +There are multiple test targets that can be run to validate the code. + +* tox -e pep8 - style guidelines enforcement +* tox -e py27 - traditional unit testing +* tox -e functional - live functional testing against an existing + openstack + +Functional testing assumes the existance of a functional_creds.conf in +the root directory. See the .sample for example format. diff --git a/functional_creds.conf.sample b/functional_creds.conf.sample new file mode 100644 index 000000000..081a73681 --- /dev/null +++ b/functional_creds.conf.sample @@ -0,0 +1,8 @@ +# Credentials for functional testing +[auth] +uri = http://10.42.0.50:5000/v2.0 + +[admin] +user = admin +tenant = admin +pass = secrete diff --git a/novaclient/tests/functional/base.py b/novaclient/tests/functional/base.py index 27015bda8..ac7953259 100644 --- a/novaclient/tests/functional/base.py +++ b/novaclient/tests/functional/base.py @@ -10,12 +10,47 @@ # License for the specific language governing permissions and limitations # under the License. +import ConfigParser import os -from tempest_lib.cli import base +import fixtures +import tempest_lib.cli.base +import testtools + +import novaclient.client -class ClientTestBase(base.ClientTestBase): +# The following are simple filter functions that filter our available +# image / flavor list so that they can be used in standard testing. +def pick_flavor(flavors): + """Given a flavor list pick a reasonable one.""" + for flavor in flavors: + if flavor.name == 'm1.tiny': + return flavor + for flavor in flavors: + if flavor.name == 'm1.small': + return flavor + raise NoFlavorException() + + +def pick_image(images): + for image in images: + if image.name.startswith('cirros') and image.name.endswith('-uec'): + return image + raise NoImageException() + + +class NoImageException(Exception): + """We couldn't find an acceptable image.""" + pass + + +class NoFlavorException(Exception): + """We couldn't find an acceptable flavor.""" + pass + + +class ClientTestBase(testtools.TestCase): """ This is a first pass at a simple read only python-novaclient test. This only exercises client commands that are read only. @@ -27,18 +62,89 @@ class ClientTestBase(base.ClientTestBase): * initially just check return codes, and later test command outputs """ - def _get_clients(self): + log_format = ('%(asctime)s %(process)d %(levelname)-8s ' + '[%(name)s] %(message)s') + + def setUp(self): + super(ClientTestBase, self).setUp() + + test_timeout = os.environ.get('OS_TEST_TIMEOUT', 0) + try: + test_timeout = int(test_timeout) + except ValueError: + test_timeout = 0 + if test_timeout > 0: + self.useFixture(fixtures.Timeout(test_timeout, gentle=True)) + + 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)) + + if (os.environ.get('OS_LOG_CAPTURE') != 'False' and + os.environ.get('OS_LOG_CAPTURE') != '0'): + self.useFixture(fixtures.LoggerFixture(nuke_handlers=False, + format=self.log_format, + level=None)) + + # Collecting of credentials: + # + # Support the existence of a functional_creds.conf for + # testing. This makes it possible to use a config file. + # + # Those variables can be overridden by environmental variables + # as well to support existing users running these the old + # way. We should deprecate that. + + # TODO(sdague): while we collect this information in + # tempest-lib, we do it in a way that's not available for top + # level tests. Long term this probably needs to be in the base + # class. + user = os.environ.get('OS_USERNAME') + passwd = os.environ.get('OS_PASSWORD') + tenant = os.environ.get('OS_TENANT_NAME') + auth_url = os.environ.get('OS_AUTH_URL') + + config = ConfigParser.RawConfigParser() + if config.read('functional_creds.conf'): + # the OR pattern means the environment is preferred for + # override + user = user or config.get('admin', 'user') + passwd = passwd or config.get('admin', 'pass') + tenant = tenant or config.get('admin', 'tenant') + auth_url = auth_url or config.get('auth', 'uri') + + # TODO(sdague): we made a lot of fun of the glanceclient team + # for version as int in first parameter. I guess we know where + # they copied it from. + self.client = novaclient.client.Client( + 2, user, passwd, tenant, + auth_url=auth_url) + + # pick some reasonable flavor / image combo + self.flavor = pick_flavor(self.client.flavors.list()) + self.image = pick_image(self.client.images.list()) + + # create a CLI client in case we'd like to do CLI + # testing. tempest_lib does this realy weird thing where it + # builds a giant factory of all the CLIs that it knows + # about. Eventually that should really be unwound into + # something more sensible. cli_dir = os.environ.get( 'OS_NOVACLIENT_EXEC_DIR', os.path.join(os.path.abspath('.'), '.tox/functional/bin')) - return base.CLIClient( - username=os.environ.get('OS_USERNAME'), - password=os.environ.get('OS_PASSWORD'), - tenant_name=os.environ.get('OS_TENANT_NAME'), - uri=os.environ.get('OS_AUTH_URL'), + self.cli_clients = tempest_lib.cli.base.CLIClient( + username=user, + password=passwd, + tenant_name=tenant, + uri=auth_url, cli_dir=cli_dir) def nova(self, *args, **kwargs): - return self.clients.nova(*args, - **kwargs) + return self.cli_clients.nova(*args, + **kwargs) diff --git a/novaclient/tests/functional/hooks/post_test_hook.sh b/novaclient/tests/functional/hooks/post_test_hook.sh index e9e35b24e..5878ace9f 100755 --- a/novaclient/tests/functional/hooks/post_test_hook.sh +++ b/novaclient/tests/functional/hooks/post_test_hook.sh @@ -28,15 +28,28 @@ function generate_testr_results { export NOVACLIENT_DIR="$BASE/new/python-novaclient" +sudo chown -R jenkins:stack $NOVACLIENT_DIR + # Get admin credentials cd $BASE/new/devstack source openrc admin admin +# pass the appropriate variables via a config file +CREDS_FILE=$NOVACLIENT_DIR/functional_creds.conf +cat < $CREDS_FILE +# Credentials for functional testing +[auth] +uri = $OS_AUTH_URL + +[admin] +user = $OS_USERNAME +tenant = $OS_TENANT_NAME +pass = $OS_PASSWORD + +EOF # Go to the novaclient dir cd $NOVACLIENT_DIR -sudo chown -R jenkins:stack $NOVACLIENT_DIR - # Run tests echo "Running novaclient functional test suite" set +e diff --git a/novaclient/tests/functional/test_instances.py b/novaclient/tests/functional/test_instances.py index c963a6b71..8df28d136 100644 --- a/novaclient/tests/functional/test_instances.py +++ b/novaclient/tests/functional/test_instances.py @@ -10,33 +10,12 @@ # License for the specific language governing permissions and limitations # under the License. -import os import time import uuid -import novaclient.client from novaclient.tests.functional import base -# TODO(sdague): content that probably should be in utils, also throw -# Exceptions when they fail. -def pick_flavor(flavors): - """Given a flavor list pick a reasonable one.""" - for flavor in flavors: - if flavor.name == 'm1.tiny': - return flavor - - for flavor in flavors: - if flavor.name == 'm1.small': - return flavor - - -def pick_image(images): - for image in images: - if image.name.startswith('cirros') and image.name.endswith('-uec'): - return image - - def volume_id_from_cli_create(output): """Scrape the volume id out of the 'volume create' command @@ -67,27 +46,6 @@ def volume_at_status(output, volume_id, status): class TestInstanceCLI(base.ClientTestBase): - def setUp(self): - super(TestInstanceCLI, self).setUp() - # TODO(sdague): while we collect this information in - # tempest-lib, we do it in a way that's not available for top - # level tests. Long term this probably needs to be in the base - # class. - user = os.environ['OS_USERNAME'] - passwd = os.environ['OS_PASSWORD'] - tenant = os.environ['OS_TENANT_NAME'] - auth_url = os.environ['OS_AUTH_URL'] - - # TODO(sdague): we made a lot of fun of the glanceclient team - # for version as int in first parameter. I guess we know where - # they copied it from. - self.client = novaclient.client.Client( - 2, user, passwd, tenant, - auth_url=auth_url) - - # pick some reasonable flavor / image combo - self.flavor = pick_flavor(self.client.flavors.list()) - self.image = pick_image(self.client.images.list()) def test_attach_volume(self): """Test we can attach a volume via the cli. diff --git a/novaclient/tests/functional/test_volumes_api.py b/novaclient/tests/functional/test_volumes_api.py index e427c2e21..38c63bbf5 100644 --- a/novaclient/tests/functional/test_volumes_api.py +++ b/novaclient/tests/functional/test_volumes_api.py @@ -10,13 +10,11 @@ # License for the specific language governing permissions and limitations # under the License. -import os import time import uuid import six.moves -from novaclient import client from novaclient import exceptions from novaclient.tests.functional import base @@ -35,15 +33,6 @@ def wait_for_delete(test, name, thing, get_func): class TestVolumesAPI(base.ClientTestBase): - def setUp(self): - super(TestVolumesAPI, self).setUp() - user = os.environ['OS_USERNAME'] - passwd = os.environ['OS_PASSWORD'] - tenant = os.environ['OS_TENANT_NAME'] - auth_url = os.environ['OS_AUTH_URL'] - - self.client = client.Client(2, user, passwd, tenant, auth_url=auth_url) - def test_volumes_snapshots_types_create_get_list_delete(self): # Create a volume volume = self.client.volumes.create(1) diff --git a/requirements.txt b/requirements.txt index bc729cf8c..324426b30 100644 --- a/requirements.txt +++ b/requirements.txt @@ -4,8 +4,8 @@ pbr>=0.6,!=0.7,<1.0 argparse iso8601>=0.1.9 -oslo.i18n>=1.3.0 # Apache-2.0 -oslo.serialization>=1.2.0 # Apache-2.0 +oslo.i18n<1.6.0,>=1.5.0 # Apache-2.0 +oslo.serialization<1.5.0,>=1.4.0 # Apache-2.0 oslo.utils>=1.2.0 # Apache-2.0 PrettyTable>=0.7,<0.8 requests>=2.2.0,!=2.4.0 diff --git a/test-requirements.txt b/test-requirements.txt index acdbc2441..4e98deccc 100644 --- a/test-requirements.txt +++ b/test-requirements.txt @@ -7,7 +7,7 @@ coverage>=3.6 discover fixtures>=0.3.14 keyring>=2.1,!=3.3 -mock>=1.0 +mock<1.1.0,>=1.0 requests-mock>=0.5.1 # Apache-2.0 sphinx>=1.1.2,!=1.2.0,!=1.3b1,<1.3 oslosphinx>=2.2.0 # Apache-2.0