Skip to content

Commit f15b3fb

Browse files
author
Paul Breaux
committed
Adding the cluster_manager module
Issues: Fixes #400 Problem: Initial commit for cluster_manager module for managing a BigIP cluster Analysis: Initial commit is using one large module to do all of the heavy lifting. Will break out in future commits when common functionality arises Tests: Only manual tests so far
1 parent d42acc3 commit f15b3fb

4 files changed

Lines changed: 248 additions & 0 deletions

File tree

f5/common/pollster.py

Lines changed: 43 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,43 @@
1+
# Copyright 2016 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+
17+
import functools
18+
import time
19+
20+
21+
class MaximumAttemptsReached(Exception):
22+
pass
23+
24+
25+
def poll_by_method(method, attempts=30, interval=2):
26+
'''Poll with a given method for a specified number of times.
27+
28+
:param method: callable to invoke in loop -- if no exception is raised
29+
the call is considered succeeded
30+
:param attempts: number of iterations to attempt
31+
:param interval: seconds to wait before next attempt
32+
'''
33+
34+
@functools.wraps(method)
35+
def poll(*args, **kwargs):
36+
for attempt in range(attempts):
37+
try:
38+
return method(*args, **kwargs)
39+
except Exception:
40+
time.sleep(interval)
41+
continue
42+
raise MaximumAttemptsReached('Polled method maximum number of times')
43+
return poll

f5/common/test.py

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,7 @@
1+
from f5.common.iapp_parser import IappParser as ip
2+
3+
hi = open('/Users/breaux/Documents/f5-openstack-heat-plugins/f5_heat/examples/iapps/hitesh.tmpl').read()
4+
5+
d = ip(hi).parse_template()
6+
import json
7+
print json.dumps(d)

f5/managers/__init__.py

Whitespace-only changes.

f5/managers/cluster_manager.py

Lines changed: 198 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,198 @@
1+
# Copyright 2016 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+
17+
from f5.common import pollster
18+
19+
20+
class BigIPDeviceNotInExpectedState(Exception):
21+
pass
22+
23+
24+
class ClusterNotSupported(Exception):
25+
pass
26+
27+
28+
class UnexpectedBigIPState(Exception):
29+
pass
30+
31+
32+
class ClusterManager(object):
33+
'''Manage a cluster of BigIPs.
34+
35+
This is accomplished with REST URI calls only, but some operations are
36+
only permitted through the CLI (such as adding peers via cm/trust-domain).
37+
We get around this issue by deploying iApps (sys/application).
38+
'''
39+
40+
iapp_actions = {'definition': {'implementation': None, 'presentation': ''}}
41+
sync_status_entry = 'https://localhost/mgmt/tm/cm/sync-status/0'
42+
43+
def __init__(self, bigips, cluster_name, partition, cluster_type):
44+
if len(bigips) > 4:
45+
raise ClusterNotSupported(
46+
'The number of devices to cluster is not supported.'
47+
)
48+
self.bigips = bigips
49+
self.bigip_trust_root = self.bigips[0]
50+
self.peers = self.bigips[1:]
51+
self.cluster_name = cluster_name
52+
self.device_iapp_name = 'device_iapp'
53+
self.cluster_iapp_name = 'cluster_iapp'
54+
self.partition = partition
55+
self.cluster_type = cluster_type
56+
57+
def cluster_bigips(self):
58+
if len(self._get_bigips_by_activation_state('active')) != \
59+
len(self.bigips):
60+
raise BigIPDeviceNotInExpectedState(
61+
'One or more BigIP devices was not in a active/licensed state.'
62+
)
63+
print('Adding trusted peers...')
64+
self._add_peers()
65+
print('Creating cluster group...')
66+
self._create_cluster_group()
67+
if self.cluster_type == 'sync-failover':
68+
print('Ensure cluster has settled into active/standby...')
69+
self._ensure_active_standby()
70+
elif self.cluster_type == 'sync-only':
71+
print('Ensure devices are all in sync and active...')
72+
self.bigip_trust_root.cm.sync(self.cluster_name)
73+
self._all_devices_in_sync()
74+
75+
def _add_peers(self):
76+
peer_cmds = []
77+
for peer in self.peers:
78+
peer_cmds.append(self._get_add_peer_command(peer))
79+
iapp_actions = self.iapp_actions.copy()
80+
iapp_actions['definition']['implementation'] = '\n'.join(peer_cmds)
81+
self._deploy_iapp(self.cluster_iapp_name, iapp_actions)
82+
83+
def _create_cluster_group(self):
84+
self.bigip_trust_root.cm.device_groups.device_group.create(
85+
name=self.cluster_name,
86+
partition=self.partition,
87+
type=self.cluster_type
88+
)
89+
for bigip in self.bigips:
90+
self._add_device_to_device_group(bigip)
91+
92+
def _ensure_active_standby(self):
93+
self._devices_in_standby()
94+
95+
@pollster.poll_by_method
96+
def _all_devices_in_sync(self):
97+
assert len(self._get_bigips_by_failover_status('In Sync')) == \
98+
len(self.bigips)
99+
100+
@pollster.poll_by_method
101+
def _devices_in_standby(self):
102+
standby_bigips = \
103+
self._get_bigips_by_activation_state('standby')
104+
assert len(standby_bigips) == (len(self.bigips)-1)
105+
return standby_bigips
106+
107+
def _get_bigips_by_failover_status(self, status):
108+
bigips = []
109+
for bigip in self.bigips:
110+
sync_status = bigip.cm.sync_status
111+
sync_status.refresh()
112+
current_status = (sync_status.entries[self.sync_status_entry]
113+
['nestedStats']['entries']['status']
114+
['description'])
115+
print(current_status)
116+
if status == current_status:
117+
bigips.append(bigip)
118+
return bigips
119+
120+
def _get_bigips_by_activation_state(self, state):
121+
bigips = []
122+
for bigip in self.bigips:
123+
reg = bigip.shared.licensing.registration.load()
124+
act = bigip.cm.devices.device.load(
125+
name=self._get_device_info(bigip).name,
126+
partition=self.partition
127+
)
128+
if reg.licensedVersion != '' and act.failoverState == state:
129+
bigips.append(bigip)
130+
return bigips
131+
132+
def teardown_cluster(self):
133+
self._remove_iapp(self.cluster_iapp_name)
134+
if self.cluster_type == 'sync-failover':
135+
self._devices_in_standby()
136+
self.bigip_trust_root.cm.sync(self.cluster_name)
137+
dg = self.bigip_trust_root.cm.device_groups.device_group.load(
138+
name=self.cluster_name, partition=self.partition
139+
)
140+
for bigip in self.bigips:
141+
bigip_info = self._get_device_info(bigip)
142+
dgd = dg.devices_s.devices.load(
143+
name=bigip_info.name, partition=self.partition
144+
)
145+
dgd.delete()
146+
dg.delete()
147+
148+
def _remove_iapp(self, iapp_name):
149+
iapp = self.bigip_trust_root.sys.applications
150+
iapp_service = iapp.services.service.load(
151+
name=iapp_name, partition=self.partition
152+
)
153+
iapp_service.delete()
154+
iapp_template = iapp.templates.template.load(
155+
name=iapp_name, partition=self.partition
156+
)
157+
iapp_template.delete()
158+
159+
def _get_device_info(self, bigip):
160+
coll = bigip.cm.devices.get_collection()
161+
device = [device for device in coll if device.selfDevice == 'true']
162+
assert len(device) == 1
163+
return device[0]
164+
165+
def _add_device_to_device_group(self, bigip):
166+
bigip_info = self._get_device_info(bigip)
167+
poll_for_dg = pollster.poll_by_method(
168+
bigip.cm.device_groups.device_group.load
169+
)
170+
dg = poll_for_dg(
171+
name=self.cluster_name, partition=self.partition
172+
)
173+
dg.devices_s.devices.create(
174+
name=bigip_info.name, partition=self.partition
175+
)
176+
root_dg = self.bigip_trust_root.cm.device_groups.device_group.load(
177+
name=self.cluster_name, partition=self.partition
178+
)
179+
trust_root_poll = pollster.poll_by_method(
180+
root_dg.devices_s.devices.load
181+
)
182+
trust_root_poll(name=bigip_info.name, partition=self.partition)
183+
184+
def _deploy_iapp(self, iapp_name, actions):
185+
tmpl = self.bigip_trust_root.sys.applications.templates.template
186+
serv = self.bigip_trust_root.sys.applications.services.service
187+
tmpl.create(name=iapp_name, partition=self.partition, actions=actions)
188+
serv.create(
189+
name=iapp_name, partition=self.partition, template=iapp_name
190+
)
191+
192+
def _get_add_peer_command(self, peer):
193+
peer_device = self._get_device_info(peer)
194+
add_peer_cmd = 'tmsh::modify cm trust-domain Root ca-devices add ' \
195+
'\\{{ {0} \\}} name {1} username admin password admin'.format(
196+
peer_device.managementIp, peer_device.name
197+
)
198+
return add_peer_cmd

0 commit comments

Comments
 (0)