Extend loadbalancer_create valid VIP parameters combinations

Octavia client was incoherent with Octavia's api-ref, hence it
supported only a subset of the possible combinations for specifying
VIP parameters in loadbalancer_create.

Validation functions were updated and moved from utils.py to
validate.py and load_balancer.py, accompanied by new unit tests.

Aesthetics: Refined loadabalancer's and l7Policy's creation synopses.

Task: 5825
Task: 5828
Story: 2001279

Change-Id: I1b4da3f32a9b58ed85f2abc462dee4d55b8afd47
This commit is contained in:
Bar RH 2017-11-13 19:59:10 +02:00
parent 6dcd7405bb
commit e3341ea625
7 changed files with 178 additions and 56 deletions

View File

@ -20,6 +20,7 @@ from osc_lib import utils
from octaviaclient.osc.v2 import constants as const
from octaviaclient.osc.v2 import utils as v2_utils
from octaviaclient.osc.v2 import validate
ACTION_CHOICES = ['REDIRECT_TO_URL', 'REDIRECT_TO_POOL', 'REJECT']
@ -45,11 +46,7 @@ class CreateL7Policy(command.ShowOne):
metavar='<description>',
help="Set l7policy description."
)
parser.add_argument(
'--redirect-pool',
metavar='<pool>',
help="Set the pool to redirect requests to (name or ID)."
)
parser.add_argument(
'--action',
metavar="{REDIRECT_TO_URL,REDIRECT_TO_POOL,REJECT}",
@ -57,11 +54,19 @@ class CreateL7Policy(command.ShowOne):
choices=ACTION_CHOICES,
help="Set the action of the policy."
)
parser.add_argument(
redirect_group = parser.add_mutually_exclusive_group()
redirect_group.add_argument(
'--redirect-pool',
metavar='<pool>',
help="Set the pool to redirect requests to (name or ID)."
)
redirect_group.add_argument(
'--redirect-url',
metavar='<url>',
help="Set the URL to redirect requests to."
)
parser.add_argument(
'--position',
metavar='<position>',
@ -88,7 +93,7 @@ class CreateL7Policy(command.ShowOne):
rows = const.L7POLICY_ROWS
attrs = v2_utils.get_l7policy_attrs(self.app.client_manager,
parsed_args)
v2_utils.check_l7policy_attrs(attrs)
validate.check_l7policy_attrs(attrs)
body = {"l7policy": attrs}
data = self.app.client_manager.load_balancer.l7policy_create(
@ -195,22 +200,25 @@ class SetL7Policy(command.Command):
metavar='<description>',
help="Set l7policy description."
)
parser.add_argument(
'--redirect-pool',
metavar='<pool>',
help="Set the pool to redirect requests to (name or ID)."
)
parser.add_argument(
'--action',
metavar="{REDIRECT_TO_URL,REDIRECT_TO_POOL,REJECT}",
choices=ACTION_CHOICES,
help="Set the action of the policy."
)
parser.add_argument(
redirect_group = parser.add_mutually_exclusive_group()
redirect_group.add_argument(
'--redirect-pool',
metavar='<pool>',
help="Set the pool to redirect requests to (name or ID)."
)
redirect_group.add_argument(
'--redirect-url',
metavar='<url>',
help="Set the URL to redirect requests to."
)
parser.add_argument(
'--position',
metavar='<position>',
@ -237,6 +245,7 @@ class SetL7Policy(command.Command):
attrs = v2_utils.get_l7policy_attrs(self.app.client_manager,
parsed_args)
validate.check_l7policy_attrs(attrs)
l7policy_id = attrs.pop('l7policy_id')
body = {'l7policy': attrs}

View File

@ -20,6 +20,7 @@ from osc_lib import utils
from octaviaclient.osc.v2 import constants as const
from octaviaclient.osc.v2 import utils as v2_utils
from octaviaclient.osc.v2 import validate
COMPARE_TYPES = ['REGEX', 'EQUAL_TO', 'CONTAINS', 'ENDS_WITH', 'STARTS_WITH']
TYPES = ['FILE_TYPE', 'PATH', 'COOKIE', 'HOST_NAME', 'HEADER']
@ -86,7 +87,7 @@ class CreateL7Rule(command.ShowOne):
rows = const.L7RULE_ROWS
attrs = v2_utils.get_l7rule_attrs(self.app.client_manager,
parsed_args)
v2_utils.check_l7rule_attrs(attrs)
validate.check_l7rule_attrs(attrs)
l7policy_id = attrs.pop('l7policy_id')
body = {"rule": attrs}
@ -248,7 +249,7 @@ class SetL7Rule(command.Command):
def take_action(self, parsed_args):
attrs = v2_utils.get_l7rule_attrs(self.app.client_manager, parsed_args)
v2_utils.check_l7rule_attrs(attrs)
validate.check_l7rule_attrs(attrs)
l7policy_id = attrs.pop('l7policy_id')
l7rule_id = attrs.pop('l7rule_id')

View File

@ -15,6 +15,7 @@
from cliff import lister
from osc_lib.command import command
from osc_lib import exceptions
from osc_lib import utils
from octaviaclient.osc.v2 import constants as const
@ -24,6 +25,18 @@ from octaviaclient.osc.v2 import utils as v2_utils
class CreateLoadBalancer(command.ShowOne):
"""Create a load balancer"""
@staticmethod
def _check_attrs(attrs):
verify_args = ['vip_subnet_id', 'vip_network_id', 'vip_port_id']
if not any(i in attrs for i in verify_args):
msg = ("Missing required argument: Requires one of "
"--vip-subnet-id, --vip-network-id or --vip-port-id")
raise exceptions.CommandError(msg)
if all(i in attrs for i in ('vip_network_id', 'vip_port_id')):
msg = ("Argument error: --vip-port-id can not be used with "
"--vip-network-id")
raise exceptions.CommandError(msg)
def get_parser(self, prog_name):
parser = super(CreateLoadBalancer, self).get_parser(prog_name)
@ -42,21 +55,27 @@ class CreateLoadBalancer(command.ShowOne):
metavar='<vip_address>',
help="Set the VIP IP Address."
)
parser.add_argument(
vip_group = parser.add_argument_group(
"VIP Network",
description="At least one of the following arguments is required."
)
vip_group.add_argument(
'--vip-port-id',
metavar='<vip_port_id>',
help="Set Port for the load balancer (name or ID)."
)
parser.add_argument(
vip_group.add_argument(
'--vip-subnet-id',
metavar='<vip_subnet_id>',
help="Set subnet for the load balancer (name or ID)."
)
parser.add_argument(
vip_group.add_argument(
'--vip-network-id',
metavar='<vip_network_id>',
help="Set network for the load balancer (name or ID)."
)
parser.add_argument(
'--project',
metavar='<project>',
@ -82,7 +101,7 @@ class CreateLoadBalancer(command.ShowOne):
rows = const.LOAD_BALANCER_ROWS
attrs = v2_utils.get_loadbalancer_attrs(self.app.client_manager,
parsed_args)
v2_utils.check_loadbalancer_attrs(attrs)
self._check_attrs(attrs)
body = {'loadbalancer': attrs}
data = self.app.client_manager.load_balancer.load_balancer_create(

View File

@ -116,19 +116,6 @@ def get_loadbalancer_attrs(client_manager, parsed_args):
return attrs
def check_loadbalancer_attrs(attrs):
verify_args = ['vip_subnet_id', 'vip_network_id', 'vip_port_id']
if not any(i in attrs.keys() for i in verify_args):
msg = ("Missing required argument: Requires one of --vip-subnet-id, "
"--vip-network-id or --vip-port-id")
raise exceptions.CommandError(msg)
elif 'vip_port_id' in attrs:
if any(i in attrs.keys() for i in ['vip_subnet_id', 'vip_address']):
msg = ("Argument error: --port-id can not be used with "
"--vip-network-id or --vip-subnet-id")
raise exceptions.CommandError(msg)
def get_listener_attrs(client_manager, parsed_args):
attr_map = {
'name': ('name', str),
@ -282,18 +269,6 @@ def get_l7policy_attrs(client_manager, parsed_args):
return attrs
def check_l7policy_attrs(attrs):
msg = None
if attrs['action'] == 'REDIRECT_TO_POOL':
if 'redirect_pool_id' not in attrs:
msg = 'Missing argument: --redirect-pool'
elif attrs['action'] == 'REDIRECT_TO_URL':
if 'redirect_url' not in attrs:
msg = 'Missing argument: --redirect-url'
if msg is not None:
raise exceptions.CommandError(msg)
def get_l7rule_attrs(client_manager, parsed_args):
attr_map = {
'action': ('action', str),
@ -328,18 +303,6 @@ def get_l7rule_attrs(client_manager, parsed_args):
return attrs
def check_l7rule_attrs(attrs):
msg = None
if 'type' in attrs.keys() and attrs['type'] == 'COOKIE':
if 'key' not in attrs:
msg = 'Missing argument: --type COOKIE requires --key <key>'
elif 'type' in attrs.keys() and attrs['type'] == 'HEADER':
if 'key' not in attrs:
msg = 'Missing argument: --type HEADER requires --key <key>'
if msg is not None:
raise exceptions.CommandError(msg)
def get_health_monitor_attrs(client_manager, parsed_args):
attr_map = {
'health_monitor': (

View File

@ -0,0 +1,38 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from osc_lib import exceptions
def check_l7policy_attrs(attrs):
msg = None
if 'action' not in attrs:
return
if attrs['action'] == 'REDIRECT_TO_POOL':
if 'redirect_pool_id' not in attrs:
msg = 'Missing argument: --redirect-pool'
elif attrs['action'] == 'REDIRECT_TO_URL':
if 'redirect_url' not in attrs:
msg = 'Missing argument: --redirect-url'
if msg is not None:
raise exceptions.CommandError(msg)
def check_l7rule_attrs(attrs):
if 'type' in attrs:
if attrs['type'] in ('COOKIE', 'HEADER'):
if 'key' not in attrs:
msg = (
"Missing argument: --type {type_name} requires "
"--key <key>".format(type_name=attrs['type']))
raise exceptions.CommandError(msg)

View File

@ -12,6 +12,7 @@
#
import copy
import itertools
import mock
from osc_lib import exceptions
@ -159,6 +160,34 @@ class TestLoadBalancerCreate(TestLoadBalancer):
self.api_mock.load_balancer_create.assert_called_with(
json={'loadbalancer': self.lb_info['loadbalancers'][0]})
@mock.patch('octaviaclient.osc.v2.utils.get_loadbalancer_attrs')
def test_load_balancer_create_missing_args(self, mock_client):
attrs_list = self.lb_info['loadbalancers'][0]
args = ("vip_subnet_id", "vip_network_id", "vip_port_id")
for a in args:
# init missing keys
attrs_list[a] = ''
# verify all valid combinations of args
for n in range(len(args)+1):
for comb in itertools.combinations(args, n):
# subtract comb's keys from attrs_list
filtered_attrs = {k: v for k, v in attrs_list.items() if (
k not in comb)}
mock_client.return_value = filtered_attrs
if not any(k in filtered_attrs for k in args) or all(
k in filtered_attrs for k in ("vip_network_id",
"vip_port_id")
):
self.assertRaises(
exceptions.CommandError,
self.cmd.take_action,
filtered_attrs)
else:
try:
self.cmd.take_action(filtered_attrs)
except exceptions.CommandError as e:
self.fail("%s raised unexpectedly" % e)
class TestLoadBalancerShow(TestLoadBalancer):

View File

@ -0,0 +1,63 @@
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
#
from octaviaclient.osc.v2 import validate
from osc_lib import exceptions
from osc_lib.tests import utils
class TestValidations(utils.TestCommand):
def setUp(self):
super(TestValidations, self).setUp()
def test_check_l7policy_attrs(self):
attrs_dict = {
"action": "redirect_to_pool".upper(),
"redirect_pool_id": "id",
}
try:
validate.check_l7policy_attrs(attrs_dict)
except exceptions.CommandError as e:
self.fail("%s raised unexpectedly" % e)
attrs_dict.pop("redirect_pool_id")
self.assertRaises(
exceptions.CommandError,
validate.check_l7policy_attrs, attrs_dict)
attrs_dict = {
"action": "redirect_to_url".upper(),
"redirect_url": "url",
}
try:
validate.check_l7policy_attrs(attrs_dict)
except exceptions.CommandError as e:
self.fail("%s raised unexpectedly" % e)
attrs_dict.pop("redirect_url")
self.assertRaises(
exceptions.CommandError,
validate.check_l7policy_attrs, attrs_dict)
def test_check_l7rule_attrs(self):
for i in ("cookie", "header"):
attrs_dict = {
"type": i.upper(),
"key": "key",
}
try:
validate.check_l7rule_attrs(attrs_dict)
except exceptions.CommandError as e:
self.fail("%s raised unexpectedly" % e)
attrs_dict.pop("key")
self.assertRaises(
exceptions.CommandError,
validate.check_l7rule_attrs, attrs_dict)