From f3c5da5402ea2d1c129684a84c91dbc72556f14f Mon Sep 17 00:00:00 2001 From: Anton Vazhnetsov Date: Sat, 5 Mar 2022 20:40:10 +0300 Subject: [PATCH] nb: add support for lb health checks API This change adds functions and commands to add, delete, list, get and update health checks of load balancers. Also added a method to get one load balancer by name or uuid and methods to modify 'ip_port_mappings' column in the load balancer. Closes-Bug: 1964382 Change-Id: I3f8b63c64c7ac9c570dbd400c562fa97840429ca --- ovsdbapp/schema/ovn_northbound/api.py | 82 ++++++++++++++ ovsdbapp/schema/ovn_northbound/commands.py | 94 ++++++++++++++++ ovsdbapp/schema/ovn_northbound/impl_idl.py | 22 ++++ .../schema/ovn_northbound/test_impl_idl.py | 103 ++++++++++++++++++ .../provide-lb-hc-api-8ff13ccaf75f1eee.yaml | 6 + 5 files changed, 307 insertions(+) create mode 100644 releasenotes/notes/provide-lb-hc-api-8ff13ccaf75f1eee.yaml diff --git a/ovsdbapp/schema/ovn_northbound/api.py b/ovsdbapp/schema/ovn_northbound/api.py index 90a9f290..296b3294 100644 --- a/ovsdbapp/schema/ovn_northbound/api.py +++ b/ovsdbapp/schema/ovn_northbound/api.py @@ -885,6 +885,88 @@ class API(api.API, metaclass=abc.ABCMeta): def lb_list(self): """Get the UUIDs of all load balanacers""" + @abc.abstractmethod + def lb_get(self, lb): + """Get load balancer for 'lb' + + :returns: :class:`Command` with RowView result + """ + + @abc.abstractmethod + def lb_add_health_check(self, lb, vip, **options): + """Add health check for 'lb' + + :param lb: The name or uuid of a load balancer + :type lb: string or uuid.UUID + :param vip: A virtual IP + :type vip: string + :param options: keys and values for the port 'options' dict + :type options: key: string, value: string + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def lb_del_health_check(self, lb, hc_uuid, if_exists=False): + """Remove health check from 'lb' + + :param lb: The name or uuid of a load balancer + :type lb: string or uuid.UUID + :param hc_uuid: uuid of a health check + :type hc_uuid: uuid.UUID + :param if_exists: If True, don't fail if the hc_uuid doesn't exist + :type if_exists: boolean + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def lb_add_ip_port_mapping(self, lb, endpoint_ip, port_name, source_ip): + """Add IP port mapping to 'lb' + + Maps from endpoint IP to a colon-separated pair of logical port name + and source IP, e.g. port_name:sourc_ip. + + :param lb: The name or uuid of a load balancer + :type lb: string or uuid.UUID + :param endpoint_ip: IPv4 address + :type endpoint_ip: string + :param port_name: The name of a logical port + :type port_name: string + :param source_ip: IPv4 address + :type source_ip: string + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def lb_del_ip_port_mapping(self, lb, endpoint_ip): + """Remove IP port mapping from 'lb' + + :param lb: The name or uuid of a load balancer + :type lb: string or uuid.UUID + :param endpoint_ip: IPv4 address + :type endpoint_ip: string + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def health_check_set_options(self, hc_uuid, **options): + """Set options to the 'health_check' + + :param hc_uuid: uuid of the health check + :type hc_uuid: uuid.UUID + :param options: keys and values for the port 'options' dict + :type options: key: string, value: string + :returns: :class:`Command` with no result + """ + + @abc.abstractmethod + def health_check_get_options(self, hc_uuid): + """Get the options for 'health_check' + + :param hc_uuid: uuid of the health check + :type hc_uuid: uuid.UUID + :returns: :class:`Command` with dict result + """ + @abc.abstractmethod def lr_lb_add(self, router, lb, may_exist=False): """Add a load-balancer to 'router' diff --git a/ovsdbapp/schema/ovn_northbound/commands.py b/ovsdbapp/schema/ovn_northbound/commands.py index 01bbaee7..b1a4209d 100644 --- a/ovsdbapp/schema/ovn_northbound/commands.py +++ b/ovsdbapp/schema/ovn_northbound/commands.py @@ -1341,6 +1341,100 @@ class LbListCommand(cmd.ReadOnlyCommand): for r in self.api.tables['Load_Balancer'].rows.values()] +class LbGetCommand(cmd.BaseGetRowCommand): + table = 'Load_Balancer' + + +class LbAddHealthCheckCommand(cmd.BaseCommand): + table = 'Load_Balancer' + + def __init__(self, api, lb, vip, **options): + super().__init__(api) + self.lb = lb + self.vip = vip + self.options = options + + def run_idl(self, txn): + lb = self.api.lookup(self.table, self.lb) + cmd = HealthCheckAddCommand(self.api, self.vip, **self.options) + cmd.run_idl(txn) + lb.addvalue('health_check', cmd.result) + + +class LbDelHealthCheckCommand(cmd.BaseCommand): + table = 'Load_Balancer' + + def __init__(self, api, lb, hc_uuid, if_exists=False): + super().__init__(api) + self.lb = lb + self.hc_uuid = hc_uuid + self.if_exists = if_exists + + def run_idl(self, txn): + lb = self.api.lookup(self.table, self.lb) + for health_check in lb.health_check: + if health_check.uuid == self.hc_uuid: + lb.delvalue('health_check', health_check) + health_check.delete() + return + if not self.if_exists: + msg = "Health check '%s' on lb %s does not exist" % ( + self.hc_uuid, self.lb) + raise RuntimeError(msg) + + +class LbAddIpPortMappingСommand(cmd.BaseCommand): + table = 'Load_Balancer' + + def __init__(self, api, lb, endpoint_ip, port_name, source_ip): + super().__init__(api) + self.lb = lb + self.endpoint_ip = str(netaddr.IPAddress(endpoint_ip)) + self.port_name = port_name + self.source_ip = str(netaddr.IPAddress(source_ip)) + + def run_idl(self, txn): + lb = self.api.lookup(self.table, self.lb) + lb.setkey('ip_port_mappings', self.endpoint_ip, + '%s:%s' % (self.port_name, self.source_ip)) + + +class LbDelIpPortMappingCommand(cmd.BaseCommand): + table = 'Load_Balancer' + + def __init__(self, api, lb, endpoint_ip): + super().__init__(api) + self.lb = lb + self.endpoint_ip = str(netaddr.IPAddress(endpoint_ip)) + + def run_idl(self, txn): + lb = self.api.lookup(self.table, self.lb) + lb.delkey('ip_port_mappings', self.endpoint_ip) + + +class HealthCheckAddCommand(cmd.AddCommand): + table_name = 'Load_Balancer_Health_Check' + + def __init__(self, api, vip, **options): + super().__init__(api) + self.vip = utils.normalize_ip_port(vip) + self.options = options + + def run_idl(self, txn): + hc = txn.insert(self.api.tables[self.table_name]) + hc.vip = self.vip + hc.options = self.options + self.result = hc + + +class HealthCheckSetOptionsCommand(cmd.BaseSetOptionsCommand): + table = 'Load_Balancer_Health_Check' + + +class HealthCheckGetOptionsCommand(cmd.BaseGetOptionsCommand): + table = 'Load_Balancer_Health_Check' + + class LrLbAddCommand(cmd.BaseCommand): def __init__(self, api, router, lb, may_exist=False): super().__init__(api) diff --git a/ovsdbapp/schema/ovn_northbound/impl_idl.py b/ovsdbapp/schema/ovn_northbound/impl_idl.py index 980ced64..c2911e36 100644 --- a/ovsdbapp/schema/ovn_northbound/impl_idl.py +++ b/ovsdbapp/schema/ovn_northbound/impl_idl.py @@ -273,6 +273,28 @@ class OvnNbApiIdlImpl(ovs_idl.Backend, api.API): def lb_list(self): return cmd.LbListCommand(self) + def lb_get(self, lb): + return cmd.LbGetCommand(self, lb) + + def lb_add_health_check(self, lb, vip, **options): + return cmd.LbAddHealthCheckCommand(self, lb, vip, **options) + + def lb_del_health_check(self, lb, hc_uuid, if_exists=False): + return cmd.LbDelHealthCheckCommand(self, lb, hc_uuid, if_exists) + + def lb_add_ip_port_mapping(self, lb, endport_ip, port_name, source_ip): + return cmd.LbAddIpPortMappingСommand(self, lb, endport_ip, + port_name, source_ip) + + def lb_del_ip_port_mapping(self, lb, endport_ip): + return cmd.LbDelIpPortMappingCommand(self, lb, endport_ip) + + def health_check_set_options(self, hc_uuid, **options): + return cmd.HealthCheckSetOptionsCommand(self, hc_uuid, **options) + + def health_check_get_options(self, hc_uuid): + return cmd.HealthCheckGetOptionsCommand(self, hc_uuid) + def lr_lb_add(self, router, lb, may_exist=False): return cmd.LrLbAddCommand(self, router, lb, may_exist) diff --git a/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py b/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py index 820b5e40..e3e83fc3 100644 --- a/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py +++ b/ovsdbapp/tests/functional/schema/ovn_northbound/test_impl_idl.py @@ -1669,6 +1669,109 @@ class TestLoadBalancerOps(OvnNorthboundTest): lbset = self.api.lb_list().execute(check_error=True) self.assertTrue(lbs.issubset(lbset)) + def _test_lb_get(self, col): + lb = self._lb_add(utils.get_rand_device_name(), + '192.0.0.1', ['10.0.0.1']) + val = getattr(lb, col) + found = self.api.lb_get(val).execute(check_error=True) + self.assertEqual(lb, found) + + def test_lb_get_uuid(self): + self._test_lb_get('uuid') + + def test_lb_get_name(self): + self._test_lb_get('name') + + def _test_lb_add_del_health_check(self, col): + hc_options = { + 'interval': '2', + 'timeout': '10', + 'success_count': '3', + 'failure_count': '3', + } + hc_vip = '172.31.0.1' + + lb = self._lb_add(utils.get_rand_device_name(), + '192.0.0.1', ['10.0.0.1']) + self.assertEqual(lb.health_check, []) + val = getattr(lb, col) + self.api.lb_add_health_check(val, + hc_vip, + **hc_options).execute(check_error=True) + self.assertEqual(len(lb.health_check), 1) + hc = self.api.lookup('Load_Balancer_Health_Check', + lb.health_check[0].uuid) + self.assertEqual(hc.vip, hc_vip) + self.assertEqual(hc.options, hc_options) + + self.api.lb_del_health_check(val, hc.uuid).execute(check_error=True) + self.assertEqual(len(lb.health_check), 0) + self.assertNotIn(hc.uuid, + self.api.tables['Load_Balancer_Health_Check'].rows) + + def test_lb_add_del_health_check_uuid(self): + self._test_lb_add_del_health_check('uuid') + + def test_lb_add_del_health_check_name(self): + self._test_lb_add_del_health_check('name') + + def test_lb_del_health_check_if_exists(self): + lb = self._lb_add(utils.get_rand_device_name(), + '192.0.0.1', ['10.0.0.1']) + self.api.lb_del_health_check(lb.name, uuid.uuid4(), + if_exists=True).execute(check_error=True) + + def _test_lb_add_del_ip_port_mapping(self, col): + endpoint_ip = '172.31.0.4' + port_name = 'sw1-p1' + source_ip = '172.31.0.6' + lb = self._lb_add(utils.get_rand_device_name(), + '192.0.0.1', ['10.0.0.1']) + self.assertEqual(lb.ip_port_mappings, {}) + val = getattr(lb, col) + self.api.lb_add_ip_port_mapping(val, + endpoint_ip, + port_name, + source_ip).execute(check_error=True) + self.assertEqual(lb.ip_port_mappings[endpoint_ip], + '%s:%s' % (port_name, source_ip)) + + self.api.lb_del_ip_port_mapping(val, + endpoint_ip).execute(check_error=True) + self.assertEqual(lb.ip_port_mappings, {}) + + def test_lb_add_del_ip_port_mapping_uuid(self): + self._test_lb_add_del_ip_port_mapping('uuid') + + def test_lb_add_del_ip_port_mapping_name(self): + self._test_lb_add_del_ip_port_mapping('name') + + def test_hc_get_set_options(self): + hc_options = { + 'interval': '2', + 'timeout': '10', + 'success_count': '3', + 'failure_count': '3', + } + lb = self._lb_add(utils.get_rand_device_name(), + '192.0.0.1', ['10.0.0.1']) + self.api.lb_add_health_check(lb.uuid, + '172.31.0.1', + **hc_options).execute(check_error=True) + hc = self.api.lookup('Load_Balancer_Health_Check', + lb.health_check[0].uuid) + options = self.api.health_check_get_options( + hc.uuid).execute(check_error=True) + self.assertEqual(hc_options, options) + + options.update({ + 'interval': '5', + 'new-option': 'option', + }) + self.api.health_check_set_options(hc.uuid, + **options).execute(check_error=True) + self.assertEqual(hc.options, options) + class TestObLbOps(testscenarios.TestWithScenarios, OvnNorthboundTest): scenarios = [ diff --git a/releasenotes/notes/provide-lb-hc-api-8ff13ccaf75f1eee.yaml b/releasenotes/notes/provide-lb-hc-api-8ff13ccaf75f1eee.yaml new file mode 100644 index 00000000..dc2b6551 --- /dev/null +++ b/releasenotes/notes/provide-lb-hc-api-8ff13ccaf75f1eee.yaml @@ -0,0 +1,6 @@ +--- +features: + - | + Added functions and commands to add, delete, list and update records of + 'Load_Balancer_Health_Check' table. Also added a method to get one load balancer + by name or uuid and methods to modify 'ip_port_mappings' column in the load balancer.