Merge "loadbalancer vip-network-id IP availability check" into stable/queens

This commit is contained in:
Zuul 2019-11-27 15:26:32 +00:00 committed by Gerrit Code Review
commit 96e5962e05
12 changed files with 220 additions and 7 deletions

View File

@ -127,9 +127,29 @@ class LoadBalancersController(base.BaseController):
"VIP address specified." "VIP address specified."
)) ))
else: else:
# If subnet and IP are not provided, pick the first subnet, # If subnet and IP are not provided, pick the first subnet with
# preferring ipv4 # enough available IPs, preferring ipv4
for subnet_id in network.subnets: if not network.subnets:
raise exceptions.ValidationException(detail=_(
"Supplied network does not contain a subnet."
))
ip_avail = network_driver.get_network_ip_availability(
network)
if (CONF.controller_worker.loadbalancer_topology ==
constants.TOPOLOGY_SINGLE):
num_req_ips = 2
if (CONF.controller_worker.loadbalancer_topology ==
constants.TOPOLOGY_ACTIVE_STANDBY):
num_req_ips = 3
subnets = [subnet_id for subnet_id in network.subnets if
utils.subnet_ip_availability(ip_avail, subnet_id,
num_req_ips)]
if not subnets:
raise exceptions.ValidationException(detail=_(
"Subnet(s) in the supplied network do not contain "
"enough available IPs."
))
for subnet_id in subnets:
# Use the first subnet, in case there are no ipv4 subnets # Use the first subnet, in case there are no ipv4 subnets
if not load_balancer.vip_subnet_id: if not load_balancer.vip_subnet_id:
load_balancer.vip_subnet_id = subnet_id load_balancer.vip_subnet_id = subnet_id
@ -137,10 +157,6 @@ class LoadBalancersController(base.BaseController):
if subnet.ip_version == 4: if subnet.ip_version == 4:
load_balancer.vip_subnet_id = subnet_id load_balancer.vip_subnet_id = subnet_id
break break
if not load_balancer.vip_subnet_id:
raise exceptions.ValidationException(detail=_(
"Supplied network does not contain a subnet."
))
@staticmethod @staticmethod
def _validate_port_and_fill_or_validate_subnet(load_balancer): def _validate_port_and_fill_or_validate_subnet(load_balancer):

View File

@ -105,6 +105,13 @@ def get_six_compatible_server_certs_key_passphrase():
return base64.urlsafe_b64encode(key) return base64.urlsafe_b64encode(key)
def subnet_ip_availability(nw_ip_avail, subnet_id, req_num_ips):
for subnet in nw_ip_avail.subnet_ip_availability:
if subnet['subnet_id'] == subnet_id:
return subnet['total_ips'] - subnet['used_ips'] >= req_num_ips
return None
class exception_logger(object): class exception_logger(object):
"""Wrap a function and log raised exception """Wrap a function and log raised exception

View File

@ -329,3 +329,12 @@ class AbstractNetworkDriver(object):
:return: Boolean :return: Boolean
""" """
@abc.abstractmethod
def get_network_ip_availability(self, network):
"""Retrieves network IP availability.
:param network: octavia.network.data_models.Network
:return: octavia.network.data_models.Network_IP_Availability
:raises: NetworkException, NetworkNotFound
"""

View File

@ -148,3 +148,17 @@ class HostRoute(data_models.BaseDataModel):
class QosPolicy(data_models.BaseDataModel): class QosPolicy(data_models.BaseDataModel):
def __init__(self, id): def __init__(self, id):
self.id = id self.id = id
class Network_IP_Availability(data_models.BaseDataModel):
def __init__(self, network_id=None, tenant_id=None, project_id=None,
network_name=None, total_ips=None, used_ips=None,
subnet_ip_availability=None):
self.network_id = network_id
self.tenant_id = tenant_id
self.project_id = project_id
self.network_name = network_name
self.total_ips = total_ips
self.used_ips = used_ips
self.subnet_ip_availability = subnet_ip_availability

View File

@ -253,3 +253,6 @@ class BaseNeutronDriver(base.AbstractNetworkDriver):
def qos_enabled(self): def qos_enabled(self):
return self._qos_enabled return self._qos_enabled
def get_network_ip_availability(self, network):
return self._get_resource('network_ip_availability', network.id)

View File

@ -94,3 +94,12 @@ def convert_floatingip_dict_to_model(floating_ip_dict):
fixed_ip_address=floating_ip.get('fixed_ip_address'), fixed_ip_address=floating_ip.get('fixed_ip_address'),
fixed_port_id=floating_ip.get('fixed_port_id') fixed_port_id=floating_ip.get('fixed_port_id')
) )
def convert_network_ip_availability_dict_to_model(
network_ip_availability_dict):
nw_ip_avail = network_ip_availability_dict.get(
'network_ip_availability', network_ip_availability_dict)
ip_avail = network_models.Network_IP_Availability.from_dict(nw_ip_avail)
ip_avail.subnet_ip_availability = nw_ip_avail.get('subnet_ip_availability')
return ip_avail

View File

@ -224,6 +224,21 @@ class NoopManager(object):
def qos_enabled(self): def qos_enabled(self):
return self._qos_extension_enabled return self._qos_extension_enabled
def get_network_ip_availability(self, network):
LOG.debug("Network %s no-op, network_ip_availability network_id %s",
self.__class__.__name__, network.id)
self.networkconfigconfig[(network.id, 'ip_availability')] = (
network.id, 'get_network_ip_availability')
ip_avail = network_models.Network_IP_Availability(
network_id=network.id)
subnet_ip_availability = []
network.subnets = list(network.subnets)
for subnet_id in network.subnets:
subnet_ip_availability.append({'subnet_id': subnet_id,
'used_ips': 0, 'total_ips': 254})
ip_avail.subnet_ip_availability = subnet_ip_availability
return ip_avail
class NoopNetworkDriver(driver_base.AbstractNetworkDriver): class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def __init__(self): def __init__(self):
@ -296,3 +311,6 @@ class NoopNetworkDriver(driver_base.AbstractNetworkDriver):
def qos_enabled(self): def qos_enabled(self):
return self.driver.qos_enabled() return self.driver.qos_enabled()
def get_network_ip_availability(self, network):
return self.driver.get_network_ip_availability(network)

View File

@ -197,3 +197,19 @@ MOCK_VRRP_PORT2 = {'port': {'network_id': MOCK_VIP_NET_ID,
'device_owner': MOCK_DEVICE_OWNER, 'device_owner': MOCK_DEVICE_OWNER,
'id': MOCK_VRRP_PORT_ID2, 'id': MOCK_VRRP_PORT_ID2,
'fixed_ips': MOCK_VRRP_FIXED_IPS2}} 'fixed_ips': MOCK_VRRP_FIXED_IPS2}}
MOCK_NETWORK_TOTAL_IPS = 254
MOCK_NETWORK_USED_IPS = 0
MOCK_SUBNET_TOTAL_IPS = 254
MOCK_SUBNET_USED_IPS = 0
MOCK_SUBNET_IP_AVAILABILITY = [{'used_ips': MOCK_SUBNET_USED_IPS,
'subnet_id': MOCK_SUBNET_ID,
'total_ips': MOCK_SUBNET_TOTAL_IPS}]
MOCK_NETWORK_IP_AVAILABILITY = {'network_ip_availability': (
{'network_id': MOCK_NETWORK_ID,
'tenant_id': MOCK_PROJECT_ID,
'network_name': MOCK_NETWORK_NAME,
'total_ips': MOCK_NETWORK_TOTAL_IPS,
'used_ips': MOCK_NETWORK_USED_IPS,
'subnet_ip_availability': MOCK_SUBNET_IP_AVAILABILITY})}

View File

@ -197,6 +197,89 @@ class TestLoadBalancer(base.BaseAPITest):
self.assertEqual(subnet.id, api_lb.get('vip_subnet_id')) self.assertEqual(subnet.id, api_lb.get('vip_subnet_id'))
self.assertEqual(network_id, api_lb.get('vip_network_id')) self.assertEqual(network_id, api_lb.get('vip_network_id'))
def test_create_with_vip_network_picks_subnet_ipv4_avail_ips(self):
self.conf.config(
group='controller_worker',
loadbalancer_topology=constants.TOPOLOGY_ACTIVE_STANDBY)
network_id = uuidutils.generate_uuid()
subnet1 = network_models.Subnet(id=uuidutils.generate_uuid(),
network_id=network_id,
ip_version=4)
subnet2 = network_models.Subnet(id=uuidutils.generate_uuid(),
network_id=network_id,
ip_version=4)
subnet3 = network_models.Subnet(id=uuidutils.generate_uuid(),
network_id=network_id,
ip_version=4)
network = network_models.Network(id=network_id,
subnets=[subnet1.id, subnet2.id,
subnet3.id])
subnet_ip_availability = [{'subnet_id': subnet1.id, 'used_ips': 254,
'total_ips': 254}, {'subnet_id': subnet2.id,
'used_ips': 128, 'total_ips': 254},
{'subnet_id': subnet3.id, 'used_ips': 254,
'total_ips': 254}]
ip_avail = network_models.Network_IP_Availability(
network_id=network.id,
subnet_ip_availability=subnet_ip_availability)
lb_json = {'vip_network_id': network.id,
'project_id': self.project_id}
body = self._build_body(lb_json)
with mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network") as mock_get_network, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_subnet") as mock_get_subnet, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network_ip_availability") as (
mock_get_network_ip_availability):
mock_get_network.return_value = network
mock_get_subnet.side_effect = [subnet1, subnet2, subnet3]
mock_get_network_ip_availability.return_value = ip_avail
response = self.post(self.LBS_PATH, body)
api_lb = response.json.get(self.root_tag)
self._assert_request_matches_response(lb_json, api_lb)
self.assertEqual(subnet2.id, api_lb.get('vip_subnet_id'))
self.assertEqual(network_id, api_lb.get('vip_network_id'))
def test_create_with_vip_network_not_enough_avail_ips(self):
self.conf.config(
group='controller_worker',
loadbalancer_topology=constants.TOPOLOGY_ACTIVE_STANDBY)
network_id = uuidutils.generate_uuid()
subnet1 = network_models.Subnet(id=uuidutils.generate_uuid(),
network_id=network_id,
ip_version=4)
subnet2 = network_models.Subnet(id=uuidutils.generate_uuid(),
network_id=network_id,
ip_version=4)
network = network_models.Network(id=network_id,
subnets=[subnet1.id, subnet2.id])
subnet_ip_availability = [{'subnet_id': subnet1.id, 'used_ips': 254,
'total_ips': 254}, {'subnet_id': subnet2.id,
'used_ips': 254, 'total_ips': 254}]
ip_avail = network_models.Network_IP_Availability(
network_id=network.id,
subnet_ip_availability=subnet_ip_availability)
lb_json = {'vip_network_id': network.id,
'project_id': self.project_id}
body = self._build_body(lb_json)
with mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network") as mock_get_network, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_subnet") as mock_get_subnet, mock.patch(
"octavia.network.drivers.noop_driver.driver.NoopManager"
".get_network_ip_availability") as (
mock_get_network_ip_availability):
mock_get_network.return_value = network
mock_get_subnet.side_effect = [subnet1, subnet2]
mock_get_network_ip_availability.return_value = ip_avail
response = self.post(self.LBS_PATH, body, status=400)
err_msg = ('Validation failure: Subnet(s) in the supplied network do '
'not contain enough available IPs.')
self.assertEqual(err_msg, response.json.get('faultstring'))
def test_create_with_vip_network_and_address(self): def test_create_with_vip_network_and_address(self):
ip_address = '198.51.100.10' ip_address = '198.51.100.10'
network_id = uuidutils.generate_uuid() network_id = uuidutils.generate_uuid()

View File

@ -479,3 +479,18 @@ class TestBaseNeutronNetworkDriver(base.TestCase):
self.driver.apply_qos_on_port, self.driver.apply_qos_on_port,
t_constants.MOCK_PORT_ID, t_constants.MOCK_PORT_ID,
t_constants.MOCK_NEUTRON_QOS_POLICY_ID) t_constants.MOCK_NEUTRON_QOS_POLICY_ID)
def test_get_network_ip_availability(self):
show_network_ip_availability = (
self.driver.neutron_client.show_network_ip_availability)
show_network_ip_availability.return_value = (
{'network_ip_availability': {
'network_id': t_constants.MOCK_NETWORK_ID,
'subnet_ip_availability': t_constants.MOCK_SUBNET_IP_AVAILABILITY
}})
ip_avail = self.driver.get_network_ip_availability(
network_models.Network(t_constants.MOCK_NETWORK_ID))
self.assertIsInstance(ip_avail, network_models.Network_IP_Availability)
self.assertEqual(t_constants.MOCK_NETWORK_ID, ip_avail.network_id)
self.assertEqual(t_constants.MOCK_SUBNET_IP_AVAILABILITY,
ip_avail.subnet_ip_availability)

View File

@ -115,3 +115,19 @@ class TestNeutronUtils(base.TestCase):
fixed_port_id=t_constants.MOCK_PORT_ID2 fixed_port_id=t_constants.MOCK_PORT_ID2
) )
self._compare_ignore_value_none(model_obj.to_dict(), assert_dict) self._compare_ignore_value_none(model_obj.to_dict(), assert_dict)
def test_convert_network_ip_availability_dict_to_model(self):
model_obj = utils.convert_network_ip_availability_dict_to_model(
t_constants.MOCK_NETWORK_IP_AVAILABILITY)
assert_dict = dict(
network_id=t_constants.MOCK_NETWORK_ID,
tenant_id=t_constants.MOCK_PROJECT_ID,
network_name=t_constants.MOCK_NETWORK_NAME,
total_ips=t_constants.MOCK_NETWORK_TOTAL_IPS,
used_ips=t_constants.MOCK_NETWORK_USED_IPS,
subnet_ip_availability=t_constants.MOCK_SUBNET_IP_AVAILABILITY
)
model_dict = model_obj.to_dict(recurse=True)
model_dict['subnet_ip_availability'] = (
model_obj.subnet_ip_availability)
self._compare_ignore_value_none(model_dict, assert_dict)

View File

@ -0,0 +1,7 @@
---
fixes:
- |
Fixes an issue in the selection of vip-subnet-id on multi-subnet networks
by checking the IP availability of the subnets, ensuring enough IPs are
available for loadbalancer when creating loadbalancer specifying
vip-network-id.