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
 | 
					# 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
 | 
				
			||||||
 
 | 
				
			|||||||
		Reference in New Issue
	
	Block a user