Add Ironic enrolment Ansible role

This commit is contained in:
Will Miller 2018-08-31 16:57:34 +00:00
parent 8e9e91ac79
commit 81f1696263
10 changed files with 219 additions and 1 deletions

View File

@ -51,6 +51,8 @@ class ActionModule(ActionBase):
for typ, cnt in six.iteritems(task_vars['specs']):
for _ in six.moves.range(cnt):
node = deepcopy(task_vars['node_types'][typ])
# Set the type, for future reference.
node['type'] = typ
# Sequentially number the node and volume names.
node['name'] = "%s%d" % (task_vars['node_name_prefix'], idx)
for vol_idx, vol in enumerate(node['volumes']):

View File

@ -89,3 +89,27 @@
loop_control:
loop_var: domain
index_var: port_offset
- hosts: localhost
tasks:
- name: Check that OpenStack credentials exist in the environment
fail:
msg: >
$OS_USERNAME was not found in the environment. Ensure the OpenStack
credentials exist in your environment, perhaps by sourcing your RC file.
when: not lookup('env', 'OS_USERNAME')
- name: Perform Ironic enrolment for each hypervisor's nodes
include_role:
name: ironic-enrolment
vars:
ironic_deploy_kernel_uuid: "{{ deploy_kernel_uuid }}"
ironic_deploy_ramdisk_uuid: "{{ deploy_ramdisk_uuid }}"
ironic_nodes: "{{ alloc.1 }}"
ironic_hypervisor: "{{ alloc.0 }}"
ironic_virtualenv_path: "{{ virtualenv_path }}"
ironic_python_upper_constraints_url: >-
{{ python_upper_constraints_url }}
loop: "{{ allocations.result.iteritems() | list }}"
loop_control:
loop_var: alloc

View File

@ -111,7 +111,7 @@ def ovs_link_name(context, node, physnet):
def source_to_ovs_link_name(context, source):
"""Get the corresponding OVS link name for a source link name.
"""
base = source[:len(_get_hostvar(context, 'veth_node_source_suffix'))]
base = source[:-len(_get_hostvar(context, 'veth_node_source_suffix'))]
return base + _get_hostvar(context, 'veth_node_ovs_suffix')

View File

@ -32,3 +32,8 @@ node_types: {}
# # 'type0'.
# type0: 4
specs: {}
# The Glance UUID of the image to use for the deployment kernel.
deploy_kernel_uuid:
# The Glance UUID of the image to use for the deployment ramdisk.
deploy_ramdisk_uuid:

View File

@ -0,0 +1,30 @@
Ironic Enrolment
================
This role enrols nodes with OpenStack Ironic, creates Ironic ports for each of
the nodes' NICs, and sets relevant attributes on created resources.
Requirements
------------
- *OS_\** environment variables for the OpenStack cloud in question present in
the shell environment. These can be sourced from an OpenStack RC file, for
example.
- The `virsh` command-line tool present at `/bin/virsh`.
Role Variables
--------------
- `ironic_nodes`: A list of dicts of details for nodes that are to be enroled
in Ironic.
- `ironic_hypervisor`: The hostname of the hypervisor on which `ironic_nodes`
exist.
- `ironic_deploy_kernel_uuid`: The Glance UUID of the image to use for the
deployment kernel.
- `ironic_deploy_ramdisk_uuid`: The Glance UUID of the image to use for the
deployment ramdisk.
- `ironic_virtualenv_path`: The path to the virtualenv in which to install the
OpenStack clients.
- `ironic_python_upper_constraints_url`: The URL of the upper constraints file
to pass to pip when installing Python packages.

View File

@ -0,0 +1,14 @@
---
# A list of dicts of details for nodes that are to be enroled in Ironic.
ironic_nodes: []
# The hostname of the hypervisor where these nodes exist.
ironic_hypervisor:
# The Glance UUID of the image to use for the deployment kernel.
ironic_deploy_kernel_uuid:
# The Glance UUID of the image to use for the deployment ramdisk.
ironic_deploy_ramdisk_uuid:
# The path to the virtualenv in which to install the OpenStack clients.
ironic_virtualenv_path:
# The URL of the upper constraints file to pass to pip when installing Python
# packages.
ironic_python_upper_constraints_url:

View File

@ -0,0 +1,6 @@
# This file contains the Python packages that are needed in the Tenks virtual
# env.
openstacksdk>=0.17.2 # Apache
python-ironicclient>=2.5.0 # Apache
python-openstackclient>=3.16.0 # Apache

View File

@ -0,0 +1,18 @@
---
- name: Ensure Python requirements are installed
pip:
requirements: "{{ '/'.join([role_path, 'files', 'requirements.txt']) }}"
extra_args: >-
-c {{ ironic_python_upper_constraints_url }}
virtualenv: "{{ ironic_virtualenv_path }}"
- name: Enrol the Ironic nodes
include_tasks: node.yml
vars:
node: "{{ ironic_node }}"
ipmi_port: >-
{{ hostvars[ironic_hypervisor].ipmi_port_range_start + port_offset }}
loop: "{{ ironic_nodes | sort(attribute='name') }}"
loop_control:
loop_var: ironic_node
index_var: port_offset

View File

@ -0,0 +1,90 @@
---
- name: Get vNIC MAC addresses
# The output format of this command gives two lines of header, followed by
# (for each vNIC):
# <name> <type> <source interface> <model> <MAC>
# The VMs will have been created with the virt module, using become: true.
# This targets /bin/virsh rather than /usr/bin/virsh.
command: /bin/virsh domiflist '{{ node.name }}'
register: iflist_res
changed_when: false
become: true
delegate_to: "{{ ironic_hypervisor }}"
run_once: true
# We need to do this for each run to ensure other nodes' NICs don't carry over
# to this run.
- name: Reset list of NICs
set_fact:
nics: []
- name: Collect MAC addresses into NIC list
set_fact:
nics: "{{ nics | union([{'mac': item.split()[4]}]) }}"
loop: "{{ iflist_res.stdout_lines[2:] }}"
- name: Create node in Ironic
os_ironic:
auth_type: password
driver: ipmi
driver_info:
power:
ipmi_address: "{{ hostvars[ironic_hypervisor].ipmi_address }}"
# This is passed in from main.yml.
ipmi_port: "{{ ipmi_port }}"
ipmi_username: "{{ hostvars[ironic_hypervisor].ipmi_username }}"
ipmi_password: "{{ hostvars[ironic_hypervisor].ipmi_password }}"
deploy:
deploy_kernel: "{{ ironic_deploy_kernel_uuid | default(omit, true) }}"
deploy_ramdisk: "{{ ironic_deploy_ramdisk_uuid | default(omit, true) }}"
name: "{{ node.name }}"
nics: "{{ nics }}"
properties:
ram: "{{ node.memory_mb }}"
# FIXME(w-miller): Instead of assuming the first volume is the primary
# volume, make this configurable?
disk_size: >-
{{ (node.volumes.0.capacity | default('1')) | size_string_to_gb }}
cpus: "{{ node.vcpus }}"
vars:
# This module requires the openstacksdk package, which is installed within
# our virtualenv.
ansible_python_interpreter: >-
{{ '/'.join([ironic_virtualenv_path, 'bin', 'python']) }}
register: created_node
# The os_ironic module automatically brings the node from 'enrol' to
# 'available' state, but we still need to set more port and node attributes.
# Use maintenance mode to do this.
- name: Put Ironic node into maintenance mode
command: >-
'{{ ironic_virtualenv_path }}/bin/openstack' baremetal node maintenance set
'{{ created_node.uuid }}'
# FIXME(w-miller): Make interfaces/driver configurable, for example to allow
# use of Redfish instead of IPMI.
- name: Set Ironic node resource class
command: >-
'{{ ironic_virtualenv_path }}/bin/openstack' baremetal node set
'{{ created_node.uuid }}'
--resource-class {{ node.type }}
# --boot-interface pxe
# --deploy-interface iscsi
# --management-interface ipmitool
# --network-interface neutron
# --power-interface ipmitool
- name: Set additional Ironic port attributes
include_tasks: port.yml
vars:
source_interface: "{{ vnic.split()[2] }}"
mac: "{{ vnic.split()[4] }}"
# Loop over each NIC.
loop: "{{ iflist_res.stdout_lines[2:] }}"
loop_control:
loop_var: vnic
- name: Bring Ironic node out of maintenance mode
command: >-
'{{ ironic_virtualenv_path }}/bin/openstack' baremetal node maintenance
unset '{{ created_node.uuid }}'

View File

@ -0,0 +1,29 @@
---
- name: Get Ironic port UUID
command: >-
'{{ ironic_virtualenv_path }}/bin/openstack' baremetal port list
--format value
--column UUID
--address {{ mac }}
register: uuid
changed_when: false
- name: Get physical network name
set_fact:
physnet: "{{ source_interface | source_link_to_physnet_name }}"
- name: Get bridge name
set_fact:
bridge: "{{ physnet | bridge_name }}"
- name: Set Ironic port attributes
command: >-
'{{ ironic_virtualenv_path }}/bin/openstack' baremetal port set
{{ uuid.stdout }}
--physical-network '{{ physnet }}'
--local-link-connection switch_id='{{ hostvars[ironic_hypervisor][
'ansible_' + bridge
].macaddress }}'
--local-link-connection switch_info='{{ bridge }}'
--local-link-connection port_id='{{ source_interface
| source_to_ovs_link_name }}'