Add unit tests

Added unit tests and tidy up calls to initialise Keystone client.

Closes-Bug: 1628491
Change-Id: I14ea09539e5781ded75bcfa47108f10e27c816df
This commit is contained in:
Liam Young 2016-10-29 14:05:25 +02:00
parent c2ccf415c5
commit f9100ec85d
8 changed files with 494 additions and 41 deletions

View File

@ -65,15 +65,24 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
"""Collection keystone information from keystone relation"""
return self.relation.credentials()
def init_keystone_client(self):
"""Initialise keystone client"""
if self.kc:
return
self.keystone_auth_url = '{}://{}:{}/v2.0'.format(
@property
def ks_client(self):
if not self.kc:
self.init_keystone_client()
return self.kc
@property
def keystone_auth_url(self):
return '{}://{}:{}/v2.0'.format(
'http',
self.keystone_info['service_hostname'],
self.keystone_info['service_port']
)
def init_keystone_client(self):
"""Initialise keystone client"""
if self.kc:
return
auth = {
'username': self.keystone_info['service_username'],
'password': self.keystone_info['service_password'],
@ -92,14 +101,15 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
@returns {'access_token' token1, 'secret_token': token2}
"""
self.init_keystone_client()
if not self.kc:
if not self.ks_client:
return {}
current_creds = self.kc.ec2.list(self.kc.user_id)
current_creds = self.ks_client.ec2.list(self.ks_client.user_id)
if current_creds:
creds = current_creds[0]
else:
creds = self.kc.ec2.create(self.kc.user_id, self.kc.tenant_id)
creds = self.ks_client.ec2.create(
self.ks_client.user_id,
self.ks_client.tenant_id)
return {'access_token': creds.access, 'secret_token': creds.secret}
@property
@ -108,14 +118,13 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
@returns {'image_id' id1, 'image_alt_id': id2}
"""
self.init_keystone_client()
image_info = {}
try:
glance_endpoint = self.kc.service_catalog.url_for(
glance_endpoint = self.ks_client.service_catalog.url_for(
service_type='image',
endpoint_type='publicURL')
glance_client = glanceclient.Client(
'2', glance_endpoint, token=self.kc.auth_token)
'2', glance_endpoint, token=self.ks_client.auth_token)
for image in glance_client.images.list():
if self.uconfig.get('glance-image-name') == image.name:
image_info['image_id'] = image.id
@ -138,15 +147,14 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
@returns {'public_network_id' id1, 'router_id': id2}
"""
self.init_keystone_client()
network_info = {}
try:
neutron_ep = self.kc.service_catalog.url_for(
neutron_ep = self.ks_client.service_catalog.url_for(
service_type='network',
endpoint_type='publicURL')
neutron_client = neutronclient.Client(
endpoint_url=neutron_ep,
token=self.kc.auth_token)
token=self.ks_client.auth_token)
routers = neutron_client.list_routers(
name=self.uconfig['router-name'])
if len(routers['routers']) == 0:
@ -178,10 +186,9 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
@returns {'flavor_id' id1, 'flavor_alt_id': id2}
"""
self.init_keystone_client()
compute_info = {}
try:
nova_ep = self.kc.service_catalog.url_for(
nova_ep = self.ks_client.service_catalog.url_for(
service_type='compute',
endpoint_type='publicURL'
)
@ -210,8 +217,9 @@ class TempestAdminAdapter(adapters.OpenStackRelationAdapter):
@returns [svc1, svc2, ...]: List of registered services
"""
self.init_keystone_client()
services = [svc.name for svc in self.kc.services.list() if svc.enabled]
services = [svc.name
for svc in self.ks_client.services.list()
if svc.enabled]
return services
@property
@ -338,7 +346,7 @@ class TempestCharm(charm.OpenStackCharm):
env['https_proxy'] = conf['https-proxy']
cmd = ['tox', '-e', tox_target]
f = open(logfile, "w")
subprocess.call(cmd, cwd=run_dir, stdout=f, stderr=f)
subprocess.call(cmd, cwd=run_dir, stdout=f, stderr=f, env=env)
def get_tempest_files(self, branch_name):
"""Prepare tempest files and directories
@ -357,7 +365,7 @@ class TempestCharm(charm.OpenStackCharm):
@return dict: Dictonary of summary data
"""
summary = {}
with open(logfile) as tempest_log:
with open(logfile, 'r') as tempest_log:
summary_line = False
for line in tempest_log:
if line.strip() == "Totals":

View File

@ -1,7 +1,11 @@
# Unit test requirements
flake8>=2.2.4,<=2.4.1
# Lint and unit test requirements
flake8
os-testr>=0.4.1
charms.reactive
mock>=1.2
coverage>=3.6
git+https://github.com/openstack/charms.openstack#egg=charms.openstack
requests
python-glanceclient
python-neutronclient
python-novaclient
git+https://github.com/openstack/charms.openstack.git#egg=charms-openstack

View File

@ -1,5 +1,46 @@
# By design, this unit_tests dir is outside the src charm (layer),
# and it will not be included in the resultant built charm asset.
# Copyright 2016 Canonical Ltd
#
# Include unit tests here which are intended to be executable
# from the source charm but not the built charm.
# 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 sys
import mock
sys.path.append('src')
sys.path.append('src/lib')
# Mock out charmhelpers so that we can test without it.
# also stops sideeffects from occuring.
charmhelpers = mock.MagicMock()
apt_pkg = mock.MagicMock()
sys.modules['apt_pkg'] = apt_pkg
sys.modules['charmhelpers'] = charmhelpers
sys.modules['charmhelpers.core'] = charmhelpers.core
sys.modules['charmhelpers.core.decorators'] = charmhelpers.core.decorators
sys.modules['charmhelpers.core.hookenv'] = charmhelpers.core.hookenv
sys.modules['charmhelpers.core.host'] = charmhelpers.core.host
sys.modules['charmhelpers.core.unitdata'] = charmhelpers.core.unitdata
sys.modules['charmhelpers.core.templating'] = charmhelpers.core.templating
sys.modules['charmhelpers.contrib'] = charmhelpers.contrib
sys.modules['charmhelpers.contrib.openstack'] = charmhelpers.contrib.openstack
sys.modules['charmhelpers.contrib.openstack.utils'] = (
charmhelpers.contrib.openstack.utils)
sys.modules['charmhelpers.contrib.openstack.templating'] = (
charmhelpers.contrib.openstack.templating)
sys.modules['charmhelpers.contrib.network'] = charmhelpers.contrib.network
sys.modules['charmhelpers.contrib.network.ip'] = (
charmhelpers.contrib.network.ip)
sys.modules['charmhelpers.fetch'] = charmhelpers.fetch
sys.modules['charmhelpers.cli'] = charmhelpers.cli
sys.modules['charmhelpers.contrib.hahelpers'] = charmhelpers.contrib.hahelpers
sys.modules['charmhelpers.contrib.hahelpers.cluster'] = (
charmhelpers.contrib.hahelpers.cluster)

View File

@ -0,0 +1,21 @@
TEMPEST_OUT = """
======
Totals
======
Ran: 62 tests in 64.8226 sec.
- Passed: 21
- Skipped: 41
- Expected Fail: 0
- Unexpected Success: 0
- Failed: 0
Sum of execute time for each test: 13.7436 sec.
==============
Worker Balance
==============
- Worker 0 (62 tests) => 0:00:59.719541
___________________________________ summary
____________________________________
smoke: commands succeeded
congratulations :)
"""

View File

@ -0,0 +1,358 @@
# Copyright 2016 Canonical Ltd
#
# 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 __future__ import absolute_import
from __future__ import print_function
import io
import mock
import charms_openstack.test_utils as test_utils
import charm.openstack.tempest as tempest
import unit_tests.tempest_output
class Helper(test_utils.PatchHelper):
def setUp(self):
super().setUp()
self.patch_release(tempest.TempestCharm.release)
class TestTempestAdminAdapter(test_utils.PatchHelper):
def test_init(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
tempest.TempestAdminAdapter('rel2')
self.init_keystone_client.assert_called_once_with()
self.__init__.assert_called_once_with('rel2')
def test_init_keystone_client(self):
ks_info = {
'service_hostname': 'kshost',
'service_port': '5001',
'service_username': 'user1',
'service_password': 'pass1',
'service_tenant_name': 'svc',
'service_region': 'reg1'}
self.patch_object(tempest.keystoneclient.client, 'Client')
self.patch_object(tempest.hookenv, 'config')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
self.patch_object(
tempest.TempestAdminAdapter,
'keystone_info',
new=ks_info)
a = tempest.TempestAdminAdapter('rel2')
a.init_keystone_client()
self.Client.assert_called_once_with(
auth_url='http://kshost:5001/v2.0',
password='pass1',
region_name='reg1',
tenant_name='svc',
username='user1')
def test_ec2_creds(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
kc = mock.MagicMock()
creds = mock.MagicMock()
creds.access = 'ac2'
creds.secret = 'st2'
kc.user_id = 'bob'
kc.ec2.list = lambda x: [creds]
self.patch_object(tempest.TempestAdminAdapter, 'ks_client', new=kc)
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(a.ec2_creds, {'access_token': 'ac2',
'secret_token': 'st2'})
def test_image_info(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
self.patch_object(tempest.TempestAdminAdapter, 'ks_client')
self.patch_object(tempest.glanceclient, 'Client')
self.config.return_value = {
'glance-image-name': 'img1',
'glance-alt-image-name': 'altimg',
}
kc = mock.MagicMock()
kc.service_catalog.url_for = \
lambda service_type=None, endpoint_type=None: 'http://glance'
self.ks_client.return_value = kc
img1 = mock.MagicMock()
img1.name = 'img1'
img1.id = 'img1_id'
img2 = mock.MagicMock()
img2.name = 'img2'
img2.id = 'img2_id'
gc = mock.MagicMock()
gc.images.list = lambda: [img1, img2]
self.Client.return_value = gc
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(a.image_info, {'image_id': 'img1_id'})
def test_network_info(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
self.patch_object(tempest.TempestAdminAdapter, 'ks_client')
self.patch_object(tempest.neutronclient, 'Client')
router1 = {'id': '16'}
net1 = {'id': 'pubnet1'}
kc = mock.MagicMock()
kc.service_catalog.url_for = \
lambda service_type=None, endpoint_type=None: 'http://neutron'
self.ks_client.return_value = kc
self.config.return_value = {
'router-name': 'route1',
'network-name': 'net1'}
nc = mock.MagicMock()
nc.list_routers = lambda name=None: {'routers': [router1]}
nc.list_networks = lambda name=None: {'networks': [net1]}
self.Client.return_value = nc
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(
a.network_info,
{'public_network_id': 'pubnet1', 'router_id': '16'})
def test_compute_info(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
ki = {
'service_username': 'user',
'service_password': 'pass',
'service_tenant_name': 'ten',
}
self.patch_object(
tempest.TempestAdminAdapter,
'keystone_info',
new=ki)
self.patch_object(
tempest.TempestAdminAdapter,
'keystone_auth_url',
new='auth_url')
self.config.return_value = {
'flavor-name': 'm3.huuge'}
kc = mock.MagicMock()
kc.service_catalog.url_for = \
lambda service_type=None, endpoint_type=None: 'http://nova:999/bob'
self.patch_object(tempest.TempestAdminAdapter, 'ks_client', new=kc)
self.patch_object(tempest.novaclient.client, 'Client')
_flavor1 = mock.MagicMock()
_flavor1.name = 'm3.huuge'
_flavor1.id = 'id1'
nc = mock.MagicMock()
nc.flavors.list = lambda: [_flavor1]
self.Client.return_value = nc
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(
a.compute_info,
{
'flavor_id': 'id1',
'nova_base': 'http://nova',
'nova_endpoint': 'http://nova:999/bob'})
def test_get_present_services(self):
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
kc = mock.MagicMock()
svc1 = mock.Mock()
svc2 = mock.Mock()
svc3 = mock.Mock()
svc1.name = 'compute'
svc1.enabled = True
svc2.name = 'image'
svc2.enabled = False
svc3.name = 'network'
svc3.enabled = True
svcs = [svc1, svc2, svc3]
kc.services.list = lambda: svcs
self.patch_object(tempest.TempestAdminAdapter, 'ks_client', new=kc)
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(
a.get_present_services(),
['compute', 'network'])
def test_service_info(self):
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
self.patch_object(tempest.TempestAdminAdapter, 'get_present_services')
self.get_present_services.return_value = ['cinder', 'glance']
self.patch_object(tempest.hookenv, 'action_get')
self.action_get.return_value = {
'service-whitelist': 'swift glance'}
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(
a.service_info,
{
'ceilometer': 'false',
'cinder': 'false',
'glance': 'true',
'heat': 'false',
'horizon': 'false',
'ironic': 'false',
'neutron': 'false',
'nova': 'false',
'sahara': 'false',
'swift': 'true',
'trove': 'false',
'zaqar': 'false',
'neutron': 'false'})
def test_service_info_auto(self):
self.patch_object(tempest.TempestAdminAdapter, 'init_keystone_client')
self.patch_object(
tempest.adapters.OpenStackRelationAdapter, '__init__')
self.patch_object(tempest.TempestAdminAdapter, 'get_present_services')
self.get_present_services.return_value = ['cinder', 'glance']
self.patch_object(tempest.hookenv, 'action_get')
self.action_get.return_value = {
'service-whitelist': 'auto'}
a = tempest.TempestAdminAdapter('rel2')
self.assertEqual(
a.service_info,
{
'ceilometer': 'false',
'cinder': 'true',
'glance': 'true',
'heat': 'false',
'horizon': 'false',
'ironic': 'false',
'neutron': 'false',
'nova': 'false',
'sahara': 'false',
'swift': 'false',
'trove': 'false',
'zaqar': 'false',
'neutron': 'false'})
class TestTempestCharm(Helper):
def test_setup_directories(self):
self.patch_object(tempest.os.path, 'exists')
self.patch_object(tempest.os, 'mkdir')
self.exists.return_value = False
c = tempest.TempestCharm()
c.setup_directories()
calls = [
mock.call('/var/lib/tempest'),
mock.call('/var/lib/tempest/logs')
]
self.mkdir.assert_has_calls(calls)
def test_setup_git(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.os.path, 'exists')
self.patch_object(tempest.os, 'symlink')
self.patch_object(tempest.fetch, 'install_remote')
self.config.return_value = {'tempest-source': 'git_url'}
self.exists.return_value = False
c = tempest.TempestCharm()
c.setup_git('git_branch', 'git_dir')
self.install_remote.assert_called_once_with(
'git_url',
branch='git_branch',
depth='1',
dest='git_dir')
self.symlink.assert_called_once_with(
'/var/lib/tempest/tempest.conf',
'git_dir/tempest/etc/tempest.conf')
def test_setup_git_noop(self):
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.os.path, 'exists')
self.config.return_value = {'tempest-source': 'git_url'}
self.patch_object(tempest.os, 'symlink')
self.patch_object(tempest.fetch, 'install_remote')
self.exists.return_value = True
c = tempest.TempestCharm()
c.setup_git('git_branch', 'git_dir')
self.assertFalse(self.install_remote.called)
self.assertFalse(self.symlink.called)
def test_execute_tox(self):
# XXX env seems unused
self.patch_object(tempest.hookenv, 'config')
self.patch_object(tempest.os.environ, 'copy')
self.patch_object(tempest.subprocess, 'call')
os_env = mock.MagicMock()
self.copy.return_value = os_env
self.config.return_value = {
'http-proxy': 'http://proxy',
'https-proxy': 'https://proxy',
}
with mock.patch("builtins.open", return_value="fhandle"):
c = tempest.TempestCharm()
c.execute_tox('/tmp/run', '/tmp/t.log', 'py38')
self.call.assert_called_with(
['tox', '-e', 'py38'],
cwd='/tmp/run',
stderr='fhandle',
stdout='fhandle',
env=os_env)
def test_get_tempest_files(self):
self.patch_object(tempest.time, 'strftime')
self.strftime.return_value = 'teatime'
c = tempest.TempestCharm()
self.assertEqual(
c.get_tempest_files('br1'),
('/var/lib/tempest/tempest-br1',
'/var/lib/tempest/logs/run_teatime.log',
'/var/lib/tempest/tempest-br1/tempest'))
def test_parse_tempest_log(self):
_log_contents = io.StringIO(unit_tests.tempest_output.TEMPEST_OUT)
expect = {
'expected-fail': '0',
'failed': '0',
'passed': '21',
'skipped': '41',
'unexpected-success': '0'}
with mock.patch("builtins.open", return_value=_log_contents):
c = tempest.TempestCharm()
self.assertEqual(c.parse_tempest_log("logfile"), expect)
def test_run_test(self):
self.patch_object(tempest.hookenv, 'action_set')
self.patch_object(tempest.hookenv, 'action_get')
self.action_get.return_value = {
'branch': 'br1'}
self.patch_object(tempest.TempestCharm, 'get_tempest_files')
self.patch_object(tempest.TempestCharm, 'setup_directories')
self.patch_object(tempest.TempestCharm, 'setup_git')
self.patch_object(tempest.TempestCharm, 'execute_tox')
self.patch_object(tempest.TempestCharm, 'parse_tempest_log')
self.get_tempest_files.return_value = (
'git_dir1',
'/var/log/t.log',
'/var/tempest/run')
self.parse_tempest_log.return_value = {'run_info': 'OK'}
c = tempest.TempestCharm()
c.run_test('py39')
self.action_set.assert_called_once_with(
{'run_info': 'OK', 'tempest-logfile': '/var/log/t.log'})

View File

@ -1,13 +0,0 @@
import unittest
class TestNoOp(unittest.TestCase):
"""Placeholder - Write Me!"""
# XXX (beisner): with the charm.openstack vs lib/charm/openstack/tempest
# module namespace collision, and with the hard requirement to have some
# sort of unit test passing, here is a temporary inert noop test. After
# charms_openstack module is completed, and this tempest charm is
# refactored to use it, revisit this and add actual unit tests.
def test_noop(self):
"""Test Nothing"""
pass

View File

@ -0,0 +1,34 @@
# Copyright 2016 Canonical Ltd
#
# 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 __future__ import absolute_import
from __future__ import print_function
import reactive.tempest_handlers as handlers
import charms_openstack.test_utils as test_utils
class TestRegisteredHooks(test_utils.TestRegisteredHooks):
def test_hooks(self):
defaults = []
hook_set = {
'when': {
'install_packages': ('charm.installed',),
'assess_status': ('charm.installed',),
}
}
# test that the hooks were registered via the
# reactive.barbican_handlers
self.registered_hooks_test_helper(handlers, hook_set, defaults)