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 <name> network on [random|one|single|<fqdn>] node[s]
   [with <service> service]
 * connect <name> network on [random|one|single|<fqdn>] node[s]
   [with <service> service]
Also keyword 'service' is made mandatory while referencing to service.

Change-Id: I98eb842c39f7a1d08bcab185cd22e24523a1d7c6
This commit is contained in:
Ilya Shakhat 2016-09-27 16:26:09 +03:00 committed by Ilya Shakhat
parent 8ffe4a9d6c
commit 1012048515
8 changed files with 105 additions and 48 deletions

View File

@ -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::
<action> <service> service [on (random|one|single|<fqdn> node[s])]
Node-based command performs specified `action` on all or selected service's
node::
<action> [random|one|single] <service> node[s]
Network-management command is a subset of node-based query::
disable|enable network <network name> on <service> node[s]
<action> <service> service [on (random|one|single|<fqdn> 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::
<action> [random|one|single|<fqdn>] node[s] [with <service> 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::
<action> <network> network on [random|one|single|<fqdn>] node[s]
[with <service> 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
~~~~~~~~~~~~~~~~~~~~~~~~

View File

@ -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')

View File

@ -28,9 +28,12 @@ Human API understands commands like these (examples):
* freeze <service> service [on (random|one|single|<fqdn> node[s])]
[for <T> seconds]
* unfreeze <service> service [on (random|one|single|<fqdn> node[s])]
* reboot [random|one|single|<fqdn>] node[s] [with <service>]
* disable network <name> on <service> node[s]
* enable network <name> on <service> node[s]
* reboot [random|one|single|<fqdn>] node[s] [with <service> service]
* reset [random|one|single|<fqdn>] node[s] [with <service> service]
* disconnect <name> network on [random|one|single|<fqdn>] node[s]
[with <service> service]
* connect <name> network on [random|one|single|<fqdn>] node[s]
[with <service> service]
"""
@ -54,10 +57,10 @@ PATTERNS = [
'(\s+for\s+(?P<duration>\d+)\s+seconds)?' %
SERVICE_ACTIONS_PATTERN),
re.compile('(?P<action>%s)'
'(\s+(?P<network>\w+)\s+on)?'
'(\s+(?P<network>\w+)\s+network\s+on)?'
'(\s+(?P<node>%s|\S+))?'
'\s+node'
'(\s+with\s+(?P<service>\S+))?' %
'\s+nodes?'
'(\s+with\s+(?P<service>\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])

View File

@ -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_name> 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_name> network
:param network_name: name of network
"""

View File

@ -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

View File

@ -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',

View File

@ -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()

View File

@ -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',