diff --git a/doc/source/command-objects/floating-ip.rst b/doc/source/command-objects/floating-ip.rst index 5a38bc1b0..5e126fe31 100644 --- a/doc/source/command-objects/floating-ip.rst +++ b/doc/source/command-objects/floating-ip.rst @@ -143,6 +143,32 @@ List floating IP(s) *Network version 2 only* +floating ip set +--------------- + +Set floating IP properties + +.. program:: floating ip set +.. code:: bash + + openstack floating ip set + --port + [--fixed-ip-address ] + + +.. option:: --port + + Assocaite the floating IP with port (name or ID) + +.. option:: --fixed-ip-address + + Fixed IP of the port (required only if port has multiple IPs) + +.. _floating_ip_set-floating-ip: +.. describe:: + + Floating IP to associate (IP address or ID) + floating ip show ---------------- @@ -156,3 +182,24 @@ Display floating IP details .. describe:: Floating IP to display (IP address or ID) + +floating ip unset +----------------- + +Unset floating IP Properties + +.. program:: floating ip unset +.. code:: bash + + openstack floating ip unset + --port + + +.. option:: --port + + Disassociate any port associated with the floating IP + +.. _floating_ip_unset-floating-ip: +.. describe:: + + Floating IP to disassociate (IP address or ID) diff --git a/openstackclient/network/v2/floating_ip.py b/openstackclient/network/v2/floating_ip.py index 3ac76aa3d..9bfc7a64a 100644 --- a/openstackclient/network/v2/floating_ip.py +++ b/openstackclient/network/v2/floating_ip.py @@ -17,6 +17,7 @@ import logging from openstack import exceptions as sdk_exceptions from openstack.network.v2 import floating_ip as _floating_ip +from osc_lib.command import command from osc_lib import utils from openstackclient.i18n import _ @@ -164,7 +165,7 @@ class CreateFloatingIP(common.NetworkAndComputeShowOne): ) parser.add_argument( '--fixed-ip-address', - metavar='', + metavar='', dest='fixed_ip_address', help=_("Fixed IP address mapped to the floating IP") ) @@ -446,6 +447,47 @@ class ListIPFloating(ListFloatingIP): client, parsed_args) +class SetFloatingIP(command.Command): + _description = _("Set floating IP Properties") + + def get_parser(self, prog_name): + parser = super(SetFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP to associate (IP address or ID)")) + parser.add_argument( + '--port', + metavar='', + required=True, + help=_("Assocaite the floating IP with port (name or ID)")), + parser.add_argument( + '--fixed-ip-address', + metavar='', + dest='fixed_ip_address', + help=_("Fixed IP of the port " + "(required only if port has multiple IPs)") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + attrs = {} + # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released + obj = _find_floating_ip( + self.app.client_manager.sdk_connection.session, + parsed_args.floating_ip, + ignore_missing=False, + ) + port = client.find_port(parsed_args.port, + ignore_missing=False) + attrs['port_id'] = port.id + if parsed_args.fixed_ip_address: + attrs['fixed_ip_address'] = parsed_args.fixed_ip_address + + client.update_ip(obj, **attrs) + + class ShowFloatingIP(common.NetworkAndComputeShowOne): _description = _("Display floating IP details") @@ -499,3 +541,35 @@ class ShowIPFloating(ShowFloatingIP): 'Please use "floating ip show" instead.')) return super(ShowIPFloating, self).take_action_compute( client, parsed_args) + + +class UnsetFloatingIP(command.Command): + _description = _("Unset floating IP Properties") + + def get_parser(self, prog_name): + parser = super(UnsetFloatingIP, self).get_parser(prog_name) + parser.add_argument( + 'floating_ip', + metavar='', + help=_("Floating IP to disassociate (IP address or ID)")) + parser.add_argument( + '--port', + action='store_true', + default=False, + help=_("Disassociate any port associated with the floating IP") + ) + return parser + + def take_action(self, parsed_args): + client = self.app.client_manager.network + # TODO(sindhu) Use client.find_ip() once SDK 0.9.15 is released + obj = _find_floating_ip( + self.app.client_manager.sdk_connection.session, + parsed_args.floating_ip, + ignore_missing=False, + ) + if parsed_args.port: + attrs = { + 'port_id': None, + } + client.update_ip(obj, **attrs) diff --git a/openstackclient/tests/functional/network/v2/test_floating_ip.py b/openstackclient/tests/functional/network/v2/test_floating_ip.py index 8fbec3d5f..5da0e474c 100644 --- a/openstackclient/tests/functional/network/v2/test_floating_ip.py +++ b/openstackclient/tests/functional/network/v2/test_floating_ip.py @@ -21,6 +21,10 @@ class FloatingIpTests(base.TestCase): """Functional tests for floating ip""" SUBNET_NAME = uuid.uuid4().hex NETWORK_NAME = uuid.uuid4().hex + PRIVATE_NETWORK_NAME = uuid.uuid4().hex + PRIVATE_SUBNET_NAME = uuid.uuid4().hex + ROUTER = uuid.uuid4().hex + PORT_NAME = uuid.uuid4().hex @classmethod def setUpClass(cls): @@ -30,6 +34,8 @@ class FloatingIpTests(base.TestCase): cls.re_fixed_ip = re.compile("fixed_ip_address\s+\|\s+(\S+)") cls.re_description = re.compile("description\s+\|\s+([^|]+?)\s+\|") cls.re_network_id = re.compile("floating_network_id\s+\|\s+(\S+)") + cls.re_port_id = re.compile("\s+id\s+\|\s+(\S+)") + cls.re_fp_port_id = re.compile("\s+port_id\s+\|\s+(\S+)") # Create a network for the floating ip raw_output = cls.openstack( @@ -37,6 +43,12 @@ class FloatingIpTests(base.TestCase): ) cls.network_id = re.search(cls.re_id, raw_output).group(1) + # Create a private network for the port + raw_output = cls.openstack( + 'network create ' + cls.PRIVATE_NETWORK_NAME + ) + cls.private_network_id = re.search(cls.re_id, raw_output).group(1) + # Try random subnet range for subnet creating # Because we can not determine ahead of time what subnets are already # in use, possibly by another test running in parallel, try 4 times @@ -46,6 +58,10 @@ class FloatingIpTests(base.TestCase): str, (random.randint(0, 223) for _ in range(3)) )) + ".0/26" + cls.private_subnet = ".".join(map( + str, + (random.randint(0, 223) for _ in range(3)) + )) + ".0/26" try: # Create a subnet for the network raw_output = cls.openstack( @@ -54,6 +70,13 @@ class FloatingIpTests(base.TestCase): '--subnet-range ' + cls.subnet + ' ' + cls.SUBNET_NAME ) + # Create a subnet for the private network + priv_raw_output = cls.openstack( + 'subnet create ' + + '--network ' + cls.PRIVATE_NETWORK_NAME + ' ' + + '--subnet-range ' + cls.private_subnet + ' ' + + cls.PRIVATE_SUBNET_NAME + ) except Exception: if (i == 3): # raise the exception at the last time @@ -64,13 +87,19 @@ class FloatingIpTests(base.TestCase): break cls.subnet_id = re.search(cls.re_id, raw_output).group(1) + cls.private_subnet_id = re.search(cls.re_id, priv_raw_output).group(1) @classmethod def tearDownClass(cls): raw_output = cls.openstack('subnet delete ' + cls.SUBNET_NAME) cls.assertOutput('', raw_output) + raw_output = cls.openstack('subnet delete ' + cls.PRIVATE_SUBNET_NAME) + cls.assertOutput('', raw_output) raw_output = cls.openstack('network delete ' + cls.NETWORK_NAME) cls.assertOutput('', raw_output) + raw_output = cls.openstack( + 'network delete ' + cls.PRIVATE_NETWORK_NAME) + cls.assertOutput('', raw_output) def test_floating_ip_delete(self): """Test create, delete multiple""" @@ -168,3 +197,50 @@ class FloatingIpTests(base.TestCase): # re.search(self.re_floating_ip, raw_output).group(1), # ) self.assertIsNotNone(re.search(self.re_network_id, raw_output)) + + def test_floating_ip_set_and_unset_port(self): + """Test Floating IP Set and Unset port""" + raw_output = self.openstack( + 'floating ip create ' + + '--description shosho ' + + self.NETWORK_NAME + ) + re_ip = re.search(self.re_floating_ip, raw_output) + fp_ip = re_ip.group(1) + self.addCleanup(self.openstack, 'floating ip delete ' + fp_ip) + self.assertIsNotNone(fp_ip) + + raw_output1 = self.openstack( + 'port create --network ' + self.PRIVATE_NETWORK_NAME + + ' --fixed-ip subnet=' + self.PRIVATE_SUBNET_NAME + + ' ' + self.PORT_NAME + ) + re_port_id = re.search(self.re_port_id, raw_output1) + self.assertIsNotNone(re_port_id) + port_id = re_port_id.group(1) + + router = self.openstack('router create ' + self.ROUTER) + self.assertIsNotNone(router) + self.addCleanup(self.openstack, 'router delete ' + self.ROUTER) + + self.openstack('router add port ' + self.ROUTER + + ' ' + port_id) + self.openstack('router set --external-gateway ' + self.NETWORK_NAME + + ' ' + self.ROUTER) + + self.addCleanup(self.openstack, 'router unset --external-gateway ' + + self.ROUTER) + self.addCleanup(self.openstack, 'router remove port ' + self.ROUTER + + ' ' + port_id) + + raw_output = self.openstack( + 'floating ip set ' + + fp_ip + ' --port ' + port_id) + self.addCleanup(self.openstack, 'floating ip unset --port ' + fp_ip) + + show_output = self.openstack( + 'floating ip show ' + fp_ip) + + self.assertEqual( + port_id, + re.search(self.re_fp_port_id, show_output).group(1)) diff --git a/openstackclient/tests/unit/network/v2/test_floating_ip.py b/openstackclient/tests/unit/network/v2/test_floating_ip.py index 0b3fd888a..69fb14196 100644 --- a/openstackclient/tests/unit/network/v2/test_floating_ip.py +++ b/openstackclient/tests/unit/network/v2/test_floating_ip.py @@ -16,7 +16,7 @@ from mock import call from osc_lib import exceptions -from openstackclient.network.v2 import floating_ip +from openstackclient.network.v2 import floating_ip as fip from openstackclient.tests.unit.compute.v2 import fakes as compute_fakes from openstackclient.tests.unit.identity.v3 import fakes as identity_fakes_v3 from openstackclient.tests.unit.network.v2 import fakes as network_fakes @@ -92,7 +92,7 @@ class TestCreateFloatingIPNetwork(TestFloatingIPNetwork): self.network.find_port = mock.Mock(return_value=self.port) # Get the command object to test - self.cmd = floating_ip.CreateFloatingIP(self.app, self.namespace) + self.cmd = fip.CreateFloatingIP(self.app, self.namespace) def test_create_no_options(self): arglist = [] @@ -210,12 +210,9 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): self.network.delete_ip = mock.Mock(return_value=None) # Get the command object to test - self.cmd = floating_ip.DeleteFloatingIP(self.app, self.namespace) + self.cmd = fip.DeleteFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -239,10 +236,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): self.network.delete_ip.assert_called_once_with(self.floating_ips[0]) self.assertIsNone(result) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete_multi(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -280,10 +274,7 @@ class TestDeleteFloatingIPNetwork(TestFloatingIPNetwork): self.network.delete_ip.assert_has_calls(calls) self.assertIsNone(result) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_delete_multi_exception(self, find_floating_ip_mock): find_floating_ip_mock.side_effect = [ self.floating_ips[0], @@ -380,7 +371,7 @@ class TestListFloatingIPNetwork(TestFloatingIPNetwork): self.network.find_router = mock.Mock(return_value=self.fake_router) # Get the command object to test - self.cmd = floating_ip.ListFloatingIP(self.app, self.namespace) + self.cmd = fip.ListFloatingIP(self.app, self.namespace) def test_floating_ip_list(self): arglist = [] @@ -572,12 +563,9 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): self.network.find_ip = mock.Mock(return_value=self.floating_ip) # Get the command object to test - self.cmd = floating_ip.ShowFloatingIP(self.app, self.namespace) + self.cmd = fip.ShowFloatingIP(self.app, self.namespace) - @mock.patch( - "openstackclient.tests.unit.network.v2.test_floating_ip." + - "floating_ip._find_floating_ip" - ) + @mock.patch.object(fip, '_find_floating_ip') def test_floating_ip_show(self, find_floating_ip_mock): find_floating_ip_mock.return_value = self.floating_ip arglist = [ @@ -599,6 +587,154 @@ class TestShowFloatingIPNetwork(TestFloatingIPNetwork): self.assertEqual(self.data, data) +class TestSetFloatingIP(TestFloatingIPNetwork): + + # Fake data for option tests. + floating_network = network_fakes.FakeNetwork.create_one_network() + subnet = network_fakes.FakeSubnet.create_one_subnet() + port = network_fakes.FakePort.create_one_port() + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( + attrs={ + 'floating_network_id': floating_network.id, + 'port_id': port.id, + } + ) + + def setUp(self): + super(TestSetFloatingIP, self).setUp() + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.find_port = mock.Mock(return_value=self.port) + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = fip.SetFloatingIP(self.app, self.namespace) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_port_option(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + '--port', self.floating_ip.port_id, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', self.floating_ip.port_id), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': self.floating_ip.port_id, + } + + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_fixed_ip_option(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + '--port', self.floating_ip.port_id, + "--fixed-ip-address", self.floating_ip.fixed_ip_address, + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', self.floating_ip.port_id), + ('fixed_ip_address', self.floating_ip.fixed_ip_address), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': self.floating_ip.port_id, + 'fixed_ip_address': self.floating_ip.fixed_ip_address, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + +class TestUnsetFloatingIP(TestFloatingIPNetwork): + + floating_network = network_fakes.FakeNetwork.create_one_network() + subnet = network_fakes.FakeSubnet.create_one_subnet() + port = network_fakes.FakePort.create_one_port() + + # The floating ip to be deleted. + floating_ip = network_fakes.FakeFloatingIP.create_one_floating_ip( + attrs={ + 'floating_network_id': floating_network.id, + 'port_id': port.id, + } + ) + + def setUp(self): + super(TestUnsetFloatingIP, self).setUp() + self.network.find_ip = mock.Mock(return_value=self.floating_ip) + self.network.update_ip = mock.Mock(return_value=None) + + # Get the command object to test + self.cmd = fip.UnsetFloatingIP(self.app, self.namespace) + + @mock.patch( + "openstackclient.tests.unit.network.v2.test_floating_ip." + + "fip._find_floating_ip" + ) + def test_floating_ip_unset_port(self, find_floating_ip_mock): + find_floating_ip_mock.side_effect = [ + self.floating_ip, + ] + arglist = [ + self.floating_ip.id, + "--port", + ] + verifylist = [ + ('floating_ip', self.floating_ip.id), + ('port', True), + ] + parsed_args = self.check_parser(self.cmd, arglist, verifylist) + + result = self.cmd.take_action(parsed_args) + + attrs = { + 'port_id': None, + } + find_floating_ip_mock.assert_called_once_with( + mock.ANY, + self.floating_ip.id, + ignore_missing=False, + ) + self.network.update_ip.assert_called_once_with( + self.floating_ip, **attrs) + + self.assertIsNone(result) + + # Tests for Nova network # class TestFloatingIPCompute(compute_fakes.TestComputev2): @@ -639,7 +775,7 @@ class TestCreateFloatingIPCompute(TestFloatingIPCompute): self.compute.floating_ips.create.return_value = self.floating_ip # Get the command object to test - self.cmd = floating_ip.CreateFloatingIP(self.app, None) + self.cmd = fip.CreateFloatingIP(self.app, None) def test_create_no_options(self): arglist = [] @@ -682,7 +818,7 @@ class TestDeleteFloatingIPCompute(TestFloatingIPCompute): compute_fakes.FakeFloatingIP.get_floating_ips(self.floating_ips)) # Get the command object to test - self.cmd = floating_ip.DeleteFloatingIP(self.app, None) + self.cmd = fip.DeleteFloatingIP(self.app, None) def test_floating_ip_delete(self): arglist = [ @@ -782,7 +918,7 @@ class TestListFloatingIPCompute(TestFloatingIPCompute): self.compute.floating_ips.list.return_value = self.floating_ips # Get the command object to test - self.cmd = floating_ip.ListFloatingIP(self.app, None) + self.cmd = fip.ListFloatingIP(self.app, None) def test_floating_ip_list(self): arglist = [] @@ -826,7 +962,7 @@ class TestShowFloatingIPCompute(TestFloatingIPCompute): self.compute.floating_ips.get.return_value = self.floating_ip # Get the command object to test - self.cmd = floating_ip.ShowFloatingIP(self.app, None) + self.cmd = fip.ShowFloatingIP(self.app, None) def test_floating_ip_show(self): arglist = [ diff --git a/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml b/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml new file mode 100644 index 000000000..b4a899a42 --- /dev/null +++ b/releasenotes/notes/floating-ip-set-unset-port-28e33875937b69cf.yaml @@ -0,0 +1,5 @@ +--- +features: + - | + Add ``floating ip set`` and ``floating ip unset`` commands. + [:lpbug:`1560297`] diff --git a/setup.cfg b/setup.cfg index 6b4e2e1d6..92f7bbb7b 100644 --- a/setup.cfg +++ b/setup.cfg @@ -347,7 +347,9 @@ openstack.network.v2 = floating_ip_create = openstackclient.network.v2.floating_ip:CreateFloatingIP floating_ip_delete = openstackclient.network.v2.floating_ip:DeleteFloatingIP floating_ip_list = openstackclient.network.v2.floating_ip:ListFloatingIP + floating_ip_set = openstackclient.network.v2.floating_ip:SetFloatingIP floating_ip_show = openstackclient.network.v2.floating_ip:ShowFloatingIP + floating_ip_unset = openstackclient.network.v2.floating_ip:UnsetFloatingIP floating_ip_pool_list = openstackclient.network.v2.floating_ip_pool:ListFloatingIPPool