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