Release 'fuel2 health *' command

As we deprecate old cli a new 'fuel2 health *' commands
with extended functionality were implemented:

  fuel2 health list
  fuel2 health start
  fuel2 health stop
  fuel2 health restart
  fuel2 health status list
  fuel2 health status show

DocImpact

Change-Id: Ib9ba1912d0e0b391500d6bc257232fee0184620b
Closes-Bug: 1619196
This commit is contained in:
tivaliy 2016-09-05 22:20:25 +03:00
parent d4140d5472
commit 418dd206a4
14 changed files with 947 additions and 2 deletions

View File

@ -67,6 +67,7 @@ def get_client(resource, version='v1', connection=None):
'extension': v1.extension,
'fuel-version': v1.fuelversion,
'graph': v1.graph,
'health': v1.health,
'network-configuration': v1.network_configuration,
'network-group': v1.network_group,
'node': v1.node,

View File

@ -116,6 +116,7 @@ def quote_and_join(words):
return '"{0}"'.format(words[0])
# TODO(vkulanov): remove when deprecate old cli
def print_health_check(env):
tests_states = [{"status": "not finished"}]
finished_tests = set()

View File

@ -162,14 +162,15 @@ class APIClient(object):
return self._decode_content(resp)
def put_request(self, api, data, **params):
def put_request(self, api, data, ostf=False, **params):
"""Make PUT request to specific API with some data.
:param api: API endpoint (path)
:param data: Data send in request, will be serialized to JSON
:param ostf: is this a call to OSTF API
:param params: Params of query string
"""
url = self.api_root + api
url = (self.ostf_root if ostf else self.api_root) + api
data_json = json.dumps(data)
resp = self.session.put(url, data=data_json, params=params)

View File

@ -0,0 +1,177 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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 abc
import six
from fuelclient.commands import base
from fuelclient.common import data_utils
class HealthMixIn(object):
entity_name = 'health'
class HealthTestSetsList(HealthMixIn, base.BaseListCommand):
"""List of all available test sets for a given environment."""
columns = ("id",
"name")
filters = {'environment_id': 'env'}
def get_parser(self, prog_name):
parser = super(HealthTestSetsList, self).get_parser(prog_name)
parser.add_argument('-e',
'--env',
type=int,
required=True,
help='Id of the environment.')
return parser
class HealthCheckStart(HealthMixIn, base.BaseListCommand):
"""Run specified test sets for a given environment."""
columns = ("id",
"testset",
"cluster_id")
def get_parser(self, prog_name):
parser = super(HealthCheckStart, self).get_parser(prog_name)
parser.add_argument('-e',
'--env',
type=int,
required=True,
help='Id of the environment.')
parser.add_argument('--force',
action='store_true',
help='Force run health test sets.')
parser.add_argument('-t',
'--tests',
nargs='+',
help='Name of the test sets to run.')
parser.add_argument('--ostf-username',
default=None,
help='OSTF username.')
parser.add_argument('--ostf-password',
default=None,
help='OSTF password.')
parser.add_argument('--ostf-tenant-name',
default=None,
help='OSTF tenant name.')
return parser
def take_action(self, parsed_args):
ostf_credentials = {}
if parsed_args.ostf_tenant_name is not None:
ostf_credentials['tenant'] = parsed_args.ostf_tenant_name
if parsed_args.ostf_username is not None:
ostf_credentials['username'] = parsed_args.ostf_username
if parsed_args.ostf_password is not None:
ostf_credentials['password'] = parsed_args.ostf_password
if not ostf_credentials:
self.app.stdout.write("WARNING: ostf credentials are going to be "
"mandatory in the next release.\n")
data = self.client.start(parsed_args.env,
ostf_credentials=ostf_credentials,
test_sets=parsed_args.tests,
force=parsed_args.force)
msg = ("\nHealth check tests for environment with id {0} has been "
"started:\n".format(parsed_args.env))
self.app.stdout.write(msg)
data = data_utils.get_display_data_multi(self.columns, data)
return self.columns, data
@six.add_metaclass(abc.ABCMeta)
class HealthCheckBaseAction(HealthMixIn, base.BaseShowCommand):
"""Base class for implementing action over a given test set."""
columns = ("id",
"testset",
"cluster_id",
"status")
@abc.abstractproperty
def action_status(self):
"""String with the name of the action."""
pass
def take_action(self, parsed_args):
data = self.client.action(parsed_args.id, self.action_status)
data = data_utils.get_display_data_single(self.columns, data)
return self.columns, data
class HealthCheckStop(HealthCheckBaseAction):
"""Stop test set with given id."""
action_status = "stopped"
class HealthCheckRestart(HealthCheckBaseAction):
"""Restart test set with given id."""
action_status = "restarted"
class HealthTestSetsStatusList(HealthMixIn, base.BaseListCommand):
"""Show list of statuses of all test sets ever been executed in Fuel."""
columns = ("id",
"testset",
"cluster_id",
"status",
"started_at",
"ended_at")
def get_parser(self, prog_name):
parser = super(HealthTestSetsStatusList, self).get_parser(prog_name)
parser.add_argument('-e',
'--env',
type=int,
help='Id of the environment.')
return parser
def take_action(self, parsed_args):
data = self.client.get_status_all(parsed_args.env)
data = data_utils.get_display_data_multi(self.columns, data)
return self.columns, data
class HealthTestSetsStatusShow(HealthMixIn, base.BaseShowCommand):
"""Show status about a test set with given id."""
columns = ("id",
"testset",
"cluster_id",
"status",
"started_at",
"ended_at",
"tests")
def take_action(self, parsed_args):
data = self.client.get_status_single(parsed_args.id)
data = data_utils.get_display_data_single(self.columns, data)
return self.columns, data

View File

@ -19,6 +19,7 @@ functionality from nailgun objects.
from fuelclient.objects.base import BaseObject
from fuelclient.objects.environment import Environment
from fuelclient.objects.extension import Extension
from fuelclient.objects.health import Health
from fuelclient.objects.node import Node
from fuelclient.objects.node import NodeCollection
from fuelclient.objects.openstack_config import OpenstackConfig

View File

@ -398,6 +398,7 @@ class Environment(BaseObject):
"nodes": node_facts
}
# TODO(vkulanov): remove method when deprecate old cli
def get_testsets(self):
return self.connection.get_request(
'testsets/{0}'.format(self.id),
@ -409,9 +410,11 @@ class Environment(BaseObject):
data = self.get_fresh_data()
return data["is_customized"]
# TODO(vkulanov): remove method when deprecate old cli
def is_in_running_test_sets(self, test_set):
return test_set["testset"] in self._test_sets_to_run
# TODO(vkulanov): remove method when deprecate old cli
def run_test_sets(self, test_sets_to_run, ostf_credentials=None):
self._test_sets_to_run = test_sets_to_run
@ -440,6 +443,7 @@ class Environment(BaseObject):
self._testruns_ids = [tr['id'] for tr in testruns]
return testruns
# TODO(vkulanov): remove method when deprecate old cli
def get_state_of_tests(self):
return [
self.connection.get_request(

View File

@ -0,0 +1,86 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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 fuelclient.objects.base import BaseObject
class Health(BaseObject):
class_api_path = "testruns/"
instance_api_path = "testruns/{0}/"
test_sets_api_path = "testsets/{0}/"
@classmethod
def get_test_sets(cls, environment_id):
return cls.connection.get_request(
cls.test_sets_api_path.format(environment_id),
ostf=True
)
@classmethod
def get_tests_status_all(cls):
return cls.connection.get_request(cls.class_api_path, ostf=True)
def get_tests_status_single(self):
return self.connection.get_request(
self.instance_api_path.format(self.id),
ostf=True
)
@classmethod
def get_last_tests_status(cls, environment_id):
return cls.connection.get_request(
'testruns/last/{0}'.format(environment_id),
ostf=True
)
@classmethod
def run_test_sets(cls, environment_id, test_sets_to_run,
ostf_credentials=None):
def make_test_set(name):
result = {
"testset": name,
"metadata": {
"config": {},
"cluster_id": environment_id,
}
}
if ostf_credentials:
creds = result['metadata'].setdefault(
'ostf_os_access_creds', {})
if 'tenant' in ostf_credentials:
creds['ostf_os_tenant_name'] = ostf_credentials['tenant']
if 'username' in ostf_credentials:
creds['ostf_os_username'] = ostf_credentials['username']
if 'password' in ostf_credentials:
creds['ostf_os_password'] = ostf_credentials['password']
return result
tests_data = [make_test_set(ts) for ts in test_sets_to_run]
test_runs = cls.connection.post_request(cls.class_api_path,
tests_data,
ostf=True)
return test_runs
def action_test(self, action_status):
data = [{
"id": self.id,
"status": action_status
}]
return self.connection.put_request(
'testruns/', data, ostf=True
)

View File

@ -0,0 +1,169 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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 mock
from fuelclient.tests.unit.v2.cli import test_engine
from fuelclient.tests.utils import fake_health
class TestHealthCommand(test_engine.BaseCLITest):
"""Tests for fuel2 health * commands."""
def test_health_list_for_cluster(self):
self.m_client.get_all.return_value = fake_health.get_fake_test_sets(10)
cluster_id = 45
args = 'health list -e {id}'.format(id=cluster_id)
self.exec_command(args)
self.m_client.get_all.assert_called_once_with(
environment_id=cluster_id)
self.m_get_client.assert_called_once_with('health', mock.ANY)
@mock.patch('sys.stderr')
def test_health_list_for_cluster_fail(self, mocked_stderr):
args = 'health list'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('-e/--env',
mocked_stderr.write.call_args_list[-1][0][0])
def test_health_status_list(self):
self.m_client.get_status_all.return_value = [
fake_health.get_fake_test_set_item(testset_id=12, cluster_id=30),
fake_health.get_fake_test_set_item(testset_id=13, cluster_id=32),
fake_health.get_fake_test_set_item(testset_id=14, cluster_id=35)
]
args = 'health status list'
self.exec_command(args)
self.m_client.get_status_all.assert_called_once_with(None)
self.m_get_client.assert_called_once_with('health', mock.ANY)
def test_health_status_list_for_cluster(self):
cluster_id = 45
self.m_client.get_status_all.return_value = [
fake_health.get_fake_test_set_item(testset_id=12,
cluster_id=cluster_id),
fake_health.get_fake_test_set_item(testset_id=13,
cluster_id=cluster_id),
fake_health.get_fake_test_set_item(testset_id=14,
cluster_id=cluster_id)
]
args = 'health status list -e {id}'.format(id=cluster_id)
self.exec_command(args)
self.m_client.get_status_all.assert_called_once_with(cluster_id)
self.m_get_client.assert_called_once_with('health', mock.ANY)
def test_health_status_show(self):
testset_id = 66
self.m_client.get_status_single.return_value = \
fake_health.get_fake_test_set_item(testset_id=testset_id)
args = 'health status show {id}'.format(id=testset_id)
self.exec_command(args)
self.m_client.get_status_single.assert_called_once_with(testset_id)
self.m_get_client.assert_called_once_with('health', mock.ANY)
@mock.patch('sys.stderr')
def test_health_status_show_fail(self, mocked_stderr):
args = 'health status show'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('id', mocked_stderr.write.call_args_list[0][0][0])
def test_health_start_force(self):
cluster_id = 45
testset = ['fake_test_set1', 'fake_test_set2']
args = 'health start -e {id} -t {testset} --force'.format(
id=cluster_id, testset=' '.join(testset))
self.exec_command(args)
self.m_client.start.assert_called_once_with(cluster_id,
ostf_credentials={},
test_sets=testset,
force=True)
self.m_get_client.assert_called_once_with('health', mock.ANY)
@mock.patch('sys.stderr')
def test_health_start_w_wrong_parameters(self, mocked_stderr):
args = 'health start'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('-e/--env',
mocked_stderr.write.call_args_list[-1][0][0])
def test_health_start_wo_force(self):
cluster_id = 45
testset = ['fake_test_set1', 'fake_test_set2']
args = 'health start -e {id} -t {testset}'.format(
id=cluster_id, testset=' '.join(testset))
self.exec_command(args)
self.m_client.start.assert_called_once_with(cluster_id,
ostf_credentials={},
test_sets=testset,
force=False)
self.m_get_client.assert_called_once_with('health', mock.ANY)
def test_health_start_all_wo_force(self):
cluster_id = 45
args = 'health start -e {id}'.format(id=cluster_id)
self.exec_command(args)
self.m_client.start.assert_called_once_with(cluster_id,
ostf_credentials={},
test_sets=None,
force=False)
self.m_get_client.assert_called_once_with('health', mock.ANY)
def test_health_start_force_w_ostf_credentials(self):
cluster_id = 45
testset = ['fake_test_set1', 'fake_test_set2']
ostf_credentials = {'username': 'fake_user',
'password': 'fake_password',
'tenant': 'fake_tenant_name'}
args = ('health start -e {id} -t {testset} --force --ostf-username '
'fake_user --ostf-password fake_password --ostf-tenant-name '
'fake_tenant_name'.format(id=cluster_id,
testset=' '.join(testset)))
self.exec_command(args)
self.m_client.start.assert_called_once_with(
cluster_id,
ostf_credentials=ostf_credentials,
test_sets=testset,
force=True
)
self.m_get_client.assert_called_once_with('health', mock.ANY)
def test_health_stop(self):
testset_id = 66
args = 'health stop {id}'.format(id=testset_id)
self.exec_command(args)
self.m_client.action.assert_called_once_with(testset_id, 'stopped')
self.m_get_client.assert_called_once_with('health', mock.ANY)
@mock.patch('sys.stderr')
def test_health_stop_fail(self, mocked_stderr):
args = 'health stop'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('id', mocked_stderr.write.call_args_list[0][0][0])
def test_health_restart(self):
testset_id = 66
args = 'health restart {id}'.format(id=testset_id)
self.exec_command(args)
self.m_client.action.assert_called_once_with(testset_id, 'restarted')
self.m_get_client.assert_called_once_with('health', mock.ANY)
@mock.patch('sys.stderr')
def test_health_restart_fail(self, mocked_stderr):
args = 'health restart'
self.assertRaises(SystemExit, self.exec_command, args)
self.assertIn('id', mocked_stderr.write.call_args_list[0][0][0])

View File

@ -0,0 +1,292 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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 mock
import fuelclient
from fuelclient.cli import error
from fuelclient.tests.unit.v2.lib import test_api
from fuelclient.tests import utils
class TestHealthFacade(test_api.BaseLibTest):
def setUp(self):
super(TestHealthFacade, self).setUp()
self.version = 'v1'
self.res_uri = '/ostf/'
self.fake_test_sets = utils.get_fake_test_sets(10)
self.fake_test_sets_items = utils.get_fake_test_set_items(10)
self.client = fuelclient.get_client('health', self.version)
def test_health_list_for_cluster(self):
cluster_id = 65
expected_uri = self.res_uri + 'testsets/{0}/'.format(cluster_id)
matcher = self.m_request.get(expected_uri, json=self.fake_test_sets)
data = self.client.get_all(cluster_id)
self.assertTrue(matcher.called)
self.assertEqual(len(data), 10)
def test_health_status_list(self):
expected_uri = self.res_uri + 'testruns/'
matcher = self.m_request.get(expected_uri,
json=self.fake_test_sets_items)
data = self.client.get_status_all()
self.assertTrue(matcher.called)
self.assertEqual(len(data), 10)
def test_health_status_list_for_cluster(self):
cluster_id = 32
expected_uri = self.res_uri + 'testruns/'
fake_test_sets_items = [
utils.get_fake_test_set_item(testset_id=12, cluster_id=cluster_id),
utils.get_fake_test_set_item(testset_id=13, cluster_id=cluster_id),
utils.get_fake_test_set_item(testset_id=14, cluster_id=35)
]
matcher = self.m_request.get(expected_uri,
json=fake_test_sets_items)
data = self.client.get_status_all(cluster_id)
self.assertTrue(matcher.called)
self.assertEqual(len(data), 2)
def test_health_status_show(self):
testrun_id = 65
cluster_id = 32
fake_test_set_item = utils.get_fake_test_set_item(
testset_id=testrun_id, cluster_id=cluster_id)
expected_uri = self.get_object_uri(self.res_uri + 'testruns/',
testrun_id)
matcher = self.m_request.get(expected_uri,
json=fake_test_set_item)
data = self.client.get_status_single(testrun_id)
self.assertTrue(matcher.called)
self.assertEqual(testrun_id, data["id"])
self.assertEqual(cluster_id, data["cluster_id"])
def test_health_status_show_non_existing_testrun(self):
testrun_id = 65
expected_uri = self.get_object_uri(self.res_uri + 'testruns/',
testrun_id)
matcher = self.m_request.get(expected_uri, json={})
msg = "Test sets with id {id} does not exist".format(id=testrun_id)
self.assertRaisesRegexp(error.ActionException,
msg,
self.client.get_status_single,
testrun_id)
self.assertTrue(matcher.called)
@mock.patch('fuelclient.objects.Environment')
def test_health_start(self, m_env_obj):
cluster_id = 32
cluster_state = 'operational'
test_sets = ['fake_test_set1', 'fake_test_set2']
expected_uri = self.res_uri + 'testruns/'
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
type(m_env_obj.return_value).is_customized = mock.PropertyMock(
return_value=False)
expected_body = [
{'testset': test_sets[0],
'metadata': {'cluster_id': cluster_id, 'config': {}}},
{'testset': test_sets[1],
'metadata': {'cluster_id': cluster_id, 'config': {}}}
]
matcher = self.m_request.post(expected_uri, json=expected_body)
data = self.client.start(cluster_id,
ostf_credentials={},
test_sets=test_sets,
force=False)
self.assertTrue(matcher.called)
self.assertEqual(expected_body, matcher.last_request.json())
self.assertEqual(data, matcher.last_request.json())
@mock.patch('fuelclient.objects.Environment')
def test_health_start_fail_not_allowed_env_status(self, m_env_obj):
cluster_id = 32
cluster_state = 'new'
test_sets = ['fake_test_set1', 'fake_test_set2']
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
msg = ("Environment is not ready to run health check "
"because it is in '{0}' state.".format(cluster_state))
self.assertRaisesRegexp(error.EnvironmentException,
msg,
self.client.start,
cluster_id,
ostf_credentials={},
test_sets=test_sets,
force=False)
@mock.patch('fuelclient.objects.Environment')
def test_health_start_not_allowed_env_status_w_force(self, m_env_obj):
cluster_id = 32
cluster_state = 'new'
test_sets = ['fake_test_set1', 'fake_test_set2']
expected_uri = self.res_uri + 'testruns/'
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
type(m_env_obj.return_value).is_customized = mock.PropertyMock(
return_value=False)
expected_body = [
{'testset': test_sets[0],
'metadata': {'cluster_id': cluster_id, 'config': {}}},
{'testset': test_sets[1],
'metadata': {'cluster_id': cluster_id, 'config': {}}}
]
matcher = self.m_request.post(expected_uri, json=expected_body)
data = self.client.start(cluster_id,
ostf_credentials={},
test_sets=test_sets,
force=True)
self.assertTrue(matcher.called)
self.assertEqual(expected_body, matcher.last_request.json())
self.assertEqual(data, matcher.last_request.json())
@mock.patch('fuelclient.objects.Environment')
def test_health_start_fail_customized_env(self, m_env_obj):
cluster_id = 32
cluster_state = 'operational'
is_customized = True
test_sets = ['fake_test_set1', 'fake_test_set2']
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
type(m_env_obj.return_value).is_customized = mock.PropertyMock(
return_value=is_customized)
msg = ("Environment deployment facts were updated. "
"Health check is likely to fail because of that.")
self.assertRaisesRegexp(error.EnvironmentException,
msg,
self.client.start,
cluster_id,
ostf_credentials={},
test_sets=test_sets,
force=False)
@mock.patch('fuelclient.objects.Environment')
def test_health_start_customized_env_w_force(self, m_env_obj):
cluster_id = 32
cluster_state = 'operational'
is_customized = True
test_sets = ['fake_test_set1', 'fake_test_set2']
expected_uri = self.res_uri + 'testruns/'
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
type(m_env_obj.return_value).is_customized = mock.PropertyMock(
return_value=is_customized)
expected_body = [
{'testset': test_sets[0],
'metadata': {'cluster_id': cluster_id, 'config': {}}},
{'testset': test_sets[1],
'metadata': {'cluster_id': cluster_id, 'config': {}}}
]
matcher = self.m_request.post(expected_uri, json=expected_body)
data = self.client.start(cluster_id,
ostf_credentials={},
test_sets=test_sets,
force=True)
self.assertTrue(matcher.called)
self.assertEqual(expected_body, matcher.last_request.json())
self.assertEqual(data, matcher.last_request.json())
@mock.patch('fuelclient.objects.Environment')
def test_health_start_w_ostf_credentials(self, m_env_obj):
cluster_id = 32
cluster_state = 'operational'
is_customized = False
test_sets = ['fake_test_set1', 'fake_test_set2']
ostf_credentials = {
'username': 'admin',
'password': 'admin',
'tenant': 'admin'
}
expected_uri = self.res_uri + 'testruns/'
type(m_env_obj.return_value).status = mock.PropertyMock(
return_value=cluster_state)
type(m_env_obj.return_value).is_customized = mock.PropertyMock(
return_value=is_customized)
expected_body = [
{'testset': test_sets[0],
'metadata': {
'ostf_os_access_creds':
{'ostf_os_username': 'admin',
'ostf_os_tenant_name': 'admin',
'ostf_os_password': 'admin'},
'cluster_id': cluster_id,
'config': {}}},
{'testset': test_sets[1],
'metadata': {
'ostf_os_access_creds':
{'ostf_os_username': 'admin',
'ostf_os_tenant_name': 'admin',
'ostf_os_password': 'admin'},
'cluster_id': cluster_id,
'config': {}}}
]
matcher = self.m_request.post(expected_uri, json=expected_body)
data = self.client.start(cluster_id,
ostf_credentials=ostf_credentials,
test_sets=test_sets,
force=False)
self.assertTrue(matcher.called)
self.assertEqual(expected_body, matcher.last_request.json())
self.assertEqual(data, matcher.last_request.json())
def test_health_stop_action(self):
testrun_id = 65
cluster_id = 32
expected_uri = self.res_uri + 'testruns/'
fake_test_set_item = utils.get_fake_test_set_item(
testset_id=testrun_id, cluster_id=cluster_id)
matcher = self.m_request.put(expected_uri,
json=[fake_test_set_item])
data = self.client.action(testrun_id, action_status='stopped')
self.assertTrue(matcher.called)
self.assertEqual(testrun_id, data["id"])
self.assertEqual(cluster_id, data["cluster_id"])
def test_health_restart_action(self):
testrun_id = 65
cluster_id = 32
expected_uri = self.res_uri + 'testruns/'
fake_test_set_item = utils.get_fake_test_set_item(
testset_id=testrun_id, cluster_id=cluster_id)
matcher = self.m_request.put(expected_uri,
json=[fake_test_set_item])
data = self.client.action(testrun_id, action_status='restarted')
self.assertTrue(matcher.called)
self.assertEqual(testrun_id, data["id"])
self.assertEqual(cluster_id, data["cluster_id"])

View File

@ -36,6 +36,10 @@ from fuelclient.tests.utils.fake_extension import get_fake_env_extensions
from fuelclient.tests.utils.fake_extension import get_fake_extension
from fuelclient.tests.utils.fake_extension import get_fake_extensions
from fuelclient.tests.utils.fake_fuel_version import get_fake_fuel_version
from fuelclient.tests.utils.fake_health import get_fake_test_set
from fuelclient.tests.utils.fake_health import get_fake_test_sets
from fuelclient.tests.utils.fake_health import get_fake_test_set_item
from fuelclient.tests.utils.fake_health import get_fake_test_set_items
from fuelclient.tests.utils.fake_task import get_fake_task
from fuelclient.tests.utils.fake_node_group import get_fake_node_group
from fuelclient.tests.utils.fake_node_group import get_fake_node_groups
@ -59,6 +63,10 @@ __all__ = (get_fake_deployment_history,
get_fake_yaml_network_conf,
get_fake_env,
get_fake_env_network_conf,
get_fake_test_set,
get_fake_test_sets,
get_fake_test_set_item,
get_fake_test_set_items,
get_fake_release,
get_fake_releases,
get_fake_attributes_metadata,

View File

@ -0,0 +1,70 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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.
def get_fake_test_set(testset_id=None, name=None):
"""Create a random fake test set for environment."""
return {
"id": testset_id or "fake_test_set",
"name": name or "Fake tests. Duration 30 sec - 2 min"
}
def get_fake_test_sets(testsets_count, **kwargs):
"""Create a random fake list of test sets for environment."""
return [get_fake_test_set(**kwargs)
for _ in range(testsets_count)]
def get_fake_test_set_item(testset_id=None, testset=None, cluster_id=None,
status=None, tests=None):
"""Create a random fake test set item."""
return {
"id": testset_id or 45,
"testset": testset or "fake_test_set",
"cluster_id": cluster_id or 65,
"status": status or "finished",
"started_at": "2016-09-15 09:03:07.697393",
"ended_at": "2016-09-15 09:03:19.280296",
"tests": tests or [
{
"status": "failure",
"taken": 1.0,
"testset": "fake_test_set",
"name": "Create fake instance",
"duration": "30 s.",
"message": "Fake test message",
"id": "fuel_health.tests.fake_test",
"description": "fake description"
},
{
"status": "stopped",
"taken": 0.5,
"testset": "fake_test_set",
"name": "Check create, update and delete fake instance image",
"duration": "70 s.",
"message": "Can not set proxy for Health Check.",
"id": "fuel_health.tests.fake_test.test_update_fake_images",
"description": "fake description"
}
]
}
def get_fake_test_set_items(items_count, **kwargs):
"""Create a random fake list of test sets items."""
return [get_fake_test_set_item(**kwargs)
for _ in range(items_count)]

View File

@ -19,6 +19,7 @@ from fuelclient.v1 import environment
from fuelclient.v1 import extension
from fuelclient.v1 import fuelversion
from fuelclient.v1 import graph
from fuelclient.v1 import health
from fuelclient.v1 import network_configuration
from fuelclient.v1 import network_group
from fuelclient.v1 import node
@ -39,6 +40,7 @@ __all__ = ('cluster_settings',
'extension',
'fuelversion',
'graph',
'health',
'network_configuration',
'network_group',
'node',

127
fuelclient/v1/health.py Normal file
View File

@ -0,0 +1,127 @@
# -*- coding: utf-8 -*-
#
# Copyright 2016 Vitalii Kulanov
#
# 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 fuelclient.cli import error
from fuelclient import objects
from fuelclient.v1 import base_v1
class HealthClient(base_v1.BaseV1Client):
_entity_wrapper = objects.Health
_allowed_statuses = (
'error',
'operational',
'update_error',
)
def get_all(self, environment_id):
"""Get list of test sets for a given environment.
:param environment_id: Id of environment
:type environment_id: int
:return: health test sets as a list of dict
:rtype: list
"""
return self._entity_wrapper.get_test_sets(environment_id)
def get_status_all(self, environment_id=None):
"""Get test sets statuses. If environment_id is None then statuses of
all test sets will be retrieved.
:param environment_id: Id of environment
:type environment_id: int
:return: health test sets as a list of dict
:rtype: list
"""
data = self._entity_wrapper.get_tests_status_all()
# OSTF API doesn't support filtering by cluster, then do it 'manually'
if environment_id is not None:
data = [i for i in data if i['cluster_id'] == environment_id]
return data
def get_status_single(self, testset_id):
testrun_obj = self._entity_wrapper(testset_id)
data = testrun_obj.get_tests_status_single()
if data:
result = []
# Retrieve and re-format 'tests' from nested data for clarity
for tests in data.get('tests'):
result.append("\n* {} - {}, ('{}')".format(tests["status"],
tests["name"],
tests["message"]))
else:
msg = "Test sets with id {0} does not exist".format(testset_id)
raise error.ActionException(msg)
data['tests'] = ' '.join(result)
return data
def get_last_test_status(self, environment_id):
return self._entity_wrapper.get_last_tests_status(environment_id)
def start(self, environment_id, ostf_credentials=None, test_sets=None,
force=False):
"""Run test sets for a given environment. If test_sets is None then
all test sets will be run
:param environment_id: Id of environment
:type environment_id: int
:param ostf_credentials: ostf credentials
:type ostf_credentials: dict
:param test_sets: list of test sets
:type test_sets: list
:param force:
:type force: bool
:return: running health test sets as a list of dict
:rtype: list
"""
env_obj = objects.Environment(environment_id)
if env_obj.status not in self._allowed_statuses and not force:
raise error.EnvironmentException(
"Environment is not ready to run health check "
"because it is in '{0}' state. Health check is likely "
"to fail because of this. Use '--force' flag "
"to proceed anyway.".format(env_obj.status)
)
if env_obj.is_customized and not force:
raise error.EnvironmentException(
"Environment deployment facts were updated. "
"Health check is likely to fail because of "
"that. Use '--force' flag to proceed anyway."
)
test_sets_to_run = test_sets or set(ts['id'] for ts in
self.get_all(environment_id))
return self._entity_wrapper.run_test_sets(environment_id,
test_sets_to_run,
ostf_credentials)
def action(self, testrun_id, action_status):
"""Make an action on specific test set.
:param testrun_id: id of test set
:type testrun_id: int
:param action_status: the type of action ('stopped', 'restarted')
:type action_status: str
"""
testrun_obj = self._entity_wrapper(obj_id=testrun_id)
return testrun_obj.action_test(action_status)[0]
def get_client(connection):
return HealthClient(connection)

View File

@ -66,6 +66,12 @@ fuelclient =
graph_execute=fuelclient.commands.graph:GraphExecute
graph_list=fuelclient.commands.graph:GraphList
graph_upload=fuelclient.commands.graph:GraphUpload
health_list=fuelclient.commands.health:HealthTestSetsList
health_restart=fuelclient.commands.health:HealthCheckRestart
health_start=fuelclient.commands.health:HealthCheckStart
health_status_list=fuelclient.commands.health:HealthTestSetsStatusList
health_status_show=fuelclient.commands.health:HealthTestSetsStatusShow
health_stop=fuelclient.commands.health:HealthCheckStop
network-group_create=fuelclient.commands.network_group:NetworkGroupCreate
network-group_delete=fuelclient.commands.network_group:NetworkGroupDelete
network-group_list=fuelclient.commands.network_group:NetworkGroupList