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:
		
				
					committed by
					
						
						Ian Cordasco
					
				
			
			
				
	
			
			
			
						parent
						
							052caea612
						
					
				
				
					commit
					8b4463d24d
				
			@@ -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.
 | 
			
		||||
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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())
 | 
			
		||||
 
 | 
			
		||||
@@ -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))
 | 
			
		||||
 
 | 
			
		||||
@@ -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)
 | 
			
		||||
 
 | 
			
		||||
@@ -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',
 | 
			
		||||
 
 | 
			
		||||
@@ -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
 | 
			
		||||
 
 | 
			
		||||
		Reference in New Issue
	
	Block a user