Merge "Support JSON data for port binding profile"
This commit is contained in:
		| @@ -54,7 +54,8 @@ Create new port | ||||
|  | ||||
| .. option:: --binding-profile <binding-profile> | ||||
|  | ||||
|     Custom data to be passed as binding:profile: <key>=<value> | ||||
|     Custom data to be passed as binding:profile. Data may | ||||
|     be passed as <key>=<value> or JSON. | ||||
|     (repeat option to set multiple binding:profile data) | ||||
|  | ||||
| .. option:: --host <host-id> | ||||
| @@ -162,7 +163,8 @@ Set port properties | ||||
|  | ||||
| .. option:: --binding-profile <binding-profile> | ||||
|  | ||||
|     Custom data to be passed as binding:profile: <key>=<value> | ||||
|     Custom data to be passed as binding:profile. Data may | ||||
|     be passed as <key>=<value> or JSON. | ||||
|     (repeat option to set multiple binding:profile data) | ||||
|  | ||||
| .. option:: --no-binding-profile | ||||
|   | ||||
| @@ -14,6 +14,7 @@ | ||||
| """Port action implementations""" | ||||
|  | ||||
| import argparse | ||||
| import json | ||||
| import logging | ||||
|  | ||||
| from osc_lib.cli import parseractions | ||||
| @@ -63,6 +64,32 @@ def _get_columns(item): | ||||
|     return tuple(sorted(columns)) | ||||
|  | ||||
|  | ||||
| class JSONKeyValueAction(argparse.Action): | ||||
|     """A custom action to parse arguments as JSON or key=value pairs | ||||
|  | ||||
|     Ensures that ``dest`` is a dict | ||||
|     """ | ||||
|  | ||||
|     def __call__(self, parser, namespace, values, option_string=None): | ||||
|  | ||||
|         # Make sure we have an empty dict rather than None | ||||
|         if getattr(namespace, self.dest, None) is None: | ||||
|             setattr(namespace, self.dest, {}) | ||||
|  | ||||
|         # Try to load JSON first before falling back to <key>=<value>. | ||||
|         current_dest = getattr(namespace, self.dest) | ||||
|         try: | ||||
|             current_dest.update(json.loads(values)) | ||||
|         except ValueError as e: | ||||
|             if '=' in values: | ||||
|                 current_dest.update([values.split('=', 1)]) | ||||
|             else: | ||||
|                 msg = _("Expected '<key>=<value>' or JSON data for option " | ||||
|                         "%(option)s, but encountered JSON parsing error: " | ||||
|                         "%(error)s") % {"option": option_string, "error": e} | ||||
|                 raise argparse.ArgumentTypeError(msg) | ||||
|  | ||||
|  | ||||
| def _get_attrs(client_manager, parsed_args): | ||||
|     attrs = {} | ||||
|  | ||||
| @@ -219,9 +246,9 @@ class CreatePort(command.ShowOne): | ||||
|         parser.add_argument( | ||||
|             '--binding-profile', | ||||
|             metavar='<binding-profile>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_("Custom data to be passed as binding:profile: " | ||||
|                    "<key>=<value> " | ||||
|             action=JSONKeyValueAction, | ||||
|             help=_("Custom data to be passed as binding:profile. Data may " | ||||
|                    "be passed as <key>=<value> or JSON. " | ||||
|                    "(repeat option to set multiple binding:profile data)") | ||||
|         ) | ||||
|         admin_group = parser.add_mutually_exclusive_group() | ||||
| @@ -390,9 +417,9 @@ class SetPort(command.Command): | ||||
|         binding_profile.add_argument( | ||||
|             '--binding-profile', | ||||
|             metavar='<binding-profile>', | ||||
|             action=parseractions.KeyValueAction, | ||||
|             help=_("Custom data to be passed as binding:profile: " | ||||
|                    "<key>=<value> " | ||||
|             action=JSONKeyValueAction, | ||||
|             help=_("Custom data to be passed as binding:profile. Data may " | ||||
|                    "be passed as <key>=<value> or JSON. " | ||||
|                    "(repeat option to set multiple binding:profile data)") | ||||
|         ) | ||||
|         binding_profile.add_argument( | ||||
|   | ||||
| @@ -11,6 +11,7 @@ | ||||
| #   under the License. | ||||
| # | ||||
|  | ||||
| import argparse | ||||
| import mock | ||||
|  | ||||
| from mock import call | ||||
| @@ -174,6 +175,58 @@ class TestCreatePort(TestPort): | ||||
|         self.assertEqual(ref_columns, columns) | ||||
|         self.assertEqual(ref_data, data) | ||||
|  | ||||
|     def test_create_invalid_json_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--network', self._port.network_id, | ||||
|             '--binding-profile', '{"parent_name":"fake_parent"', | ||||
|             'test-port', | ||||
|         ] | ||||
|         self.assertRaises(argparse.ArgumentTypeError, | ||||
|                           self.check_parser, | ||||
|                           self.cmd, | ||||
|                           arglist, | ||||
|                           None) | ||||
|  | ||||
|     def test_create_invalid_key_value_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--network', self._port.network_id, | ||||
|             '--binding-profile', 'key', | ||||
|             'test-port', | ||||
|         ] | ||||
|         self.assertRaises(argparse.ArgumentTypeError, | ||||
|                           self.check_parser, | ||||
|                           self.cmd, | ||||
|                           arglist, | ||||
|                           None) | ||||
|  | ||||
|     def test_create_json_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--network', self._port.network_id, | ||||
|             '--binding-profile', '{"parent_name":"fake_parent"}', | ||||
|             '--binding-profile', '{"tag":42}', | ||||
|             'test-port', | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('network', self._port.network_id,), | ||||
|             ('enable', True), | ||||
|             ('binding_profile', {'parent_name': 'fake_parent', 'tag': 42}), | ||||
|             ('name', 'test-port'), | ||||
|         ] | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|  | ||||
|         columns, data = (self.cmd.take_action(parsed_args)) | ||||
|  | ||||
|         self.network.create_port.assert_called_once_with(**{ | ||||
|             'admin_state_up': True, | ||||
|             'network_id': self._port.network_id, | ||||
|             'binding:profile': {'parent_name': 'fake_parent', 'tag': 42}, | ||||
|             'name': 'test-port', | ||||
|         }) | ||||
|  | ||||
|         ref_columns, ref_data = self._get_common_cols_data(self._port) | ||||
|         self.assertEqual(ref_columns, columns) | ||||
|         self.assertEqual(ref_data, data) | ||||
|  | ||||
|  | ||||
| class TestDeletePort(TestPort): | ||||
|  | ||||
| @@ -442,6 +495,48 @@ class TestSetPort(TestPort): | ||||
|         self.network.update_port.assert_called_once_with(self._port, **attrs) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|     def test_set_invalid_json_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--binding-profile', '{"parent_name"}', | ||||
|             'test-port', | ||||
|         ] | ||||
|         self.assertRaises(argparse.ArgumentTypeError, | ||||
|                           self.check_parser, | ||||
|                           self.cmd, | ||||
|                           arglist, | ||||
|                           None) | ||||
|  | ||||
|     def test_set_invalid_key_value_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--binding-profile', 'key', | ||||
|             'test-port', | ||||
|         ] | ||||
|         self.assertRaises(argparse.ArgumentTypeError, | ||||
|                           self.check_parser, | ||||
|                           self.cmd, | ||||
|                           arglist, | ||||
|                           None) | ||||
|  | ||||
|     def test_set_mixed_binding_profile(self): | ||||
|         arglist = [ | ||||
|             '--binding-profile', 'foo=bar', | ||||
|             '--binding-profile', '{"foo2": "bar2"}', | ||||
|             self._port.name, | ||||
|         ] | ||||
|         verifylist = [ | ||||
|             ('binding_profile', {'foo': 'bar', 'foo2': 'bar2'}), | ||||
|             ('port', self._port.name), | ||||
|         ] | ||||
|  | ||||
|         parsed_args = self.check_parser(self.cmd, arglist, verifylist) | ||||
|         result = self.cmd.take_action(parsed_args) | ||||
|  | ||||
|         attrs = { | ||||
|             'binding:profile': {'foo': 'bar', 'foo2': 'bar2'}, | ||||
|         } | ||||
|         self.network.update_port.assert_called_once_with(self._port, **attrs) | ||||
|         self.assertIsNone(result) | ||||
|  | ||||
|  | ||||
| class TestShowPort(TestPort): | ||||
|  | ||||
|   | ||||
| @@ -1,5 +1,9 @@ | ||||
| --- | ||||
| features: | ||||
|   - Update ``--binding-profile`` option on the ``port create`` and | ||||
|     ``port set`` commands to support JSON input for more advanced | ||||
|     binding profile data. | ||||
|     [Blueprint :oscbp:`neutron-client`] | ||||
|   - Add ``geneve`` choice to the  ``network create`` command | ||||
|     ``--provider-network-type`` option. | ||||
|     [Blueprint :oscbp:`neutron-client`] | ||||
|   | ||||
		Reference in New Issue
	
	Block a user
	 Jenkins
					Jenkins