From 10120485150990f88c68c6e1e8af54b583991daf Mon Sep 17 00:00:00 2001 From: Ilya Shakhat Date: Tue, 27 Sep 2016 16:26:09 +0300 Subject: [PATCH] Rename and fix network-management commands in NodesCollection Both network-management methods are renamed: disable_network -> disconnect enable_network -> connect In Human API following commands are now available: * disconnect network on [random|one|single|] node[s] [with service] * connect network on [random|one|single|] node[s] [with service] Also keyword 'service' is made mandatory while referencing to service. Change-Id: I98eb842c39f7a1d08bcab185cd22e24523a1d7c6 --- README.rst | 48 ++++++++++------- examples/uno.py | 4 +- os_faults/api/human.py | 21 +++++--- os_faults/api/node_collection.py | 8 +-- os_faults/drivers/devstack.py | 4 +- os_faults/drivers/fuel.py | 9 ++-- os_faults/tests/unit/api/test_human_api.py | 51 ++++++++++++++++--- .../unit/drivers/test_fuel_node_collection.py | 8 +-- 8 files changed, 105 insertions(+), 48 deletions(-) diff --git a/README.rst b/README.rst index 72a3e82..0b63439 100644 --- a/README.rst +++ b/README.rst @@ -12,7 +12,7 @@ IPMI driver). * Free software: Apache license * Documentation: http://os-faults.readthedocs.io * Source: https://github.com/openstack/os-faults -* Bugs: http://bugs.launchpad.net/os_faults +* Bugs: http://bugs.launchpad.net/os-faults Usage ----- @@ -68,26 +68,36 @@ Simplified API Simplified API is used to inject faults in a human-friendly form. -Service-based command performs specified `action` against `service` on +**Service-oriented** command performs specified `action` against `service` on all, on one random node or on the node specified by FQDN:: - service [on (random|one|single| node[s])] - -Node-based command performs specified `action` on all or selected service's -node:: - - [random|one|single] node[s] - -Network-management command is a subset of node-based query:: - - disable|enable network on node[s] + service [on (random|one|single| node[s])] Examples: + * `Restart Keystone service` - restarts Keystone service on all nodes. + * `kill nova-api service on one node` - restarts Nova API on one + randomly-picked node. - * `Restart Keystone service` - restarts Keystone service on all nodes - * `kill nova-api service on one node` - restarts Nova API on one of nodes - * `Reboot one node with mysql` - reboots one random node with MySQL - * `Reboot node-2.domain.tld node` - reboot node with specified name +**Node-oriented** command performs specified `action` on node specified by FQDN +or set of service's nodes:: + + [random|one|single|] node[s] [with service] + +Examples: + * `Reboot one node with mysql` - reboots one random node with MySQL. + * `Reset node-2.domain.tld node` - reset node `node-2.domain.tld`. + +**Network-oriented** command is a subset of node-oriented and performs network +management operation on selected nodes:: + + network on [random|one|single|] node[s] + [with service] + +Examples: + * `Disconnect management network on nodes with rabbitmq service` - shuts + down management network interface on all nodes where rabbitmq runs. + * `Connect storage network on node-1.domain.tld node` - enables storage + network interface on node-1.domain.tld. Extended API @@ -127,8 +137,8 @@ Available actions: * `poweroff` - power off all nodes abruptly * `reset` - reset (cold restart) all nodes * `oom` - fill all node's RAM - * `disable_network` - disable network with the specified name on all nodes - * `enable_network` - enable network with the specified name on all nodes + * `disconnect` - disable network with the specified name on all nodes + * `connect` - enable network with the specified name on all nodes 3. Operate with nodes ~~~~~~~~~~~~~~~~~~~~~ @@ -147,7 +157,7 @@ Get nodes where l3-agent runs and disable the management network on them: fqdns = neutron.l3_agent_list_hosting_router(router_id) nodes = destructor.get_nodes(fqdns=fqdns) - nodes.disable_network(network_name='management') + nodes.disconnect(network_name='management') 4. Operate with services ~~~~~~~~~~~~~~~~~~~~~~~~ diff --git a/examples/uno.py b/examples/uno.py index 34e9ae9..1f20f1a 100644 --- a/examples/uno.py +++ b/examples/uno.py @@ -65,10 +65,10 @@ def main(): logging.info('Node node-2.domain.tld: %s', nodes) logging.info('# Disable public network on node-2.domain.tld') - nodes.disable_network(network_name='public') + nodes.disconnect(network_name='public') logging.info('# Enable public network on node-2.domain.tld') - nodes.enable_network(network_name='public') + nodes.connect(network_name='public') logging.info('# Kill Glance API service on a single node') service = destructor.get_service(name='glance-api') diff --git a/os_faults/api/human.py b/os_faults/api/human.py index 7423341..6c0856c 100644 --- a/os_faults/api/human.py +++ b/os_faults/api/human.py @@ -28,9 +28,12 @@ Human API understands commands like these (examples): * freeze service [on (random|one|single| node[s])] [for seconds] * unfreeze service [on (random|one|single| node[s])] - * reboot [random|one|single|] node[s] [with ] - * disable network on node[s] - * enable network on node[s] + * reboot [random|one|single|] node[s] [with service] + * reset [random|one|single|] node[s] [with service] + * disconnect network on [random|one|single|] node[s] + [with service] + * connect network on [random|one|single|] node[s] + [with service] """ @@ -54,10 +57,10 @@ PATTERNS = [ '(\s+for\s+(?P\d+)\s+seconds)?' % SERVICE_ACTIONS_PATTERN), re.compile('(?P%s)' - '(\s+(?P\w+)\s+on)?' + '(\s+(?P\w+)\s+network\s+on)?' '(\s+(?P%s|\S+))?' - '\s+node' - '(\s+with\s+(?P\S+))?' % + '\s+nodes?' + '(\s+with\s+(?P\S+)\s+service)?' % (NODE_ACTIONS_PATTERN, RANDOMNESS_PATTERN)), ] @@ -104,8 +107,12 @@ def execute(destructor, command): if node_name in RANDOMNESS: nodes = nodes.pick() + kwargs = {} + if network_name: + kwargs['network_name'] = network_name + fn = getattr(nodes, action) - fn() + fn(**kwargs) else: # nodes operation nodes = destructor.get_nodes(fqdns=[node_name]) diff --git a/os_faults/api/node_collection.py b/os_faults/api/node_collection.py index 4820c90..0daf51c 100644 --- a/os_faults/api/node_collection.py +++ b/os_faults/api/node_collection.py @@ -64,16 +64,16 @@ class NodeCollection(object): raise NotImplementedError @public - def disable_network(self, network_name): - """Disable network with name network_name on each of the nodes + def disconnect(self, network_name): + """Disconnect nodes from network :param network_name: name of network """ raise NotImplementedError @public - def enable_network(self, network_name): - """Enable network with name network_name on each of the nodes + def connect(self, network_name): + """Connect nodes to network :param network_name: name of network """ diff --git a/os_faults/drivers/devstack.py b/os_faults/drivers/devstack.py index 2cf0711..07c3480 100644 --- a/os_faults/drivers/devstack.py +++ b/os_faults/drivers/devstack.py @@ -60,10 +60,10 @@ class DevStackNode(node_collection.NodeCollection): logging.info('Reset nodes: %s', self) self.power_management.reset([self.host.mac]) - def enable_network(self, network_name): + def connect(self, network_name): raise NotImplementedError - def disable_network(self, network_name): + def disconnect(self, network_name): raise NotImplementedError diff --git a/os_faults/drivers/fuel.py b/os_faults/drivers/fuel.py index 6352a3d..2626780 100644 --- a/os_faults/drivers/fuel.py +++ b/os_faults/drivers/fuel.py @@ -77,16 +77,17 @@ class FuelNodeCollection(node_collection.NodeCollection): logging.info('Reset nodes: %s', self) self.power_management.reset(self.get_macs()) - def enable_network(self, network_name): - logging.info("Enable '%s' network on nodes: %s", network_name, self) + def connect(self, network_name): + logging.info("Connect network '%s' on nodes: %s", network_name, self) task = {'fuel_network_mgmt': { 'network_name': network_name, 'operation': 'up', }} self.cloud_management.execute_on_cloud(self.get_ips(), task) - def disable_network(self, network_name): - logging.info("Disable '%s' network on nodes: %s", network_name, self) + def disconnect(self, network_name): + logging.info("Disconnect network '%s' on nodes: %s", + network_name, self) task = {'fuel_network_mgmt': { 'network_name': network_name, 'operation': 'down', diff --git a/os_faults/tests/unit/api/test_human_api.py b/os_faults/tests/unit/api/test_human_api.py index bdb7712..06df1e5 100644 --- a/os_faults/tests/unit/api/test_human_api.py +++ b/os_faults/tests/unit/api/test_human_api.py @@ -89,7 +89,7 @@ class TestHumanAPI(test.TestCase): self.service.get_nodes = mock.MagicMock(return_value=nodes) - command = '%s node with %s' % (action, service_name) + command = '%s node with %s service' % (action, service_name) human.execute(self.destructor, command) self.destructor.get_service.assert_called_once_with(name=service_name) @@ -105,7 +105,7 @@ class TestHumanAPI(test.TestCase): self.service.get_nodes = mock.MagicMock(return_value=nodes) nodes.pick = mock.MagicMock(return_value=nodes2) - command = '%s one node with %s' % (action, service_name) + command = '%s one node with %s service' % (action, service_name) human.execute(self.destructor, command) self.destructor.get_service.assert_called_once_with(name=service_name) @@ -124,20 +124,59 @@ class TestHumanAPI(test.TestCase): destructor.get_nodes.assert_called_once_with(fqdns=['node-2.local']) getattr(nodes, action).assert_called_once() - @ddt.data(('Disable network', 'disable_network'), - ('Enable network', 'enable_network')) + @ddt.data(('Disconnect', 'disconnect'), + ('Connect', 'connect')) @ddt.unpack - def test_disable_network_node_by_fqdn(self, user_action, action): + def test_network_on_nodes_by_fqdn(self, user_action, action): destructor = mock.MagicMock() nodes = mock.MagicMock(node_collection.NodeCollection) destructor.get_nodes = mock.MagicMock(return_value=nodes) - command = '%s storage on node-2.local node' % user_action + command = '%s storage network on node-2.local node' % user_action human.execute(destructor, command) destructor.get_nodes.assert_called_once_with(fqdns=['node-2.local']) getattr(nodes, action).assert_called_once_with(network_name='storage') + @ddt.data(('disconnect', 'storage', 'mysql'), + ('connect', 'management', 'rabbitmq')) + @ddt.unpack + def test_network_on_nodes_by_service( + self, action, network_name, service_name): + nodes = mock.MagicMock(node_collection.NodeCollection) + + self.service.get_nodes = mock.MagicMock(return_value=nodes) + + command = '%s %s network on node with %s service' % ( + action, network_name, service_name) + human.execute(self.destructor, command) + + self.destructor.get_service.assert_called_once_with(name=service_name) + self.service.get_nodes.assert_called_once() + getattr(nodes, action).assert_called_once_with( + network_name=network_name) + + @ddt.data(('disconnect', 'storage', 'one', 'mysql'), + ('connect', 'management', 'random', 'rabbitmq')) + @ddt.unpack + def test_network_on_nodes_by_service_picked_node( + self, action, network_name, node, service_name): + nodes = mock.MagicMock(node_collection.NodeCollection) + nodes2 = mock.MagicMock(node_collection.NodeCollection) + + self.service.get_nodes = mock.MagicMock(return_value=nodes) + nodes.pick = mock.MagicMock(return_value=nodes2) + + command = '%s %s network on %s node with %s service' % ( + action, network_name, node, service_name) + human.execute(self.destructor, command) + + self.destructor.get_service.assert_called_once_with(name=service_name) + self.service.get_nodes.assert_called_once() + nodes.pick.assert_called_once() + getattr(nodes2, action).assert_called_once_with( + network_name=network_name) + def test_malformed_query(self): destructor = mock.MagicMock() diff --git a/os_faults/tests/unit/drivers/test_fuel_node_collection.py b/os_faults/tests/unit/drivers/test_fuel_node_collection.py index b8c3d2a..db747ad 100644 --- a/os_faults/tests/unit/drivers/test_fuel_node_collection.py +++ b/os_faults/tests/unit/drivers/test_fuel_node_collection.py @@ -91,15 +91,15 @@ class FuelNodeCollectionTestCase(test.TestCase): ['09:7b:74:90:63:c1', '09:7b:74:90:63:c2', '09:7b:74:90:63:c3', '09:7b:74:90:63:c4']) - def test_enable_network(self): - self.node_collection.enable_network(network_name='storage') + def test_connect(self): + self.node_collection.connect(network_name='storage') self.mock_cloud_management.execute_on_cloud.assert_called_once_with( ['10.0.0.2', '10.0.0.3', '10.0.0.4', '10.0.0.5'], {'fuel_network_mgmt': {'operation': 'up', 'network_name': 'storage'}}) - def test_disable_network(self): - self.node_collection.disable_network(network_name='storage') + def test_disconnect(self): + self.node_collection.disconnect(network_name='storage') self.mock_cloud_management.execute_on_cloud.assert_called_once_with( ['10.0.0.2', '10.0.0.3', '10.0.0.4', '10.0.0.5'], {'fuel_network_mgmt': {'operation': 'down',