Layout some functional tests for the V2 CLI

Change-Id: Ib2d1261bcb0362c586c0aae4b9c5a8a563f07c71
This commit is contained in:
Paul Glass 2015-08-24 15:41:17 +00:00
parent 649e7d48c8
commit 6daf82777e
14 changed files with 419 additions and 1 deletions

1
.gitignore vendored
View File

@ -22,3 +22,4 @@ doc/build/*
dist
designateclient/versioninfo
.testrepository
*.log

View File

@ -1,4 +1,10 @@
[DEFAULT]
test_command=OS_STDOUT_CAPTURE=1 OS_STDERR_CAPTURE=1 ${PYTHON:-python} -m subunit.run discover -t ./ ./designateclient/tests $LISTOPT $IDOPTION
test_command=OS_STDOUT_CAPTURE=${OS_STDOUT_CAPTURE:-1} \
OS_STDERR_CAPTURE=${OS_STDERR_CAPTURE:-1} \
OS_LOG_CAPTURE=${OS_LOG_CAPTURE:-1} \
OS_DEBUG=${OS_DEBUG:-1} \
OS_TEST_TIMEOUT=${OS_TEST_TIMEOUT:-160} \
${PYTHON:-python} -m subunit.run discover -t ./ ${OS_TEST_PATH:-./designateclient/tests} $LISTOPT $IDOPTION
test_id_option=--load-list $IDFILE
test_list_option=--list

View File

@ -0,0 +1,7 @@
import logging
logging.basicConfig(
filename='functional-tests.log',
filemode='w',
level=logging.DEBUG,
)

View File

@ -0,0 +1,26 @@
"""
Copyright 2015 Rackspace
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_lib.cli import base
from designateclient.functionaltests import client
from designateclient.functionaltests import config
class BaseDesignateTest(base.ClientTestBase):
def _get_clients(self):
config.read_config()
return client.DesignateCLI.as_user('default')

View File

@ -0,0 +1,86 @@
"""
Copyright 2015 Rackspace
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 logging
from tempest_lib.cli import base
from designateclient.functionaltests.config import cfg
from designateclient.functionaltests.models import FieldValueModel
from designateclient.functionaltests.models import ListModel
LOG = logging.getLogger(__name__)
class ZoneCommands(object):
"""This is a mixin that provides zone commands to DesignateCLI"""
def zone_list(self, *args, **kwargs):
return self.parsed_cmd('zone list', ListModel, *args, **kwargs)
def zone_show(self, id, *args, **kwargs):
return self.parsed_cmd('zone show %s' % id, FieldValueModel, *args,
**kwargs)
def zone_delete(self, id, *args, **kwargs):
return self.parsed_cmd('zone delete %s' % id, *args, **kwargs)
def zone_create(self, name, email, *args, **kwargs):
cmd = 'zone create %s --email %s' % (name, email)
return self.parsed_cmd(cmd, FieldValueModel, *args, **kwargs)
class DesignateCLI(base.CLIClient, ZoneCommands):
@classmethod
def get_clients(cls):
return {
'default': DesignateCLI(
cli_dir=cfg.CONF.designateclient.directory,
username=cfg.CONF.identity.username,
password=cfg.CONF.identity.password,
tenant_name=cfg.CONF.identity.tenant_name,
uri=cfg.CONF.identity.uri,
),
'alt': DesignateCLI(
cli_dir=cfg.CONF.designateclient.directory,
username=cfg.CONF.identity.alt_username,
password=cfg.CONF.identity.alt_password,
tenant_name=cfg.CONF.identity.alt_tenant_name,
uri=cfg.CONF.identity.uri,
),
'admin': DesignateCLI(
cli_dir=cfg.CONF.designateclient.directory,
username=cfg.CONF.identity.admin_username,
password=cfg.CONF.identity.admin_password,
tenant_name=cfg.CONF.identity.admin_tenant_name,
uri=cfg.CONF.identity.uri,
)
}
@classmethod
def as_user(self, user):
clients = self.get_clients()
if user in clients:
return clients[user]
raise Exception("User '{0}' does not exist".format(user))
def parsed_cmd(self, cmd, model=None, *args, **kwargs):
out = self.openstack(cmd, *args, **kwargs)
LOG.debug(out)
if model is not None:
return model(out)
return out

View File

@ -0,0 +1,64 @@
"""
Copyright 2015 Rackspace
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
cfg.CONF.register_group(cfg.OptGroup(
name='identity', title="Configuration for Keystone auth"
))
cfg.CONF.register_group(cfg.OptGroup(
name='designateclient', title="Configuration for the Designate client"
))
cfg.CONF.register_opts([
cfg.StrOpt('uri', help="The Keystone v2 endpoint"),
cfg.StrOpt('uri_v3', help="The Keystone v3 endpoint"),
cfg.StrOpt('auth_version', default='v2'),
cfg.StrOpt('region', default='RegionOne'),
cfg.StrOpt('username'),
cfg.StrOpt('tenant_name'),
cfg.StrOpt('password', secret=True),
cfg.StrOpt('domain_name'),
cfg.StrOpt('alt_username'),
cfg.StrOpt('alt_tenant_name'),
cfg.StrOpt('alt_password', secret=True),
cfg.StrOpt('alt_domain_name'),
cfg.StrOpt('admin_username'),
cfg.StrOpt('admin_tenant_name'),
cfg.StrOpt('admin_password', secret=True),
cfg.StrOpt('admin_domain_name'),
], group='identity')
cfg.CONF.register_opts([
cfg.StrOpt('directory',
help='the directory containing the client executable'),
], group='designateclient')
def find_config_file():
return os.environ.get(
'TEMPEST_CONFIG', '/opt/stack/tempest/etc/tempest.conf')
def read_config():
cfg.CONF(args=[], default_config_files=[find_config_file()])

View File

@ -0,0 +1,22 @@
"""
Copyright 2015 Rackspace
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 random
import string
def random_zone_name(name='testdomain'):
digits = "".join([random.choice(string.digits) for _ in range(8)])
return "{0}{1}.com.".format(name, digits)

View File

@ -0,0 +1,80 @@
"""
Copyright 2015 Rackspace
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_lib.cli import output_parser
class Model(object):
def __str__(self):
return str(self.__dict__)
class FieldValueModel(Model):
"""This converts cli output from messy lists/dicts to neat attributes."""
def __init__(self, out):
"""This parses output with fields and values like:
+----------------+------------------------------+
| Field | Value |
+----------------+------------------------------+
| action | CREATE |
| created_at | 2015-08-20T17:22:17.000000 |
| description | None |
+----------------+------------------------------+
These are then accessible as:
model.action
model.created_at
model.description
"""
table = output_parser.table(out)
for field, value in table['values']:
setattr(self, field, value)
class ListEntryModel(Model):
def __init__(self, fields, values):
for k, v in zip(fields, values):
setattr(self, k, v)
class ListModel(Model, list):
def __init__(self, out):
"""This parses an output table with any number of headers, and any
number of entries:
+--------------------------------------+----------+---------+
| id | name | type |
+--------------------------------------+----------+---------+
| e658a875-1024-4f88-a347-e5b244ec5a10 | aaa.com. | PRIMARY |
+--------------------------------------+----------+---------+
| 98d1fb5f-2954-448e-988e-6f1df0f24c52 | bbb.com. | PRIMARY |
+--------------------------------------+----------+---------+
These are then accessible as:
model[0].name == 'aaa.com.'
model[1].name == 'bbb.com.'
"""
table = output_parser.table(out)
for entry in table['values']:
self.append(ListEntryModel(table['headers'], entry))

View File

@ -0,0 +1,47 @@
"""
Copyright 2015 Rackspace
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_lib.exceptions import CommandFailed
from designateclient.functionaltests.base import BaseDesignateTest
from designateclient.functionaltests.datagen import random_zone_name
class TestZone(BaseDesignateTest):
def setUp(self):
super(TestZone, self).setUp()
self.zone_name = random_zone_name()
zone = self.clients.zone_create(name=self.zone_name,
email='test@example.com')
self.zone_id = zone.id
def test_zone_list(self):
zones = self.clients.zone_list()
self.assertGreater(len(zones), 0)
def test_zone_create_and_show(self):
zone = self.clients.zone_show(self.zone_id)
self.assertEqual(zone.name, self.zone_name)
self.assertEqual(zone.id, self.zone_id)
def test_zone_delete(self):
self.clients.zone_delete(self.zone_id)
self.assertRaises(CommandFailed, self.clients.zone_show, self.zone_id)
def tearDown(self):
if hasattr(self, 'zone_id'):
self.clients.zone_delete(self.zone_id, fail_ok=True)
super(TestZone, self).tearDown()

View File

@ -0,0 +1,67 @@
================
Functional Tests
================
The functional tests invoke the client executable to see that it actually works
with a running Designate. WARNING: these tests will create and delete zones,
recordsets, and other resources in Designate.
Installation
------------
.. code-block:: shell-session
cd python-designateclient
pip install python-openstackclient
pip install -r requirements.txt -r test-requirements.txt
pip install -e .
Configuration
-------------
The functional tests look for a variable ``TEMPEST_CONFIG`` which specifies a
config file for the test.
.. code-block:: shell-session
export TEMPEST_CONFIG=tempest.conf
The tests will use Keystone to grab the Designate endpoint to test against.
They need at least three users (two regular users, and one admin) for all the
tests to run.
.. code-block:: shell-session
[identity]
uri = http://localhost:5000/v2.0
uri_v3 = http://localhost:5000/v3
auth_version = v2
region = RegionOne
username = demo
tenant_name = demo
password = password
domain_name = Default
alt_username = alt_demo
alt_tenant_name = alt_demo
alt_password = password
alt_domain_name = Default
admin_username = admin
admin_tenant_name = admin
admin_password = password
admin_domain_name = Default
[designateclient]
# the directory containing the openstack executable
directory=/root/python-designateclient/.venv/bin
Running the tests
-----------------
The functional tests are run with tox (installed with ``pip install tox``):
.. code-block:: shell-session
tox -e functional

View File

@ -21,6 +21,7 @@ Contents
shell-examples
shell-v2
contributing
functional-tests
Indices and tables
==================

View File

@ -12,3 +12,4 @@ requests-mock>=0.6.0 # Apache-2.0
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
testrepository>=0.0.18
oslosphinx>=2.5.0 # Apache-2.0
tempest-lib>=0.8.0

10
tox.ini
View File

@ -33,6 +33,16 @@ commands = python setup.py testr --coverage --testr-args='{posargs}'
[testenv:venv]
commands = {posargs}
[testenv:functional]
usedevelop = False
setenv = VIRTUAL_ENV={envdir}
OS_TEST_PATH=designateclient/functionaltests/
passenv = OS_STDOUT_CAPTURE
OS_STDERR_CAPTURE
OS_LOG_CAPTURE
OS_DEBUG
TEMPEST_CONFIG
[flake8]
# ignored flake8 codes:
# H302 import only modules