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
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
|
#!/usr/bin/python
# Copyright (c) 2015 Hewlett-Packard Development Company, L.P.
# GNU General Public License v3.0+ (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
DOCUMENTATION = '''
---
module: port
short_description: Add/Update/Delete ports from an OpenStack cloud.
author: OpenStack Ansible SIG
description:
- Add, Update or Remove ports from an OpenStack cloud. A I(state) of
'present' will ensure the port is created or updated if required.
options:
network:
description:
- Network ID or name this port belongs to.
- Required when creating a new port.
type: str
name:
description:
- Name that has to be given to the port.
type: str
fixed_ips:
description:
- Desired IP and/or subnet for this port. Subnet is referenced by
subnet_id and IP is referenced by ip_address.
type: list
elements: dict
suboptions:
ip_address:
description: The fixed IP address to attempt to allocate.
required: true
type: str
subnet_id:
description: The subnet to attach the IP address to.
type: str
admin_state_up:
description:
- Sets admin state.
type: bool
mac_address:
description:
- MAC address of this port.
type: str
security_groups:
description:
- Security group(s) ID(s) or name(s) associated with the port (comma
separated string or YAML list)
type: list
elements: str
no_security_groups:
description:
- Do not associate a security group with this port.
type: bool
default: 'no'
allowed_address_pairs:
description:
- "Allowed address pairs list. Allowed address pairs are supported with
dictionary structure.
e.g. allowed_address_pairs:
- ip_address: 10.1.0.12
mac_address: ab:cd:ef:12:34:56
- ip_address: ..."
type: list
elements: dict
suboptions:
ip_address:
description: The IP address.
type: str
mac_address:
description: The MAC address.
type: str
extra_dhcp_opts:
description:
- "Extra dhcp options to be assigned to this port. Extra options are
supported with dictionary structure. Note that options cannot be removed
only updated.
e.g. extra_dhcp_opts:
- opt_name: opt name1
opt_value: value1
ip_version: 4
- opt_name: ..."
type: list
elements: dict
suboptions:
opt_name:
description: The name of the DHCP option to set.
type: str
required: true
opt_value:
description: The value of the DHCP option to set.
type: str
required: true
ip_version:
description: The IP version this DHCP option is for.
type: int
required: true
device_owner:
description:
- The ID of the entity that uses this port.
type: str
device_id:
description:
- Device ID of device using this port.
type: str
state:
description:
- Should the resource be present or absent.
choices: [present, absent]
default: present
type: str
vnic_type:
description:
- The type of the port that should be created
choices: [normal, direct, direct-physical, macvtap, baremetal, virtio-forwarder]
type: str
port_security_enabled:
description:
- Whether to enable or disable the port security on the network.
type: bool
requirements:
- "python >= 3.6"
- "openstacksdk"
extends_documentation_fragment:
- openstack.cloud.openstack
'''
EXAMPLES = '''
# Create a port
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
network: foo
# Create a port with a static IP
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
network: foo
fixed_ips:
- ip_address: 10.1.0.21
# Create a port with No security groups
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
network: foo
no_security_groups: True
# Update the existing 'port1' port with multiple security groups (version 1)
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
security_groups: 1496e8c7-4918-482a-9172-f4f00fc4a3a5,057d4bdf-6d4d-472...
# Update the existing 'port1' port with multiple security groups (version 2)
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
security_groups:
- 1496e8c7-4918-482a-9172-f4f00fc4a3a5
- 057d4bdf-6d4d-472...
# Create port of type 'direct'
- openstack.cloud.port:
state: present
auth:
auth_url: https://identity.example.com
username: admin
password: admin
project_name: admin
name: port1
network: foo
vnic_type: direct
'''
RETURN = '''
id:
description: Unique UUID.
returned: success
type: str
name:
description: Name given to the port.
returned: success
type: str
network_id:
description: Network ID this port belongs in.
returned: success
type: str
security_groups:
description: Security group(s) associated with this port.
returned: success
type: list
status:
description: Port's status.
returned: success
type: str
fixed_ips:
description: Fixed ip(s) associated with this port.
returned: success
type: list
tenant_id:
description: Tenant id associated with this port.
returned: success
type: str
allowed_address_pairs:
description: Allowed address pairs with this port.
returned: success
type: list
admin_state_up:
description: Admin state up flag for this port.
returned: success
type: bool
vnic_type:
description: Type of the created port
returned: success
type: str
port_security_enabled:
description: Port security state on the network.
returned: success
type: bool
'''
from ansible.module_utils.basic import AnsibleModule, missing_required_lib
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import (openstack_full_argument_spec,
openstack_module_kwargs,
openstack_cloud_from_module)
try:
from collections import OrderedDict
HAS_ORDEREDDICT = True
except ImportError:
try:
from ordereddict import OrderedDict
HAS_ORDEREDDICT = True
except ImportError:
HAS_ORDEREDDICT = False
def _needs_update(module, port, cloud):
"""Check for differences in the updatable values.
NOTE: We don't currently allow name updates.
"""
compare_simple = ['admin_state_up',
'mac_address',
'device_owner',
'device_id',
'binding:vnic_type',
'port_security_enabled']
compare_list_dict = ['allowed_address_pairs',
'extra_dhcp_opts']
compare_list = ['security_groups']
for key in compare_simple:
if module.params[key] is not None and module.params[key] != port[key]:
return True
for key in compare_list:
if (
module.params[key] is not None
and set(module.params[key]) != set(port[key])
):
return True
for key in compare_list_dict:
if not module.params[key]:
if not port[key]:
return True
# sort dicts in list
port_ordered = [OrderedDict(sorted(d.items())) for d in port[key]]
param_ordered = [OrderedDict(sorted(d.items())) for d in module.params[key]]
for d in param_ordered:
if d not in port_ordered:
return True
for d in port_ordered:
if d not in param_ordered:
return True
# NOTE: if port was created or updated with 'no_security_groups=True',
# subsequent updates without 'no_security_groups' flag or
# 'no_security_groups=False' and no specified 'security_groups', will not
# result in an update to the port where the default security group is
# applied.
if module.params['no_security_groups'] and port['security_groups'] != []:
return True
if module.params['fixed_ips'] is not None:
for item in module.params['fixed_ips']:
if 'ip_address' in item:
# if ip_address in request does not match any in existing port,
# update is required.
if not any(match['ip_address'] == item['ip_address']
for match in port['fixed_ips']):
return True
if 'subnet_id' in item:
return True
for item in port['fixed_ips']:
# if ip_address in existing port does not match any in request,
# update is required.
if not any(match.get('ip_address') == item['ip_address']
for match in module.params['fixed_ips']):
return True
return False
def _system_state_change(module, port, cloud):
state = module.params['state']
if state == 'present':
if not port:
return True
return _needs_update(module, port, cloud)
if state == 'absent' and port:
return True
return False
def _compose_port_args(module, cloud):
port_kwargs = {}
optional_parameters = ['name',
'fixed_ips',
'admin_state_up',
'mac_address',
'security_groups',
'allowed_address_pairs',
'extra_dhcp_opts',
'device_owner',
'device_id',
'binding:vnic_type',
'port_security_enabled']
for optional_param in optional_parameters:
if module.params[optional_param] is not None:
port_kwargs[optional_param] = module.params[optional_param]
if module.params['no_security_groups']:
port_kwargs['security_groups'] = []
return port_kwargs
def get_security_group_id(module, cloud, security_group_name_or_id):
security_group = cloud.get_security_group(security_group_name_or_id)
if not security_group:
module.fail_json(msg="Security group: %s, was not found"
% security_group_name_or_id)
return security_group['id']
def main():
argument_spec = openstack_full_argument_spec(
network=dict(required=False),
name=dict(required=False),
fixed_ips=dict(type='list', default=None, elements='dict'),
admin_state_up=dict(type='bool', default=None),
mac_address=dict(default=None),
security_groups=dict(default=None, type='list', elements='str'),
no_security_groups=dict(default=False, type='bool'),
allowed_address_pairs=dict(type='list', default=None, elements='dict'),
extra_dhcp_opts=dict(type='list', default=None, elements='dict'),
device_owner=dict(default=None),
device_id=dict(default=None),
state=dict(default='present', choices=['absent', 'present']),
vnic_type=dict(default=None,
choices=['normal', 'direct', 'direct-physical',
'macvtap', 'baremetal', 'virtio-forwarder']),
port_security_enabled=dict(default=None, type='bool')
)
module_kwargs = openstack_module_kwargs(
mutually_exclusive=[
['no_security_groups', 'security_groups'],
]
)
module = AnsibleModule(argument_spec,
supports_check_mode=True,
**module_kwargs)
if not HAS_ORDEREDDICT:
module.fail_json(msg=missing_required_lib('ordereddict'))
name = module.params['name']
state = module.params['state']
sdk, cloud = openstack_cloud_from_module(module)
try:
if module.params['security_groups']:
# translate security_groups to UUID's if names where provided
module.params['security_groups'] = [
get_security_group_id(module, cloud, v)
for v in module.params['security_groups']
]
# Neutron API accept 'binding:vnic_type' as an argument
# for the port type.
module.params['binding:vnic_type'] = module.params.pop('vnic_type')
port = None
network_id = None
if name:
port = cloud.get_port(name)
if module.check_mode:
module.exit_json(changed=_system_state_change(module, port, cloud))
changed = False
if state == 'present':
if not port:
network = module.params['network']
if not network:
module.fail_json(
msg="Parameter 'network' is required in Port Create"
)
port_kwargs = _compose_port_args(module, cloud)
network_object = cloud.get_network(network)
if network_object:
network_id = network_object['id']
else:
module.fail_json(
msg="Specified network was not found."
)
port = cloud.create_port(network_id, **port_kwargs)
changed = True
else:
if _needs_update(module, port, cloud):
port_kwargs = _compose_port_args(module, cloud)
port = cloud.update_port(port['id'], **port_kwargs)
changed = True
module.exit_json(changed=changed, id=port['id'], port=port)
if state == 'absent':
if port:
cloud.delete_port(port['id'])
changed = True
module.exit_json(changed=changed)
except sdk.exceptions.OpenStackCloudException as e:
module.fail_json(msg=str(e))
if __name__ == '__main__':
main()
|