Skip to content

Commit 6d14bf7

Browse files
authored
Merge pull request #498 from caphrim007/add-gtm-datacenter-support
Add GTM Datacenter support to the SDK
2 parents 5d4f71d + 8bfd2d7 commit 6d14bf7

4 files changed

Lines changed: 369 additions & 0 deletions

File tree

f5/bigip/tm/gtm/__init__.py

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -29,6 +29,7 @@
2929

3030

3131
from f5.bigip.resource import OrganizingCollection
32+
from f5.bigip.tm.gtm.datacenter import Datacenters
3233
from f5.bigip.tm.gtm.rule import Rules
3334

3435

@@ -37,5 +38,6 @@ class Gtm(OrganizingCollection):
3738
def __init__(self, tm):
3839
super(Gtm, self).__init__(tm)
3940
self._meta_data['allowed_lazy_attributes'] = [
41+
Datacenters,
4042
Rules
4143
]

f5/bigip/tm/gtm/datacenter.py

Lines changed: 154 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,154 @@
1+
# coding=utf-8
2+
#
3+
# Copyright 2014-2015 F5 Networks Inc.
4+
#
5+
# Licensed under the Apache License, Version 2.0 (the "License");
6+
# you may not use this file except in compliance with the License.
7+
# You may obtain a copy of the License at
8+
#
9+
# http://www.apache.org/licenses/LICENSE-2.0
10+
#
11+
# Unless required by applicable law or agreed to in writing, software
12+
# distributed under the License is distributed on an "AS IS" BASIS,
13+
# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
14+
# See the License for the specific language governing permissions and
15+
# limitations under the License.
16+
#
17+
18+
"""BIG-IP® Global Traffic Manager (GTM) datacenter module.
19+
20+
REST URI
21+
``http://localhost/mgmt/tm/gtm/datacenter``
22+
23+
GUI Path
24+
``DNS --> GSLB : Data Centers``
25+
26+
REST Kind
27+
``tm:gtm:datacenter:*``
28+
"""
29+
30+
from f5.bigip.mixins import ExclusiveAttributesMixin
31+
from f5.bigip.resource import Collection
32+
from f5.bigip.resource import Resource
33+
34+
35+
class Datacenters(Collection):
36+
"""BIG-IP® GTM datacenter collection"""
37+
def __init__(self, gtm):
38+
super(Datacenters, self).__init__(gtm)
39+
self._meta_data['allowed_lazy_attributes'] = [Datacenter]
40+
self._meta_data['attribute_registry'] =\
41+
{'tm:gtm:datacenter:datacenterstate': Datacenter}
42+
43+
44+
class Datacenter(Resource, ExclusiveAttributesMixin):
45+
"""BIG-IP® GTM datacenter resource"""
46+
def __init__(self, dc_s):
47+
super(Datacenter, self).__init__(dc_s)
48+
self._meta_data['required_json_kind'] =\
49+
'tm:gtm:datacenter:datacenterstate'
50+
self._meta_data['exclusive_attributes'].append(('enabled', 'disabled'))
51+
52+
def _endis_able(self, config_dict):
53+
if 'enabled' in config_dict and not config_dict['enabled']:
54+
config_dict['disabled'] = True
55+
elif 'disabled' in config_dict and not config_dict['disabled']:
56+
config_dict['enabled'] = True
57+
return config_dict
58+
59+
def _endis_attrs(self):
60+
"""Manipulate return value to equal negation of set value
61+
62+
This function (uniquely?!) manipulates response values before the
63+
consumer has access to them. We think this is dangerous. It is likely
64+
this function will move once we figure out how to properly annotate
65+
this RISKY behavior!"
66+
67+
The BIG-IP REST API for this particular endpoint has two fields
68+
which are mutually exclusive; disabled and enabled. When using this
69+
SDK API, you may do the following
70+
71+
d = api.tm.gtm.datasources.datasource.load(name='foo')
72+
d.update(enabled=False)
73+
74+
You might expect that the behavior of the following...
75+
76+
if d.enabled:
77+
print("enabled")
78+
else:
79+
print("disabled")
80+
81+
...would result in "enabled" being printed, but that would not be
82+
the case; BIG-IP will specify that "enabled" is True and that the
83+
following is also now True
84+
85+
d.disabled == True
86+
87+
This behavior of setting a different variable instead of the one
88+
that you specified, may not be obvious to the casual user. Therefore,
89+
this method will set appropriate sister variables to be the negation
90+
of the variable you set.
91+
92+
Therefore
93+
94+
d.enabled = True
95+
96+
will also do the following automatically
97+
98+
d.disabled = False
99+
100+
This behavior will allow the SDK to behave according to most users
101+
expectations, shown below
102+
103+
d.update(enabled=False)
104+
if d.enabled:
105+
print("enabled")
106+
else:
107+
print("disabled")
108+
109+
which will print the following
110+
111+
"disabled"
112+
113+
Likewise, checking for d.disabled would return True.
114+
Returns:
115+
None
116+
"""
117+
if 'disabled' in self.__dict__:
118+
self.__dict__['enabled'] = not self.__dict__['disabled']
119+
if 'enabled' in self.__dict__:
120+
self.__dict__['disabled'] = not self.__dict__['enabled']
121+
return None
122+
123+
def create(self, **kwargs):
124+
kwargs = self._endis_able(kwargs)
125+
126+
if 'enabled' in kwargs and kwargs['enabled']:
127+
del kwargs['disabled']
128+
if 'disabled' in kwargs and kwargs['disabled']:
129+
del kwargs['enabled']
130+
131+
self._create(**kwargs)
132+
self._endis_attrs()
133+
return self
134+
135+
def load(self, **kwargs):
136+
kwargs = self._endis_able(kwargs)
137+
self._load(**kwargs)
138+
self._endis_attrs()
139+
return self
140+
141+
def refresh(self, **kwargs):
142+
kwargs = self._endis_able(kwargs)
143+
self._refresh(**kwargs)
144+
self._endis_attrs()
145+
return self
146+
147+
def update(self, **kwargs):
148+
if 'enabled' in self.__dict__ and 'enabled' not in kwargs:
149+
kwargs['enabled'] = self.__dict__.pop('enabled')
150+
elif 'disabled' in self.__dict__ and 'disabled' not in kwargs:
151+
kwargs['disabled'] = self.__dict__.pop('disabled')
152+
kwargs = self._endis_able(kwargs)
153+
self._update(**kwargs)
154+
self._endis_attrs()
Lines changed: 44 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,44 @@
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 import ManagementRoot
20+
from f5.bigip.resource import MissingRequiredCreationParameter
21+
from f5.bigip.tm.gtm.datacenter import Datacenter
22+
23+
24+
@pytest.fixture
25+
def FakeDatacenter():
26+
fake_dc_s = mock.MagicMock()
27+
fake_dc = Datacenter(fake_dc_s)
28+
return fake_dc
29+
30+
31+
class TestCreate(object):
32+
def test_create_two(self, fakeicontrolsession):
33+
b = ManagementRoot('192.168.1.1', 'admin', 'admin')
34+
r1 = b.tm.gtm.datacenters.datacenter
35+
r2 = b.tm.gtm.datacenters.datacenter
36+
assert r1 is not r2
37+
38+
def test_create_no_args(self, FakeDatacenter):
39+
with pytest.raises(MissingRequiredCreationParameter):
40+
FakeDatacenter.create()
41+
42+
def test_create_partition(self, FakeDatacenter):
43+
with pytest.raises(MissingRequiredCreationParameter):
44+
FakeDatacenter.create(partition='Common')
Lines changed: 169 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,169 @@
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 pytest
17+
18+
from f5.bigip.resource import MissingRequiredCreationParameter
19+
from f5.bigip.tm.gtm.datacenter import Datacenter
20+
from requests.exceptions import HTTPError
21+
22+
23+
def delete_dc(mgmt_root, name, partition):
24+
r = mgmt_root.tm.gtm.datacenters.datacenter
25+
try:
26+
r.load(name=name, partition=partition)
27+
except HTTPError as err:
28+
if err.response.status_code != 404:
29+
raise
30+
return
31+
r.delete()
32+
33+
34+
def setup_create_test(request, mgmt_root, name, partition):
35+
def teardown():
36+
delete_dc(mgmt_root, name, partition)
37+
request.addfinalizer(teardown)
38+
39+
40+
def setup_basic_test(request, mgmt_root, name, partition):
41+
def teardown():
42+
delete_dc(mgmt_root, name, partition)
43+
44+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
45+
dc1.create(name=name, partition=partition)
46+
request.addfinalizer(teardown)
47+
return dc1
48+
49+
50+
class TestCreate(object):
51+
def test_create_no_args(self, mgmt_root):
52+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
53+
with pytest.raises(MissingRequiredCreationParameter):
54+
dc1.create()
55+
56+
def test_create(self, request, mgmt_root):
57+
setup_create_test(request, mgmt_root, 'dc1', 'Common')
58+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
59+
dc1.create(name='dc1', partition='Common')
60+
assert dc1.name == 'dc1'
61+
assert dc1.partition == 'Common'
62+
assert dc1.generation and isinstance(dc1.generation, int)
63+
assert dc1.kind == 'tm:gtm:datacenter:datacenterstate'
64+
assert dc1.selfLink.startswith(
65+
'https://localhost/mgmt/tm/gtm/datacenter/~Common~dc1')
66+
67+
def test_create_optional_args(self, request, mgmt_root):
68+
setup_create_test(request, mgmt_root, 'dc1', 'Common')
69+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
70+
dc1.create(name='dc1',
71+
partition='Common',
72+
enabled=False,
73+
contact="admin@root.local",
74+
description="A datacenter is fine too",
75+
location="Between the earth and the moon")
76+
assert False == dc1.enabled
77+
assert "admin@root.local" == dc1.contact
78+
assert "A datacenter is fine too" == dc1.description
79+
assert "Between the earth and the moon" == dc1.location
80+
81+
def test_create_duplicate(self, request, mgmt_root):
82+
setup_create_test(request, mgmt_root, 'dc1', 'Common')
83+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
84+
dc1.create(name='dc1', partition='Common')
85+
dc2 = mgmt_root.tm.gtm.datacenters.datacenter
86+
with pytest.raises(HTTPError) as err:
87+
dc2.create(name='dc1', partition='Common')
88+
assert err.response.status_code == 400
89+
90+
91+
class TestRefresh(object):
92+
def test_refresh(self, request, mgmt_root):
93+
setup_basic_test(request, mgmt_root, 'dc1', 'Common')
94+
d1 = mgmt_root.tm.gtm.datacenters.datacenter.load(
95+
name='dc1', partition='Common')
96+
d2 = mgmt_root.tm.gtm.datacenters.datacenter.load(
97+
name='dc1', partition='Common')
98+
assert True == d1.enabled
99+
assert True == d2.enabled
100+
101+
d2.update(enabled=False)
102+
assert False == d2.enabled
103+
assert True == d1.enabled
104+
105+
d1.refresh()
106+
assert False == d1.enabled
107+
108+
109+
class TestLoad(object):
110+
def test_load_no_object(self, mgmt_root):
111+
with pytest.raises(HTTPError) as err:
112+
mgmt_root.tm.gtm.datacenters.datacenter.load(
113+
name='dc1', partition='Common')
114+
assert err.response.status_code == 404
115+
116+
def test_load(self, request, mgmt_root):
117+
setup_basic_test(request, mgmt_root, 'dc1', 'Common')
118+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter.load(
119+
name='dc1', partition='Common')
120+
assert True == dc1.enabled
121+
dc1.update(enabled=False)
122+
dc2 = mgmt_root.tm.gtm.datacenters.datacenter.load(
123+
name='dc1', partition='Common')
124+
assert False == dc1.enabled
125+
assert False == dc2.enabled
126+
127+
128+
class TestUpdate(object):
129+
def test_update(self, request, mgmt_root):
130+
dc1 = setup_basic_test(request, mgmt_root, 'dc1', 'Common')
131+
assert True == dc1.enabled
132+
assert False == dc1.disabled
133+
dc1.update(enabled=False)
134+
assert False == dc1.enabled
135+
assert True == dc1.disabled
136+
137+
def test_update_samevalue(self, request, mgmt_root):
138+
dc1 = setup_basic_test(request, mgmt_root, 'dc1', 'Common')
139+
dc1.update(enabled=True)
140+
assert False != dc1.enabled
141+
142+
143+
class TestDelete(object):
144+
def test_delete(self, request, mgmt_root):
145+
dc1 = setup_basic_test(request, mgmt_root, 'dc1', 'Common')
146+
dc1.delete()
147+
with pytest.raises(HTTPError) as err:
148+
mgmt_root.tm.gtm.datacenters.datacenter.load(
149+
name='dc1', partition='Common')
150+
assert err.response.status_code == 404
151+
152+
153+
class TestDatacenterCollection(object):
154+
def test_datacenter_collection(self, request, mgmt_root):
155+
setup_create_test(request, mgmt_root, 'dc1', 'Common')
156+
dc1 = mgmt_root.tm.gtm.datacenters.datacenter
157+
dc1.create(name='dc1', partition='Common')
158+
assert dc1.name == 'dc1'
159+
assert dc1.partition == 'Common'
160+
assert dc1.generation and isinstance(dc1.generation, int)
161+
assert dc1.fullPath == '/Common/dc1'
162+
assert dc1.kind == 'tm:gtm:datacenter:datacenterstate'
163+
assert dc1.selfLink.startswith(
164+
'https://localhost/mgmt/tm/gtm/datacenter/~Common~dc1')
165+
166+
rc = mgmt_root.tm.gtm.datacenters.get_collection()
167+
assert isinstance(rc, list)
168+
assert len(rc)
169+
assert isinstance(rc[0], Datacenter)

0 commit comments

Comments
 (0)