Allow to run custom ansible modules on NodeCollection

Added run_task method to NodeCollection
Added add_module_paths

Change-Id: I9956787945525b95523d96f3975f580fa6ff2803
This commit is contained in:
Anton Studenov 2016-10-14 13:00:01 +03:00
parent 3b40beabd8
commit 9f5966194a
8 changed files with 80 additions and 3 deletions

View File

@ -17,6 +17,7 @@ import jsonschema
import pbr.version
import yaml
from os_faults.ansible import executor
from os_faults.api import error
from os_faults.api import human
from os_faults import registry
@ -120,3 +121,13 @@ def human_api(distractor, command):
:param command: text command
"""
human.execute(distractor, command)
def register_ansible_modules(paths):
"""Registers ansible modules by provided paths
Allows to use custom ansible modules in NodeCollection.run_task method
:param path: list of paths to folders with ansible modules
"""
executor.add_module_paths(paths)

View File

@ -23,6 +23,8 @@ from ansible.plugins import callback as callback_pkg
from ansible.vars import VariableManager
from oslo_log import log as logging
from os_faults.api import error
LOG = logging.getLogger(__name__)
STATUS_OK = 'OK'
@ -89,6 +91,26 @@ def resolve_relative_path(file_name):
return path
MODULE_PATHS = {
resolve_relative_path('os_faults/ansible/modules'),
}
def get_module_paths():
global MODULE_PATHS
return MODULE_PATHS
def add_module_paths(paths):
global MODULE_PATHS
for path in paths:
if not os.path.exists(path):
raise error.OSFError('{!r} does not exist'.format(path))
# find all subfolders
dirs = [x[0] for x in os.walk(path)]
MODULE_PATHS.update(dirs)
Options = namedtuple('Options',
['connection', 'password', 'module_path', 'forks',
'remote_user', 'private_key_file',
@ -102,8 +124,6 @@ class AnsibleRunner(object):
jump_host=None, private_key_file=None, become=None):
super(AnsibleRunner, self).__init__()
module_path = resolve_relative_path('os_faults/ansible/modules')
ssh_common_args = SSH_COMMON_ARGS
if jump_host:
ssh_common_args += (
@ -112,7 +132,8 @@ class AnsibleRunner(object):
% dict(key=private_key_file, user=remote_user, host=jump_host))
self.options = Options(
connection='smart', password=password, module_path=module_path,
connection='smart', password=password,
module_path=os.pathsep.join(get_module_paths()),
forks=forks, remote_user=remote_user,
private_key_file=private_key_file,
ssh_common_args=ssh_common_args, ssh_extra_args=None,

View File

@ -28,6 +28,15 @@ class NodeCollection(object):
:return: NodeCollection consisting just one node
"""
@abc.abstractmethod
def run_task(self, task, raise_on_error=True):
"""Run ansible task on node colection
:param task: ansible task as dict
:param raise_on_error: throw exception in case of error
:return: AnsibleExecutionRecord with results of task
"""
@public
def reboot(self):
"""Reboot all nodes gracefully

View File

@ -43,6 +43,11 @@ class DevStackNode(node_collection.NodeCollection):
def pick(self):
return self
def run_task(self, task, raise_on_error=True):
# TODO(astudenov): refactor DevStackManagement.execute
# to be consistent with api
self.cloud_management.execute(self.host.ip, task)
def reboot(self):
task = {'command': 'reboot'}
self.cloud_management.execute(self.host.ip, task)

View File

@ -59,6 +59,11 @@ class FuelNodeCollection(node_collection.NodeCollection):
power_management=self.power_management,
hosts=random.sample(self.hosts, count))
def run_task(self, task, raise_on_error=True):
logging.info('Run task: %s on nodes: %s', task, self)
self.cloud_management.execute_on_cloud(self.get_ips(), task,
raise_on_error=raise_on_error)
def reboot(self):
logging.info('Reboot nodes: %s', self)
task = {'command': 'reboot now'}
@ -352,6 +357,7 @@ class FuelManagement(cloud_management.CloudManagement):
:param hosts: List of host FQDNs
:param task: Ansible task
:param raise_on_error: throw exception in case of error
:return: Ansible execution result (list of records)
"""
if raise_on_error:

View File

@ -43,6 +43,11 @@ class DevStackNodeTestCase(test.TestCase):
def test_pick(self):
self.assertIs(self.node_collection, self.node_collection.pick())
def test_run_task(self):
self.node_collection.run_task({'foo': 'bar'})
self.mock_cloud_management.execute.assert_called_once_with(
'10.0.0.2', {'foo': 'bar'})
def test_reboot(self):
self.node_collection.reboot()
self.mock_cloud_management.execute.assert_called_once_with(

View File

@ -63,6 +63,12 @@ class FuelNodeCollectionTestCase(test.TestCase):
self.assertEqual(1, len(one))
self.assertIn(one.hosts[0], self.hosts)
def test_run_task(self):
self.node_collection.run_task({'foo': 'bar'}, raise_on_error=False)
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'], {'foo': 'bar'},
raise_on_error=False)
def test_pick_count(self):
two = self.node_collection.pick(count=2)
self.assertEqual(2, len(two))

View File

@ -17,6 +17,7 @@ import mock
import yaml
import os_faults
from os_faults.ansible import executor
from os_faults.api import error
from os_faults.drivers import devstack
from os_faults.drivers import fuel
@ -132,3 +133,16 @@ class OSFaultsTestCase(test.TestCase):
@mock.patch('os.path.exists', return_value=False)
def test_connect_no_config_files(self, mock_os_path_exists):
self.assertRaises(error.OSFError, os_faults.connect)
@mock.patch('os.path.exists', side_effect=lambda x: 'bad' not in x)
@mock.patch('os.walk', side_effect=lambda x: ([x, [], []],
[x + 'subdir', [], []]))
@mock.patch.object(executor, 'MODULE_PATHS', set())
def test_register_ansible_modules(self, mock_os_walk, mock_os_path_exists):
os_faults.register_ansible_modules(['/my/path/', '/other/path/'])
self.assertEqual(executor.get_module_paths(),
{'/my/path/', '/my/path/subdir',
'/other/path/', '/other/path/subdir'})
self.assertRaises(error.OSFError, os_faults.register_ansible_modules,
['/my/bad/path/'])