diff --git a/playbooks/library/os_ironic.py b/playbooks/library/os_ironic.py
deleted file mode 100644
index 7b2137163..000000000
--- a/playbooks/library/os_ironic.py
+++ /dev/null
@@ -1,351 +0,0 @@
-#!/usr/bin/python
-# coding: utf-8 -*-
-
-# (c) 2014, Hewlett-Packard Development Company, L.P.
-#
-# This module is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This software is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this software. If not, see .
-
-try:
- import shade
- HAS_SHADE = True
-except ImportError:
- HAS_SHADE = False
-
-import jsonpatch
-DOCUMENTATION = '''
----
-module: os_ironic
-short_description: Create/Delete Bare Metal Resources from OpenStack
-extends_documentation_fragment: openstack
-description:
- - Create or Remove Ironic nodes from OpenStack.
-options:
- state:
- description:
- - Indicates desired state of the resource
- choices: ['present', 'absent']
- default: present
- uuid:
- description:
- - globally unique identifier (UUID) to be given to the resource. Will
- be auto-generated if not specified, and name is specified.
- - Definition of a UUID will always take precedence to a name value.
- required: false
- default: None
- name:
- description:
- - unique name identifier to be given to the resource.
- required: false
- default: None
- driver:
- description:
- - The name of the Ironic Driver to use with this node.
- required: true
- default: None
- chassis_uuid:
- description:
- - Associate the node with a pre-defined chassis.
- required: false
- default: None
- ironic_url:
- description:
- - If noauth mode is utilized, this is required to be set to the
- endpoint URL for the Ironic API. Use with "auth" and "auth_type"
- settings set to None.
- required: false
- default: None
- driver_info:
- description:
- - Information for this server's driver. Will vary based on which
- driver is in use. Any sub-field which is populated will be validated
- during creation.
- power:
- - Information necessary to turn this server on / off. This often
- includes such things as IPMI username, password, and IP address.
- required: true
- deploy:
- - Information necessary to deploy this server directly, without
- using Nova. THIS IS NOT RECOMMENDED.
- console:
- - Information necessary to connect to this server's serial console.
- Not all drivers support this.
- management:
- - Information necessary to interact with this server's management
- interface. May be shared by power_info in some cases.
- required: true
- nics:
- description:
- - A list of network interface cards, eg, " - mac: aa:bb:cc:aa:bb:cc"
- required: true
- properties:
- description:
- - Definition of the physical characteristics of this server, used for
- scheduling purposes
- cpu_arch:
- description:
- - CPU architecture (x86_64, i686, ...)
- default: x86_64
- cpus:
- description:
- - Number of CPU cores this machine has
- default: 1
- ram:
- description:
- - amount of RAM this machine has, in MB
- default: 1
- disk_size:
- description:
- - size of first storage device in this machine (typically
- /dev/sda), in GB
- default: 1
- skip_update_of_driver_password:
- description:
- - Allows the code that would assert changes to nodes to skip the
- update if the change is a single line consisting of the password
- field. As of Kilo, by default, passwords are always masked to API
- requests, which means the logic as a result always attempts to
- re-assert the password field.
- required: false
- default: false
-
-requirements: ["shade", "jsonpatch"]
-'''
-
-EXAMPLES = '''
-# Enroll a node with some basic properties and driver info
-- os_ironic:
- cloud: "devstack"
- driver: "pxe_ipmitool"
- uuid: "00000000-0000-0000-0000-000000000002"
- properties:
- cpus: 2
- cpu_arch: "x86_64"
- ram: 8192
- disk_size: 64
- nics:
- - mac: "aa:bb:cc:aa:bb:cc"
- - mac: "dd:ee:ff:dd:ee:ff"
- driver_info:
- power:
- ipmi_address: "1.2.3.4"
- ipmi_username: "admin"
- ipmi_password: "adminpass"
- chassis_uuid: "00000000-0000-0000-0000-000000000001"
-
-'''
-
-
-def _parse_properties(module):
- p = module.params['properties']
- props = dict(
- cpu_arch=p.get('cpu_arch') if p.get('cpu_arch') else 'x86_64',
- cpus=p.get('cpus') if p.get('cpus') else 1,
- memory_mb=p.get('ram') if p.get('ram') else 1,
- local_gb=p.get('disk_size') if p.get('disk_size') else 1,
- )
- return props
-
-
-def _parse_driver_info(module):
- p = module.params['driver_info']
- info = p.get('power')
- if not info:
- raise shade.OpenStackCloudException(
- "driver_info['power'] is required")
- if p.get('console'):
- info.update(p.get('console'))
- if p.get('management'):
- info.update(p.get('management'))
- if p.get('deploy'):
- info.update(p.get('deploy'))
- return info
-
-
-def _choose_id_value(module):
- if module.params['uuid']:
- return module.params['uuid']
- if module.params['name']:
- return module.params['name']
- return None
-
-
-def _is_value_true(value):
- true_values = [True, 'yes', 'Yes', 'True', 'true']
- if value in true_values:
- return True
- return False
-
-
-def _choose_if_password_only(module, patch):
- if len(patch) is 1:
- if 'password' in patch[0]['path'] and _is_value_true(
- module.params['skip_update_of_masked_password']):
- # Return false to aabort update as the password appears
- # to be the only element in the patch.
- return False
- return True
-
-
-def _exit_node_not_updated(module, server):
- module.exit_json(
- changed=False,
- result="Node not updated",
- uuid=server['uuid'],
- provision_state=server['provision_state']
- )
-
-
-def main():
- argument_spec = openstack_full_argument_spec(
- auth_type=dict(required=False),
- uuid=dict(required=False),
- name=dict(required=False),
- driver=dict(required=False),
- driver_info=dict(type='dict', required=True),
- nics=dict(type='list', required=True),
- properties=dict(type='dict', default={}),
- ironic_url=dict(required=False),
- chassis_uuid=dict(required=False),
- skip_update_of_masked_password=dict(required=False, choices=BOOLEANS),
- state=dict(required=False, default='present')
- )
- module_kwargs = openstack_module_kwargs()
- module = AnsibleModule(argument_spec, **module_kwargs)
-
- if not HAS_SHADE:
- module.fail_json(msg='shade is required for this module')
- if (module.params['auth_type'] in [None, 'None'] and
- module.params['ironic_url'] is None):
- module.fail_json(msg="Authentication appears to be disabled, "
- "Please define an ironic_url parameter")
-
- if (module.params['ironic_url'] and
- module.params['auth_type'] in [None, 'None']):
- module.params['auth'] = dict(
- endpoint=module.params['ironic_url']
- )
-
- node_id = _choose_id_value(module)
-
- try:
- cloud = shade.operator_cloud(**module.params)
- server = cloud.get_machine(node_id)
- if module.params['state'] == 'present':
- if module.params['driver'] is None:
- module.fail_json(msg="A driver must be defined in order "
- "to set a node to present.")
-
- properties = _parse_properties(module)
- driver_info = _parse_driver_info(module)
- kwargs = dict(
- driver=module.params['driver'],
- properties=properties,
- driver_info=driver_info,
- name=module.params['name'],
- )
-
- if module.params['chassis_uuid']:
- kwargs['chassis_uuid'] = module.params['chassis_uuid']
-
- if server is None:
- # Note(TheJulia): Add a specific UUID to the request if
- # present in order to be able to re-use kwargs for if
- # the node already exists logic, since uuid cannot be
- # updated.
- if module.params['uuid']:
- kwargs['uuid'] = module.params['uuid']
-
- server = cloud.register_machine(module.params['nics'],
- **kwargs)
- module.exit_json(changed=True, uuid=server['uuid'],
- provision_state=server['provision_state'])
- else:
- # TODO(TheJulia): Presently this does not support updating
- # nics. Support needs to be added.
- #
- # Note(TheJulia): This message should never get logged
- # however we cannot realistically proceed if neither a
- # name or uuid was supplied to begin with.
- if not node_id:
- module.fail_json(msg="A uuid or name value "
- "must be defined")
-
- # Note(TheJulia): Constructing the configuration to compare
- # against. The items listed in the server_config block can
- # be updated via the API.
-
- server_config = dict(
- driver=server['driver'],
- properties=server['properties'],
- driver_info=server['driver_info'],
- name=server['name'],
- )
-
- # Add the pre-existing chassis_uuid only if
- # it is present in the server configuration.
- if hasattr(server, 'chassis_uuid'):
- server_config['chassis_uuid'] = server['chassis_uuid']
-
- # Note(TheJulia): If a password is defined and concealed, a
- # patch will always be generated and re-asserted.
- patch = jsonpatch.JsonPatch.from_diff(server_config, kwargs)
-
- if not patch:
- _exit_node_not_updated(module, server)
- elif _choose_if_password_only(module, list(patch)):
- # Note(TheJulia): Normally we would allow the general
- # exception catch below, however this allows a specific
- # message.
- try:
- server = cloud.patch_machine(
- server['uuid'],
- list(patch))
- except Exception as e:
- module.fail_json(msg="Failed to update node, "
- "Error: %s" % e.message)
-
- # Enumerate out a list of changed paths.
- change_list = []
- for change in list(patch):
- change_list.append(change['path'])
- module.exit_json(changed=True,
- result="Node Updated",
- changes=change_list,
- uuid=server['uuid'],
- provision_state=server['provision_state'])
-
- # Return not updated by default as the conditions were not met
- # to update.
- _exit_node_not_updated(module, server)
-
- if module.params['state'] == 'absent':
- if not node_id:
- module.fail_json(msg="A uuid or name value must be defined "
- "in order to remove a node.")
-
- if server is not None:
- cloud.unregister_machine(module.params['nics'],
- server['uuid'])
- module.exit_json(changed=True, result="deleted")
- else:
- module.exit_json(changed=False, result="Server not found")
-
- except shade.OpenStackCloudException as e:
- module.fail_json(msg=e.message)
-
-
-# this is magic, see lib/ansible/module_common.py
-from ansible.module_utils.basic import *
-from ansible.module_utils.openstack import *
-main()
diff --git a/playbooks/library/os_ironic_node.py b/playbooks/library/os_ironic_node.py
deleted file mode 100644
index a7186ad4a..000000000
--- a/playbooks/library/os_ironic_node.py
+++ /dev/null
@@ -1,333 +0,0 @@
-#!/usr/bin/python
-# coding: utf-8 -*-
-
-# (c) 2015, Hewlett-Packard Development Company, L.P.
-#
-# This module is free software: you can redistribute it and/or modify
-# it under the terms of the GNU General Public License as published by
-# the Free Software Foundation, either version 3 of the License, or
-# (at your option) any later version.
-#
-# This software is distributed in the hope that it will be useful,
-# but WITHOUT ANY WARRANTY; without even the implied warranty of
-# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
-# GNU General Public License for more details.
-#
-# You should have received a copy of the GNU General Public License
-# along with this software. If not, see .
-
-try:
- import shade
- HAS_SHADE = True
-except ImportError:
- HAS_SHADE = False
-
-DOCUMENTATION = '''
----
-module: os_ironic_node
-short_description: Activate/Deactivate Bare Metal Resources from OpenStack
-extends_documentation_fragment: openstack
-description:
- - Deploy to nodes controlled by Ironic.
-options:
- state:
- description:
- - Indicates desired state of the resource
- choices: ['present', 'absent']
- default: present
- deploy:
- description:
- - Indicates if the resource should be deployed. Allows for deployment
- logic to be disengaged and control of the node power or maintenance
- state to be changed.
- choices: ['true', 'false']
- default: true
- uuid:
- description:
- - globally unique identifier (UUID) to be given to the resource.
- required: false
- default: None
- ironic_url:
- description:
- - If noauth mode is utilized, this is required to be set to the
- endpoint URL for the Ironic API. Use with "auth" and "auth_type"
- settings set to None.
- required: false
- default: None
- config_drive:
- description:
- - A configdrive file or HTTP(S) URL that will be passed along to the
- node.
- required: false
- default: None
- instance_info:
- description:
- - Definition of the instance information which is used to deploy
- the node. This information is only required when an instance is
- set to present.
- image_source:
- description:
- - An HTTP(S) URL where the image can be retrieved from.
- image_checksum:
- description:
- - The checksum of image_source.
- image_disk_format:
- description:
- - The type of image that has been requested to be deployed.
- power:
- description:
- - A setting to allow power state to be asserted allowing nodes
- that are not yet deployed to be powered on, and nodes that
- are deployed to be powered off.
- choices: ['present', 'absent']
- default: present
- maintenance:
- description:
- - A setting to allow the direct control if a node is in
- maintenance mode.
- required: false
- default: false
- maintenance_reason:
- description:
- - A string expression regarding the reason a node is in a
- maintenance mode.
- required: false
- default: None
-
-requirements: ["shade"]
-'''
-
-EXAMPLES = '''
-# Activate a node by booting an image with a configdrive attached
-os_ironic_node:
- cloud: "openstack"
- uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69"
- state: present
- power: present
- deploy: True
- maintenance: False
- config_drive: "http://192.168.1.1/host-configdrive.iso"
- instance_info:
- image_source: "http://192.168.1.1/deploy_image.img"
- image_checksum: "356a6b55ecc511a20c33c946c4e678af"
- image_disk_format: "qcow"
- delegate_to: localhost
-'''
-
-
-def _choose_id_value(module):
- if module.params['uuid']:
- return module.params['uuid']
- if module.params['name']:
- return module.params['name']
- return None
-
-
-# TODO(TheJulia): Change this over to use the machine patch method
-# in shade once it is available.
-def _prepare_instance_info_patch(instance_info):
- patch = []
- patch.append({
- 'op': 'replace',
- 'path': '/instance_info',
- 'value': instance_info
- })
- return patch
-
-
-def _is_true(value):
- true_values = [True, 'yes', 'Yes', 'True', 'true', 'present', 'on']
- if value in true_values:
- return True
- return False
-
-
-def _is_false(value):
- false_values = [False, None, 'no', 'No', 'False', 'false', 'absent', 'off']
- if value in false_values:
- return True
- return False
-
-
-def _check_set_maintenance(module, cloud, node):
- if _is_true(module.params['maintenance']):
- if _is_false(node['maintenance']):
- cloud.set_machine_maintenance_state(
- node['uuid'],
- True,
- reason=module.params['maintenance_reason'])
- module.exit_json(changed=True, msg="Node has been set into "
- "maintenance mode")
- else:
- # User has requested maintenance state, node is already in the
- # desired state, checking to see if the reason has changed.
- if (str(node['maintenance_reason']) not in
- str(module.params['maintenance_reason'])):
- cloud.set_machine_maintenance_state(
- node['uuid'],
- True,
- reason=module.params['maintenance_reason'])
- module.exit_json(changed=True, msg="Node maintenance reason "
- "updated, cannot take any "
- "additional action.")
- elif _is_false(module.params['maintenance']):
- if node['maintenance'] is True:
- cloud.remove_machine_from_maintenance(node['uuid'])
- return True
- else:
- module.fail_json(msg="maintenance parameter was set but a valid "
- "the value was not recognized.")
- return False
-
-
-def _check_set_power_state(module, cloud, node):
- if 'power on' in str(node['power_state']):
- if _is_false(module.params['power']):
- # User has requested the node be powered off.
- cloud.set_machine_power_off(node['uuid'])
- module.exit_json(changed=True, msg="Power requested off")
- if 'power off' in str(node['power_state']):
- if (_is_false(module.params['power']) and
- _is_false(module.params['state'])):
- return False
- if (_is_false(module.params['power']) and
- _is_false(module.params['state'])):
- module.exit_json(
- changed=False,
- msg="Power for node is %s, node must be reactivated "
- "OR set to state absent"
- )
- # In the event the power has been toggled on and
- # deployment has been requested, we need to skip this
- # step.
- if (_is_true(module.params['power']) and
- _is_false(module.params['deploy'])):
- # Node is powered down when it is not awaiting to be provisioned
- cloud.set_machine_power_on(node['uuid'])
- return True
- # Default False if no action has been taken.
- return False
-
-
-def main():
- argument_spec = openstack_full_argument_spec(
- auth_type=dict(required=False),
- uuid=dict(required=False),
- name=dict(required=False),
- instance_info=dict(type='dict', required=False),
- config_drive=dict(required=False),
- ironic_url=dict(required=False),
- state=dict(required=False, default='present'),
- maintenance=dict(required=False),
- maintenance_reason=dict(required=False),
- power=dict(required=False, default='present'),
- deploy=dict(required=False, default=True),
- )
- module_kwargs = openstack_module_kwargs()
- module = AnsibleModule(argument_spec, **module_kwargs)
- if not HAS_SHADE:
- module.fail_json(msg='shade is required for this module')
- if (module.params['auth_type'] in [None, 'None'] and
- module.params['ironic_url'] is None):
- module.fail_json(msg="Authentication appears disabled, Please "
- "define an ironic_url parameter")
-
- if (module.params['ironic_url'] and
- module.params['auth_type'] in [None, 'None']):
- module.params['auth'] = dict(
- endpoint=module.params['ironic_url']
- )
-
- node_id = _choose_id_value(module)
-
- if not node_id:
- module.fail_json(msg="A uuid or name value must be defined "
- "to use this module.")
- try:
- cloud = shade.operator_cloud(**module.params)
- node = cloud.get_machine(node_id)
-
- if node is None:
- module.fail_json(msg="node not found")
-
- uuid = node['uuid']
- instance_info = module.params['instance_info']
- changed = False
-
- # User has reqeusted desired state to be in maintenance state.
- if module.params['state'] is 'maintenance':
- module.params['maintenance'] = True
-
- if node['provision_state'] in [
- 'cleaning',
- 'deleting',
- 'wait call-back']:
- module.fail_json(msg="Node is in %s state, cannot act upon the "
- "request as the node is in a transition "
- "state" % node['provision_state'])
- # TODO(TheJulia) This is in-development code, that requires
- # code in the shade library that is still in development.
- if _check_set_maintenance(module, cloud, node):
- if node['provision_state'] in 'active':
- module.exit_json(changed=True,
- result="Maintenance state changed")
- changed = True
- node = cloud.get_machine(node_id)
-
- if _check_set_power_state(module, cloud, node):
- changed = True
- node = cloud.get_machine(node_id)
-
- if _is_true(module.params['state']):
- if _is_false(module.params['deploy']):
- module.exit_json(
- changed=changed,
- result="User request has explicitly disabled "
- "deployment logic"
- )
-
- if 'active' in node['provision_state']:
- module.exit_json(
- changed=changed,
- result="Node already in an active state."
- )
-
- if instance_info is None:
- module.fail_json(
- changed=changed,
- msg="When setting an instance to present, "
- "instance_info is a required variable.")
-
- # TODO(TheJulia): Update instance info, however info is
- # deployment specific. Perhaps consider adding rebuild
- # support, although there is a known desire to remove
- # rebuild support from Ironic at some point in the future.
- patch = _prepare_instance_info_patch(instance_info)
- cloud.set_node_instance_info(uuid, patch)
- cloud.validate_node(uuid)
- cloud.activate_node(uuid, module.params['config_drive'])
- # TODO(TheJulia): Add more error checking and a wait option.
- # We will need to loop, or just add the logic to shade,
- # although this could be a very long running process as
- # baremetal deployments are not a "quick" task.
- module.exit_json(changed=changed, result="node activated")
-
- elif _is_false(module.params['state']):
- if node['provision_state'] not in "deleted":
- cloud.purge_node_instance_info(uuid)
- cloud.deactivate_node(uuid)
- module.exit_json(changed=True, result="deleted")
- else:
- module.exit_json(changed=False, result="node not found")
- else:
- module.fail_json(msg="State must be present, absent, "
- "maintenance, off")
-
- except shade.OpenStackCloudException as e:
- module.fail_json(msg=e.message)
-
-
-# this is magic, see lib/ansible/module_common.py
-from ansible.module_utils.basic import *
-from ansible.module_utils.openstack import *
-main()
diff --git a/releasenotes/notes/ansible-2.0-support-81fa15cc27c28ba8.yaml b/releasenotes/notes/ansible-2.0-support-81fa15cc27c28ba8.yaml
new file mode 100644
index 000000000..00b3915a1
--- /dev/null
+++ b/releasenotes/notes/ansible-2.0-support-81fa15cc27c28ba8.yaml
@@ -0,0 +1,16 @@
+---
+upgrade:
+ - Bifrost has moved to focusing its use on Ansible 2.0.
+ While Ansible 2.0 is relatively new, it has been stable
+ development for quite some time. If a pre-existing user
+ intends to reinstall/upgrade their environment, they may find
+ the need to remove their pre-existing ansible environment
+ located at ``/opt/stack/ansible``.
+deprecations:
+ - Moving forward, bifrost will be targeting use of
+ Ansible 2.0. Due to some style/configuration changes,
+ some roles have been marked in their metadata as being
+ intended and only functional with Ansible 2.0 due to
+ required features having been added in 2.0 that were
+ not present or available to reproduce in a 1.9.x
+ compatible way.
diff --git a/scripts/env-setup.sh b/scripts/env-setup.sh
index 496b3faa5..e421d4883 100755
--- a/scripts/env-setup.sh
+++ b/scripts/env-setup.sh
@@ -1,5 +1,5 @@
#!/bin/bash
-set -e
+set -eu
ANSIBLE_GIT_URL=${ANSIBLE_GIT_URL:-https://github.com/ansible/ansible.git}
# Note(TheJulia): Normally this should be stable-2.0, pinning due to
@@ -7,6 +7,14 @@ ANSIBLE_GIT_URL=${ANSIBLE_GIT_URL:-https://github.com/ansible/ansible.git}
# https://github.com/ansible/ansible-modules-core/issues/2804
ANSIBLE_GIT_BRANCH=${ANSIBLE_GIT_BRANCH:-v2.0.0.0-1}
+function check_get_module () {
+ local file=${1}
+ local url=${2}
+ if [ ! -e ${file} ]; then
+ wget -O ${file} ${url}
+ fi
+}
+
if [ -x '/usr/bin/apt-get' ]; then
if ! $(gcc -v &>/dev/null); then
sudo -H apt-get -y install gcc
@@ -68,6 +76,7 @@ cd /opt/stack
if [ ! -d ansible ]; then
git clone $ANSIBLE_GIT_URL --recursive -b $ANSIBLE_GIT_BRANCH
+ cd ansible
else
cd ansible
git checkout $ANSIBLE_GIT_BRANCH
@@ -75,6 +84,15 @@ else
git submodule update --init --recursive
git fetch
fi
+# Note(TheJulia): These files should be in the ansible folder
+# and this functionality exists for a level of ansible 1.9.x
+# backwards compatability although the modules were developed
+# for Ansible 2.0.
+
+check_get_module `pwd`/lib/ansible/modules/core/cloud/openstack/os_ironic.py \
+ https://raw.githubusercontent.com/ansible/ansible-modules-core/stable-2.0/cloud/openstack/os_ironic.py
+check_get_module `pwd`/lib/ansible/modules/core/cloud/openstack/os_ironic_node.py \
+ https://raw.githubusercontent.com/ansible/ansible-modules-core/stable-2.0/cloud/openstack/os_ironic_node.py
echo
echo "If you're using this script directly, execute the"