Here’s a practical approach to managing network configuration on IBM Power servers using Ansible and the ibm.power_hmc
collection. Using a playbook as an example, I’ll show how to add a new VLAN and remove an existing one from the SEA (Shared Ethernet Adapter) configuration on VIOS (Virtual I/O Server) servers. This is a basic automation example that can be easily expanded into more advanced scenarios.
Playbook Goal:
The playbook allows you to add a new VLAN or remove existing VLAN from the SEA configuration on all VIOS servers within a managed system in the HMC (Hardware Management Console). This operation is performed dynamically, without needing to restart the VIOS or adapters.
Technologies Used:
- Ansible: An IT automation tool.
- Dynamic Inventory from the ibm.power_hmc collection: Instead of a static inventory file, the playbook uses the dynamic inventory provided by the ibm.power_hmc collection. This allows for automatic retrieval of information about managed systems and their configurations directly from the HMC, eliminating the need to manually maintain an inventory file. How to use dynamic inventory can be read in the article @Michal Wiktorek https://osadmins.com/pl/devops-z-powervm-ansible-i-dynamiczne-inventory/
- ibm.power_hmc Collection: A set of Ansible modules for managing IBM Power systems via the HMC.
- hmc_command Module: The ibm.power_hmc collection does not (at the time of writing) have a dedicated module for managing VLANs on the SEA. Therefore, we are using the universal hmc_command module, which allows us to execute arbitrary commands on the HMC. These commands are in accordance with the IBM documentation on dynamically adding and removing VLANs on the VIOS: https://www.ibm.com/docs/en/power9/9040-MR9?topic=networks-dynamically-adding-removing-vlans-virtual-io-server
Playbook (with detailed comments):
- name: Manage vlans on vios # Playbook name: Managing VLANs on VIOS
hosts: all # The playbook will run on all hosts defined in the inventory.
connection: local # Use a local connection (assumes the playbook is run from the HMC management host).
gather_facts: no # Do not gather facts about the hosts (speeds up execution when facts are not needed).
vars: # Definition of variables used in the playbook.
curr_hmc_auth: # Authentication data for the HMC.
username: "{{ ansible_user }}" # HMC username. Jinja2: gets the value of a global variable.
password: "{{ ansible_password }}" # HMC user password. Jinja2: gets the value of a global variable.
vlan_id: "432" # ID of the VLAN to be managed.
vea_id: "4" # ID of the Virtual Ethernet Adapter (VEA). **<-- NOTE: We are assuming here that the VEA ID is constant.**
vlan_action: "add_vlan" # Action to perform: "add_vlan".
tasks: # Section of tasks performed in the playbook.
- name: Get system capabilities # Retrieve system capabilities.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP. Jinja2: gets a variable's value from inventory.
hmc_auth: "{{ curr_hmc_auth }}" # HMC authentication data. Jinja2: gets the value of a previously defined variable.
cmd: "lssyscfg -r sys -m {{ inventory_hostname }} -F capabilities" # Command to get system capabilities. Jinja2: Inserts the hostname.
register: hmc_output # Save the result.
- name: Check for virtual_eth_dlpar_capable # Check if DLPAR for virtual Ethernet is supported.
set_fact:
is_virtual_eth_dlpar_capable: "{{ 'virtual_eth_dlpar_capable' in (hmc_output.command_output[0] | trim('\"')).split(',') }}" # Check if 'virtual_eth_dlpar_capable' is in the list. Jinja2: Checks array membership.
# hmc_output.command_output[0]: First element of the result.
# | trim('\"'): Remove quotes.
# .split(','): Split into a list.
# 'virtual_eth_dlpar_capable' in ...: Check if present.
- name: Fail if virtual_eth_dlpar_capable is not present # Terminate if DLPAR is not supported.
fail:
msg: "virtual_eth_dlpar_capable is NOT present!"
when: not is_virtual_eth_dlpar_capable # Condition: if the fact is false.
- name: Get VIOS IDs # Get the IDs of the VIOS partitions.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP. Jinja2: variable from inventory.
hmc_auth: "{{ curr_hmc_auth }}" # HMC authentication data. Jinja2: previously defined variable.
cmd: "lssyscfg -r lpar -m {{ inventory_hostname }} -F lpar_id,lpar_env" # Command: get partition ID and environment. Jinja2: hostname.
register: lpar_output # Save the result.
- name: Extract VIOS IDs # Extract VIOS partition IDs.
set_fact:
vios_ids: "{{ lpar_output.command_output | select('search', 'vioserver') | map('split', ',') | map(attribute=0) | map('int') | list | sort }}" # Extract VIOS IDs. Jinja2: processes the command result.
# lpar_output.command_output: Command result.
# | select('search', 'vioserver'): Select elements containing 'vioserver'.
# | map('split', ','): Split into a list.
# | map(attribute=0): Get the first element (partition ID).
# | map('int'): Convert to integer.
# | list: Convert to a list.
# | sort: Sort the list.
- name: Fail if no VIOS servers found # Terminate if no VIOS partitions are found.
fail:
msg: "No VIOS servers found. Check HMC configuration."
when: vios_ids | length == 0 # Condition: if the list is empty. Jinja2: Checks list length.
- name: Get VLAN configurations from VIOS # Retrieve VLAN configuration from each VIOS
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP.
hmc_auth: "{{ curr_hmc_auth }}" # Authentication data.
cmd: "lshwres -m {{ inventory_hostname }} -r virtualio --rsubtype eth --level lpar -F addl_vlan_ids --filter lpar_ids={{ item }}" # Command to retrieve VLANs. Jinja2: hostname.
loop: "{{ vios_ids }}" # Loop for each VIOS ID. Jinja2: Loops through the vios_ids list.
register: vlan_output # Save the results.
- name: Extract and clean VLANs # Extract and clean the VLAN list.
set_fact:
vlan_lists: "{{ vlan_output.results | map(attribute='command_output') | join(',') | replace('[', '') | replace(']', '') | replace('\"', '') | replace(\"'\", '') | split(',') | map('trim') | list }}" # Process result.
# vlan_output.results: List of results.
# | map(attribute='command_output'): Get 'command_output'.
# | join(','): Join into a string.
# | replace('[', ''): Remove '['.
# | replace(']', ''): Remove ']'.
# | replace('\"', ''): Remove '"'.
# | replace(\"'\", ''): Remove " ' ".
# | split(','): Split into a list.
# | map('trim'): Remove whitespace.
# | list: Convert to list.
- name: Check VLAN presence # Check if the VLAN is present.
set_fact:
vlan_missing: "{{ vlan_id not in vlan_lists }}" # Checks if NOT in the list. Jinja2: Checks membership.
- name: Debug VLAN list # Display debugging information.
debug:
msg:
- "VLAN LIST -> {{ vlan_lists }}" # Display the VLAN list.
- "VLAN MISSING -> {{ vlan_missing }}" # Display whether the VLAN is missing.
- name: Handle VLAN action failures # Handle errors related to the VLAN action.
fail:
msg: "VLAN {{ vlan_id }} cannot be {{ 'removed' if vlan_action == 'remove_vlan' else 'added' }}!" # Checks action. Jinja2: Conditional string.
when:
- (vlan_action == "remove_vlan" and vlan_missing) or # If removal and VLAN doesn't exist.
(vlan_action == "add_vlan" and not vlan_missing) # If addition and VLAN already exists.
- name: Perform VLAN action - Add VLAN # Add the VLAN.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP.
hmc_auth: "{{ curr_hmc_auth }}" # Authentication data.
cmd: "chhwres -r virtualio --rsubtype eth -m {{ inventory_hostname }} -o s --id {{ item }} -s {{ vea_id }} -a 'addl_vlan_ids+={{ vlan_id }},ieee_virtual_eth=1'" # Command to add. Jinja2: insertions.
loop: "{{ vios_ids }}" # Loop for each VIOS ID. Jinja2: Loops through vios_ids.
when: vlan_action == "add_vlan" and vlan_missing # If addition and VLAN doesn't exist.
loop_control:
pause: 3 # Will add a 3 second break after each iteration
- name: Perform VLAN action - Remove VLAN # Perform the action of removing the VLAN. This part is not used but is present for completeness.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP address.
hmc_auth: "{{ curr_hmc_auth }}" # Authentication data.
cmd: "chhwres -r virtualio --rsubtype eth -m {{ inventory_hostname }} -o s --id {{ item }} -s {{ vea_id }} -a 'addl_vlan_ids-={{ vlan_id }},ieee_virtual_eth=1'" # Command to remove the VLAN. Jinja2 expressions: data insertion.
loop: "{{ vios_ids }}" # Loop for each VIOS ID. Jinja2 expression: Loops through the vios_ids list
when: vlan_action == "remove_vlan" and not vlan_missing # Execution condition: if the action is removal and the VLAN exists.
loop_control:
pause: 3 # Will add a 3 second break after each iteration
- name: Recheck VLAN configurations from VIOS # Re-retrieve the VLAN configuration.
ibm.power_hmc.hmc_command:
hmc_host: "{{ hostvars[inventory_hostname]['HMCIP'] }}" # HMC IP.
hmc_auth: "{{ curr_hmc_auth }}" # Authentication data.
cmd: "lshwres -m {{ inventory_hostname }} -r virtualio --rsubtype eth --level lpar -F addl_vlan_ids --filter lpar_ids={{ item }}" # Same command.
loop: "{{ vios_ids }}" # Loop for each VIOS ID. Jinja2: Loops through vios_ids.
register: vlan_output_after # Save the results.
- name: Extract VLANs after action # Extract the VLAN list after the action.
set_fact:
vlan_lists_after: "{{ vlan_output_after.results | map(attribute='command_output') | join(',') | replace('[', '') | replace(']', '') | replace('\"', '') | replace(\"'\", '') | split(',') | map('trim') | list }}" #Identical to line 104.
- name: Check VLAN presence after action # Check VLAN presence/absence after.
set_fact:
vlan_missing_after_action: "{{ vlan_id not in vlan_lists_after }}" # Checks if NOT in the list. Jinja2: Checks membership.
- name: Debug vlan_lists_after # Debug
debug:
msg:
- "VLAN LIST AFTER -> {{ vlan_lists_after }}" # Display list after action.
- "VLAN MISSING AFTER ACTION -> {{ vlan_missing_after_action }}"
- name: Handle post-action VLAN failures # Handle errors after the VLAN action.
fail:
msg: "VLAN {{ vlan_id }} was not {{ 'removed' if vlan_action == 'remove_vlan' else 'added' }} successfully!" # Display add/remove. Jinja2: Conditional.
when:
- (vlan_action == "remove_vlan" and vlan_id in vlan_lists_after) or # If removal and VLAN still exists.
(vlan_action == "add_vlan" and vlan_id not in vlan_lists_after) # If addition and VLAN wasn't added.
This diagram breaks down the playbook’s logic into major steps:
+---------------------+
| START Playbook |
+---------------------+
|
V
+-------------------------------------+
| Get System Capabilities (lssyscfg) | --> Check "virtual_eth_dlpar_capable"
+-------------------------------------+
| (Fail if not capable)
V
+--------------------------------+
| Get VIOS LPAR IDs (lssyscfg) | --> Extract VIOS IDs (integers)
+--------------------------------+
| (Fail if no VIOSes)
V
+------------------------------------------+
| Get VLANs from VIOSes (lshwres, LOOP) | --> Collect existing VLAN IDs
+------------------------------------------+
|
V
+--------------------------------+
| Check if Target VLAN Exists | --> vlan_missing (boolean)
+--------------------------------+
| (Fail if action is invalid)
V
+-----------------------------------------------------------------+
| (Conditional) |
| IF adding VLAN and it's missing: |
| - Add VLAN to each VIOS (chhwres, LOOP, addl_vlan_ids+=) |
| ELSE IF removing VLAN and it exists: |
| - Remove VLAN from each VIOS (chhwres, LOOP, addl_vlan_ids-=)|
+-----------------------------------------------------------------+
|
V
+------------------------------------------------+
| Re-check VLANs from VIOSes (lshwres, LOOP) | --> Get updated VLAN IDs
+------------------------------------------------+
|
V
+-------------------------------------------------+
| Check if VLAN Action was Successful |
+-------------------------------------------------+
| (Fail if action failed)
V
+---------------------+
| END Playbook |
+---------------------+
Key:
* lssyscfg: HMC command to get system configuration.
* lshwres: HMC command to get hardware resources (including VLANs).
* chhwres: HMC command to change hardware resources (add/remove VLANs).
* LOOP: Indicates the action is performed in a loop for each VIOS.
* addl_vlan_ids+= : Part of the chhwres command to ADD a VLAN.
* addl_vlan_ids-= : Part of the chhwres command to REMOVE a VLAN.
* (Conditional): Indicates a conditional block based on `vlan_action` and `vlan_missing`.
* --> : Indicates data flow or result of a step.
Notes and possible improvements:
- Automatic SEA Detection: The playbook assumes that we know which VEA (Virtual Ethernet Adapter) we want to modify (vea_id). In a more advanced scenario, logic could be added to automatically detect available SEAs and VEA adapters.
- Choosing the Least Loaded VEA: Instead of a fixed vea_id assignment, a mechanism could be implemented to select the VEA adapter within the SEA that has the fewest assigned VLANs to balance the load evenly.
- VLAN Removal Handling: Currently, the playbook contains a section to remove a VLAN. However it requires you to manually set adapter id from where vlan needs to be removed, it could be improved by detecting the adapter, this will improve the process, and minimise the risk of errors.
- SEA and VEA ID Detection: The playbook relies on statically defined vea_id values. In a production environment, these IDs should be detected dynamically.
- Playbook structure and clarity: While implementing these modifications, a simple role could be created to break the playbook into separate tasks, improving clarity and making automation management easier in more complex environments.
Adding these improvements would significantly increase the complexity of the playbook, which is not the goal of this first introductory article. I wanted to show a simple but working example of using Ansible and the ibm.power_hmc collection to manage network configuration on IBM Power servers. In future articles, we will expand on this example and add more functionality.
I’m learning Ansible, and this text is part of my learning process and knowledge reinforcement. If you notice any major mistakes, let me know—I’ll be happy to correct them. 🚀