This repository has been archived by the owner on Feb 29, 2024. It is now read-only.
-
Notifications
You must be signed in to change notification settings - Fork 28
/
tripleo_service_vip.py
354 lines (289 loc) · 10.6 KB
/
tripleo_service_vip.py
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
#!/usr/bin/python
# -*- coding: utf-8 -*-
# Copyright (c) 2021 OpenStack Foundation
# All Rights Reserved.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
import yaml
import keystoneauth1.exceptions as kauth1_exc
try:
from ansible.module_utils import network_data_v2
except ImportError:
from tripleo_ansible.ansible_plugins.module_utils import network_data_v2
from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.openstack import openstack_full_argument_spec
from ansible.module_utils.openstack import openstack_module_kwargs
from ansible.module_utils.openstack import openstack_cloud_from_module
ANSIBLE_METADATA = {
'metadata_version': '1.1',
'status': ['preview'],
'supported_by': 'community'
}
DOCUMENTATION = '''
---
module: tripleo_service_vip
short_description: Create a Virtual IP address for a service
version_added: "2.8"
description:
- "Create a Virtual IP address for a service"
options:
playbook_dir:
description:
- The path to the directory of the playbook that was passed to the
ansible-playbook command line.
type: str
stack_name:
description:
- Name of the overcloud stack which will be deployed on these instances
type: str
default: overcloud
service_name:
description:
- Name of the service the Virtual IP is intended for
type: str
state:
description:
- The desired provision state, "present" to provision, "absent" to
unprovision
default: present
choices:
- present
- absent
network:
description:
- Neutron network where the Virtual IP port will be created
type: str
fixed_ips:
description:
- A list of ip allocation definitions
type: list
elements: dict
suboptions:
ip_address:
description:
- IP address
type: str
subnet:
description:
- Neutron subnet name or id
type: str
use_neutron:
description:
- Boolean option to allow not to create a neutron port.
type: bool
author:
- Harald Jensås <hjensas@redhat.com>
'''
RETURN = '''
'''
EXAMPLES = '''
- name: Create redis Virtual IP
tripleo_service_vip:
stack_name: overcloud
service_name: redis
network: internal_api
fixed_ip:
- subnet: internal_api_subnet
register: redis_vip
- name: Create foo Virtual IP (Not creating a neutron port)
tripleo_service_vip:
stack_name: overcloud
service_name: foo
network: foo
fixed_ip:
- ip_address: 192.0.2.5
use_neutron: false
register: redis_vip
'''
VIRTUAL_IP_NAME_SUFFIX = '_virtual_ip'
class FakePort:
def __init__(self, fixed_ips):
self.fixed_ips = fixed_ips
def create_or_update_port(conn, net, stack=None, service=None,
fixed_ips=None):
if not fixed_ips:
raise Exception('ERROR: No IP allocation definition provided. '
'Please provide at least one IP allocation '
'definition using the fixed_ips argument.')
tags = {'tripleo_stack_name={}'.format(stack),
'tripleo_service_vip={}'.format(service)}
port_def = dict(name=service + VIRTUAL_IP_NAME_SUFFIX, network_id=net.id)
try:
port = next(conn.network.ports(tags=list(tags), network_id=net.id))
except StopIteration:
port = None
fixed_ips_def = port_def['fixed_ips'] = []
for fixed_ip in fixed_ips:
ip_address = fixed_ip.get('ip_address')
subnet_name = fixed_ip.get('subnet')
ip_def = {}
if ip_address:
ip_def['ip_address'] = ip_address
if subnet_name:
subnet = conn.network.find_subnet(subnet_name, network_id=net.id)
if subnet is None:
raise Exception('ERROR: Subnet {} does not exist for network '
'{}. Service {} is mapped to a subnet that '
'does not exist. Verify that the VipSubnetMap '
'parameter has the correct values.'.format(
subnet_name, net.name, service))
ip_def['subnet_id'] = subnet.id
fixed_ips_def.append(ip_def)
if not port:
port = conn.network.create_port(**port_def)
else:
# TODO: Check if port needs update
port = conn.network.update_port(port, **port_def)
p_tags = set(port.tags)
if not tags.issubset(p_tags):
p_tags.update(tags)
conn.network.set_tags(port, list(p_tags))
return port
def find_ctlplane_vip(conn, stack=None, service=None):
tags = ['tripleo_stack_name={}'.format(stack),
'tripleo_vip_net=ctlplane']
try:
port = next(conn.network.ports(tags=tags))
except StopIteration:
raise Exception('Virtual IP address on the ctlplane network for stack '
'{} not found. Service {} is mapped to the ctlplane '
'network and thus require a virtual IP address to be '
'present on the ctlplane network.'.format(stack,
service))
return port
def validate_service_vip_vars_file(service_vip_var_file):
if not os.path.isfile(service_vip_var_file):
raise Exception(
'ERROR: Service VIP var file {} is not a file'.format(
service_vip_var_file))
def write_vars_file(port, service, playbook_dir):
ips = [x['ip_address'] for x in port.fixed_ips]
if len(ips) == 1:
ips = ips[0]
playbook_dir_path = os.path.abspath(playbook_dir)
network_data_v2.validate_playbook_dir(playbook_dir)
service_vip_var_file = os.path.join(playbook_dir_path,
'service_vip_vars.yaml')
if not os.path.exists(service_vip_var_file):
data = dict()
else:
validate_service_vip_vars_file(service_vip_var_file)
with open(service_vip_var_file, 'r') as f:
data = yaml.safe_load(f.read())
data.update({service: ips})
with open(service_vip_var_file, 'w') as f:
f.write(yaml.safe_dump(data, default_flow_style=False))
def use_neutron(conn, stack, service, network, fixed_ips):
net = conn.network.find_network(network)
# NOTE: If the network does'nt exist fall back to use the ctlplane VIP
if net is None or net.name == 'ctlplane':
port = find_ctlplane_vip(conn, stack=stack, service=service)
else:
port = create_or_update_port(conn, net, stack=stack, service=service,
fixed_ips=fixed_ips)
return port
def use_fake(service, fixed_ips):
if [fixed_ip for fixed_ip in fixed_ips if 'ip_address' in fixed_ip]:
port = FakePort(fixed_ips)
else:
raise Exception('Neutron service is not available and no fixed IP '
'address provided for {} service virtual IP. When '
'neutron service is not available a fixed IP '
'address must be provided'.format(service))
return port
# This method is here so that openstack_cloud_from_module
# can be mocked in tests.
def _openstack_cloud_from_module(module):
_, conn = openstack_cloud_from_module(module)
return _, conn
def delete_service_vip(module, stack, service='all'):
try:
_, conn = _openstack_cloud_from_module(module)
_use_neutron = conn.identity.find_service('neutron') is not None
except kauth1_exc.MissingRequiredOptions:
return
if not _use_neutron:
return
if service == 'all':
tags = {'tripleo_stack_name={}'.format(stack)}
ports = conn.network.ports(tags=list(tags))
matching = [p for p in ports
if any("tripleo_service_vip" in tag for tag in p.tags)]
else:
tags = {'tripleo_stack_name={}'.format(stack),
'tripleo_service_vip={}'.format(service)}
matching = conn.network.ports(tags=list(tags))
for p in matching:
try:
conn.network.delete_port(p.id)
except Exception:
pass
def create_service_vip(module, stack, service, network, fixed_ips,
playbook_dir):
_use_neutron = True
for fixed_ip in fixed_ips:
if ('use_neutron', False) in fixed_ip.items():
_use_neutron = False
break
if _use_neutron:
try:
_, conn = _openstack_cloud_from_module(module)
_use_neutron = conn.identity.find_service('neutron') is not None
except kauth1_exc.MissingRequiredOptions:
_use_neutron = False
if _use_neutron:
port = use_neutron(conn, stack, service, network, fixed_ips)
else:
port = use_fake(service, fixed_ips)
write_vars_file(port, service, playbook_dir)
def run_module():
result = dict(
success=False,
changed=False,
error="",
)
argument_spec = openstack_full_argument_spec(
**yaml.safe_load(DOCUMENTATION)['options']
)
module = AnsibleModule(
argument_spec,
supports_check_mode=False,
**openstack_module_kwargs()
)
stack = module.params.get('stack_name')
state = module.params.get('state')
service = module.params.get('service_name') or 'all'
try:
if state == 'present' and service == 'all':
raise Exception("Provide service_name for service_vip creation.")
if state == 'absent':
delete_service_vip(module, stack, service)
else:
network = module.params['network']
fixed_ips = module.params.get('fixed_ips', [])
playbook_dir = module.params['playbook_dir']
create_service_vip(module, stack, service, network, fixed_ips,
playbook_dir)
result['changed'] = True
result['success'] = True
module.exit_json(**result)
except Exception as err:
result['error'] = str(err)
result['msg'] = ('ERROR: Failed creating/deleting service virtual IP!'
' {}'.format(err))
module.fail_json(**result)
def main():
run_module()
if __name__ == '__main__':
main()