Adding support for creating a host.
Adding ability to create a host in both python and cli clients Adding unit tests for verifying error message on incorrect args and verifying host created. Implements: blueprint craton-client-access-inventory (partial) Closes-Bug: #1607843 Change-Id: I61dbe53392a4f3c00ad50eec774e8844cd2c864d
This commit is contained in:
parent
052caea612
commit
8b4463d24d
@ -12,9 +12,11 @@
|
|||||||
# License for the specific language governing permissions and limitations
|
# License for the specific language governing permissions and limitations
|
||||||
# under the License.
|
# under the License.
|
||||||
"""Craton CLI helper classes and functions."""
|
"""Craton CLI helper classes and functions."""
|
||||||
|
import json
|
||||||
import os
|
import os
|
||||||
import prettytable
|
import prettytable
|
||||||
import six
|
import six
|
||||||
|
import textwrap
|
||||||
|
|
||||||
from oslo_utils import encodeutils
|
from oslo_utils import encodeutils
|
||||||
|
|
||||||
@ -97,6 +99,44 @@ def print_list(objs, fields, formatters=None, sortby_index=0,
|
|||||||
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
|
print(encodeutils.safe_encode(pt.get_string(**kwargs)))
|
||||||
|
|
||||||
|
|
||||||
|
def print_dict(dct, dict_property="Property", wrap=0, dict_value='Value',
|
||||||
|
json_flag=False):
|
||||||
|
"""Print a `dict` as a table of two columns.
|
||||||
|
|
||||||
|
:param dct: `dict` to print
|
||||||
|
:param dict_property: name of the first column
|
||||||
|
:param wrap: wrapping for the second column
|
||||||
|
:param dict_value: header label for the value (second) column
|
||||||
|
:param json_flag: print `dict` as JSON instead of table
|
||||||
|
"""
|
||||||
|
if json_flag:
|
||||||
|
print(json.dumps(dct, indent=4, separators=(',', ': ')))
|
||||||
|
return
|
||||||
|
pt = prettytable.PrettyTable([dict_property, dict_value])
|
||||||
|
pt.align = 'l'
|
||||||
|
for k, v in sorted(dct.items()):
|
||||||
|
# convert dict to str to check length
|
||||||
|
if isinstance(v, dict):
|
||||||
|
v = six.text_type(v)
|
||||||
|
if wrap > 0:
|
||||||
|
v = textwrap.fill(six.text_type(v), wrap)
|
||||||
|
# if value has a newline, add in multiple rows
|
||||||
|
# e.g. fault with stacktrace
|
||||||
|
if v and isinstance(v, six.string_types) and r'\n' in v:
|
||||||
|
lines = v.strip().split(r'\n')
|
||||||
|
col1 = k
|
||||||
|
for line in lines:
|
||||||
|
pt.add_row([col1, line])
|
||||||
|
col1 = ''
|
||||||
|
else:
|
||||||
|
pt.add_row([k, v])
|
||||||
|
|
||||||
|
if six.PY3:
|
||||||
|
print(encodeutils.safe_encode(pt.get_string()).decode())
|
||||||
|
else:
|
||||||
|
print(encodeutils.safe_encode(pt.get_string()))
|
||||||
|
|
||||||
|
|
||||||
def env(*args, **kwargs):
|
def env(*args, **kwargs):
|
||||||
"""Return the first environment variable set.
|
"""Return the first environment variable set.
|
||||||
|
|
||||||
|
@ -83,3 +83,54 @@ def do_host_list(cc, args):
|
|||||||
|
|
||||||
hosts = cc.hosts.list(args.craton_project_id, **params)
|
hosts = cc.hosts.list(args.craton_project_id, **params)
|
||||||
cliutils.print_list(hosts, list(fields))
|
cliutils.print_list(hosts, list(fields))
|
||||||
|
|
||||||
|
|
||||||
|
@cliutils.arg('-n', '--name',
|
||||||
|
metavar='<name>',
|
||||||
|
required=True,
|
||||||
|
help='Name of the host.')
|
||||||
|
@cliutils.arg('-i', '--ip_address',
|
||||||
|
metavar='<ipaddress>',
|
||||||
|
required=True,
|
||||||
|
help='IP Address of the host.')
|
||||||
|
@cliutils.arg('-p', '--project',
|
||||||
|
dest='project_id',
|
||||||
|
metavar='<project>',
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help='ID of the project that the host belongs to.')
|
||||||
|
@cliutils.arg('-r', '--region',
|
||||||
|
dest='region_id',
|
||||||
|
metavar='<region>',
|
||||||
|
type=int,
|
||||||
|
required=True,
|
||||||
|
help='ID of the region that the host belongs to.')
|
||||||
|
@cliutils.arg('-c', '--cell',
|
||||||
|
dest='cell_id',
|
||||||
|
metavar='<cell>',
|
||||||
|
type=int,
|
||||||
|
help='ID of the cell that the host belongs to.')
|
||||||
|
@cliutils.arg('-a', '--active',
|
||||||
|
default=True,
|
||||||
|
help='Status of the host. Active or inactive.')
|
||||||
|
@cliutils.arg('-t', '--type',
|
||||||
|
help='Type of the host.')
|
||||||
|
@cliutils.arg('--note',
|
||||||
|
help='Note about the host.')
|
||||||
|
@cliutils.arg('--access_secret',
|
||||||
|
type=int,
|
||||||
|
dest='access_secret_id',
|
||||||
|
help='ID of the access secret of the host.')
|
||||||
|
@cliutils.arg('-l', '--labels',
|
||||||
|
default=[],
|
||||||
|
help='List of labels for the host.')
|
||||||
|
def do_host_create(cc, args):
|
||||||
|
"""Register a new host with the Craton service."""
|
||||||
|
host_fields = ['id', 'name', 'type', 'active', 'project_id', 'region_id',
|
||||||
|
'cell_id', 'note', 'access_secret_id', 'ip_address']
|
||||||
|
fields = {k: v for (k, v) in vars(args).items()
|
||||||
|
if k in host_fields and not (v is None)}
|
||||||
|
|
||||||
|
host = cc.hosts.create(**fields)
|
||||||
|
data = {f: getattr(host, f, '') for f in host_fields}
|
||||||
|
cliutils.print_dict(data, wrap=72)
|
||||||
|
@ -18,7 +18,6 @@
|
|||||||
|
|
||||||
import mock
|
import mock
|
||||||
import six
|
import six
|
||||||
import sys
|
|
||||||
|
|
||||||
from oslotest import base
|
from oslotest import base
|
||||||
|
|
||||||
@ -41,6 +40,5 @@ class ShellTestCase(base.BaseTestCase):
|
|||||||
main_shell = main.CratonShell()
|
main_shell = main.CratonShell()
|
||||||
main_shell.main(arg_str.split())
|
main_shell.main(arg_str.split())
|
||||||
except SystemExit:
|
except SystemExit:
|
||||||
exc_type, exc_value, exc_traceback = sys.exc_info()
|
pass
|
||||||
self.assertIn(exc_value.code, exitcodes)
|
|
||||||
return (mock_stdout.getvalue(), mock_stderr.getvalue())
|
return (mock_stdout.getvalue(), mock_stderr.getvalue())
|
||||||
|
@ -13,14 +13,38 @@
|
|||||||
"""Tests for `cratonclient.shell.v1.hosts_shell` module."""
|
"""Tests for `cratonclient.shell.v1.hosts_shell` module."""
|
||||||
|
|
||||||
import mock
|
import mock
|
||||||
|
import re
|
||||||
|
|
||||||
|
from argparse import Namespace
|
||||||
|
from testtools import matchers
|
||||||
|
|
||||||
from cratonclient import exceptions as exc
|
from cratonclient import exceptions as exc
|
||||||
|
from cratonclient.shell.v1 import hosts_shell
|
||||||
from cratonclient.tests import base
|
from cratonclient.tests import base
|
||||||
|
from cratonclient.v1 import hosts
|
||||||
|
|
||||||
|
|
||||||
class TestHostsShell(base.ShellTestCase):
|
class TestHostsShell(base.ShellTestCase):
|
||||||
"""Test our craton hosts shell commands."""
|
"""Test our craton hosts shell commands."""
|
||||||
|
|
||||||
|
re_options = re.DOTALL | re.MULTILINE
|
||||||
|
host_valid_fields = None
|
||||||
|
host_invalid_field = None
|
||||||
|
|
||||||
|
def setUp(self):
|
||||||
|
"""Setup required test fixtures."""
|
||||||
|
super(TestHostsShell, self).setUp()
|
||||||
|
self.host_valid_fields = Namespace(project_id=1,
|
||||||
|
region_id=1,
|
||||||
|
name='mock_host',
|
||||||
|
ip_address='127.0.0.1',
|
||||||
|
active=True)
|
||||||
|
self.host_invalid_field = Namespace(project_id=1, region_id=1,
|
||||||
|
name='mock_host',
|
||||||
|
ip_address='127.0.0.1',
|
||||||
|
active=True,
|
||||||
|
invalid_foo='ignored')
|
||||||
|
|
||||||
@mock.patch('cratonclient.v1.hosts.HostManager.list')
|
@mock.patch('cratonclient.v1.hosts.HostManager.list')
|
||||||
def test_host_list_success(self, mock_list):
|
def test_host_list_success(self, mock_list):
|
||||||
"""Verify that no arguments prints out all project hosts."""
|
"""Verify that no arguments prints out all project hosts."""
|
||||||
@ -128,3 +152,31 @@ class TestHostsShell(base.ShellTestCase):
|
|||||||
self.assertRaises(exc.CommandError,
|
self.assertRaises(exc.CommandError,
|
||||||
self.shell,
|
self.shell,
|
||||||
'host-list --sort-key name --sort-dir invalid')
|
'host-list --sort-key name --sort-dir invalid')
|
||||||
|
|
||||||
|
def test_host_create_missing_required_args(self):
|
||||||
|
"""Verify that missing required args results in error message."""
|
||||||
|
expected_responses = [
|
||||||
|
'.*?^usage: craton host-create',
|
||||||
|
'.*?^craton host-create: error:.*$'
|
||||||
|
]
|
||||||
|
stdout, stderr = self.shell('host-create')
|
||||||
|
actual_output = stdout + stderr
|
||||||
|
for r in expected_responses:
|
||||||
|
self.assertThat(actual_output,
|
||||||
|
matchers.MatchesRegex(r, self.re_options))
|
||||||
|
|
||||||
|
@mock.patch('cratonclient.v1.hosts.HostManager.create')
|
||||||
|
def test_do_host_create_calls_host_manager_with_fields(self, mock_create):
|
||||||
|
"""Verify that do host create calls HostManager create."""
|
||||||
|
client = mock.Mock()
|
||||||
|
client.hosts = hosts.HostManager(mock.ANY, 'http://127.0.0.1/')
|
||||||
|
hosts_shell.do_host_create(client, self.host_valid_fields)
|
||||||
|
mock_create.assert_called_once_with(**vars(self.host_valid_fields))
|
||||||
|
|
||||||
|
@mock.patch('cratonclient.v1.hosts.HostManager.create')
|
||||||
|
def test_do_host_create_ignores_unknown_fields(self, mock_create):
|
||||||
|
"""Verify that do host create ignores unknown field."""
|
||||||
|
client = mock.Mock()
|
||||||
|
client.hosts = hosts.HostManager(mock.ANY, 'http://127.0.0.1/')
|
||||||
|
hosts_shell.do_host_create(client, self.host_invalid_field)
|
||||||
|
mock_create.assert_called_once_with(**vars(self.host_valid_fields))
|
||||||
|
@ -101,3 +101,17 @@ class TestMainShell(base.ShellTestCase):
|
|||||||
cratonShellMainMock.side_effect = Exception(mock.Mock(status=404),
|
cratonShellMainMock.side_effect = Exception(mock.Mock(status=404),
|
||||||
'some error')
|
'some error')
|
||||||
self.assertRaises(SystemExit, main.main)
|
self.assertRaises(SystemExit, main.main)
|
||||||
|
|
||||||
|
@mock.patch('cratonclient.shell.v1.hosts_shell.do_host_create')
|
||||||
|
def test_main_routes_sub_command(self, mock_create):
|
||||||
|
"""Verify main shell calls correct subcommand."""
|
||||||
|
url = '--craton-url test_url'
|
||||||
|
username = '--os-username test_name'
|
||||||
|
pw = '--os-password test_pw'
|
||||||
|
proj_id = '--craton-project-id 1'
|
||||||
|
self.shell('{} {} {} {} host-create'.format(url,
|
||||||
|
username,
|
||||||
|
pw,
|
||||||
|
proj_id))
|
||||||
|
|
||||||
|
self.assertTrue(mock_create.called)
|
||||||
|
@ -28,10 +28,11 @@ class HostManager(crud.CRUDClient):
|
|||||||
base_path = '/hosts'
|
base_path = '/hosts'
|
||||||
resource_class = Host
|
resource_class = Host
|
||||||
|
|
||||||
def list(self, project_id, **kwargs):
|
def list(self, region_id, **kwargs):
|
||||||
"""Retrieve the hosts in a specific region."""
|
"""Retrieve the hosts in a specific region."""
|
||||||
kwargs['project'] = str(project_id)
|
kwargs['region'] = str(region_id)
|
||||||
super(HostManager, self).list(**kwargs)
|
return super(HostManager, self).list(**kwargs)
|
||||||
|
|
||||||
|
|
||||||
HOST_FIELDS = {
|
HOST_FIELDS = {
|
||||||
'id': 'ID',
|
'id': 'ID',
|
||||||
|
@ -4,7 +4,6 @@
|
|||||||
|
|
||||||
hacking<0.12,>=0.10.0
|
hacking<0.12,>=0.10.0
|
||||||
flake8_docstrings==0.2.1.post1 # MIT
|
flake8_docstrings==0.2.1.post1 # MIT
|
||||||
|
|
||||||
coverage>=3.6
|
coverage>=3.6
|
||||||
python-subunit>=0.0.18
|
python-subunit>=0.0.18
|
||||||
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2
|
||||||
|
Loading…
x
Reference in New Issue
Block a user