From b235bcf65af046d9d4a06248bbd87c5690035497 Mon Sep 17 00:00:00 2001 From: Mark Goddard Date: Fri, 3 Mar 2017 14:19:53 +0000 Subject: [PATCH] Support Ironic inspector in Bifrost container Adds support for setting which ports to add during inspection and a role for creating introspection rules in inspector. --- ansible/bifrost-introspection-rules.yml | 11 ++ ansible/group_vars/all/bifrost | 10 ++ .../ironic-inspector-rules/defaults/main.yml | 16 ++ .../library/os_ironic_inspector_rule.py | 159 ++++++++++++++++++ .../ironic-inspector-rules/tasks/main.yml | 50 ++++++ ansible/roles/kolla-bifrost/defaults/main.yml | 4 + .../kolla-bifrost/templates/bifrost.yml.j2 | 6 + etc/kayobe/bifrost.yml | 10 ++ 8 files changed, 266 insertions(+) create mode 100644 ansible/bifrost-introspection-rules.yml create mode 100644 ansible/roles/ironic-inspector-rules/defaults/main.yml create mode 100644 ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py create mode 100644 ansible/roles/ironic-inspector-rules/tasks/main.yml diff --git a/ansible/bifrost-introspection-rules.yml b/ansible/bifrost-introspection-rules.yml new file mode 100644 index 000000000..b577b3927 --- /dev/null +++ b/ansible/bifrost-introspection-rules.yml @@ -0,0 +1,11 @@ +--- +- name: Ensure introspection rules are registered in Bifrost + hosts: seed + roles: + - role: ironic-inspector-rules + ironic_inspector_venv: "{{ ansible_env.PWD }}/shade-venv" + # No auth required for Bifrost. + ironic_inspector_auth_type: None + ironic_inspector_auth: {} + ironic_inspector_url: "http://localhost:5050" + ironic_inspector_rules: "{{ kolla_bifrost_inspector_rules }}" diff --git a/ansible/group_vars/all/bifrost b/ansible/group_vars/all/bifrost index dc5b68783..4e5c36f67 100644 --- a/ansible/group_vars/all/bifrost +++ b/ansible/group_vars/all/bifrost @@ -32,6 +32,16 @@ kolla_bifrost_dib_packages: [] # Whether to enable ipmitool-based drivers. kolla_bifrost_enable_ipmitool_drivers: true +############################################################################### +# Ironic Inspector configuration. + +# Which MAC addresses to add as ports during introspection. One of 'all', +# 'active' or 'pxe'. +kolla_bifrost_inspector_port_addition: "all" + +# List of introspection rules for Bifrost's Ironic Inspector service. +kolla_bifrost_inspector_rules: [] + ############################################################################### # Inventory configuration. diff --git a/ansible/roles/ironic-inspector-rules/defaults/main.yml b/ansible/roles/ironic-inspector-rules/defaults/main.yml new file mode 100644 index 000000000..a23418082 --- /dev/null +++ b/ansible/roles/ironic-inspector-rules/defaults/main.yml @@ -0,0 +1,16 @@ +--- +# Path to a directory in which to create a virtualenv. +ironic_inspector_venv: + +# Authentication type. +ironic_inspector_auth_type: + +# Authentication information. +ironic_inspector_auth: {} + +# URL of Ironic Inspector API endpoint. +ironic_inspector_url: + +# List of rules which should exist. See the Inspector rules API for details of +# parameters available for rules. +ironic_inspector_rules: [] diff --git a/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py b/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py new file mode 100644 index 000000000..f5070410c --- /dev/null +++ b/ansible/roles/ironic-inspector-rules/library/os_ironic_inspector_rule.py @@ -0,0 +1,159 @@ +#!/usr/bin/python + +from ansible.module_utils.basic import * +from ansible.module_utils.openstack import * + +# Store a list of import errors to report to the user. +IMPORT_ERRORS = [] +try: + import ironic_inspector_client +except Exception as e: + IMPORT_ERRORS.append(e) +try: + import shade +except Exception as e: + IMPORT_ERRORS.append(e) + + +DOCUMENTATION = """ +module: os_ironic_inspector_rule +short_description: Create or destroy an Ironic Inspector rule. +author: "Mark Goddard " +extends_documentation_fragment: openstack +description: + - Create or destroy an Ironic inspector rule. +options: + state: + description: + - State of the rule + choices: ["present", "absent"] + uuid: + description: + - Globally unique identifier for the rule. + required: false + description: + description: + - Description for the rule. + required: false + conditions: + description: + - List of conditions that must be met in order to apply the rule. + required: true + actions: + description: + - List of actions to be taken when the conditions are met. + required: true +""" + +EXAMPLES = """ +# Ensure that an inspector rule exists. +os_ironic_inspector_rule: + cloud: "openstack" + state: present + uuid: "d44666e1-35b3-4f6b-acb0-88ab7052da69" + description: Set IPMI username in driver_info if not set + conditions: + - field: "node://driver_info.ipmi_username" + op: "is-empty" + actions: + - action: "set-attribute" + path: "driver_info/ipmi_username" + value: "root" +""" + + +def _build_client(module): + """Create and return an Ironic inspector client.""" + cloud = shade.operator_cloud(**module.params) + session = cloud.cloud_config.get_session() + client = ironic_inspector_client.v1.ClientV1( + inspector_url=module.params['inspector_url'], + session=session, region_name=module.params['region_name']) + return client + + +def _ensure_rule_present(module, client): + """Ensure that an inspector rule is present.""" + if module.params['uuid']: + try: + rule = client.rules.get(module.params['uuid']) + except ironic_inspector_client.ClientError as e: + if e.response.status_code != 404: + module.fail_json(msg="Failed retrieving Inspector rule %s: %s" + % (module.params['uuid'], repr(e))) + else: + # Check whether the rule differs from the request. + keys = ('conditions', 'actions', 'description') + for key in keys: + if rule[key] != module.params[key]: + break + else: + # Nothing to do - rule exists and is as requested. + return False + # Rule differs - delete it before recreating. + _ensure_rule_absent(module, client) + + client.rules.create(module.params['conditions'], module.params['actions'], + module.params['uuid'], module.params['description']) + return True + + +def _ensure_rule_absent(module, client): + """Ensure that an inspector rule is absent.""" + if not module.params['uuid']: + module.fail_json(msg="UUID is required to ensure rules are absent") + try: + client.rules.delete(module.params['uuid']) + except ironic_inspector_client.ClientError as e: + # If the rule does not exist, no problem and no change. + if e.response.status_code == 404: + return False + module.fail_json(msg="Failed retrieving Inspector rule %s: %s" + % (module.params['uuid'], repr(e))) + return True + + +def main(): + argument_spec = openstack_full_argument_spec( + conditions=dict(type='list', required=True), + actions=dict(type='list', required=True), + description=dict(required=False), + uuid=dict(required=False), + state=dict(required=False, default='present', + choices=['present', 'absent']), + inspector_url=dict(required=False), + ) + 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['inspector_url'] is None): + module.fail_json(msg="Authentication appears disabled, please " + "define an inspector_url parameter") + + if (module.params['inspector_url'] and + module.params['auth_type'] in [None, 'None']): + module.params['auth'] = dict( + endpoint=module.params['inspector_url'] + ) + + try: + client = _build_client(module) + if module.params["state"] == "present": + changed = _ensure_rule_present(module, client) + else: + changed = _ensure_rule_absent(module, client) + except Exception as e: + module.fail_json(msg="Failed to configure Ironic Inspector rule: %s" % + repr(e)) + else: + module.exit_json(changed=changed) + + +if __name__ == '__main__': + main() diff --git a/ansible/roles/ironic-inspector-rules/tasks/main.yml b/ansible/roles/ironic-inspector-rules/tasks/main.yml new file mode 100644 index 000000000..3567ceb4e --- /dev/null +++ b/ansible/roles/ironic-inspector-rules/tasks/main.yml @@ -0,0 +1,50 @@ +--- +- name: Ensure required packages are installed + yum: + name: "{{ item }}" + state: installed + become: True + with_items: + - python-devel + - python-pip + - python-virtualenv + +- name: Ensure the latest version of pip is installed + pip: + name: "{{ item.name }}" + state: latest + virtualenv: "{{ ironic_inspector_venv }}" + with_items: + - { name: pip } + +- name: Ensure required Python packages are installed + pip: + name: "{{ item.name }}" + version: "{{ item.version | default(omit) }}" + state: present + virtualenv: "{{ ironic_inspector_venv }}" + with_items: + - name: python-ironic-inspector-client + - name: shade + +- name: Set a fact to ensure Ansible uses the python interpreter in the virtualenv + set_fact: + ansible_python_interpreter: "{{ ironic_inspector_venv }}/bin/python" + +- name: Ensure introspection rules exist + os_ironic_inspector_rule: + auth_type: "{{ ironic_inspector_auth_type }}" + auth: "{{ ironic_inspector_auth }}" + conditions: "{{ item.conditions }}" + actions: "{{ item.actions }}" + description: "{{ item.description | default(omit) }}" + uuid: "{{ item.uuid | default(item.description | to_uuid) | default(omit) }}" + state: present + inspector_url: "{{ ironic_inspector_url }}" + with_items: "{{ ironic_inspector_rules }}" + +# This variable is unset before we set it, and it does not appear to be +# possible to unset a variable in Ansible. +- name: Set a fact to reset the Ansible python interpreter + set_fact: + ansible_python_interpreter: /usr/bin/python diff --git a/ansible/roles/kolla-bifrost/defaults/main.yml b/ansible/roles/kolla-bifrost/defaults/main.yml index e9b47372a..705c9c6e8 100644 --- a/ansible/roles/kolla-bifrost/defaults/main.yml +++ b/ansible/roles/kolla-bifrost/defaults/main.yml @@ -42,6 +42,10 @@ kolla_bifrost_dnsmasq_dns_servers: [] # DNS domain provided to nodes via DHCP. kolla_bifrost_domain: +# Which MAC addresses to add as ports during introspection. One of 'all', +# 'active' or 'pxe'. +kolla_bifrost_inspector_port_addition: + # Server inventory to be configured in {{ kolla_node_custom_config_path }}/bifrost/servers.yml. kolla_bifrost_servers: {} diff --git a/ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 b/ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 index c2afec124..e4c867a8f 100644 --- a/ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 +++ b/ansible/roles/kolla-bifrost/templates/bifrost.yml.j2 @@ -27,6 +27,12 @@ dnsmasq_dns_servers: "{{ kolla_bifrost_dnsmasq_dns_servers | join(',') }}" domain: "{{ kolla_bifrost_domain }}" {% endif %} +{% if kolla_bifrost_inspector_port_addition %} +# Which MAC addresses to add as ports during introspection. One of 'all', +# 'active' or 'pxe'. +inspector_port_addition: "{{ kolla_bifrost_inspector_port_addition }}" +{% endif %} + {% if kolla_bifrost_extra_globals %} ############################################################################### # Extra configuration diff --git a/etc/kayobe/bifrost.yml b/etc/kayobe/bifrost.yml index a9860f947..b4936d23a 100644 --- a/etc/kayobe/bifrost.yml +++ b/etc/kayobe/bifrost.yml @@ -28,6 +28,16 @@ # Whether to enable ipmitool-based drivers. #kolla_bifrost_enable_ipmitool_drivers: +############################################################################### +# Ironic Inspector configuration. + +# Which MAC addresses to add as ports during introspection. One of 'all', +# 'active' or 'pxe'. +#kolla_bifrost_inspector_port_addition: + +# List of introspection rules for Bifrost's Ironic Inspector service. +#kolla_bifrost_inspector_rules: + ############################################################################### # Inventory configuration.