Merge "loadbalancer vip-network-id IP availability check" into stable/queens
This commit is contained in:
commit
96e5962e05
@ -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):
|
||||||
|
@ -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
|
||||||
|
|
||||||
|
@ -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
|
||||||
|
"""
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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
|
||||||
|
@ -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)
|
||||||
|
@ -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})}
|
||||||
|
@ -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()
|
||||||
|
@ -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)
|
||||||
|
@ -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)
|
||||||
|
@ -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.
|
Loading…
Reference in New Issue
Block a user