Skip to content

Commit b85b9dc

Browse files
Zuulopenstack-gerrit
authored andcommitted
Merge "Move functional tests from watcher_tempest_plugin to watcherclient"
2 parents edbc62c + e9fc2c8 commit b85b9dc

File tree

14 files changed

+830
-1
lines changed

14 files changed

+830
-1
lines changed

.zuul.yaml

Lines changed: 28 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,30 @@
1+
- job:
2+
name: python-watcherclient-functional
3+
parent: devstack-tox-functional
4+
timeout: 7200
5+
required-projects:
6+
- openstack/watcher
7+
- openstack/python-watcherclient
8+
vars:
9+
# Run cross-project watcherclient functional tests on watcher repo.
10+
zuul_work_dir: src/opendev.org/openstack/python-watcherclient
11+
openrc_enable_export: true
12+
devstack_plugins:
13+
watcher: https://opendev.org/openstack/watcher
14+
devstack_services:
15+
watcher-api: true
16+
watcher-decision-engine: true
17+
watcher-applier: true
18+
s-account: false
19+
s-container: false
20+
s-object: false
21+
s-proxy: false
22+
irrelevant-files:
23+
- ^.*\.rst$
24+
- ^doc/.*$
25+
- ^releasenotes/.*$
26+
27+
128
- project:
229
templates:
330
- openstack-cover-jobs
@@ -7,4 +34,4 @@
734
- openstackclient-plugin-jobs
835
check:
936
jobs:
10-
- watcherclient-tempest-functional
37+
- python-watcherclient-functional

test-requirements.txt

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,3 +6,4 @@ python-subunit>=1.0.0 # Apache-2.0/BSD
66
stestr>=2.0.0 # Apache-2.0
77
testscenarios>=0.4 # Apache-2.0/BSD
88
testtools>=2.2.0 # MIT
9+
tempest>=17.1.0 # Apache-2.0

tox.ini

Lines changed: 5 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -77,3 +77,8 @@ commands = python setup.py bdist_wheel
7777

7878
[hacking]
7979
import_exceptions = watcherclient._i18n
80+
81+
[testenv:functional]
82+
passenv = OS_*
83+
commands =
84+
stestr --test-path=./watcherclient/tests/client_functional/ run --concurrency=1 {posargs}

watcherclient/tests/client_functional/__init__.py

Whitespace-only changes.

watcherclient/tests/client_functional/v1/__init__.py

Whitespace-only changes.
Lines changed: 141 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,141 @@
1+
# Licensed under the Apache License, Version 2.0 (the "License"); you may
2+
# not use this file except in compliance with the License. You may obtain
3+
# a copy of the License at
4+
#
5+
# http://www.apache.org/licenses/LICENSE-2.0
6+
#
7+
# Unless required by applicable law or agreed to in writing, software
8+
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
9+
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
10+
# License for the specific language governing permissions and limitations
11+
# under the License.
12+
13+
import os
14+
15+
import re
16+
import shlex
17+
import subprocess
18+
import testtools
19+
20+
from tempest.lib.cli import output_parser
21+
from tempest.lib import exceptions
22+
23+
24+
def credentials():
25+
# You can get credentials from OS environment.
26+
creds_dict = {
27+
'--os-username': os.environ.get('OS_USERNAME'),
28+
'--os-password': os.environ.get('OS_PASSWORD'),
29+
'--os-project-name': os.environ.get('OS_PROJECT_NAME'),
30+
'--os-auth-url': os.environ.get('OS_AUTH_URL'),
31+
'--os-project-domain-name': os.environ.get('OS_PROJECT_DOMAIN_ID'),
32+
'--os-user-domain-name': os.environ.get('OS_USER_DOMAIN_ID'),
33+
}
34+
return [x for sub in creds_dict.items() for x in sub]
35+
36+
37+
def execute(cmd, fail_ok=False, merge_stderr=False):
38+
"""Executes specified command for the given action."""
39+
cmdlist = shlex.split(cmd)
40+
cmdlist.extend(credentials())
41+
stdout = subprocess.PIPE
42+
stderr = subprocess.STDOUT if merge_stderr else subprocess.PIPE
43+
proc = subprocess.Popen(cmdlist, stdout=stdout, stderr=stderr)
44+
result, result_err = proc.communicate()
45+
result = result.decode('utf-8')
46+
if not fail_ok and proc.returncode != 0:
47+
raise exceptions.CommandFailed(proc.returncode, cmd, result,
48+
result_err)
49+
return result
50+
51+
52+
class TestCase(testtools.TestCase):
53+
54+
delimiter_line = re.compile(r'^\+\-[\+\-]+\-\+$')
55+
56+
api_version = 1.0
57+
58+
@classmethod
59+
def watcher(cls, cmd, fail_ok=False):
60+
"""Executes watcherclient command for the given action."""
61+
return execute(
62+
'openstack optimize --os-infra-optim-api-version {0} {1}'.format(
63+
cls.api_version, cmd), fail_ok=fail_ok)
64+
65+
@classmethod
66+
def get_opts(cls, fields, format='value'):
67+
return ' -f {0} {1}'.format(format,
68+
' '.join(['-c ' + it for it in fields]))
69+
70+
@classmethod
71+
def assertOutput(cls, expected, actual):
72+
if expected != actual:
73+
raise Exception('{0} != {1}'.format(expected, actual))
74+
75+
@classmethod
76+
def assertInOutput(cls, expected, actual):
77+
if expected not in actual:
78+
raise Exception('{0} not in {1}'.format(expected, actual))
79+
80+
def assert_table_structure(self, items, field_names):
81+
"""Verify that all items have keys listed in field_names."""
82+
for item in items:
83+
for field in field_names:
84+
self.assertIn(field, item)
85+
86+
def assert_show_fields(self, items, field_names):
87+
"""Verify that all items have keys listed in field_names."""
88+
for item in items:
89+
for key in item.keys():
90+
self.assertIn(key, field_names)
91+
92+
def assert_show_structure(self, items, field_names):
93+
"""Verify that all field_names listed in keys of all items."""
94+
if isinstance(items, list):
95+
o = {}
96+
for d in items:
97+
o.update(d)
98+
else:
99+
o = items
100+
item_keys = o.keys()
101+
for field in field_names:
102+
self.assertIn(field, item_keys)
103+
104+
@staticmethod
105+
def parse_show_as_object(raw_output):
106+
"""Return a dict with values parsed from cli output."""
107+
items = TestCase.parse_show(raw_output)
108+
o = {}
109+
for item in items:
110+
o.update(item)
111+
return o
112+
113+
@staticmethod
114+
def parse_show(raw_output):
115+
"""Return list of dicts with item values parsed from cli output."""
116+
117+
items = []
118+
table_ = output_parser.table(raw_output)
119+
for row in table_['values']:
120+
item = {}
121+
item[row[0]] = row[1]
122+
items.append(item)
123+
return items
124+
125+
def parse_listing(self, raw_output):
126+
"""Return list of dicts with basic item parsed from cli output."""
127+
return output_parser.listing(raw_output)
128+
129+
def has_actionplan_succeeded(self, ap_uuid):
130+
return self.parse_show_as_object(
131+
self.watcher('actionplan show %s' % ap_uuid)
132+
)['State'] == 'SUCCEEDED'
133+
134+
@classmethod
135+
def has_audit_created(cls, audit_uuid):
136+
audit = cls.parse_show_as_object(
137+
cls.watcher('audit show %s' % audit_uuid))
138+
if audit['Audit Type'] == 'ONESHOT':
139+
return audit['State'] == 'SUCCEEDED'
140+
else:
141+
return audit['State'] == 'ONGOING'
Lines changed: 81 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,81 @@
1+
# Copyright (c) 2016 Servionica
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
# implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from oslo_utils import uuidutils
17+
18+
import functools
19+
20+
from tempest.lib.common.utils import test_utils
21+
22+
from watcherclient.tests.client_functional.v1 import base
23+
24+
25+
class ActionTests(base.TestCase):
26+
"""Functional tests for action."""
27+
28+
dummy_name = 'dummy'
29+
list_fields = ['UUID', 'Parents', 'State', 'Action Plan', 'Action']
30+
detailed_list_fields = list_fields + ['Created At', 'Updated At',
31+
'Deleted At', 'Parameters']
32+
audit_template_name = 'a' + uuidutils.generate_uuid()
33+
audit_uuid = None
34+
35+
@classmethod
36+
def setUpClass(cls):
37+
template_raw_output = cls.watcher(
38+
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
39+
template_output = cls.parse_show_as_object(template_raw_output)
40+
audit_output = cls.parse_show_as_object(cls.watcher(
41+
'audit create -a %s' % template_output['Name']))
42+
cls.audit_uuid = audit_output['UUID']
43+
audit_created = test_utils.call_until_true(
44+
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
45+
duration=600,
46+
sleep_for=2)
47+
if not audit_created:
48+
raise Exception('Audit has not been succeeded')
49+
50+
@classmethod
51+
def tearDownClass(cls):
52+
# Delete Action Plan and all related actions.
53+
output = cls.parse_show(
54+
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
55+
action_plan_uuid = list(output[0])[0]
56+
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
57+
cls.assertOutput('', raw_output)
58+
# Delete audit
59+
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
60+
cls.assertOutput('', raw_output)
61+
# Delete Template
62+
raw_output = cls.watcher(
63+
'audittemplate delete %s' % cls.audit_template_name)
64+
cls.assertOutput('', raw_output)
65+
66+
def test_action_list(self):
67+
raw_output = self.watcher('action list')
68+
self.assert_table_structure([raw_output], self.list_fields)
69+
70+
def test_action_detailed_list(self):
71+
raw_output = self.watcher('action list --detail')
72+
self.assert_table_structure([raw_output], self.detailed_list_fields)
73+
74+
def test_action_show(self):
75+
action_list = self.parse_show(self.watcher('action list --audit %s'
76+
% self.audit_uuid))
77+
action_uuid = list(action_list[0])[0]
78+
action = self.watcher('action show %s' % action_uuid)
79+
self.assertIn(action_uuid, action)
80+
self.assert_table_structure([action],
81+
self.detailed_list_fields)
Lines changed: 97 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,97 @@
1+
# Copyright (c) 2016 Servionica
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
12+
# implied.
13+
# See the License for the specific language governing permissions and
14+
# limitations under the License.
15+
16+
from oslo_utils import uuidutils
17+
18+
import functools
19+
20+
from tempest.lib.common.utils import test_utils
21+
22+
from watcherclient.tests.client_functional.v1 import base
23+
24+
25+
class ActionPlanTests(base.TestCase):
26+
"""Functional tests for action plan."""
27+
28+
dummy_name = 'dummy'
29+
list_fields = ['UUID', 'Audit', 'State', 'Updated At', 'Global efficacy']
30+
detailed_list_fields = list_fields + ['Created At', 'Deleted At',
31+
'Strategy', 'Efficacy indicators',
32+
'Hostname']
33+
audit_template_name = 'a' + uuidutils.generate_uuid()
34+
audit_uuid = None
35+
36+
@classmethod
37+
def setUpClass(cls):
38+
template_raw_output = cls.watcher(
39+
'audittemplate create %s dummy -s dummy' % cls.audit_template_name)
40+
template_output = cls.parse_show_as_object(template_raw_output)
41+
audit_raw_output = cls.watcher('audit create -a %s'
42+
% template_output['Name'])
43+
audit_output = cls.parse_show_as_object(audit_raw_output)
44+
cls.audit_uuid = audit_output['UUID']
45+
audit_created = test_utils.call_until_true(
46+
func=functools.partial(cls.has_audit_created, cls.audit_uuid),
47+
duration=600,
48+
sleep_for=2)
49+
if not audit_created:
50+
raise Exception('Audit has not been succeeded')
51+
52+
@classmethod
53+
def tearDownClass(cls):
54+
# Delete action plan
55+
output = cls.parse_show(
56+
cls.watcher('actionplan list --audit %s' % cls.audit_uuid))
57+
action_plan_uuid = list(output[0])[0]
58+
raw_output = cls.watcher('actionplan delete %s' % action_plan_uuid)
59+
cls.assertOutput('', raw_output)
60+
# Delete audit
61+
raw_output = cls.watcher('audit delete %s' % cls.audit_uuid)
62+
cls.assertOutput('', raw_output)
63+
# Delete Template
64+
raw_output = cls.watcher(
65+
'audittemplate delete %s' % cls.audit_template_name)
66+
cls.assertOutput('', raw_output)
67+
68+
def test_action_plan_list(self):
69+
raw_output = self.watcher('actionplan list')
70+
self.assert_table_structure([raw_output], self.list_fields)
71+
72+
def test_action_plan_detailed_list(self):
73+
raw_output = self.watcher('actionplan list --detail')
74+
self.assert_table_structure([raw_output], self.detailed_list_fields)
75+
76+
def test_action_plan_show(self):
77+
action_plan_list = self.parse_show(self.watcher('actionplan list'))
78+
action_plan_uuid = list(action_plan_list[0])[0]
79+
actionplan = self.watcher('actionplan show %s' % action_plan_uuid)
80+
self.assertIn(action_plan_uuid, actionplan)
81+
self.assert_table_structure([actionplan],
82+
self.detailed_list_fields)
83+
84+
def test_action_plan_start(self):
85+
output = self.parse_show(self.watcher('actionplan list --audit %s'
86+
% self.audit_uuid))
87+
action_plan_uuid = list(output[0])[0]
88+
self.watcher('actionplan start %s' % action_plan_uuid)
89+
raw_output = self.watcher('actionplan show %s' % action_plan_uuid)
90+
self.assert_table_structure([raw_output], self.detailed_list_fields)
91+
92+
self.assertTrue(test_utils.call_until_true(
93+
func=functools.partial(
94+
self.has_actionplan_succeeded, action_plan_uuid),
95+
duration=600,
96+
sleep_for=2
97+
))

0 commit comments

Comments
 (0)