Layout some functional tests for the V2 CLI
Change-Id: Ib2d1261bcb0362c586c0aae4b9c5a8a563f07c71
This commit is contained in:
parent
649e7d48c8
commit
6daf82777e
|
@ -22,3 +22,4 @@ doc/build/*
|
||||||
dist
|
dist
|
||||||
designateclient/versioninfo
|
designateclient/versioninfo
|
||||||
.testrepository
|
.testrepository
|
||||||
|
*.log
|
||||||
|
|
|
@ -1,4 +1,10 @@
|
||||||
[DEFAULT]
|
[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_id_option=--load-list $IDFILE
|
||||||
test_list_option=--list
|
test_list_option=--list
|
||||||
|
|
|
@ -0,0 +1,7 @@
|
||||||
|
import logging
|
||||||
|
|
||||||
|
logging.basicConfig(
|
||||||
|
filename='functional-tests.log',
|
||||||
|
filemode='w',
|
||||||
|
level=logging.DEBUG,
|
||||||
|
)
|
|
@ -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')
|
|
@ -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
|
|
@ -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()])
|
|
@ -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)
|
|
@ -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))
|
|
@ -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()
|
|
@ -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
|
|
@ -21,6 +21,7 @@ Contents
|
||||||
shell-examples
|
shell-examples
|
||||||
shell-v2
|
shell-v2
|
||||||
contributing
|
contributing
|
||||||
|
functional-tests
|
||||||
|
|
||||||
Indices and tables
|
Indices and tables
|
||||||
==================
|
==================
|
||||||
|
|
|
@ -12,3 +12,4 @@ requests-mock>=0.6.0 # Apache-2.0
|
||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||||
testrepository>=0.0.18
|
testrepository>=0.0.18
|
||||||
oslosphinx>=2.5.0 # Apache-2.0
|
oslosphinx>=2.5.0 # Apache-2.0
|
||||||
|
tempest-lib>=0.8.0
|
||||||
|
|
10
tox.ini
10
tox.ini
|
@ -33,6 +33,16 @@ commands = python setup.py testr --coverage --testr-args='{posargs}'
|
||||||
[testenv:venv]
|
[testenv:venv]
|
||||||
commands = {posargs}
|
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]
|
[flake8]
|
||||||
# ignored flake8 codes:
|
# ignored flake8 codes:
|
||||||
# H302 import only modules
|
# H302 import only modules
|
||||||
|
|
Loading…
Reference in New Issue