Skip to content

Commit cee6fb4

Browse files
author
Paul Breaux
committed
Fixing opt-in for cluster tests and trust domain sync
Issues: Fixes #456 and #460 Problem: Developers must be able to opt-in to the cluster tests, since they require a set of at least four bigip devices. Also, a trust domain is implemented as a device group on the bigip devices, and we must be able to sync that group when we add members to the trust domain. Analysis: Used pytest.mark.skip to skip cluster tests unless a user has set a key of 'run_cluster_tests' and a value of JSON/YAML truthiness. The trust domain now instantiates a device group manager to sync to that group when adding devices to the trust domain. This is a part of the validation process in the device group and the trust domain. Tests: All unit and functional tests pass
1 parent 59c691c commit cee6fb4

6 files changed

Lines changed: 229 additions & 199 deletions

File tree

f5/multi_device/cluster/__init__.py

Lines changed: 2 additions & 43 deletions
Original file line numberDiff line numberDiff line change
@@ -34,18 +34,12 @@
3434
3535
Currently the only supported type of cluster is a 'sync-failover' cluster.
3636
37-
ClusterManager Methods:
37+
Methods:
3838
3939
* create -- creates a cluster based on kwargs given by user
4040
* teardown -- tears down an existing cluster
41-
* scale_up_by_one -- add one device to the cluster
42-
* scale_down_by_one -- remove one device from the cluster
4341
44-
Classes:
45-
46-
* ClusterManager -- manages a cluster of devices with the methods above
47-
48-
Usage:
42+
Examples:
4943
5044
There are two major use-cases here:
5145
@@ -58,9 +52,6 @@
5852
device_group_type='sync-failover',
5953
device_group_partition='Common'
6054
)
61-
new_bigip_device = ManagementRoot(...)
62-
cluster_mgr.scale_up_by_one(new_bigip_device)
63-
list_of_bigips.append(new_bigip_device)
6455
assert cluster_mgr.cluster.devices == list_of_bigips
6556
6657
* Create a new cluster and manage it:
@@ -178,35 +169,3 @@ def teardown(self):
178169
self.device_group.teardown()
179170
self.trust_domain.teardown()
180171
self.cluster = None
181-
182-
def scale_up_by_one(self, device):
183-
'''Scale cluster up by one device.
184-
185-
:param bigip: bigip object -- bigip to add
186-
:raises: ClusterNotSupported
187-
'''
188-
189-
if len(self.cluster.devices) == 8:
190-
msg = 'The number of devices to cluster is not supported.'
191-
raise ClusterNotSupported(msg)
192-
print('Scaling cluster up by one device...')
193-
self.trust_domain.scale_up_by_one(device)
194-
self.device_group.scale_up_by_one(device)
195-
self.cluster.devices.append(device)
196-
self.device_group.ensure_all_devices_in_sync()
197-
198-
def scale_down_by_one(self, device):
199-
'''Scale cluster down by one device.
200-
201-
:param device: ManagementRoot object -- device to delete
202-
:raises: ClusterNotSupported
203-
'''
204-
205-
if len(self.cluster.devices) < 3:
206-
msg = 'The number of devices to cluster is not supported.'
207-
raise ClusterNotSupported(msg)
208-
print('Scaling cluster down by one device...')
209-
self.device_group.scale_down_by_one(device)
210-
self.trust_domain.scale_down_by_one(device, self.device_group)
211-
self.cluster.devices.remove(device)
212-
self.device_group.ensure_all_devices_in_sync()

f5/multi_device/cluster/test/test_cluster_manager.py

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -147,7 +147,7 @@ def test_teardown_cluster(ClusterManagerCreateNew, BigIPs):
147147
assert cm.cluster is None
148148

149149

150-
def test_scale_up_too_many_devices(ClusterManagerCreateNew, BigIPs):
150+
def itest_scale_up_too_many_devices(ClusterManagerCreateNew, BigIPs):
151151
cm = ClusterManagerCreateNew
152152
mock_bigips = BigIPs
153153
cm.create(
@@ -168,7 +168,7 @@ def test_scale_up_too_many_devices(ClusterManagerCreateNew, BigIPs):
168168
ex.value.message
169169

170170

171-
def test_scale_down_cluster_not_supported(ClusterManagerCreateNew, BigIPs):
171+
def itest_scale_down_cluster_not_supported(ClusterManagerCreateNew, BigIPs):
172172
cm = ClusterManagerCreateNew
173173
mock_bigips = BigIPs
174174
cm.create(

f5/multi_device/device_group.py

Lines changed: 64 additions & 48 deletions
Original file line numberDiff line numberDiff line change
@@ -1,3 +1,4 @@
1+
# coding=utf-8
12
# Copyright 2016 F5 Networks Inc.
23

34
# Licensed under the Apache License, Version 2.0 (the "License");
@@ -14,24 +15,76 @@
1415
#
1516
#
1617

17-
'''Class for managing a DeviceGroup for a set of BIG-IP devices.'''
18+
'''Class for managing a DeviceGroup for a set of BIG-IP® devices
19+
20+
Managing a device group for clustering is an event-driven process. Please
21+
use the methods here to control that process. The fundamental idea is that
22+
any action should have an observable outcome. Adding a device to the device
23+
group should have a consequence for each member of the device group,
24+
including the newly added member.
25+
26+
Examples:
27+
28+
There are two major use-cases here:
29+
30+
* Manage an existing device group:
31+
32+
list_of_bigips = [ManagementRoot(...), ManagementRoot(...)]
33+
device_group = DeviceGroup(
34+
devices=list_of_bigips,
35+
device_group_name='my_cluster',
36+
device_group_type='sync-failover',
37+
device_group_partition='Common'
38+
)
39+
device_group.ensure_all_devices_in_sync()
40+
41+
* Create a new device group and manage it:
42+
43+
list_of_bigips = [ManagementRoot(...), ManagementRoot(...)]
44+
device_group = DeviceGroup()
45+
device_group.create(
46+
devices=list_of_bigips,
47+
device_group_name='my_cluster',
48+
device_group_type='sync-failover',
49+
device_group_partition='Common'
50+
)
51+
device_group.ensure_all_devices_in_sync()
52+
53+
Methods:
54+
55+
* create -- create a device group from a list of devices
56+
* teardown -- teardown a device group, but leave the trust domain intact
57+
* validate -- ensure a device group is in the proper state based on inputs
58+
* manage_extant -- manage an existing device group
59+
60+
'''
1861

1962
from f5.multi_device.exceptions import DeviceGroupNotSupported
20-
from f5.multi_device.exceptions import DeviceGroupOperationNotSupported
2163
from f5.multi_device.exceptions import MissingRequiredDeviceGroupParameter
2264
from f5.multi_device.exceptions import UnexpectedDeviceGroupDevices
2365
from f5.multi_device.exceptions import UnexpectedDeviceGroupState
2466
from f5.multi_device.exceptions import UnexpectedDeviceGroupType
2567

2668
from f5.multi_device.utils import get_device_info
27-
from f5.multi_device.utils import get_device_names_to_objects
2869
from f5.multi_device.utils import pollster
2970

3071

3172
class DeviceGroup(object):
32-
'''Class to manage device service group.'''
73+
'''Class to manage device service group
74+
75+
For the non-public methods, there are a few flavors of behavior:
76+
get, check, and ensure. A 'get' retrieves some info from the device
77+
without any assumptions about that info. A 'check' will assert a device's
78+
info is as expected. An 'ensure' method often does one or more of the
79+
above and also may take some other action to enforce the expected state,
80+
such as syncing config.
81+
82+
Example:
83+
3384
34-
available_types = ['sync-failover']
85+
'''
86+
87+
available_types = ['sync-failover', 'sync-only']
3588
sync_status_entry = 'https://localhost/mgmt/tm/cm/sync-status/0'
3689

3790
def __init__(self, **kwargs):
@@ -78,8 +131,9 @@ def validate(self, **kwargs):
78131
device_name = get_device_info(device).name
79132
given_device_names.append(device_name)
80133
if sorted(queried_device_names) != sorted(given_device_names):
81-
msg = ''
134+
msg = 'Given devices does not match queried devices.'
82135
raise UnexpectedDeviceGroupDevices(msg)
136+
self.ensure_all_devices_in_sync()
83137

84138
def _check_type(self):
85139
'''Check that the device group type is correct.
@@ -90,6 +144,10 @@ def _check_type(self):
90144
if self.type not in self.available_types:
91145
msg = 'Unsupported cluster type was given: %s' % self.type
92146
raise DeviceGroupNotSupported(msg)
147+
elif self.type == 'sync-only' and self.name != 'device_trust_group':
148+
msg = "Management of sync-only device groups only supported for " \
149+
"built-in device group named 'device_trust_group'"
150+
raise DeviceGroupNotSupported(msg)
93151

94152
def manage_extant(self, **kwargs):
95153
self.validate(**kwargs)
@@ -121,48 +179,6 @@ def teardown(self):
121179
pollster(self._check_devices_active_licensed)()
122180
pollster(self._check_all_devices_in_sync)()
123181

124-
def scale_up_by_one(self, device):
125-
'''Scale device group up by one device
126-
127-
:param device: ManagementRoot object -- device to add to device group
128-
'''
129-
130-
device_name = get_device_info(device).name
131-
if device_name in self._get_device_names_in_group():
132-
msg = 'Device: %r is already in device group' % device_name
133-
raise DeviceGroupOperationNotSupported(msg)
134-
self._check_device_failover_status(device, 'In Sync')
135-
self._add_device_to_device_group(device)
136-
device.tm.sys.config.exec_cmd('save')
137-
self.devices.append(device)
138-
self.ensure_all_devices_in_sync()
139-
140-
def scale_down_by_one(self, device):
141-
'''Scale device group down by one device
142-
143-
:param device: ManagementRoot object -- device to delete from device
144-
group
145-
'''
146-
147-
device_name = get_device_info(device).name
148-
if device_name not in self._get_device_names_in_group():
149-
msg = 'Device: %r is not in device group' % device_name
150-
raise DeviceGroupOperationNotSupported(msg)
151-
self._delete_device_from_device_group(device)
152-
device.tm.sys.config.exec_cmd('save')
153-
name_to_objects = get_device_names_to_objects(self.devices)
154-
self.devices.remove(name_to_objects[device_name])
155-
self.ensure_all_devices_in_sync()
156-
157-
def cleanup_scaled_down_device(self, device):
158-
'''Remove all devices from device group on orphaned device.
159-
160-
:param device: bigip object -- device to cleanup
161-
'''
162-
163-
dg = self._delete_all_devices_from_device_group(device)
164-
dg.delete()
165-
166182
def _get_device_names_in_group(self):
167183
'''_get_device_names_in_group
168184

f5/multi_device/test/test_device_group.py

Lines changed: 11 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -82,6 +82,15 @@ def test_validate_unsupported_type(BigIPs):
8282
assert 'Unsupported cluster type was given: wrong' == ex.value.message
8383

8484

85+
def test_validate_sync_only_not_device_trust_group(BigIPs):
86+
with pytest.raises(DeviceGroupNotSupported) as ex:
87+
DeviceGroup(
88+
devices=BigIPs, device_group_name='test',
89+
device_group_type='sync-only', device_group_partition='Common')
90+
assert "Management of sync-only device groups only supported for " \
91+
"built-in device group named 'device_trust_group'" in ex.value.message
92+
93+
8594
def test_validate_type_mismatch(BigIPs):
8695
with pytest.raises(UnexpectedDeviceGroupType) as ex:
8796
with mock.patch(
@@ -96,7 +105,7 @@ def test_validate_type_mismatch(BigIPs):
96105
"device group type: 'sync-failover'" == ex.value.message
97106

98107

99-
def test_scale_up_device_already_in_group(DeviceGroupCreateNew, BigIPs):
108+
def itest_scale_up_device_already_in_group(DeviceGroupCreateNew, BigIPs):
100109
dg, mock_bigips = DeviceGroupCreateNew
101110
mock_bigip = mock.MagicMock()
102111
mock_bigip.tm.cm.devices.get_collection.return_value = \
@@ -111,7 +120,7 @@ def test_scale_up_device_already_in_group(DeviceGroupCreateNew, BigIPs):
111120
assert "Device: 'test' is already in device group" == ex.value.message
112121

113122

114-
def test_scale_down_device_not_in_group(DeviceGroupCreateNew, BigIPs):
123+
def itest_scale_down_device_not_in_group(DeviceGroupCreateNew, BigIPs):
115124
dg, mock_bigips = DeviceGroupCreateNew
116125
mock_bigip = mock.MagicMock()
117126
mock_bigip.tm.cm.devices.get_collection.return_value = \

0 commit comments

Comments
 (0)