Skip to content

Commit 468ef87

Browse files
committed
Issues:
Fixes #378, #452, #474, #481, #484, #485 Problem: Given the latest additions and changes to the core classes some of the sub-modules coding needed change, also during the process few issues were discovered with create method allowing mutually exclusive attributes to be sent to the BIGIP. Also a decision was made to create a helper class to handle required attributes within different parts of code. Analysis: Removed the hardcoded payload in failover.py. Kept the legacy method in failover.py for backwards compatibility. Added 2 new helper methods inside PathElement:, - _check_exclusive_parameters to handle those exclusive attributes during create/exec_cmd. - _check_required_parameters - a static method to handle checking for required paremeters Modified CommandExecutionMixin _exec_cmd method to utilize the 2 new helpers from PathElement, modified Resource _create method to do the same. Modified _check_load_parameters method inside PathElement to call _check_required_parameters. In addition to the above I have moved self._meta_data['exclusive_attributes'] declaration into ResourceBase. Added new dictionary entry for: self._meta_data['required_command_parameters'] into PathElement to accomodate splitting this from required_creation_parameters as per discussion with @za. The self._meta_data['required_command_parameters'] is empty by default, this is to allow utilizing classes where 'name' attribute is not present or required, also to allow utilizing commands by the means of utilCmdArgs= argument. e.g. exec_cmd('foocommand', utilCmdArgs='foo bar') Files modified: f5\bigip\resource.py f5\bigip\mixins.py f5\bigip\tm\cm\device_group.py f5\bigip\tm\cm\sync_status.py f5\bigip\tm\cm\traffic_group.py f5\bigip\tm\cm\trust.py f5\bigip\tm\cm\trust_domain.py f5\bigip\tm\ltm\profile.py f5\bigip\tm\sys\failover.py f5-common-python\test\functional\cm\test_sync_status.py f5-common-python\test\functional\tm\sys\test_failover.py New files added: f5\bigip\tm\sys\test\test_failover.py f5\bigip\tm\ltm\test\test_profile_ocsp.py
1 parent def3f1d commit 468ef87

13 files changed

Lines changed: 287 additions & 74 deletions

File tree

f5/bigip/mixins.py

Lines changed: 11 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -39,6 +39,11 @@ class UnsupportedTmosVersion(F5SDKError):
3939
pass
4040

4141

42+
class MissingRequiredCommandParameter(F5SDKError):
43+
"""Various values MUST be provided to execute a command."""
44+
pass
45+
46+
4247
class LazyAttributesRequired(F5SDKError):
4348
"""Raised when a object accesses a lazy attribute that is not listed"""
4449
pass
@@ -250,7 +255,13 @@ def _exec_cmd(self, command, **kwargs):
250255
'''
251256

252257
kwargs['command'] = command
258+
self._check_exclusive_parameters(**kwargs)
253259
requests_params = self._handle_requests_params(kwargs)
260+
rset = self._meta_data['required_command_parameters']
261+
check = self._check_required_parameters(rset, **kwargs)
262+
if check:
263+
error_message = 'Missing required params: {}'.format(check[1])
264+
raise MissingRequiredCommandParameter(error_message)
254265
session = self._meta_data['bigip']._meta_data['icr_session']
255266
response = session.post(
256267
self._meta_data['uri'], json=kwargs, **requests_params)

f5/bigip/resource.py

Lines changed: 58 additions & 21 deletions
Original file line numberDiff line numberDiff line change
@@ -87,11 +87,18 @@
8787
from requests.exceptions import HTTPError
8888

8989

90+
class ExclusiveAttributesPresent(F5SDKError):
91+
"""Raises this when exclusive attributes are present
92+
93+
during resource creation
94+
"""
95+
pass
96+
97+
9098
class MissingUpdateParameter(F5SDKError):
9199
"""Raises this when update requires specific
92100
93101
parameters together
94-
95102
"""
96103
pass
97104

@@ -174,6 +181,12 @@ def __init__(self, container):
174181
# Supported versions for each class will be defined here.
175182
# List can be modified downstream in each sub-class
176183
self._meta_data['supported_versions'] = set(['11.6.0', '12.0.0'])
184+
# Commands you can run on a resource or collection, we define it here
185+
self._meta_data['allowed_commands'] = []
186+
# Define required command parameters
187+
self._meta_data['required_command_parameters'] = set()
188+
# You can't have more than one of the attributes in any of these sets.
189+
self._meta_data['exclusive_attributes'] = []
177190

178191
def _set_meta_data_uri(self):
179192
base_uri = self.__class__.__name__.lower()
@@ -190,18 +203,15 @@ def _set_meta_data_uri(self):
190203
self._meta_data['uri'] = final_uri
191204

192205
def _check_load_parameters(self, **kwargs):
193-
'''Params given to load should at least satisfy required params.
206+
"""Params given to load should at least satisfy required params.
194207
195208
:params: kwargs
196209
:raises: MissingRequiredReadParameter
197-
'''
198-
key_set = set(kwargs.keys())
199-
required_minus_received =\
200-
self._meta_data['required_load_parameters'] - key_set
201-
if required_minus_received != set():
202-
error_message = 'Missing required params: %r'\
203-
% required_minus_received
204-
raise MissingRequiredReadParameter(error_message)
210+
"""
211+
rset = self._meta_data['required_load_parameters']
212+
check = self._check_required_parameters(rset, **kwargs)
213+
if check:
214+
raise MissingRequiredReadParameter(check[1])
205215

206216
def _local_update(self, rdict):
207217
"""Call this with a response dictionary to update instance attrs.
@@ -295,6 +305,36 @@ def _handle_requests_params(self, kwargs):
295305
requests_params.update({'params': params})
296306
return requests_params
297307

308+
def _check_exclusive_parameters(self, **kwargs):
309+
"""Check for mutually exclusive attributes in kwargs.
310+
311+
:raises ExclusiveAttributesPresent
312+
"""
313+
if len(self._meta_data['exclusive_attributes']) > 0:
314+
attr_set = set(kwargs.keys())
315+
ex_set = set(self._meta_data['exclusive_attributes'][0])
316+
common_set = attr_set.intersection(ex_set)
317+
if len(common_set) > 1:
318+
error = 'Mutually exclusive arguments submitted. ' \
319+
'The following arguments cannot be set ' \
320+
'together: "{}".'.format(', '.join(common_set))
321+
raise ExclusiveAttributesPresent(error)
322+
323+
@staticmethod
324+
def _check_required_parameters(rqset, **kwargs):
325+
"""Helper function to do operation on sets
326+
327+
::returns bool and variable
328+
"""
329+
key_set = set(kwargs.keys())
330+
required_minus_received = rqset - key_set
331+
if required_minus_received != set():
332+
error_message = 'Missing required params: {}'\
333+
.format(required_minus_received)
334+
return True, error_message
335+
else:
336+
return False
337+
298338
@property
299339
def raw(self):
300340
"""Display the attributes that the current object has and their values.
@@ -362,8 +402,6 @@ def __init__(self, container):
362402
:param container: instance is an attribute of a ResourceBase container
363403
"""
364404
super(ResourceBase, self).__init__(container)
365-
# Commands you can run on a resource or collection, we define it here
366-
self._meta_data['allowed_commands'] = []
367405

368406
def _update(self, **kwargs):
369407
"""wrapped with update, override that in a subclass to customize"""
@@ -603,8 +641,6 @@ def __init__(self, container):
603641
self._meta_data['required_creation_parameters'] = set(('name',))
604642
# Refresh fails without these.
605643
self._meta_data['required_load_parameters'] = set(('name',))
606-
# You can't have more than one of the attrs in any of these sets.
607-
self._meta_data['exclusive_attributes'] = []
608644
# You can't set these attributes, only 'read' them.
609645
self._meta_data['read_only_attributes'] = []
610646

@@ -665,14 +701,15 @@ def _create(self, **kwargs):
665701
"resource, the _meta_data['uri'] is %s and it should"\
666702
" not be changed." % (self._meta_data['uri'])
667703
raise URICreationCollision(error)
704+
self._check_exclusive_parameters(**kwargs)
668705
requests_params = self._handle_requests_params(kwargs)
669-
key_set = set(kwargs.keys())
670-
required_minus_received =\
671-
self._meta_data['required_creation_parameters'] - key_set
672-
if required_minus_received != set():
673-
error_message = 'Missing required params: %r'\
674-
% required_minus_received
675-
raise MissingRequiredCreationParameter(error_message)
706+
707+
# Using helper method to check for required keys.
708+
# Defining convenience variables to make code readable
709+
rset = self._meta_data['required_creation_parameters']
710+
check = self._check_required_parameters(rset, **kwargs)
711+
if check:
712+
raise MissingRequiredCreationParameter(check[1])
676713

677714
# Make convenience variable with short names for this method.
678715
_create_uri = self._meta_data['container']._meta_data['uri']

f5/bigip/tm/cm/device_group.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ class Device_Groups(Collection):
3535
"""BIG-IP® cluster device-groups collection."""
3636
def __init__(self, cm):
3737
super(Device_Groups, self).__init__(cm)
38-
endpoint = 'device-group'
39-
self._meta_data['uri'] =\
40-
self._meta_data['container']._meta_data['uri'] + endpoint + '/'
4138
self._meta_data['allowed_lazy_attributes'] = [Device_Group]
4239
self._meta_data['attribute_registry'] =\
4340
{'tm:cm:device:device-groupstate': Device_Group}

f5/bigip/tm/cm/sync_status.py

Lines changed: 2 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -28,17 +28,14 @@
2828
"""
2929

3030
from f5.bigip.mixins import UnnamedResourceMixin
31-
from f5.bigip.resource import Resource
31+
from f5.bigip.resource import ResourceBase
3232

3333

34-
class Sync_Status(Resource, UnnamedResourceMixin):
34+
class Sync_Status(ResourceBase, UnnamedResourceMixin):
3535
'''A Resource concrete subclass.'''
3636
def __init__(self, cm):
3737
'''Autogenerated constructor.'''
3838
super(Sync_Status, self).__init__(cm)
39-
base_uri = type(self).__name__.replace('_', '-').lower()
40-
self._meta_data['uri'] =\
41-
self._meta_data['container']._meta_data['uri'] + base_uri
4239
self._meta_data['template_generated'] = True
4340
self._meta_data['required_json_kind'] =\
4441
u"tm:cm:sync-status:sync-statusstats"

f5/bigip/tm/cm/traffic_group.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -35,9 +35,6 @@ class Traffic_Groups(Collection):
3535
"""BIG-IP® cluster traffic-group collection"""
3636
def __init__(self, cm):
3737
super(Traffic_Groups, self).__init__(cm)
38-
endpoint = 'traffic-group'
39-
self._meta_data['uri'] =\
40-
self._meta_data['container']._meta_data['uri'] + endpoint + '/'
4138
self._meta_data['allowed_lazy_attributes'] = [Traffic_Group]
4239
self._meta_data['attribute_registry'] =\
4340
{'tm:cm:traffic-group:traffic-groupstate': Traffic_Group}

f5/bigip/tm/cm/trust.py

Lines changed: 9 additions & 8 deletions
Original file line numberDiff line numberDiff line change
@@ -18,11 +18,11 @@
1818
from f5.bigip.mixins import CommandExecutionMixin
1919
from f5.bigip.mixins import ExclusiveAttributesMixin
2020
from f5.bigip.mixins import UnnamedResourceMixin
21-
from f5.bigip.resource import Resource
21+
from f5.bigip.resource import PathElement
2222

2323

24-
class Add_To_Trust(UnnamedResourceMixin, ExclusiveAttributesMixin,
25-
CommandExecutionMixin, Resource):
24+
class Add_To_Trust(PathElement, UnnamedResourceMixin, ExclusiveAttributesMixin,
25+
CommandExecutionMixin):
2626
"""BIG-IP® Add-To-Trust resource
2727
2828
Use this object to set or overwrite device trust
@@ -33,15 +33,16 @@ def __init__(self, cm):
3333
super(Add_To_Trust, self).__init__(cm)
3434
self._meta_data['exclusive_attributes'].append(
3535
('caDevice', 'nonCaDevice'))
36-
self._meta_data['required_creation_parameters'].update(
37-
('device', 'deviceName', 'username', 'password'))
36+
self._meta_data['required_command_parameters'].update(
37+
('name', 'device', 'deviceName', 'username', 'password'))
3838
self._meta_data['required_json_kind'] = \
3939
'tm:cm:add-to-trust:runstate'
4040
self._meta_data['allowed_commands'].append('run')
4141
self._meta_data['supported_versions'].discard('11.6.0')
4242

4343

44-
class Remove_From_Trust(UnnamedResourceMixin, CommandExecutionMixin, Resource):
44+
class Remove_From_Trust(PathElement, UnnamedResourceMixin,
45+
CommandExecutionMixin):
4546
"""BIG-IP®« Remove-From-Trust resource
4647
4748
Use this object to remove device trust
@@ -55,8 +56,8 @@ class Remove_From_Trust(UnnamedResourceMixin, CommandExecutionMixin, Resource):
5556

5657
def __init__(self, cm):
5758
super(Remove_From_Trust, self).__init__(cm)
58-
self._meta_data['required_creation_parameters'].update(
59-
('deviceName',))
59+
self._meta_data['required_command_parameters'].update(
60+
('deviceName', 'name'))
6061
self._meta_data['required_json_kind'] = \
6162
'tm:cm:remove-from-trust:runstate'
6263
self._meta_data['allowed_commands'].append('run')

f5/bigip/tm/cm/trust_domain.py

Lines changed: 0 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -36,9 +36,6 @@ class Trust_Domains(Collection):
3636

3737
def __init__(self, cm):
3838
super(Trust_Domains, self).__init__(cm)
39-
endpoint = 'trust-domain'
40-
self._meta_data['uri'] =\
41-
self._meta_data['container']._meta_data['uri'] + endpoint + '/'
4239
self._meta_data['allowed_lazy_attributes'] = [Trust_Domain]
4340
self._meta_data['attribute_registry'] = \
4441
{'tm:cm:trust-domain:trust-domainstate': Trust_Domain}

f5/bigip/tm/ltm/profile.py

Lines changed: 21 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828

2929

3030
from f5.bigip.resource import Collection
31+
from f5.bigip.resource import MissingUpdateParameter
3132
from f5.bigip.resource import OrganizingCollection
3233
from f5.bigip.resource import Resource
3334
from f5.bigip.resource import UnsupportedOperation
@@ -632,6 +633,26 @@ def create(self, **kwargs):
632633

633634
return self
634635

636+
def update(self, **kwargs):
637+
"""When setting useProxyServer to enable we need to supply
638+
639+
proxyServerPool value as well
640+
"""
641+
if 'useProxyServer' in kwargs and \
642+
kwargs['useProxyServer'] == 'enabled':
643+
if 'proxyServerPool' not in kwargs:
644+
error = 'Missing proxyServerPool parameter value.'
645+
raise MissingUpdateParameter(error)
646+
if hasattr(self, 'useProxyServer'):
647+
if getattr(self, 'useProxyServer') == 'enabled' and \
648+
not hasattr(self, 'proxyServerPool'):
649+
error = 'Missing proxyServerPool parameter value.'
650+
raise MissingUpdateParameter(error)
651+
652+
self._update(**kwargs)
653+
654+
return self
655+
635656

636657
class One_Connects(Collection):
637658
"""BIG-IP® One_Connect profile collection."""
Lines changed: 37 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,37 @@
1+
# Copyright 2015 F5 Networks Inc.
2+
#
3+
# Licensed under the Apache License, Version 2.0 (the "License");
4+
# you may not use this file except in compliance with the License.
5+
# You may obtain a copy of the License at
6+
#
7+
# http://www.apache.org/licenses/LICENSE-2.0
8+
#
9+
# Unless required by applicable law or agreed to in writing, software
10+
# distributed under the License is distributed on an "AS IS" BASIS,
11+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
# See the License for the specific language governing permissions and
13+
# limitations under the License.
14+
#
15+
16+
import mock
17+
import pytest
18+
19+
from f5.bigip.resource import MissingUpdateParameter
20+
from f5.bigip.tm.ltm.profile import Ocsp_Stapling_Params
21+
22+
23+
@pytest.fixture
24+
def FakeProfile():
25+
fake_profiles = mock.MagicMock()
26+
fake_profile = Ocsp_Stapling_Params(fake_profiles)
27+
return fake_profile
28+
29+
30+
class Test_OCSP_update(object):
31+
32+
def test_without_proxyserverpool(self):
33+
profile = FakeProfile()
34+
profile.useProxyServer = 'enabled'
35+
with pytest.raises(MissingUpdateParameter) as err:
36+
profile.update()
37+
assert 'Missing proxyServerPool parameter value' in err.value.message

0 commit comments

Comments
 (0)