From 1106ba52cead55cea438af5b11c5e5d5e08c53a2 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 23 Nov 2023 12:56:07 +0000 Subject: [PATCH 1/7] Add os_ironic_state role Imported from https://github.com/stackhpc/ansible-role-os-ironic-state --- roles/os_ironic_state/README.md | 50 +++++ roles/os_ironic_state/defaults/main.yml | 29 +++ .../library/os_ironic_state.py | 175 ++++++++++++++++++ roles/os_ironic_state/meta/main.yml | 19 ++ roles/os_ironic_state/tasks/main.yml | 16 ++ roles/os_ironic_state/tests/inventory | 1 + roles/os_ironic_state/tests/test.yml | 5 + 7 files changed, 295 insertions(+) create mode 100644 roles/os_ironic_state/README.md create mode 100644 roles/os_ironic_state/defaults/main.yml create mode 100644 roles/os_ironic_state/library/os_ironic_state.py create mode 100644 roles/os_ironic_state/meta/main.yml create mode 100644 roles/os_ironic_state/tasks/main.yml create mode 100644 roles/os_ironic_state/tests/inventory create mode 100644 roles/os_ironic_state/tests/test.yml diff --git a/roles/os_ironic_state/README.md b/roles/os_ironic_state/README.md new file mode 100644 index 0000000..9990599 --- /dev/null +++ b/roles/os_ironic_state/README.md @@ -0,0 +1,50 @@ +OpenStack Ironic Node State +=========================== + +This role can be used to set the provision state of ironic nodes. + +Requirements +------------ + +The OpenStack keystone and ironic APIs should be accessible from the target +host. + +Role Variables +-------------- + +`os_ironic_state_auth_type`: Authentication type as used by `os_*` modules' +`auth_type` argument. + +`os_ironic_state_auth`: Authentication options as used by `os_*` modules' +`auth` argument. + +`os_ironic_state_cacert`: CA certificate as used by `os_*` modules' `cacert` +argument. + +`os_ironic_state_interface` is the endpoint URL type to fetch from the service +catalog. Maybe be one of `public`, `admin`, or `internal`. + +`os_ironic_state_name`: Name of the ironic node. + +`os_ironic_state_provision_state`: Desired provision state. + +`os_ironic_state_delegate_to`: Host to delegate to. + +`os_ironic_state_wait`: Whether to wait for the state transition to complete. +Default is `True`. + +`os_ironic_state_timeout`: Time to wait for state transition to complete, if +`os_ironic_state_wait` is `True`. Default is 1200 seconds. + +Dependencies +------------ + +The delegate host should have the `openstacksdk` Python package installed. + +Example Playbook +---------------- + +Author Information +------------------ + +- Mark Goddard () diff --git a/roles/os_ironic_state/defaults/main.yml b/roles/os_ironic_state/defaults/main.yml new file mode 100644 index 0000000..3fa6064 --- /dev/null +++ b/roles/os_ironic_state/defaults/main.yml @@ -0,0 +1,29 @@ +--- +# Authentication type as used by os_* modules' 'auth_type' argument. +os_ironic_state_auth_type: + +# Authentication options as used by os_* modules' 'auth' argument. +os_ironic_state_auth: {} + +# CA certificate as used by os_* modules' 'cacert' argument. +os_ironic_state_cacert: + +# Endpoint URL type to fetch from the service catalog. Maybe be one of: +# public, admin, or internal. +os_networks_interface: + +# Name of the ironic node. +os_ironic_state_name: + +# Desired provision state. +os_ironic_state_provision_state: + +# Host to delegate to. +os_ironic_state_delegate_to: + +# Whether to wait for the state transition to complete. +os_ironic_state_wait: True + +# Time to wait for state transition to complete, if os_ironic_state_wait is +# True. +os_ironic_state_timeout: 1200 diff --git a/roles/os_ironic_state/library/os_ironic_state.py b/roles/os_ironic_state/library/os_ironic_state.py new file mode 100644 index 0000000..8b916eb --- /dev/null +++ b/roles/os_ironic_state/library/os_ironic_state.py @@ -0,0 +1,175 @@ +#!/usr/bin/python +# coding: utf-8 -*- + +# (c) 2015, Hewlett-Packard Development Company, L.P. +# Copyright 2017 StackHPC Ltd. +# +# 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 . + +DOCUMENTATION = ''' +--- +module: os_ironic_state +short_description: Set provision state of Bare Metal Resources from OpenStack +author: "Mark Goddard " +extends_documentation_fragment: openstack +description: + - Set the provision state of OpenStack ironic bare metal nodes. +options: + provision_state: + description: + - Indicates desired provision state of the resource + choices: ['manage', 'provide'] + default: present + 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 + wait: + description: + - A boolean value instructing the module to wait for node + activation or deactivation to complete before returning. + required: false + default: False + version_added: "2.1" + timeout: + description: + - An integer value representing the number of seconds to + wait for the node activation or deactivation to complete. + version_added: "2.1" + availability_zone: + description: + - Ignored. Present for backwards compatibility + required: false +''' + +EXAMPLES = ''' +os_ironic_node: + cloud: "openstack" + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + provision_state: provide + delegate_to: localhost +''' + +from distutils.version import StrictVersion + +# Store a list of import errors to report to the user. +IMPORT_ERRORS = [] +try: + import openstack +except Exception as e: + IMPORT_ERRORS.append(e) + +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * + + +def _choose_id_value(module): + if module.params['uuid']: + return module.params['uuid'] + if module.params['name']: + return module.params['name'] + return None + + +def _change_required(current_provision_state, action): + """Return whether a change to the provision state is required. + + :param current_provision_state: The current provision state of the node. + :param action: The requested action. + """ + if action == 'manage': + if current_provision_state == 'manageable': + return False + if action == 'provide': + if current_provision_state == 'available': + return False + return True + + +def main(): + argument_spec = openstack_full_argument_spec( + uuid=dict(required=False), + name=dict(required=False), + ironic_url=dict(required=False), + provision_state=dict(required=True, + choices=['manage', 'provide']), + wait=dict(type='bool', required=False, default=False), + timeout=dict(required=False, type='int', default=1800), + ) + module_kwargs = openstack_module_kwargs() + module = AnsibleModule(argument_spec, **module_kwargs) + # Fail if there were any exceptions when importing modules. + if IMPORT_ERRORS: + module.fail_json(msg="Import errors: %s" % + ", ".join([repr(e) for e in IMPORT_ERRORS])) + + 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: + sdk, cloud = openstack_cloud_from_module(module) + node = cloud.get_machine(node_id) + + if node is None: + module.fail_json(msg="node not found") + + uuid = node['uuid'] + changed = False + wait = module.params['wait'] + timeout = module.params['timeout'] + provision_state = module.params['provision_state'] + + 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']) + + if _change_required(node['provision_state'], provision_state): + cloud.node_set_provision_state(uuid, provision_state, wait=wait, + timeout=timeout) + changed = True + + module.exit_json(changed=changed) + + except Exception as e: + module.fail_json(msg=str(e)) + + +if __name__ == "__main__": + main() diff --git a/roles/os_ironic_state/meta/main.yml b/roles/os_ironic_state/meta/main.yml new file mode 100644 index 0000000..abfc40b --- /dev/null +++ b/roles/os_ironic_state/meta/main.yml @@ -0,0 +1,19 @@ +--- +galaxy_info: + #role_name: os_ironic_state + author: Mark Goddard + description: > + Role to set OpenStack ironic node provision state + company: StackHPC Ltd + license: Apache2 + min_ansible_version: 2.0 + platforms: + - name: EL + versions: + - 7 + galaxy_tags: + - cloud + - keystone + - openstack + - baremetal + - ironic diff --git a/roles/os_ironic_state/tasks/main.yml b/roles/os_ironic_state/tasks/main.yml new file mode 100644 index 0000000..260d4b0 --- /dev/null +++ b/roles/os_ironic_state/tasks/main.yml @@ -0,0 +1,16 @@ +--- +- name: Ensure baremetal compute nodes provision states are set + os_ironic_state: + auth_type: "{{ os_ironic_state_auth_type }}" + auth: "{{ os_ironic_state_auth }}" + cacert: "{{ os_ironic_state_cacert | default(omit, true) }}" + interface: "{{ os_ironic_state_interface | default(omit, true) }}" + name: "{{ os_ironic_state_name }}" + provision_state: "{{ os_ironic_state_provision_state }}" + timeout: "{{ os_ironic_state_timeout }}" + wait: "{{ os_ironic_state_wait }}" + delegate_to: "{{ os_ironic_state_delegate_to }}" + vars: + # NOTE: Without this, the delegate hosts's ansible_host variable will not + # be respected. + ansible_host: "{{ hostvars[os_ironic_state_delegate_to].ansible_host | default(os_ironic_state_delegate_to) }}" diff --git a/roles/os_ironic_state/tests/inventory b/roles/os_ironic_state/tests/inventory new file mode 100644 index 0000000..5f8076c --- /dev/null +++ b/roles/os_ironic_state/tests/inventory @@ -0,0 +1 @@ +localhost ansible_connection='local' ansible_python_interpreter='/usr/bin/env python' diff --git a/roles/os_ironic_state/tests/test.yml b/roles/os_ironic_state/tests/test.yml new file mode 100644 index 0000000..6c30616 --- /dev/null +++ b/roles/os_ironic_state/tests/test.yml @@ -0,0 +1,5 @@ +--- +- hosts: all + connection: local + roles: + - stackhpc.os-ironic-state From 2ac51a1a20f8e46858c65ecd3ca3d3967c0f0c46 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 23 Nov 2023 12:58:18 +0000 Subject: [PATCH 2/7] Move os_ironic_state module to collection as baremetal_node_state --- .../modules/baremetal_node_state.py | 0 roles/os_ironic_state/tasks/main.yml | 2 +- 2 files changed, 1 insertion(+), 1 deletion(-) rename roles/os_ironic_state/library/os_ironic_state.py => plugins/modules/baremetal_node_state.py (100%) diff --git a/roles/os_ironic_state/library/os_ironic_state.py b/plugins/modules/baremetal_node_state.py similarity index 100% rename from roles/os_ironic_state/library/os_ironic_state.py rename to plugins/modules/baremetal_node_state.py diff --git a/roles/os_ironic_state/tasks/main.yml b/roles/os_ironic_state/tasks/main.yml index 260d4b0..4c6da00 100644 --- a/roles/os_ironic_state/tasks/main.yml +++ b/roles/os_ironic_state/tasks/main.yml @@ -1,6 +1,6 @@ --- - name: Ensure baremetal compute nodes provision states are set - os_ironic_state: + stackhpc.openstack.baremetal_node_state: auth_type: "{{ os_ironic_state_auth_type }}" auth: "{{ os_ironic_state_auth }}" cacert: "{{ os_ironic_state_cacert | default(omit, true) }}" From 68878debbbec31ccf0363aa55a92b6646145dc94 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 23 Nov 2023 13:02:01 +0000 Subject: [PATCH 3/7] baremetal_node_state: Fix some sanity issues --- plugins/modules/baremetal_node_state.py | 7 +++++-- 1 file changed, 5 insertions(+), 2 deletions(-) diff --git a/plugins/modules/baremetal_node_state.py b/plugins/modules/baremetal_node_state.py index 8b916eb..60050c9 100644 --- a/plugins/modules/baremetal_node_state.py +++ b/plugins/modules/baremetal_node_state.py @@ -17,9 +17,12 @@ # You should have received a copy of the GNU General Public License # along with this software. If not, see . +from __future__ import (absolute_import, division, print_function) +__metaclass__ = type + DOCUMENTATION = ''' --- -module: os_ironic_state +module: baremetal_node_state short_description: Set provision state of Bare Metal Resources from OpenStack author: "Mark Goddard " extends_documentation_fragment: openstack @@ -62,7 +65,7 @@ ''' EXAMPLES = ''' -os_ironic_node: +baremeta_node_state: cloud: "openstack" uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" provision_state: provide From e66c69affa98124ae0a80cb1a71616f5b41b474f Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Thu, 23 Nov 2023 13:04:05 +0000 Subject: [PATCH 4/7] lint: auto-fix os_ironic_state --- roles/os_ironic_state/defaults/main.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/roles/os_ironic_state/defaults/main.yml b/roles/os_ironic_state/defaults/main.yml index 3fa6064..616abf6 100644 --- a/roles/os_ironic_state/defaults/main.yml +++ b/roles/os_ironic_state/defaults/main.yml @@ -22,7 +22,7 @@ os_ironic_state_provision_state: os_ironic_state_delegate_to: # Whether to wait for the state transition to complete. -os_ironic_state_wait: True +os_ironic_state_wait: true # Time to wait for state transition to complete, if os_ironic_state_wait is # True. From 102d3f559428c7fbbf1a790bba64b7ec301902fb Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 24 Nov 2023 08:43:46 +0000 Subject: [PATCH 5/7] lint: Fix os_ironic_state issues --- roles/os_ironic_state/defaults/main.yml | 2 +- roles/os_ironic_state/tests/test.yml | 5 +++-- 2 files changed, 4 insertions(+), 3 deletions(-) diff --git a/roles/os_ironic_state/defaults/main.yml b/roles/os_ironic_state/defaults/main.yml index 616abf6..69965b9 100644 --- a/roles/os_ironic_state/defaults/main.yml +++ b/roles/os_ironic_state/defaults/main.yml @@ -10,7 +10,7 @@ os_ironic_state_cacert: # Endpoint URL type to fetch from the service catalog. Maybe be one of: # public, admin, or internal. -os_networks_interface: +os_ironic_state_interface: # Name of the ironic node. os_ironic_state_name: diff --git a/roles/os_ironic_state/tests/test.yml b/roles/os_ironic_state/tests/test.yml index 6c30616..a652d48 100644 --- a/roles/os_ironic_state/tests/test.yml +++ b/roles/os_ironic_state/tests/test.yml @@ -1,5 +1,6 @@ --- -- hosts: all +- name: Test os_ironic_state + hosts: all connection: local roles: - - stackhpc.os-ironic-state + - stackhpc.openstack.os_ironic_state From 78b6ca883fd721e087cd5d5efd88834c23e66c8a Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 24 Nov 2023 08:44:03 +0000 Subject: [PATCH 6/7] os_ironic_state: Remove meta/main.yml --- roles/os_ironic_state/meta/main.yml | 19 ------------------- 1 file changed, 19 deletions(-) delete mode 100644 roles/os_ironic_state/meta/main.yml diff --git a/roles/os_ironic_state/meta/main.yml b/roles/os_ironic_state/meta/main.yml deleted file mode 100644 index abfc40b..0000000 --- a/roles/os_ironic_state/meta/main.yml +++ /dev/null @@ -1,19 +0,0 @@ ---- -galaxy_info: - #role_name: os_ironic_state - author: Mark Goddard - description: > - Role to set OpenStack ironic node provision state - company: StackHPC Ltd - license: Apache2 - min_ansible_version: 2.0 - platforms: - - name: EL - versions: - - 7 - galaxy_tags: - - cloud - - keystone - - openstack - - baremetal - - ironic From 919e5c8a5cc3f6668b81a5af3b1b75828d90bfe8 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 24 Nov 2023 08:55:26 +0000 Subject: [PATCH 7/7] baremetal_node_state: mk1 --- plugins/modules/baremetal_node_state.py | 136 ++++++++++++------------ 1 file changed, 66 insertions(+), 70 deletions(-) diff --git a/plugins/modules/baremetal_node_state.py b/plugins/modules/baremetal_node_state.py index 60050c9..a8936de 100644 --- a/plugins/modules/baremetal_node_state.py +++ b/plugins/modules/baremetal_node_state.py @@ -20,8 +20,7 @@ from __future__ import (absolute_import, division, print_function) __metaclass__ = type -DOCUMENTATION = ''' ---- +DOCUMENTATION = r''' module: baremetal_node_state short_description: Set provision state of Bare Metal Resources from OpenStack author: "Mark Goddard " @@ -64,7 +63,7 @@ required: false ''' -EXAMPLES = ''' +EXAMPLES = r''' baremeta_node_state: cloud: "openstack" uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" @@ -72,17 +71,9 @@ delegate_to: localhost ''' -from distutils.version import StrictVersion - -# Store a list of import errors to report to the user. -IMPORT_ERRORS = [] -try: - import openstack -except Exception as e: - IMPORT_ERRORS.append(e) - -from ansible.module_utils.basic import * -from ansible.module_utils.openstack import * +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( + OpenStackModule +) def _choose_id_value(module): @@ -108,8 +99,8 @@ def _change_required(current_provision_state, action): return True -def main(): - argument_spec = openstack_full_argument_spec( +class BaremetalDeployTemplateModule(OpenStackModule): + argument_spec = dict( uuid=dict(required=False), name=dict(required=False), ironic_url=dict(required=False), @@ -118,60 +109,65 @@ def main(): wait=dict(type='bool', required=False, default=False), timeout=dict(required=False, type='int', default=1800), ) - module_kwargs = openstack_module_kwargs() - module = AnsibleModule(argument_spec, **module_kwargs) - # Fail if there were any exceptions when importing modules. - if IMPORT_ERRORS: - module.fail_json(msg="Import errors: %s" % - ", ".join([repr(e) for e in IMPORT_ERRORS])) - - 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: - sdk, cloud = openstack_cloud_from_module(module) - node = cloud.get_machine(node_id) - - if node is None: - module.fail_json(msg="node not found") - - uuid = node['uuid'] - changed = False - wait = module.params['wait'] - timeout = module.params['timeout'] - provision_state = module.params['provision_state'] - - 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']) - - if _change_required(node['provision_state'], provision_state): - cloud.node_set_provision_state(uuid, provision_state, wait=wait, - timeout=timeout) - changed = True - - module.exit_json(changed=changed) - - except Exception as e: - module.fail_json(msg=str(e)) + module_kwargs = dict( + required_one_of=[ + ('uuid', 'name'), + ], + ) + + def run(self): + if (self.params['auth_type'] in [None, 'None'] and + self.params['ironic_url'] is None): + self.fail_json(msg="Authentication appears disabled, Please " + "define an ironic_url parameter") + + if (self.params['ironic_url'] and + self.params['auth_type'] in [None, 'None']): + self.params['auth'] = dict( + endpoint=self.params['ironic_url'] + ) + + node_id = _choose_id_value(self) + + if not node_id: + self.fail_json(msg="A uuid or name value must be defined " + "to use this module.") + + try: + sdk, cloud = openstack_cloud_from_module(self) + node = cloud.get_machine(node_id) + + if node is None: + self.fail_json(msg="node not found") + + uuid = node['uuid'] + changed = False + wait = self.params['wait'] + timeout = self.params['timeout'] + provision_state = self.params['provision_state'] + + if node['provision_state'] in [ + 'cleaning', + 'deleting', + 'wait call-back']: + self.fail_json(msg="Node is in %s state, cannot act upon the " + "request as the node is in a transition " + "state" % node['provision_state']) + + if _change_required(node['provision_state'], provision_state): + cloud.node_set_provision_state(uuid, provision_state, wait=wait, + timeout=timeout) + changed = True + + self.exit_json(changed=changed) + + except Exception as e: + self.fail_json(msg=str(e)) + + +def main(): + module = BaremetalNodeStateModule() + module() if __name__ == "__main__":