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
					Chris Spencer
				
			
				
					committed by
					
						 Ian Cordasco
						Ian Cordasco
					
				
			
			
				
	
			
			
			 Ian Cordasco
						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