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:
Chris Spencer 2016-08-01 12:51:56 -07:00 committed by Ian Cordasco
parent 052caea612
commit 8b4463d24d
7 changed files with 162 additions and 7 deletions

View File

@ -12,9 +12,11 @@
# License for the specific language governing permissions and limitations
# under the License.
"""Craton CLI helper classes and functions."""
import json
import os
import prettytable
import six
import textwrap
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)))
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):
"""Return the first environment variable set.

View File

@ -83,3 +83,54 @@ def do_host_list(cc, args):
hosts = cc.hosts.list(args.craton_project_id, **params)
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)

View File

@ -18,7 +18,6 @@
import mock
import six
import sys
from oslotest import base
@ -41,6 +40,5 @@ class ShellTestCase(base.BaseTestCase):
main_shell = main.CratonShell()
main_shell.main(arg_str.split())
except SystemExit:
exc_type, exc_value, exc_traceback = sys.exc_info()
self.assertIn(exc_value.code, exitcodes)
pass
return (mock_stdout.getvalue(), mock_stderr.getvalue())

View File

@ -13,14 +13,38 @@
"""Tests for `cratonclient.shell.v1.hosts_shell` module."""
import mock
import re
from argparse import Namespace
from testtools import matchers
from cratonclient import exceptions as exc
from cratonclient.shell.v1 import hosts_shell
from cratonclient.tests import base
from cratonclient.v1 import hosts
class TestHostsShell(base.ShellTestCase):
"""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')
def test_host_list_success(self, mock_list):
"""Verify that no arguments prints out all project hosts."""
@ -128,3 +152,31 @@ class TestHostsShell(base.ShellTestCase):
self.assertRaises(exc.CommandError,
self.shell,
'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))

View File

@ -101,3 +101,17 @@ class TestMainShell(base.ShellTestCase):
cratonShellMainMock.side_effect = Exception(mock.Mock(status=404),
'some error')
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)

View File

@ -28,10 +28,11 @@ class HostManager(crud.CRUDClient):
base_path = '/hosts'
resource_class = Host
def list(self, project_id, **kwargs):
def list(self, region_id, **kwargs):
"""Retrieve the hosts in a specific region."""
kwargs['project'] = str(project_id)
super(HostManager, self).list(**kwargs)
kwargs['region'] = str(region_id)
return super(HostManager, self).list(**kwargs)
HOST_FIELDS = {
'id': 'ID',

View File

@ -4,7 +4,6 @@
hacking<0.12,>=0.10.0
flake8_docstrings==0.2.1.post1 # MIT
coverage>=3.6
python-subunit>=0.0.18
sphinx!=1.2.0,!=1.3b1,<1.3,>=1.1.2