Skip to content

Commit 388aedc

Browse files
author
Bharat Kunwar
authored
Merge pull request #23 from stackhpc/server_networks
Add server_networks metadata to the inventory (v19.6.1)
2 parents 087ee63 + b3f21f1 commit 388aedc

File tree

5 files changed

+188
-9
lines changed

5 files changed

+188
-9
lines changed

.travis.yml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -13,7 +13,7 @@ addons:
1313

1414
install:
1515
# Install ansible
16-
- pip install ansible
16+
- pip install ansible jmespath
1717

1818
# Check ansible version
1919
- ansible --version

README.md

Lines changed: 4 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,10 @@ The OpenStack APIs should be accessible from the target host. OpenStack
1212
Newton or later is required. Client credentials should have been set
1313
in the environment, or using the `clouds.yaml` format.
1414

15+
In terms of Python packages, the requirements are:
16+
- ansible
17+
- jmespath (required by `json_query` filter)
18+
1519
Role Variables
1620
--------------
1721

library/os_server_interface.py

Lines changed: 153 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,153 @@
1+
#!/usr/bin/env python
2+
#
3+
# Copyright (c) 2018 StackHPC Ltd.
4+
# Apache 2 Licence
5+
6+
from __future__ import absolute_import, division, print_function
7+
__metaclass__ = type
8+
9+
ANSIBLE_METADATA = {'metadata_version': '1.1',
10+
'status': ['preview'],
11+
'supported_by': 'community'}
12+
13+
DOCUMENTATION = '''
14+
---
15+
module: os_server_interface
16+
short_description: Create, update and delete server interface.
17+
author: bharat@stackhpc.com
18+
version_added: "1.0"
19+
description:
20+
- Create, update and delete server interface using Openstack Nova API.
21+
notes:
22+
- This module returns C(network_interface) fact, which
23+
contains information about server network interfaces.
24+
requirements:
25+
- "python >= 2.6"
26+
- "openstacksdk"
27+
- "python-novaclient"
28+
options:
29+
cloud:
30+
description:
31+
- Cloud name inside cloud.yaml file.
32+
type: str
33+
state:
34+
description:
35+
- Must be `present`, `query` or `absent`.
36+
type: str
37+
server_id:
38+
description:
39+
- Server name or uuid.
40+
type: str
41+
interfaces:
42+
description:
43+
- List of network interface names.
44+
type: list of str
45+
extends_documentation_fragment: openstack
46+
'''
47+
48+
EXAMPLES = '''
49+
# Attach interfaces to <server_id>:
50+
- os_container_infra:
51+
cloud: mycloud
52+
state: present
53+
server_id: xxxxx-xxxxx-xxxx-xxxx
54+
interfaces:
55+
- p3-lln
56+
- p3-bdn
57+
register: result
58+
- debug:
59+
var: result
60+
'''
61+
62+
from ansible.module_utils.basic import AnsibleModule
63+
from ansible.utils.display import Display
64+
from novaclient.client import Client
65+
from novaclient.exceptions import NotFound
66+
import openstack
67+
import time
68+
69+
class OpenStackAuthConfig(Exception):
70+
pass
71+
72+
class ServerInterface(object):
73+
def __init__(self, **kwargs):
74+
self.server_id = kwargs['server_id']
75+
self.state = kwargs['state']
76+
77+
self.connect(**kwargs)
78+
self.interfaces = []
79+
for interface in kwargs['interfaces']:
80+
network = self.cloud.network.find_network(interface)
81+
if not network:
82+
raise Exception("Unable to find network '%s'" % interface)
83+
self.interfaces.append(network)
84+
85+
def connect(self, **kwargs):
86+
if kwargs['auth_type'] == 'password':
87+
if kwargs['cloud']:
88+
self.cloud = openstack.connect(cloud=kwargs['cloud'])
89+
elif kwargs['auth']:
90+
self.cloud = openstack.connect(**kwargs['auth'])
91+
else:
92+
self.cloud = openstack.connect()
93+
else:
94+
raise OpenStackAuthConfig('Only `password` auth_type is supported.')
95+
self.cloud.authorize()
96+
self.client = Client('2', session=self.cloud.session)
97+
98+
def get_server(self):
99+
try:
100+
server = self.client.servers.find(id=self.server_id)
101+
except NotFound:
102+
server = self.client.servers.find(name=self.server_id)
103+
return server
104+
105+
def apply(self):
106+
changed = False
107+
self.server = server = self.get_server()
108+
if self.state != 'query':
109+
attached_interfaces = server.interface_list()
110+
for interface in self.interfaces:
111+
interface_exists = False
112+
for attached_interface in attached_interfaces:
113+
if interface.id == attached_interface.net_id:
114+
if self.state == 'absent':
115+
server.interface_detach(port_id=attached_interface.port_id)
116+
changed = True
117+
elif self.state == 'present':
118+
interface_exists = True
119+
if interface_exists == False and self.state == 'present':
120+
server.interface_attach(port_id=None, net_id=interface.id, fixed_ip=None)
121+
changed = True
122+
if changed:
123+
self.server = self.get_server()
124+
return changed
125+
126+
if __name__ == '__main__':
127+
module = AnsibleModule(
128+
argument_spec = dict(
129+
cloud=dict(required=False, type='str'),
130+
auth=dict(required=False, type='dict'),
131+
auth_type=dict(default='password', required=False, type='str'),
132+
state=dict(default='present', choices=['present','absent', 'query']),
133+
server_id=dict(required=True, type='str'),
134+
interfaces=dict(default=[], type='list'),
135+
),
136+
supports_check_mode=False
137+
)
138+
139+
display = Display()
140+
141+
try:
142+
server_interface = ServerInterface(**module.params)
143+
changed = server_interface.apply()
144+
except Exception as e:
145+
module.fail_json(msg=repr(e))
146+
147+
server = server_interface.server
148+
149+
module.exit_json(
150+
changed=changed,
151+
server_name=server.name,
152+
server_networks=server.networks,
153+
)

tasks/main.yml

Lines changed: 29 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -26,26 +26,42 @@
2626
- name: Extract node groups
2727
set_fact:
2828
cluster_group: "{{ cluster_stack.stack.outputs | selectattr('output_key', 'equalto', 'cluster_group') | first }}"
29-
when: cluster_state != 'query'
29+
when: cluster_state in ['present', 'absent']
3030

3131
# Case 2: we are performing a read-only query of the cluster configuration.
3232
# Read the stack's outputs via the API.
3333
- block:
3434
- name: Gather OpenStack infrastructure information
3535
command: openstack stack output show {{ cluster_name }} cluster_group -f json
3636
register: stack_output
37+
changed_when: false
3738

3839
- name: Extract node groups
3940
set_fact:
4041
cluster_group: "{{ stack_output.stdout | from_json }}"
4142
when: cluster_state == 'query'
4243

43-
- name: Reset the python interpreter
44-
set_fact:
45-
ansible_python_interpreter: "{{ old_python_interpreter }}"
46-
when: cluster_venv != None
47-
4844
- block:
45+
- name: Extract server ids from cluster group output
46+
set_fact:
47+
cluster_servers: "{{ cluster_group.output_value | json_query('[*].nodes[*].id') | flatten }}"
48+
cluster_interfaces: "{{ cluster_net | map(attribute='net') | list }}"
49+
50+
- name: Attach interfaces to servers
51+
os_server_interface:
52+
auth_type: "{{ cluster_auth_type or omit }}"
53+
auth: "{{ cluster_auth or omit }}"
54+
cloud: "{{ cluster_cloud or omit }}"
55+
state: "{{ cluster_state }}"
56+
server_id: "{{ item }}"
57+
interfaces: "{{ cluster_interfaces }}"
58+
with_items: "{{ cluster_servers }}"
59+
register: openstack_server_interfaces
60+
61+
- name: Convert list of interfaces to a dictionary for easy lookup in inventory templating step
62+
set_fact:
63+
cluster_server_interfaces: "{{ dict(cluster_servers | zip(openstack_server_interfaces.results)) }}"
64+
4965
- name: Extract node objects
5066
set_fact:
5167
cluster_nodes: "{{ cluster_group.output_value | sum(attribute='nodes', start=[]) }}"
@@ -108,4 +124,10 @@
108124
cluster_scan_host: "{{ cluster_gw_name | default(inventory_hostname) }}"
109125
hosts: "{{ cluster_output_groups_no_gw | default(cluster_group.output_value) | map(attribute='nodes') | flatten }}"
110126

111-
when: cluster_state != 'absent'
127+
when: cluster_state in ['present', 'query']
128+
129+
- name: Reset the python interpreter
130+
set_fact:
131+
ansible_python_interpreter: "{{ old_python_interpreter }}"
132+
when: cluster_venv != None
133+

templates/cluster_inventory.j2

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -16,7 +16,7 @@ cluster
1616
{% for group_data in cluster_group.output_value %}
1717
[{{ cluster_name }}_{{ group_data.group }}]
1818
{% for node_data in group_data.nodes %}
19-
{{ node_data.name }} ansible_host={{ node_data.ip }}
19+
{{ node_data.name }} ansible_host={{ node_data.ip }} server_networks='{{ cluster_server_interfaces[node_data.id].server_networks | to_json }}'
2020
{% endfor %}
2121

2222
[{{ cluster_name }}_{{ group_data.group }}:vars]

0 commit comments

Comments
 (0)