summaryrefslogtreecommitdiffstats
path: root/collections-debian-merged/ansible_collections/openstack/cloud/plugins/modules/port.py
blob: 79a484117429c64113abcc647714df762a7e4b69 (plain)
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()