Skip to content

Commit 81e35af

Browse files
author
pjbreaux
authored
Merge pull request #514 from zancas/bugfix.alias_issue_411_rebased
Fixing issue 411
2 parents 4d87753 + 8267a43 commit 81e35af

52 files changed

Lines changed: 562 additions & 654 deletions

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

conftest.py

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -17,6 +17,9 @@
1717
from f5.bigip import ManagementRoot
1818
import mock
1919
import pytest
20+
import requests
21+
22+
requests.packages.urllib3.disable_warnings()
2023

2124
from icontrol.session import iControlRESTSession
2225

f5/bigip/mixins.py

Lines changed: 3 additions & 55 deletions
Original file line numberDiff line numberDiff line change
@@ -18,20 +18,17 @@
1818

1919

2020
from distutils.version import LooseVersion
21-
from f5.sdk_exception import F5SDKError
2221
import logging
2322

23+
from f5.sdk_exception import F5SDKError
24+
from f5.sdk_exception import UnsupportedMethod
25+
2426

2527
class InvalidCommand(F5SDKError):
2628
"""Raise this if command argument supplied is invalid."""
2729
pass
2830

2931

30-
class UnsupportedMethod(F5SDKError):
31-
"""Raise this if a method supplied is unsupported."""
32-
pass
33-
34-
3532
class UnsupportedTmosVersion(F5SDKError):
3633
"""Raise the error if a class of an API is instantiated,
3734
@@ -147,55 +144,6 @@ def __setattr__(self, key, value):
147144
super(ExclusiveAttributesMixin, self).__setattr__(key, value)
148145

149146

150-
class UnnamedResourceMixin(object):
151-
'''This makes a resource object work if there is no name.
152-
153-
These objects do not support create or delete and are often found
154-
as Resources that are under an organizing collection. For example
155-
the `mgmt/tm/sys/global-settings` is one of these and has a kind of
156-
`tm:sys:global-settings:global-settingsstate` and the URI does not
157-
match the kind.
158-
'''
159-
160-
def create(self, **kwargs):
161-
'''Create is not supported for unnamed resources
162-
163-
:raises: UnsupportedOperation
164-
'''
165-
raise UnsupportedMethod(
166-
"%s does not support the create method" % self.__class__.__name__
167-
)
168-
169-
def delete(self, **kwargs):
170-
'''Delete is not supported for unnamed resources
171-
172-
:raises: UnsupportedOperation
173-
'''
174-
raise UnsupportedMethod(
175-
"%s does not support the delete method" % self.__class__.__name__
176-
)
177-
178-
def load(self, **kwargs):
179-
return self._load(**kwargs)
180-
181-
def _load(self, **kwargs):
182-
'''Override _load because Unnamed Resources use their uri directly.
183-
184-
The Unnamed resources don't have URIs that match their kinds so
185-
we need to use their URI directly instead of the container's URI
186-
with name/partitions.
187-
'''
188-
self._check_load_parameters(**kwargs)
189-
kwargs['uri_as_parts'] = True
190-
read_session = self._meta_data['bigip']._meta_data['icr_session']
191-
base_uri = self._meta_data['uri']
192-
response = read_session.get(base_uri, **kwargs)
193-
self._local_update(response.json())
194-
new_instance = self.__class__(self._meta_data['container'])
195-
new_instance._local_update(response.json())
196-
return new_instance
197-
198-
199147
class CommandExecutionMixin(object):
200148
"""This adds command execution option on the objects.
201149

f5/bigip/resource.py

Lines changed: 67 additions & 23 deletions
Original file line numberDiff line numberDiff line change
@@ -84,6 +84,7 @@
8484
from f5.bigip.mixins import LazyAttributeMixin
8585
from f5.bigip.mixins import ToDictMixin
8686
from f5.sdk_exception import F5SDKError
87+
from f5.sdk_exception import UnsupportedMethod
8788
from requests.exceptions import HTTPError
8889

8990

@@ -389,7 +390,6 @@ class ResourceBase(PathElement, ToDictMixin):
389390
This can be shortened to just the last line:
390391
391392
.. code-block:: python
392-
393393
nat1 = bigip.ltm.nats.nat.create('Foo', 'Bar', '0.1.2.3', '1.2.3.4')
394394
395395
Critically this enforces a convention relating device published uris to
@@ -531,6 +531,31 @@ def delete(self, **kwargs):
531531
error_message = "Only Resources support 'delete'."
532532
raise InvalidResource(error_message)
533533

534+
def _stamp_out_core(self):
535+
container = self._meta_data['container']
536+
core = self.__class__.__new__(self.__class__)
537+
core.__init__(container)
538+
return core
539+
540+
def _produce_instance(self, response):
541+
'''Generate a new self, which is an instance of the self.'''
542+
new_instance = self._stamp_out_core()
543+
# Post-process the response
544+
new_instance._local_update(response.json())
545+
546+
if new_instance.kind != new_instance._meta_data['required_json_kind'] \
547+
and new_instance.kind != "tm:transaction:commandsstate":
548+
error_message = "For instances of type '%r' the corresponding"\
549+
" kind must be '%r' but creation returned JSON with kind: %r"\
550+
% (new_instance.__class__.__name__,
551+
new_instance._meta_data['required_json_kind'],
552+
new_instance.kind)
553+
raise KindTypeMismatch(error_message)
554+
555+
# Update the object to have the correct functional uri.
556+
new_instance._activate_URI(new_instance.selfLink)
557+
return new_instance
558+
534559

535560
class OrganizingCollection(ResourceBase):
536561
"""Base class for objects that collect resources under them.
@@ -737,21 +762,8 @@ def _create(self, **kwargs):
737762
# Invoke the REST operation on the device.
738763
response = session.post(_create_uri, json=kwargs, **requests_params)
739764

740-
# Post-process the response
741-
self._local_update(response.json())
742-
743-
if self.kind != self._meta_data['required_json_kind'] \
744-
and self.kind != "tm:transaction:commandsstate":
745-
error_message = "For instances of type '%r' the corresponding"\
746-
" kind must be '%r' but creation returned JSON with kind: %r"\
747-
% (self.__class__.__name__,
748-
self._meta_data['required_json_kind'],
749-
self.kind)
750-
raise KindTypeMismatch(error_message)
751-
752-
# Update the object to have the correct functional uri.
753-
self._activate_URI(self.selfLink)
754-
return self
765+
# Make new instance of self
766+
return self._produce_instance(response)
755767

756768
def create(self, **kwargs):
757769
"""Create the resource on the BIG-IP®.
@@ -781,8 +793,7 @@ def create(self, **kwargs):
781793
configuration and state on the BIG-IP®.
782794
783795
"""
784-
self._create(**kwargs)
785-
return self
796+
return self._create(**kwargs)
786797

787798
def _load(self, **kwargs):
788799
"""wrapped with load, override that in a subclass to customize"""
@@ -798,9 +809,8 @@ def _load(self, **kwargs):
798809
base_uri = self._meta_data['container']._meta_data['uri']
799810
kwargs.update(requests_params)
800811
response = refresh_session.get(base_uri, **kwargs)
801-
self._local_update(response.json())
802-
self._activate_URI(self.selfLink)
803-
return self
812+
# Make new instance of self
813+
return self._produce_instance(response)
804814

805815
def load(self, **kwargs):
806816
"""Load an already configured service into this instance.
@@ -819,12 +829,12 @@ def load(self, **kwargs):
819829
be handled according to that API. THIS IS HOW TO PASS QUERY-ARGS!
820830
:returns: a Resource Instance (with a populated _meta_data['uri'])
821831
"""
822-
self._load(**kwargs)
823-
return self
832+
return self._load(**kwargs)
824833

825834
def _delete(self, **kwargs):
826835
"""wrapped with delete, override that in a subclass to customize """
827836
requests_params = self._handle_requests_params(kwargs)
837+
828838
delete_uri = self._meta_data['uri']
829839
session = self._meta_data['bigip']._meta_data['icr_session']
830840

@@ -887,3 +897,37 @@ def exists(self, **kwargs):
887897
else:
888898
raise
889899
return True
900+
901+
902+
class UnnamedResource(ResourceBase):
903+
'''This makes a resource object work if there is no name.
904+
905+
These objects do not support create or delete and are often found
906+
as Resources that are under an organizing collection. For example
907+
the `mgmt/tm/sys/global-settings` is one of these and has a kind of
908+
`tm:sys:global-settings:global-settingsstate` and the URI does not
909+
match the kind.
910+
'''
911+
912+
def create(self, **kwargs):
913+
'''Create is not supported for unnamed resources
914+
915+
:raises: UnsupportedOperation
916+
'''
917+
raise UnsupportedMethod(
918+
"%s does not support the create method" % self.__class__.__name__
919+
)
920+
921+
def delete(self, **kwargs):
922+
'''Delete is not supported for unnamed resources
923+
924+
:raises: UnsupportedOperation
925+
'''
926+
raise UnsupportedMethod(
927+
"%s does not support the delete method" % self.__class__.__name__
928+
)
929+
930+
def load(self, **kwargs):
931+
newinst = self._stamp_out_core()
932+
newinst._refresh(**kwargs)
933+
return newinst

f5/bigip/test/test_mixins.py

Lines changed: 0 additions & 13 deletions
Original file line numberDiff line numberDiff line change
@@ -17,7 +17,6 @@
1717

1818
from f5.bigip.mixins import CommandExecutionMixin
1919
from f5.bigip.mixins import ToDictMixin
20-
from f5.bigip.mixins import UnnamedResourceMixin
2120
from f5.bigip.mixins import UnsupportedMethod
2221

2322

@@ -107,18 +106,6 @@ def test_TestClass_Basic():
107106
assert json.dumps(mtc_as_dict) == '{"y": {"test_attribute": 42}}'
108107

109108

110-
class TestUnnamedResourceMixin(object):
111-
def test_create_raises(self):
112-
unnamed_resource = UnnamedResourceMixin()
113-
with pytest.raises(UnsupportedMethod):
114-
unnamed_resource.create()
115-
116-
def test_delete_raises(self):
117-
unnamed_resource = UnnamedResourceMixin()
118-
with pytest.raises(UnsupportedMethod):
119-
unnamed_resource.create()
120-
121-
122109
class TestCommandExecutionMixin(object):
123110
def test_create_raises(self):
124111
command_resource = CommandExecutionMixin()

f5/bigip/test/test_resource.py

Lines changed: 37 additions & 16 deletions
Original file line numberDiff line numberDiff line change
@@ -31,8 +31,11 @@
3131
from f5.bigip.resource import RequestParamKwargCollision
3232
from f5.bigip.resource import Resource
3333
from f5.bigip.resource import ResourceBase
34+
from f5.bigip.resource import UnnamedResource
3435
from f5.bigip.resource import UnregisteredKind
3536
from f5.bigip.resource import URICreationCollision
37+
from f5.bigip.tm.ltm.virtual import Virtual
38+
from f5.sdk_exception import UnsupportedMethod
3639

3740

3841
class MockResponse(object):
@@ -102,26 +105,26 @@ def test_missing_required_creation_parameter(self):
102105
"Missing required params: ['NONEMPTY']"
103106

104107
def test_KindTypeMismatch(self):
105-
r = Resource(mock.MagicMock())
108+
r = Virtual(mock.MagicMock())
106109
r._meta_data['bigip']._meta_data['icr_session'].post.return_value =\
107110
MockResponse({u"kind": u"tm:"})
108-
r._meta_data['required_json_kind'] = 'INCORRECT!'
109111
with pytest.raises(KindTypeMismatch) as KTMmEIO:
110112
r.create(partition="Common", name="test_create")
111113
assert KTMmEIO.value.message ==\
112-
"For instances of type ''Resource'' the corresponding kind must"\
113-
" be ''INCORRECT!'' but creation returned JSON with kind: u'tm:'"
114+
"For instances of type ''Virtual'' the corresponding kind must "\
115+
"be ''tm:ltm:virtual:virtualstate'' but creation returned "\
116+
"JSON with kind: u'tm:'"
114117

115-
def test_successful(self):
116-
r = Resource(mock.MagicMock())
117-
MRO = MockResponse({u"kind": u"tm:",
118+
def test_success(self):
119+
r = Virtual(mock.MagicMock())
120+
MRO = MockResponse({u"kind": u"tm:ltm:virtual:virtualstate",
118121
u"selfLink": u".../~Common~test_create"})
119122
r._meta_data['bigip']._meta_data['icr_session'].post.return_value = MRO
120-
r._meta_data['required_json_kind'] = u"tm:"
123+
r._meta_data['required_json_kind'] = u"tm:ltm:virtual:virtualstate"
121124
r._meta_data['allowed_lazy_attributes'] = []
122-
r.create(partition="Common", name="test_create")
123-
assert r.kind == u"tm:"
124-
assert r.selfLink == u".../~Common~test_create"
125+
x = r.create(partition="Common", name="test_create")
126+
assert x.kind == u"tm:ltm:virtual:virtualstate"
127+
assert x.selfLink == u".../~Common~test_create"
125128

126129

127130
def test__activate_URI():
@@ -338,19 +341,25 @@ def test_icontrol_version_default(self):
338341
assert 'params' not in submitted
339342

340343
def test_success(self):
341-
r = Resource(mock.MagicMock())
344+
r = Virtual(mock.MagicMock())
342345
r._meta_data['allowed_lazy_attributes'] = []
343-
mockuri = "https://localhost:443/mgmt/tm/ltm/nat/~Common~test_load"
346+
mockuri = "https://localhost:443/mgmt/tm/ltm/virtual/~Common~test_load"
344347
attrs = {'get.return_value':
345-
MockResponse({u"generation": 0, u"selfLink": mockuri})}
348+
MockResponse(
349+
{
350+
u"generation": 0,
351+
u"selfLink": mockuri,
352+
u"kind": u"tm:ltm:virtual:virtualstate"
353+
}
354+
)}
346355
mock_session = mock.MagicMock(**attrs)
347356
r._meta_data['bigip']._meta_data =\
348357
{'icr_session': mock_session,
349358
'hostname': 'TESTDOMAINNAME',
350359
'uri': 'https://TESTDOMAIN:443/mgmt/tm/'}
351360
r.generation = 0
352-
r.load(partition='Common', name='test_load')
353-
assert r.selfLink == mockuri
361+
x = r.load(partition='Common', name='test_load')
362+
assert x.selfLink == mockuri
354363

355364

356365
class TestResource_exists(object):
@@ -507,3 +516,15 @@ def test_check_exclusive_parameters_fail(self):
507516
with pytest.raises(ExclusiveAttributesPresent) as EAEIO:
508517
p._check_exclusive_parameters(**fakearg)
509518
assert 'FOOEX, BAREX' in EAEIO.value.message
519+
520+
521+
class TestUnnamedResource(object):
522+
def test_create_raises(self):
523+
unnamed_resource = UnnamedResource(mock.MagicMock())
524+
with pytest.raises(UnsupportedMethod):
525+
unnamed_resource.create()
526+
527+
def test_delete_raises(self):
528+
unnamed_resource = UnnamedResource(mock.MagicMock())
529+
with pytest.raises(UnsupportedMethod):
530+
unnamed_resource.create()

f5/bigip/tm/auth/password_policy.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -30,11 +30,10 @@
3030
``tm:auth:password-policy:password-policystate``
3131
"""
3232

33-
from f5.bigip.mixins import UnnamedResourceMixin
34-
from f5.bigip.resource import ResourceBase
33+
from f5.bigip.resource import UnnamedResource
3534

3635

37-
class Password_Policy(UnnamedResourceMixin, ResourceBase):
36+
class Password_Policy(UnnamedResource):
3837
"""BIG-IP® password policy unnamed resource
3938
4039
.. note::

f5/bigip/tm/cm/sync_status.py

Lines changed: 2 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -27,11 +27,10 @@
2727
``tm:cm:sync-status:*``
2828
"""
2929

30-
from f5.bigip.mixins import UnnamedResourceMixin
31-
from f5.bigip.resource import ResourceBase
30+
from f5.bigip.resource import UnnamedResource
3231

3332

34-
class Sync_Status(ResourceBase, UnnamedResourceMixin):
33+
class Sync_Status(UnnamedResource):
3534
'''A Resource concrete subclass.'''
3635
def __init__(self, cm):
3736
'''Autogenerated constructor.'''

0 commit comments

Comments
 (0)