Merge "Allow operators to tune VIP creation parameters"
This commit is contained in:
commit
a0b46c676a
@ -120,6 +120,13 @@
|
||||
# retry_interval = 1
|
||||
# The maximum time to wait, in seconds, for a port to detach from an amphora
|
||||
# port_detach_timeout = 300
|
||||
# Allow/disallow specific network object types when creating VIPs.
|
||||
# allow_vip_network_id = True
|
||||
# allow_vip_subnet_id = True
|
||||
# allow_vip_port_id = True
|
||||
# List of network_ids that are valid for VIP creation.
|
||||
# If this field empty, no validation is performed.
|
||||
# valid_vip_networks =
|
||||
|
||||
[haproxy_amphora]
|
||||
# base_path = /var/lib/octavia
|
||||
|
@ -110,11 +110,34 @@ class LoadBalancersController(base.BaseController):
|
||||
))
|
||||
|
||||
def _validate_vip_request_object(self, load_balancer):
|
||||
allowed_network_objects = []
|
||||
if CONF.networking.allow_vip_port_id:
|
||||
allowed_network_objects.append('vip_port_id')
|
||||
if CONF.networking.allow_vip_network_id:
|
||||
allowed_network_objects.append('vip_network_id')
|
||||
if CONF.networking.allow_vip_subnet_id:
|
||||
allowed_network_objects.append('vip_subnet_id')
|
||||
|
||||
msg = _("use of %(object)s is disallowed by this deployment's "
|
||||
"configuration.")
|
||||
if (load_balancer.vip_port_id and
|
||||
not CONF.networking.allow_vip_port_id):
|
||||
raise exceptions.ValidationException(
|
||||
detail=msg % {'object': 'vip_port_id'})
|
||||
if (load_balancer.vip_network_id and
|
||||
not CONF.networking.allow_vip_network_id):
|
||||
raise exceptions.ValidationException(
|
||||
detail=msg % {'object': 'vip_network_id'})
|
||||
if (load_balancer.vip_subnet_id and
|
||||
not CONF.networking.allow_vip_subnet_id):
|
||||
raise exceptions.ValidationException(
|
||||
detail=msg % {'object': 'vip_subnet_id'})
|
||||
|
||||
if not (load_balancer.vip_port_id or
|
||||
load_balancer.vip_network_id or
|
||||
load_balancer.vip_subnet_id):
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
"VIP must contain one of: port_id, network_id, subnet_id."))
|
||||
raise exceptions.VIPValidationException(
|
||||
objects=', '.join(allowed_network_objects))
|
||||
|
||||
# Validate the port id
|
||||
if load_balancer.vip_port_id:
|
||||
@ -129,6 +152,8 @@ class LoadBalancersController(base.BaseController):
|
||||
subnet_id=load_balancer.vip_subnet_id)
|
||||
load_balancer.vip_network_id = subnet.network_id
|
||||
|
||||
validate.network_allowed_by_config(load_balancer.vip_network_id)
|
||||
|
||||
@wsme_pecan.wsexpose(lb_types.LoadBalancerFullRootResponse,
|
||||
body=lb_types.LoadBalancerRootPOST, status_code=201)
|
||||
def post(self, load_balancer):
|
||||
|
@ -91,7 +91,17 @@ networking_opts = [
|
||||
'networking service.')),
|
||||
cfg.IntOpt('port_detach_timeout', default=300,
|
||||
help=_('Seconds to wait for a port to detach from an '
|
||||
'amphora.'))
|
||||
'amphora.')),
|
||||
cfg.BoolOpt('allow_vip_network_id', default=True,
|
||||
help=_('Can users supply a network_id for their VIP?')),
|
||||
cfg.BoolOpt('allow_vip_subnet_id', default=True,
|
||||
help=_('Can users supply a subnet_id for their VIP?')),
|
||||
cfg.BoolOpt('allow_vip_port_id', default=True,
|
||||
help=_('Can users supply a port_id for their VIP?')),
|
||||
cfg.ListOpt('valid_vip_networks',
|
||||
help=_('List of network_ids that are valid for VIP '
|
||||
'creation. If this field is empty, no validation '
|
||||
'is performed.')),
|
||||
]
|
||||
|
||||
healthmanager_opts = [
|
||||
|
@ -286,6 +286,11 @@ class ValidationException(APIException):
|
||||
code = 400
|
||||
|
||||
|
||||
class VIPValidationException(APIException):
|
||||
msg = _('Validation failure: VIP must contain one of: %(objects)s.')
|
||||
code = 400
|
||||
|
||||
|
||||
class InvalidSortKey(APIException):
|
||||
msg = _("Supplied sort key '%(key)s' is not valid.")
|
||||
code = 400
|
||||
|
@ -21,6 +21,7 @@ Defined here so these can also be used at deeper levels than the API.
|
||||
|
||||
import re
|
||||
|
||||
from oslo_config import cfg
|
||||
import rfc3986
|
||||
|
||||
from octavia.common import constants
|
||||
@ -28,6 +29,9 @@ from octavia.common import exceptions
|
||||
from octavia.common import utils
|
||||
|
||||
|
||||
CONF = cfg.CONF
|
||||
|
||||
|
||||
def url(url, require_scheme=True):
|
||||
"""Raises an error if the url doesn't look like a URL."""
|
||||
try:
|
||||
@ -238,3 +242,12 @@ def network_exists_optionally_contains_subnet(network_id, subnet_id=None):
|
||||
raise exceptions.InvalidSubresource(resource='Subnet',
|
||||
id=subnet_id)
|
||||
return network
|
||||
|
||||
|
||||
def network_allowed_by_config(network_id):
|
||||
if CONF.networking.valid_vip_networks:
|
||||
valid_networks = map(str.lower, CONF.networking.valid_vip_networks)
|
||||
if network_id not in valid_networks:
|
||||
raise exceptions.ValidationException(detail=_(
|
||||
'Supplied VIP network_id is not allowed by the configuration '
|
||||
'of this deployment.'))
|
||||
|
@ -76,7 +76,7 @@ class TestLoadBalancer(base.BaseAPITest):
|
||||
body = self._build_body(lb_json)
|
||||
response = self.post(self.LBS_PATH, body, status=400)
|
||||
err_msg = ('Validation failure: VIP must contain one of: '
|
||||
'port_id, network_id, subnet_id.')
|
||||
'vip_port_id, vip_network_id, vip_subnet_id.')
|
||||
self.assertEqual(err_msg, response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_empty_vip(self):
|
||||
@ -224,17 +224,100 @@ class TestLoadBalancer(base.BaseAPITest):
|
||||
|
||||
def test_create_with_long_name(self):
|
||||
lb_json = {'name': 'n' * 256,
|
||||
'vip_subnet_id': uuidutils.generate_uuid()}
|
||||
self.post(self.LBS_PATH, lb_json, status=400)
|
||||
'vip_subnet_id': uuidutils.generate_uuid(),
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('Invalid input for field/attribute name',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_long_description(self):
|
||||
lb_json = {'description': 'n' * 256,
|
||||
'vip_subnet_id': uuidutils.generate_uuid()}
|
||||
self.post(self.LBS_PATH, lb_json, status=400)
|
||||
'vip_subnet_id': uuidutils.generate_uuid(),
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('Invalid input for field/attribute description',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_nonuuid_vip_attributes(self):
|
||||
lb_json = {'vip_subnet_id': 'HI'}
|
||||
self.post(self.LBS_PATH, lb_json, status=400)
|
||||
lb_json = {'vip_subnet_id': 'HI',
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('Invalid input for field/attribute vip_subnet_id',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_allowed_network_id(self):
|
||||
network_id = uuidutils.generate_uuid()
|
||||
self.conf.config(group="networking", valid_vip_networks=network_id)
|
||||
subnet = network_models.Subnet(id=uuidutils.generate_uuid(),
|
||||
network_id=network_id,
|
||||
ip_version=4)
|
||||
network = network_models.Network(id=network_id, subnets=[subnet.id])
|
||||
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_get_network.return_value = network
|
||||
mock_get_subnet.return_value = subnet
|
||||
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(subnet.id, api_lb.get('vip_subnet_id'))
|
||||
self.assertEqual(network_id, api_lb.get('vip_network_id'))
|
||||
|
||||
def test_create_with_disallowed_network_id(self):
|
||||
network_id1 = uuidutils.generate_uuid()
|
||||
network_id2 = uuidutils.generate_uuid()
|
||||
self.conf.config(group="networking", valid_vip_networks=network_id1)
|
||||
subnet = network_models.Subnet(id=uuidutils.generate_uuid(),
|
||||
network_id=network_id2,
|
||||
ip_version=4)
|
||||
network = network_models.Network(id=network_id2, subnets=[subnet.id])
|
||||
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_get_network.return_value = network
|
||||
mock_get_subnet.return_value = subnet
|
||||
response = self.post(self.LBS_PATH, body, status=400)
|
||||
self.assertIn("Supplied VIP network_id is not allowed",
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_disallowed_vip_objects(self):
|
||||
self.conf.config(group="networking", allow_vip_network_id=False)
|
||||
self.conf.config(group="networking", allow_vip_subnet_id=False)
|
||||
self.conf.config(group="networking", allow_vip_port_id=False)
|
||||
|
||||
lb_json = {'vip_network_id': uuidutils.generate_uuid(),
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('use of vip_network_id is disallowed',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
lb_json = {'vip_subnet_id': uuidutils.generate_uuid(),
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('use of vip_subnet_id is disallowed',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
lb_json = {'vip_port_id': uuidutils.generate_uuid(),
|
||||
'project_id': self.project_id}
|
||||
response = self.post(self.LBS_PATH, self._build_body(lb_json),
|
||||
status=400)
|
||||
self.assertIn('use of vip_port_id is disallowed',
|
||||
response.json.get('faultstring'))
|
||||
|
||||
def test_create_with_project_id(self):
|
||||
project_id = uuidutils.generate_uuid()
|
||||
|
@ -13,6 +13,8 @@
|
||||
# under the License.
|
||||
|
||||
import mock
|
||||
from oslo_config import cfg
|
||||
from oslo_config import fixture as oslo_fixture
|
||||
from oslo_utils import uuidutils
|
||||
|
||||
from octavia.api.v1.types import load_balancer as lb_types
|
||||
@ -28,6 +30,10 @@ class TestValidations(base.TestCase):
|
||||
# Note that particularly complex validation testing is handled via
|
||||
# functional tests elsewhere (ex. repository tests)
|
||||
|
||||
def setUp(self):
|
||||
super(TestValidations, self).setUp()
|
||||
self.conf = oslo_fixture.Config(cfg.CONF)
|
||||
|
||||
def test_validate_url(self):
|
||||
ret = validate.url('http://example.com')
|
||||
self.assertTrue(ret)
|
||||
@ -329,3 +335,15 @@ class TestValidations(base.TestCase):
|
||||
exceptions.InvalidSubresource,
|
||||
validate.network_exists_optionally_contains_subnet,
|
||||
vip.network_id, vip.subnet_id)
|
||||
|
||||
def test_network_allowed_by_config(self):
|
||||
net_id1 = uuidutils.generate_uuid()
|
||||
net_id2 = uuidutils.generate_uuid()
|
||||
net_id3 = uuidutils.generate_uuid()
|
||||
valid_net_ids = ",".join((net_id1, net_id2))
|
||||
self.conf.config(group="networking", valid_vip_networks=valid_net_ids)
|
||||
validate.network_allowed_by_config(net_id1)
|
||||
validate.network_allowed_by_config(net_id2)
|
||||
self.assertRaises(
|
||||
exceptions.ValidationException,
|
||||
validate.network_allowed_by_config, net_id3)
|
||||
|
Loading…
x
Reference in New Issue
Block a user