Merge "[ansible] add defaults to config"

This commit is contained in:
Zuul 2017-12-21 20:14:26 +00:00 committed by Gerrit Code Review
commit a24e5dd116
5 changed files with 235 additions and 65 deletions

View File

@ -891,6 +891,47 @@
# value) # value)
#image_store_keyfile = <None> #image_store_keyfile = <None>
# Name of the user to use for Ansible when connecting to the
# ramdisk over SSH. It may be overriden by per-node
# 'ansible_username' option in node's 'driver_info' field.
# (string value)
#default_username = ansible
# Absolute path to the private SSH key file to use by Ansible
# by default when connecting to the ramdisk over SSH. Default
# is to use default SSH keys configured for the user running
# the ironic-conductor service. Private keys with password
# must be pre-loaded into 'ssh-agent'. It may be overriden by
# per-node 'ansible_key_file' option in node's 'driver_info'
# field. (string value)
#default_key_file = <None>
# Path (relative to $playbooks_path or absolute) to the
# default playbook used for deployment. It may be overriden by
# per-node 'ansible_deploy_playbook' option in node's
# 'driver_info' field. (string value)
#default_deploy_playbook = deploy.yaml
# Path (relative to $playbooks_path or absolute) to the
# default playbook used for graceful in-band shutdown of the
# node. It may be overriden by per-node
# 'ansible_shutdown_playbook' option in node's 'driver_info'
# field. (string value)
#default_shutdown_playbook = shutdown.yaml
# Path (relative to $playbooks_path or absolute) to the
# default playbook used for node cleaning. It may be overriden
# by per-node 'ansible_clean_playbook' option in node's
# 'driver_info' field. (string value)
#default_clean_playbook = clean.yaml
# Path (relative to $playbooks_path or absolute) to the
# default auxiliary cleaning steps file used during the node
# cleaning. It may be overriden by per-node
# 'ansible_clean_steps_config' option in node's 'driver_info'
# field. (string value)
#default_clean_steps_config = clean_steps.yaml
[api] [api]

View File

@ -89,6 +89,51 @@ opts = [
'to image store. ' 'to image store. '
'Is not used by default playbooks included with ' 'Is not used by default playbooks included with '
'the driver.')), 'the driver.')),
cfg.StrOpt('default_username',
default='ansible',
help=_("Name of the user to use for Ansible when connecting "
"to the ramdisk over SSH. It may be overriden "
"by per-node 'ansible_username' option "
"in node's 'driver_info' field.")),
cfg.StrOpt('default_key_file',
help=_("Absolute path to the private SSH key file to use "
"by Ansible by default when connecting to the ramdisk "
"over SSH. Default is to use default SSH keys "
"configured for the user running the ironic-conductor "
"service. Private keys with password must be pre-loaded "
"into 'ssh-agent'. It may be overriden by per-node "
"'ansible_key_file' option in node's "
"'driver_info' field.")),
cfg.StrOpt('default_deploy_playbook',
default='deploy.yaml',
help=_("Path (relative to $playbooks_path or absolute) "
"to the default playbook used for deployment. "
"It may be overriden by per-node "
"'ansible_deploy_playbook' option in node's "
"'driver_info' field.")),
cfg.StrOpt('default_shutdown_playbook',
default='shutdown.yaml',
help=_("Path (relative to $playbooks_path or absolute) "
"to the default playbook used for graceful in-band "
"shutdown of the node. "
"It may be overriden by per-node "
"'ansible_shutdown_playbook' option in node's "
"'driver_info' field.")),
cfg.StrOpt('default_clean_playbook',
default='clean.yaml',
help=_("Path (relative to $playbooks_path or absolute) "
"to the default playbook used for node cleaning. "
"It may be overriden by per-node "
"'ansible_clean_playbook' option in node's "
"'driver_info' field.")),
cfg.StrOpt('default_clean_steps_config',
default='clean_steps.yaml',
help=_("Path (relative to $playbooks_path or absolute) "
"to the default auxiliary cleaning steps file used "
"during the node cleaning. "
"It may be overriden by per-node "
"'ansible_clean_steps_config' option in node's "
"'driver_info' field.")),
] ]

View File

@ -48,56 +48,78 @@ LOG = log.getLogger(__name__)
METRICS = metrics_utils.get_metrics_logger(__name__) METRICS = metrics_utils.get_metrics_logger(__name__)
DEFAULT_PLAYBOOKS = {
'deploy': 'deploy.yaml',
'shutdown': 'shutdown.yaml',
'clean': 'clean.yaml'
}
DEFAULT_CLEAN_STEPS = 'clean_steps.yaml'
OPTIONAL_PROPERTIES = { OPTIONAL_PROPERTIES = {
'ansible_deploy_username': _('Deploy ramdisk username for Ansible. ' 'ansible_username': _('Deploy ramdisk username for Ansible. '
'This user must have passwordless sudo ' 'This user must have passwordless sudo '
'permissions. Default is "ansible". ' 'permissions. Optional.'),
'Optional.'), 'ansible_key_file': _('Full path to private SSH key file. '
'ansible_deploy_key_file': _('Path to private key file. If not specified, ' 'If not specified, default keys for user running '
'default keys for user running ' 'ironic-conductor process will be used. '
'ironic-conductor process will be used. ' 'Note that for keys with password, those '
'Note that for keys with password, those ' 'must be pre-loaded into ssh-agent. '
'must be pre-loaded into ssh-agent. ' 'Optional.'),
'Optional.'), 'ansible_playbooks_path': _('Path to folder holding playbooks to use '
'ansible_deploy_playbook': _('Name of the Ansible playbook used for ' 'for this node. Optional. '
'deployment. Default is %s. Optional.' 'Default is set in ironic config.'),
) % DEFAULT_PLAYBOOKS['deploy'], 'ansible_deploy_playbook': _('Name of the Ansible playbook file inside '
'ansible_shutdown_playbook': _('Name of the Ansible playbook used to ' 'the "ansible_playbooks_path" folder which '
'power off the node in-band. ' 'is used for node deployment. Optional.'),
'Default is %s. Optional.' 'ansible_shutdown_playbook': _('Name of the Ansible playbook file inside '
) % DEFAULT_PLAYBOOKS['shutdown'], 'the "ansible_playbooks_path" folder which '
'ansible_clean_playbook': _('Name of the Ansible playbook used for ' 'is used for node shutdown. Optional.'),
'cleaning. Default is %s. Optional.' 'ansible_clean_playbook': _('Name of the Ansible playbook file inside '
) % DEFAULT_PLAYBOOKS['clean'], 'the "ansible_playbooks_path" folder which '
'ansible_clean_steps_config': _('Name of the file with default cleaning ' 'is used for node cleaning. Optional.'),
'steps configuration. Default is %s. ' 'ansible_clean_steps_config': _('Name of the file inside the '
'Optional.' '"ansible_playbooks_path" folder with '
) % DEFAULT_CLEAN_STEPS 'cleaning steps configuration. Optional.'),
} }
COMMON_PROPERTIES = OPTIONAL_PROPERTIES
INVENTORY_FILE = os.path.join(CONF.ansible.playbooks_path, 'inventory') COMMON_PROPERTIES = OPTIONAL_PROPERTIES
# TODO(pas-ha) remove in Rocky
DEPRECATED_PROPERTIES = {
'ansible_deploy_username': {
'name': 'ansible_username',
'warned': False},
'ansible_deploy_key_file': {
'name': 'ansible_key_file',
'warned': False}}
class PlaybookNotFound(exception.IronicException): class PlaybookNotFound(exception.IronicException):
_msg_fmt = _('Failed to set ansible playbook for action %(action)s') _msg_fmt = _('Failed to set ansible playbook for action %(action)s')
def _get_playbooks_path(node):
return node.driver_info.get('ansible_playbooks_path',
CONF.ansible.playbooks_path)
def _parse_ansible_driver_info(node, action='deploy'): def _parse_ansible_driver_info(node, action='deploy'):
user = node.driver_info.get('ansible_deploy_username', 'ansible') # TODO(pas-ha) remove in Rocky
key = node.driver_info.get('ansible_deploy_key_file') for old, new in DEPRECATED_PROPERTIES.items():
if old in node.driver_info:
if not new['warned']:
LOG.warning("Driver property '%(old)s' is deprecated, "
"and will be ignored in Rocky release. "
"Use '%(new)s' instead.", old=old, new=new['name'])
new['warned'] = True
# TODO(pas-ha) simplify in Rocky
user = node.driver_info.get(
'ansible_username',
node.driver_info.get('ansible_deploy_username',
CONF.ansible.default_username))
key = node.driver_info.get(
'ansible_key_file',
node.driver_info.get('ansible_deploy_key_file',
CONF.ansible.default_key_file))
playbook = node.driver_info.get('ansible_%s_playbook' % action, playbook = node.driver_info.get('ansible_%s_playbook' % action,
DEFAULT_PLAYBOOKS.get(action)) getattr(CONF.ansible,
'default_%s_playbook' % action,
None))
if not playbook: if not playbook:
raise PlaybookNotFound(action=action) raise PlaybookNotFound(action=action)
return playbook, user, key return os.path.basename(playbook), user, key
def _get_configdrive_path(basename): def _get_configdrive_path(basename):
@ -119,12 +141,14 @@ def _prepare_extra_vars(host_list, variables=None):
return extra_vars return extra_vars
def _run_playbook(name, extra_vars, key, tags=None, notags=None): def _run_playbook(node, name, extra_vars, key, tags=None, notags=None):
"""Execute ansible-playbook.""" """Execute ansible-playbook."""
playbook = os.path.join(CONF.ansible.playbooks_path, name) root = _get_playbooks_path(node)
playbook = os.path.join(root, name)
inventory = os.path.join(root, 'inventory')
ironic_vars = {'ironic': extra_vars} ironic_vars = {'ironic': extra_vars}
args = [CONF.ansible.ansible_playbook_script, playbook, args = [CONF.ansible.ansible_playbook_script, playbook,
'-i', INVENTORY_FILE, '-i', inventory,
'-e', json.dumps(ironic_vars), '-e', json.dumps(ironic_vars),
] ]
@ -326,9 +350,11 @@ def _validate_clean_steps(steps, node_uuid):
def _get_clean_steps(node, interface=None, override_priorities=None): def _get_clean_steps(node, interface=None, override_priorities=None):
"""Get cleaning steps.""" """Get cleaning steps."""
clean_steps_file = node.driver_info.get('ansible_clean_steps_config', clean_steps_file = node.driver_info.get(
DEFAULT_CLEAN_STEPS) 'ansible_clean_steps_config', CONF.ansible.default_clean_steps_config)
path = os.path.join(CONF.ansible.playbooks_path, clean_steps_file) path = os.path.join(node.driver_info.get('ansible_playbooks_path',
CONF.ansible.playbooks_path),
os.path.basename(clean_steps_file))
try: try:
with open(path) as f: with open(path) as f:
internal_steps = yaml.safe_load(f) internal_steps = yaml.safe_load(f)
@ -403,6 +429,8 @@ class AnsibleDeploy(agent_base.HeartbeatMixin, base.DeployInterface):
deploy_utils.check_for_missing_params(params, error_msg) deploy_utils.check_for_missing_params(params, error_msg)
# validate root device hints, proper exceptions are raised from there # validate root device hints, proper exceptions are raised from there
_parse_root_device_hints(node) _parse_root_device_hints(node)
# TODO(pas-ha) validate that all playbooks and ssh key (if set)
# are pointing to actual files
def _ansible_deploy(self, task, node_address): def _ansible_deploy(self, task, node_address):
"""Internal function for deployment to a node.""" """Internal function for deployment to a node."""
@ -418,7 +446,7 @@ class AnsibleDeploy(agent_base.HeartbeatMixin, base.DeployInterface):
LOG.debug('Starting deploy on node %s', node.uuid) LOG.debug('Starting deploy on node %s', node.uuid)
# any caller should manage exceptions raised from here # any caller should manage exceptions raised from here
_run_playbook(playbook, extra_vars, key) _run_playbook(node, playbook, extra_vars, key)
@METRICS.timer('AnsibleDeploy.deploy') @METRICS.timer('AnsibleDeploy.deploy')
@task_manager.require_exclusive_lock @task_manager.require_exclusive_lock
@ -501,8 +529,7 @@ class AnsibleDeploy(agent_base.HeartbeatMixin, base.DeployInterface):
{'node': node.uuid, 'step': stepname}) {'node': node.uuid, 'step': stepname})
step_tags = step['args'].get('tags', []) step_tags = step['args'].get('tags', [])
try: try:
_run_playbook(playbook, extra_vars, key, _run_playbook(node, playbook, extra_vars, key, tags=step_tags)
tags=step_tags)
except exception.InstanceDeployFailure as e: except exception.InstanceDeployFailure as e:
LOG.error("Ansible failed cleaning step %(step)s " LOG.error("Ansible failed cleaning step %(step)s "
"on node %(node)s.", "on node %(node)s.",
@ -591,7 +618,7 @@ class AnsibleDeploy(agent_base.HeartbeatMixin, base.DeployInterface):
node, action='shutdown') node, action='shutdown')
node_list = [(node.uuid, node_address, user, node.extra)] node_list = [(node.uuid, node_address, user, node.extra)]
extra_vars = _prepare_extra_vars(node_list) extra_vars = _prepare_extra_vars(node_list)
_run_playbook(playbook, extra_vars, key) _run_playbook(node, playbook, extra_vars, key)
_wait_until_powered_off(task) _wait_until_powered_off(task)
except Exception as e: except Exception as e:
LOG.warning('Failed to soft power off node %(node_uuid)s ' LOG.warning('Failed to soft power off node %(node_uuid)s '

View File

@ -41,8 +41,8 @@ INSTANCE_INFO = {
DRIVER_INFO = { DRIVER_INFO = {
'deploy_kernel': 'glance://deploy_kernel_uuid', 'deploy_kernel': 'glance://deploy_kernel_uuid',
'deploy_ramdisk': 'glance://deploy_ramdisk_uuid', 'deploy_ramdisk': 'glance://deploy_ramdisk_uuid',
'ansible_deploy_username': 'test', 'ansible_username': 'test',
'ansible_deploy_key_file': '/path/key', 'ansible_key_file': '/path/key',
'ipmi_address': '127.0.0.1', 'ipmi_address': '127.0.0.1',
} }
DRIVER_INTERNAL_INFO = { DRIVER_INTERNAL_INFO = {
@ -71,12 +71,47 @@ class AnsibleDeployTestCaseBase(db_base.DbTestCase):
class TestAnsibleMethods(AnsibleDeployTestCaseBase): class TestAnsibleMethods(AnsibleDeployTestCaseBase):
def test__parse_ansible_driver_info(self): def test__parse_ansible_driver_info(self):
self.node.driver_info['ansible_deploy_playbook'] = 'spam.yaml'
playbook, user, key = ansible_deploy._parse_ansible_driver_info( playbook, user, key = ansible_deploy._parse_ansible_driver_info(
self.node, 'deploy') self.node, 'deploy')
self.assertEqual(ansible_deploy.DEFAULT_PLAYBOOKS['deploy'], playbook) self.assertEqual('spam.yaml', playbook)
self.assertEqual('test', user) self.assertEqual('test', user)
self.assertEqual('/path/key', key) self.assertEqual('/path/key', key)
def test__parse_ansible_driver_info_defaults(self):
self.node.driver_info.pop('ansible_username')
self.node.driver_info.pop('ansible_key_file')
self.config(group='ansible',
default_username='spam',
default_key_file='/ham/eggs',
default_deploy_playbook='parrot.yaml')
playbook, user, key = ansible_deploy._parse_ansible_driver_info(
self.node, 'deploy')
# testing absolute path to the playbook
self.assertEqual('parrot.yaml', playbook)
self.assertEqual('spam', user)
self.assertEqual('/ham/eggs', key)
@mock.patch.object(ansible_deploy.LOG, 'warning', autospec=True)
def test__parse_ansible_driver_info_deprecated_opts(self, warn_mock):
self.node.driver_info[
'ansible_deploy_username'] = self.node.driver_info.pop(
'ansible_username')
self.node.driver_info[
'ansible_deploy_key_file'] = self.node.driver_info.pop(
'ansible_key_file')
playbook, user, key = ansible_deploy._parse_ansible_driver_info(
self.node, 'deploy')
self.assertEqual(ansible_deploy.CONF.ansible.default_deploy_playbook,
playbook)
self.assertEqual('test', user)
self.assertEqual('/path/key', key)
self.assertEqual(2, warn_mock.call_count)
# check that we remeber about warnings havig been displayed
playbook, user, key = ansible_deploy._parse_ansible_driver_info(
self.node, 'deploy')
self.assertEqual(2, warn_mock.call_count)
def test__parse_ansible_driver_info_no_playbook(self): def test__parse_ansible_driver_info_no_playbook(self):
self.assertRaises(exception.IronicException, self.assertRaises(exception.IronicException,
ansible_deploy._parse_ansible_driver_info, ansible_deploy._parse_ansible_driver_info,
@ -101,13 +136,14 @@ class TestAnsibleMethods(AnsibleDeployTestCaseBase):
self.config(group='ansible', ansible_extra_args='--timeout=100') self.config(group='ansible', ansible_extra_args='--timeout=100')
extra_vars = {'foo': 'bar'} extra_vars = {'foo': 'bar'}
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key', ansible_deploy._run_playbook(self.node, 'deploy',
extra_vars, '/path/to/key',
tags=['spam'], notags=['ham']) tags=['spam'], notags=['ham'])
execute_mock.assert_called_once_with( execute_mock.assert_called_once_with(
'env', 'ANSIBLE_CONFIG=/path/to/config', 'env', 'ANSIBLE_CONFIG=/path/to/config',
'ansible-playbook', '/path/to/playbooks/deploy', '-i', 'ansible-playbook', '/path/to/playbooks/deploy', '-i',
ansible_deploy.INVENTORY_FILE, '-e', '{"ironic": {"foo": "bar"}}', '/path/to/playbooks/inventory', '-e', '{"ironic": {"foo": "bar"}}',
'--tags=spam', '--skip-tags=ham', '--tags=spam', '--skip-tags=ham',
'--private-key=/path/to/key', '-vvv', '--timeout=100') '--private-key=/path/to/key', '-vvv', '--timeout=100')
@ -119,12 +155,13 @@ class TestAnsibleMethods(AnsibleDeployTestCaseBase):
self.config(debug=False) self.config(debug=False)
extra_vars = {'foo': 'bar'} extra_vars = {'foo': 'bar'}
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key') ansible_deploy._run_playbook(self.node, 'deploy', extra_vars,
'/path/to/key')
execute_mock.assert_called_once_with( execute_mock.assert_called_once_with(
'env', 'ANSIBLE_CONFIG=/path/to/config', 'env', 'ANSIBLE_CONFIG=/path/to/config',
'ansible-playbook', '/path/to/playbooks/deploy', '-i', 'ansible-playbook', '/path/to/playbooks/deploy', '-i',
ansible_deploy.INVENTORY_FILE, '-e', '{"ironic": {"foo": "bar"}}', '/path/to/playbooks/inventory', '-e', '{"ironic": {"foo": "bar"}}',
'--private-key=/path/to/key') '--private-key=/path/to/key')
@mock.patch.object(com_utils, 'execute', return_value=('out', 'err'), @mock.patch.object(com_utils, 'execute', return_value=('out', 'err'),
@ -135,12 +172,13 @@ class TestAnsibleMethods(AnsibleDeployTestCaseBase):
self.config(debug=True) self.config(debug=True)
extra_vars = {'foo': 'bar'} extra_vars = {'foo': 'bar'}
ansible_deploy._run_playbook('deploy', extra_vars, '/path/to/key') ansible_deploy._run_playbook(self.node, 'deploy', extra_vars,
'/path/to/key')
execute_mock.assert_called_once_with( execute_mock.assert_called_once_with(
'env', 'ANSIBLE_CONFIG=/path/to/config', 'env', 'ANSIBLE_CONFIG=/path/to/config',
'ansible-playbook', '/path/to/playbooks/deploy', '-i', 'ansible-playbook', '/path/to/playbooks/deploy', '-i',
ansible_deploy.INVENTORY_FILE, '-e', '{"ironic": {"foo": "bar"}}', '/path/to/playbooks/inventory', '-e', '{"ironic": {"foo": "bar"}}',
'--private-key=/path/to/key', '-vvvv') '--private-key=/path/to/key', '-vvvv')
@mock.patch.object(com_utils, 'execute', @mock.patch.object(com_utils, 'execute',
@ -155,12 +193,13 @@ class TestAnsibleMethods(AnsibleDeployTestCaseBase):
exc = self.assertRaises(exception.InstanceDeployFailure, exc = self.assertRaises(exception.InstanceDeployFailure,
ansible_deploy._run_playbook, ansible_deploy._run_playbook,
'deploy', extra_vars, '/path/to/key') self.node, 'deploy', extra_vars,
'/path/to/key')
self.assertIn('VIKINGS!', six.text_type(exc)) self.assertIn('VIKINGS!', six.text_type(exc))
execute_mock.assert_called_once_with( execute_mock.assert_called_once_with(
'env', 'ANSIBLE_CONFIG=/path/to/config', 'env', 'ANSIBLE_CONFIG=/path/to/config',
'ansible-playbook', '/path/to/playbooks/deploy', '-i', 'ansible-playbook', '/path/to/playbooks/deploy', '-i',
ansible_deploy.INVENTORY_FILE, '-e', '{"ironic": {"foo": "bar"}}', '/path/to/playbooks/inventory', '-e', '{"ironic": {"foo": "bar"}}',
'--private-key=/path/to/key') '--private-key=/path/to/key')
def test__parse_partitioning_info_root_msdos(self): def test__parse_partitioning_info_root_msdos(self):
@ -619,7 +658,7 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase):
prepare_extra_mock.assert_called_once_with( prepare_extra_mock.assert_called_once_with(
ironic_nodes['ironic_nodes']) ironic_nodes['ironic_nodes'])
run_playbook_mock.assert_called_once_with( run_playbook_mock.assert_called_once_with(
'test_pl', ironic_nodes, 'test_k', tags=['clean']) task.node, 'test_pl', ironic_nodes, 'test_k', tags=['clean'])
@mock.patch.object(ansible_deploy, '_parse_ansible_driver_info', @mock.patch.object(ansible_deploy, '_parse_ansible_driver_info',
return_value=('test_pl', 'test_u', 'test_k'), return_value=('test_pl', 'test_u', 'test_k'),
@ -735,7 +774,7 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase):
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})], [(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
variables=_vars) variables=_vars)
run_playbook_mock.assert_called_once_with( run_playbook_mock.assert_called_once_with(
'test_pl', ironic_nodes, 'test_k') task.node, 'test_pl', ironic_nodes, 'test_k')
@mock.patch.object(ansible_deploy, '_run_playbook', autospec=True) @mock.patch.object(ansible_deploy, '_run_playbook', autospec=True)
@mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True) @mock.patch.object(ansible_deploy, '_prepare_extra_vars', autospec=True)
@ -770,8 +809,8 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase):
prepare_extra_mock.assert_called_once_with( prepare_extra_mock.assert_called_once_with(
[(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})], [(self.node['uuid'], '127.0.0.1', 'test_u', {'ham': 'spam'})],
variables=_vars) variables=_vars)
run_playbook_mock.assert_called_once_with('test_pl', ironic_nodes, run_playbook_mock.assert_called_once_with(
'test_k') task.node, 'test_pl', ironic_nodes, 'test_k')
@mock.patch.object(fake.FakePower, 'get_power_state', @mock.patch.object(fake.FakePower, 'get_power_state',
return_value=states.POWER_OFF) return_value=states.POWER_OFF)
@ -804,7 +843,7 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase):
@mock.patch.object(utils, 'node_power_action', autospec=True) @mock.patch.object(utils, 'node_power_action', autospec=True)
def test_reboot_and_finish_deploy_soft_poweroff_retry(self, def test_reboot_and_finish_deploy_soft_poweroff_retry(self,
power_action_mock, power_action_mock,
ansible_mock): run_playbook_mock):
self.config(group='ansible', self.config(group='ansible',
post_deploy_get_power_state_retry_interval=0) post_deploy_get_power_state_retry_interval=0)
self.config(group='ansible', self.config(group='ansible',
@ -834,8 +873,8 @@ class TestAnsibleDeploy(AnsibleDeployTestCaseBase):
((task, states.POWER_ON),)] ((task, states.POWER_ON),)]
self.assertEqual(expected_power_calls, self.assertEqual(expected_power_calls,
power_action_mock.call_args_list) power_action_mock.call_args_list)
ansible_mock.assert_called_once_with('shutdown.yaml', run_playbook_mock.assert_called_once_with(
mock.ANY, mock.ANY) task.node, 'shutdown.yaml', mock.ANY, mock.ANY)
@mock.patch.object(ansible_deploy, '_get_node_ip', autospec=True, @mock.patch.object(ansible_deploy, '_get_node_ip', autospec=True,
return_value='1.2.3.4') return_value='1.2.3.4')

View File

@ -9,3 +9,21 @@ features:
its subclasses, but must be explicitly enabled in the its subclasses, but must be explicitly enabled in the
``[DEFAULT]enabled_deploy_interfaces`` configuration file option ``[DEFAULT]enabled_deploy_interfaces`` configuration file option
to actually allow setting nodes to use it. to actually allow setting nodes to use it.
For migration from the ``staging-ansible`` interface from the
``ironic-staging-drivers`` project to this ``ansible`` interface,
operators have to consider the following differences:
- callback-less operation is not supported
- driver_info fields 'ansible_deploy_username' and
'ansible_deploy_key_file' are deprecated and will be removed
in the Rocky release, use 'ansible_username' and 'ansible_key_file'
respectively
- base path for playbooks can be defined in driver_info as well
(as 'ansible_playbooks_path' field, defaults to the value of
``[ansible]/playbooks_path`` from ironic configuration file
- default playbooks for actions and cleaning steps file can be set in
ironic configuration file as various ``[ansible]/default_*`` options.
Please read the ``ansible`` deploy interface documentation for more
information.