Refactored port and port_info modules
Define port's module attribute 'name' as a required attribute because
this parameter is used to find, update and delete ports. Technically,
a name is not required to create a port, but idempotency cannot be
implemented without an identifier to refer to a port. In this
collection we use resource names to find and identify resources. We
do not offer a dedicated id attribute in most modules.
Use port's module attribute 'network' when finding, creating,
updating or deleting ports if the user provided this attribute.
This allows to reduce ambiguity when equal names are used across
different networks.
Added 'description' parameter to port module.
Renamed port's module attributes 'vnic_type' to 'binding_vnic_type'
and 'admin_state_up' to 'is_admin_state_up' to match openstacksdk's
attribute names which are used e.g. in module results. Added aliases
for the old attribute names to keep backward compatibility.
Renamed port_info's module attribute 'port' to 'name' and added
the former as an alias to be consistent with other *_info modules.
Dropped default=None and required=False from argument_spec of port
module because those are the default in Ansible [1][2].
Dropped 'id' field from port module's results to be consistent across
other modules. Use 'port.id' instead.
Sorted argument specs and documentation of the port module and
marked attributes which are not updatable.
Updated RETURN fields documentation for the module results of both
port and port_info modules.
Added integration tests to check the update mechanism of the port
module.
Added assertions for module results to catch future changes in the
openstacksdk and our Ansible modules.
Dropped openstacksdk version check since we require a recent release
anyway.
Fixed indentation in integration tests.
Merged integration tests of port_info module into port module,
because the former does not create any ports and assumes that
ports have been created earlier.
[1] https://docs.ansible.com/ansible/latest/dev_guide/developing_modules_documenting.html
[2] 61af59c808/lib/ansible/module_utils/common/parameters.py (L489)
Signed-off-by: Jakob Meng <code@jakobmeng.de>
Change-Id: Iacca78649f8e01ae95649d8d462f5d0a1740405e
This commit is contained in:
parent
ce6193cd2f
commit
d0eb83e934
@ -95,7 +95,6 @@
|
||||
object
|
||||
object_container
|
||||
port
|
||||
port_info
|
||||
project
|
||||
project_info
|
||||
recordset
|
||||
|
@ -1,9 +1,47 @@
|
||||
network_name: ansible_port_network
|
||||
network_external: true
|
||||
subnet_name: ansible_port_subnet
|
||||
port_name: ansible_port
|
||||
secgroup_name: ansible_port_secgroup
|
||||
no_security_groups: True
|
||||
binding_profile:
|
||||
"pci_slot": "0000:03:11.1"
|
||||
"physical_network": "provider"
|
||||
expected_fields:
|
||||
- allowed_address_pairs
|
||||
- binding_host_id
|
||||
- binding_profile
|
||||
- binding_vif_details
|
||||
- binding_vif_type
|
||||
- binding_vnic_type
|
||||
- created_at
|
||||
- data_plane_status
|
||||
- description
|
||||
- device_id
|
||||
- device_owner
|
||||
- device_profile
|
||||
- dns_assignment
|
||||
- dns_domain
|
||||
- dns_name
|
||||
- extra_dhcp_opts
|
||||
- fixed_ips
|
||||
- id
|
||||
- ip_allocation
|
||||
- is_admin_state_up
|
||||
- is_port_security_enabled
|
||||
- mac_address
|
||||
- name
|
||||
- network_id
|
||||
- numa_affinity_policy
|
||||
- project_id
|
||||
- propagate_uplink_status
|
||||
- qos_network_policy_id
|
||||
- qos_policy_id
|
||||
- resource_request
|
||||
- revision_number
|
||||
- security_group_ids
|
||||
- status
|
||||
- tags
|
||||
- tenant_id
|
||||
- trunk_details
|
||||
- updated_at
|
||||
network_external: true
|
||||
network_name: ansible_port_network
|
||||
no_security_groups: True
|
||||
port_name: ansible_port
|
||||
secgroup_name: ansible_port_secgroup
|
||||
subnet_name: ansible_port_subnet
|
||||
|
@ -1,145 +1,290 @@
|
||||
---
|
||||
- name: Create network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ network_name }}"
|
||||
external: "{{ network_external }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ network_name }}"
|
||||
external: "{{ network_external }}"
|
||||
register: network
|
||||
|
||||
- name: Create subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ subnet_name }}"
|
||||
network_name: "{{ network_name }}"
|
||||
cidr: 10.5.5.0/24
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ subnet_name }}"
|
||||
network_name: "{{ network_name }}"
|
||||
cidr: 10.5.5.0/24
|
||||
register: subnet
|
||||
|
||||
- name: Create port (no security group or default security group)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: "{{ no_security_groups }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: "{{ no_security_groups }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
register: port
|
||||
|
||||
- debug: var=port
|
||||
|
||||
- name: assert return values of port module
|
||||
assert:
|
||||
that:
|
||||
# allow new fields to be introduced but prevent fields from being removed
|
||||
- expected_fields|difference(port.port.keys())|length == 0
|
||||
|
||||
- name: List all ports
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: info
|
||||
|
||||
- name: Get info about all ports
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: info
|
||||
|
||||
- name: Check info about ports
|
||||
assert:
|
||||
that:
|
||||
- info.ports|length > 0
|
||||
# allow new fields to be introduced but prevent fields from being removed
|
||||
- expected_fields|difference(info.ports[0].keys())|length == 0
|
||||
|
||||
- name: Get port by id
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
name: "{{ info.ports[0].id }}"
|
||||
register: info_id
|
||||
|
||||
- name: Assert infos by id
|
||||
assert:
|
||||
that:
|
||||
- info_id.ports|length == 1
|
||||
- info_id.ports[0].id == info.ports[0].id
|
||||
|
||||
- name: List port with device_id filter
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
filters:
|
||||
device_id: "{{ info.ports[0].device_id }}"
|
||||
register: info_filter
|
||||
|
||||
- name: Assert port was returned
|
||||
assert:
|
||||
that:
|
||||
- info_filter.ports | length >= 1
|
||||
|
||||
- name: Delete port (no security group or default security group)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create security group
|
||||
openstack.cloud.security_group:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ secgroup_name }}"
|
||||
description: Test group
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ secgroup_name }}"
|
||||
description: Test group
|
||||
register: security_group
|
||||
|
||||
- name: Create port (with security group)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
security_groups:
|
||||
- "{{ secgroup_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
security_groups:
|
||||
- "{{ secgroup_name }}"
|
||||
register: port
|
||||
|
||||
- debug: var=port
|
||||
|
||||
- name: Delete port (with security group)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create port (with dns_name, dns_domain)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
dns_name: "dns-port-name"
|
||||
dns_domain: "example.com."
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
dns_name: "dns-port-name"
|
||||
dns_domain: "example.com."
|
||||
register: port
|
||||
|
||||
- debug: var=port
|
||||
|
||||
- name: Delete port (with dns name,domain)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create port (with allowed_address_pairs and extra_dhcp_opts)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: "{{ no_security_groups }}"
|
||||
allowed_address_pairs:
|
||||
- ip_address: 10.6.7.0/24
|
||||
extra_dhcp_opts:
|
||||
- opt_name: "bootfile-name"
|
||||
opt_value: "testfile.1"
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: "{{ no_security_groups }}"
|
||||
allowed_address_pairs:
|
||||
- ip_address: 10.6.7.0/24
|
||||
extra_dhcp_opts:
|
||||
- opt_name: "bootfile-name"
|
||||
opt_value: "testfile.1"
|
||||
register: port
|
||||
|
||||
- debug: var=port
|
||||
|
||||
- name: Delete port (with allowed_address_pairs and extra_dhcp_opts)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Create port which will be updated
|
||||
openstack.cloud.port:
|
||||
allowed_address_pairs:
|
||||
- ip_address: 10.6.7.0/24
|
||||
mac_address: "aa:bb:cc:dd:ee:ff"
|
||||
cloud: "{{ cloud }}"
|
||||
description: "What a great port"
|
||||
extra_dhcp_opts:
|
||||
- ip_version: 4
|
||||
opt_name: "bootfile-name"
|
||||
opt_value: "testfile.1"
|
||||
dns_name: "dns-port-name"
|
||||
dns_domain: "example.com."
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: yes
|
||||
state: present
|
||||
register: port
|
||||
|
||||
- name: Create port which will be updated (again)
|
||||
openstack.cloud.port:
|
||||
allowed_address_pairs:
|
||||
- ip_address: 10.6.7.0/24
|
||||
mac_address: "aa:bb:cc:dd:ee:ff"
|
||||
cloud: "{{ cloud }}"
|
||||
description: "What a great port"
|
||||
extra_dhcp_opts:
|
||||
- ip_version: 4
|
||||
opt_name: "bootfile-name"
|
||||
opt_value: "testfile.1"
|
||||
# We have no valid dns name configured
|
||||
#dns_name: "dns-port-name"
|
||||
#dns_domain: "example.com."
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.69
|
||||
subnet_id: "{{ subnet.subnet.id }}"
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
no_security_groups: yes
|
||||
state: present
|
||||
register: port_again
|
||||
|
||||
- name: Assert port did not change
|
||||
assert:
|
||||
that:
|
||||
- port.port.id == port_again.port.id
|
||||
- port_again is not changed
|
||||
|
||||
- name: Update port
|
||||
openstack.cloud.port:
|
||||
allowed_address_pairs:
|
||||
- ip_address: 11.9.9.0/24
|
||||
mac_address: "aa:aa:aa:bb:bb:bb"
|
||||
cloud: "{{ cloud }}"
|
||||
description: "This port got updated"
|
||||
extra_dhcp_opts:
|
||||
- opt_name: "bootfile-name"
|
||||
opt_value: "testfile.2"
|
||||
# We have no valid dns name configured
|
||||
#dns_name: "dns-port-name-2"
|
||||
#dns_domain: "another.example.com."
|
||||
fixed_ips:
|
||||
- ip_address: 10.5.5.70
|
||||
subnet_id: "{{ subnet.subnet.id }}"
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
security_groups:
|
||||
- "{{ secgroup_name }}"
|
||||
state: present
|
||||
register: port_updated
|
||||
|
||||
- name: Assert updated port
|
||||
assert:
|
||||
that:
|
||||
- port_updated.port.id == port.port.id
|
||||
- port_updated.port.allowed_address_pairs|length == 1
|
||||
- port_updated.port.allowed_address_pairs[0].ip_address == "11.9.9.0/24"
|
||||
- port_updated.port.allowed_address_pairs[0].mac_address == "aa:aa:aa:bb:bb:bb"
|
||||
- port_updated.port.description == "This port got updated"
|
||||
- port_updated.port.extra_dhcp_opts|length == 1
|
||||
- port_updated.port.extra_dhcp_opts[0].opt_value == "testfile.2"
|
||||
# We have no valid dns name configured
|
||||
#- port_updated.port.dns_name == "dns-port-name-2"
|
||||
#- port_updated.port.dns_domain == "another.example.com."
|
||||
- port_updated.port.fixed_ips|length == 1
|
||||
- port_updated.port.fixed_ips[0].ip_address == "10.5.5.70"
|
||||
- port_updated.port.fixed_ips[0].subnet_id == subnet.subnet.id
|
||||
- port_updated.port.security_group_ids|length == 1
|
||||
- port_updated.port.security_group_ids[0] == security_group.secgroup.id
|
||||
|
||||
- name: Delete updated port
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Delete security group
|
||||
openstack.cloud.security_group:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ secgroup_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ secgroup_name }}"
|
||||
|
||||
- name: Test port binding config (runs from train release sdk > 0.28)
|
||||
block:
|
||||
- name: Create port (with binding profile)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
binding_profile: "{{ binding_profile }}"
|
||||
register: port
|
||||
- name: Create port (with binding profile)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: present
|
||||
name: "{{ port_name }}"
|
||||
network: "{{ network_name }}"
|
||||
binding_profile: "{{ binding_profile }}"
|
||||
register: port
|
||||
|
||||
- name: Assert binding:profile exists in created port
|
||||
assert:
|
||||
that: "port.port['binding_profile']"
|
||||
- name: Assert binding_profile exists in created port
|
||||
assert:
|
||||
that: "port.port['binding_profile']"
|
||||
|
||||
- debug: var=port
|
||||
- debug: var=port
|
||||
|
||||
- name: Delete port (with binding profile)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
when: sdk_version is version(0.28, '>')
|
||||
- name: Delete port (with binding profile)
|
||||
openstack.cloud.port:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ port_name }}"
|
||||
|
||||
- name: Delete subnet
|
||||
openstack.cloud.subnet:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ subnet_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ subnet_name }}"
|
||||
|
||||
- name: Delete network
|
||||
openstack.cloud.network:
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ network_name }}"
|
||||
cloud: "{{ cloud }}"
|
||||
state: absent
|
||||
name: "{{ network_name }}"
|
||||
|
@ -1,72 +0,0 @@
|
||||
---
|
||||
- name: List all ports
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
register: result_all
|
||||
|
||||
- name: Assert fields
|
||||
assert:
|
||||
that:
|
||||
- item in result_all.ports.0
|
||||
loop:
|
||||
- allowed_address_pairs
|
||||
- binding_host_id
|
||||
- binding_profile
|
||||
- binding_vif_details
|
||||
- binding_vif_type
|
||||
- binding_vnic_type
|
||||
- created_at
|
||||
- data_plane_status
|
||||
- description
|
||||
- device_id
|
||||
- device_owner
|
||||
- device_profile
|
||||
- dns_assignment
|
||||
- dns_domain
|
||||
- dns_name
|
||||
- extra_dhcp_opts
|
||||
- fixed_ips
|
||||
- id
|
||||
- ip_allocation
|
||||
- is_admin_state_up
|
||||
- is_port_security_enabled
|
||||
- mac_address
|
||||
- name
|
||||
- network_id
|
||||
- numa_affinity_policy
|
||||
- project_id
|
||||
- propagate_uplink_status
|
||||
- qos_network_policy_id
|
||||
- qos_policy_id
|
||||
- resource_request
|
||||
- revision_number
|
||||
- security_group_ids
|
||||
- status
|
||||
- tags
|
||||
- tenant_id
|
||||
- trunk_details
|
||||
- updated_at
|
||||
|
||||
- name: Get port by id
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
port: "{{ result_all.ports[0].id }}"
|
||||
register: result_id
|
||||
|
||||
- name: Assert results by id
|
||||
assert:
|
||||
that:
|
||||
- item.id == result_all.ports[0].id
|
||||
loop: "{{ result_id.ports }}"
|
||||
|
||||
- name: List port with device_id filter
|
||||
openstack.cloud.port_info:
|
||||
cloud: "{{ cloud }}"
|
||||
filters:
|
||||
device_id: "{{ result_all.ports[0].device_id }}"
|
||||
register: result_filter
|
||||
|
||||
- name: Assert port was returned
|
||||
assert:
|
||||
that:
|
||||
- result_filter.ports | length >= 1
|
@ -52,7 +52,6 @@
|
||||
when: sdk_version is version(0.44, '>=')
|
||||
- { role: object, tags: object }
|
||||
- { role: port, tags: port }
|
||||
- { role: port_info, tags: port_info }
|
||||
- { role: project, tags: project }
|
||||
- { role: project_info, tags: project_info }
|
||||
- { role: recordset, tags: recordset }
|
||||
|
@ -10,128 +10,149 @@ 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.
|
||||
- Add, Update or Remove ports from an OpenStack cloud.
|
||||
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
|
||||
binding_profile:
|
||||
description:
|
||||
- Binding profile dict that the port should be created with.
|
||||
type: dict
|
||||
dns_name:
|
||||
description:
|
||||
- The dns name of the port ( only with dns-integration enabled )
|
||||
type: str
|
||||
dns_domain:
|
||||
description:
|
||||
- The dns domain of the port ( only with dns-integration enabled )
|
||||
type: str
|
||||
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: ..."
|
||||
- The port will change during update if not all suboptions are
|
||||
specified, e.g. when ip_address is given but mac_address is not.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
ip_address:
|
||||
description: The IP address.
|
||||
type: str
|
||||
mac_address:
|
||||
description: The MAC address.
|
||||
type: str
|
||||
binding_profile:
|
||||
description:
|
||||
- Binding profile dict that the port should be created with.
|
||||
type: dict
|
||||
binding_vnic_type:
|
||||
description:
|
||||
- The type of the port that should be created
|
||||
choices: [normal,
|
||||
direct,
|
||||
direct-physical,
|
||||
macvtap,
|
||||
baremetal,
|
||||
virtio-forwarder]
|
||||
type: str
|
||||
aliases: ['vnic_type']
|
||||
description:
|
||||
description:
|
||||
- Description of the port.
|
||||
type: str
|
||||
device_id:
|
||||
description:
|
||||
- Device ID of device using this port.
|
||||
type: str
|
||||
device_owner:
|
||||
description:
|
||||
- The ID of the entity that uses this port.
|
||||
type: str
|
||||
dns_domain:
|
||||
description:
|
||||
- The dns domain of the port ( only with dns-integration enabled )
|
||||
type: str
|
||||
dns_name:
|
||||
description:
|
||||
- The dns name of the port ( only with dns-integration enabled )
|
||||
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:
|
||||
- ip_version: 4
|
||||
opt_name: bootfile-name
|
||||
opt_value: pxelinux.0
|
||||
- opt_name: ..."
|
||||
- The port will change during update if not all suboptions are
|
||||
specified, e.g. when opt_name is given but ip_version is not.
|
||||
type: list
|
||||
elements: dict
|
||||
suboptions:
|
||||
ip_version:
|
||||
description: The IP version this DHCP option is for.
|
||||
type: int
|
||||
required: true
|
||||
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
|
||||
fixed_ips:
|
||||
description:
|
||||
- Desired IP and/or subnet for this port. Subnet is referenced by
|
||||
subnet_id and IP is referenced by ip_address.
|
||||
- The port will change during update if not all suboptions are
|
||||
specified, e.g. when ip_address is given but subnet_id is not.
|
||||
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
|
||||
is_admin_state_up:
|
||||
description:
|
||||
- Sets admin state.
|
||||
type: bool
|
||||
aliases: ['admin_state_up']
|
||||
mac_address:
|
||||
description:
|
||||
- MAC address of this port.
|
||||
type: str
|
||||
name:
|
||||
description:
|
||||
- Name that has to be given to the port.
|
||||
- This port attribute cannot be updated.
|
||||
type: str
|
||||
required: true
|
||||
network:
|
||||
description:
|
||||
- ID or name of the network this port belongs to.
|
||||
- Required when creating a new port.
|
||||
- Must be a name when creating a port.
|
||||
- This port attribute cannot be updated.
|
||||
type: str
|
||||
no_security_groups:
|
||||
description:
|
||||
- Do not associate a security group with this port.
|
||||
- "Deprecated. Use I(security_groups): C([]) instead
|
||||
of I(no_security_groups): C(yes)."
|
||||
type: bool
|
||||
default: 'no'
|
||||
port_security_enabled:
|
||||
description:
|
||||
- Whether to enable or disable the port security on the network.
|
||||
type: bool
|
||||
security_groups:
|
||||
description:
|
||||
- Security group(s) ID(s) or name(s) associated with the port.
|
||||
type: list
|
||||
elements: str
|
||||
state:
|
||||
description:
|
||||
- Should the resource be present or absent.
|
||||
choices: [present, absent]
|
||||
default: present
|
||||
type: str
|
||||
requirements:
|
||||
- "python >= 3.6"
|
||||
- "openstacksdk"
|
||||
@ -211,7 +232,7 @@ EXAMPLES = '''
|
||||
project_name: admin
|
||||
name: port1
|
||||
network: foo
|
||||
vnic_type: direct
|
||||
binding_vnic_type: direct
|
||||
|
||||
# Create a port with binding profile
|
||||
- openstack.cloud.port:
|
||||
@ -224,305 +245,457 @@ EXAMPLES = '''
|
||||
name: port1
|
||||
network: foo
|
||||
binding_profile:
|
||||
"pci_slot": "0000:03:11.1"
|
||||
"physical_network": "provider"
|
||||
pci_slot: "0000:03:11.1"
|
||||
physical_network: "provider"
|
||||
'''
|
||||
|
||||
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
|
||||
binding:profile:
|
||||
description: Port binded profile
|
||||
returned: success
|
||||
port:
|
||||
description: Dictionary describing the port.
|
||||
type: dict
|
||||
returned: On success when I(state) is C(present).
|
||||
contains:
|
||||
allowed_address_pairs:
|
||||
description: Allowed address pairs.
|
||||
returned: success
|
||||
type: list
|
||||
sample: []
|
||||
binding_host_id:
|
||||
description: |
|
||||
The ID of the host where the port is allocated. In some cases,
|
||||
different implementations can run on different hosts.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
|
||||
binding_profile:
|
||||
description: |
|
||||
A dictionary the enables the application running on the
|
||||
specified host to pass and receive vif port-specific
|
||||
information to the plug-in.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {}
|
||||
binding_vif_details:
|
||||
description: |
|
||||
A dictionary that enables the application to pass
|
||||
information about functions that the Networking API provides.
|
||||
returned: success
|
||||
type: dict
|
||||
binding_vif_type:
|
||||
description: The VIF type for the port.
|
||||
returned: success
|
||||
type: dict
|
||||
binding_vnic_type:
|
||||
description: |
|
||||
The virtual network interface card (vNIC) type that is
|
||||
bound to the neutron port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "normal"
|
||||
created_at:
|
||||
description: Timestamp when the port was created.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2022-02-03T13:28:25Z"
|
||||
data_plane_status:
|
||||
description: Status of the underlying data plane of a port.
|
||||
returned: success
|
||||
type: str
|
||||
description:
|
||||
description: The port description.
|
||||
returned: success
|
||||
type: str
|
||||
device_id:
|
||||
description: Device ID of this port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
|
||||
device_owner:
|
||||
description: Device owner of this port, e.g. C(network:dhcp).
|
||||
returned: success
|
||||
type: str
|
||||
sample: "network:router_interface"
|
||||
device_profile:
|
||||
description: |
|
||||
Device profile of this port, refers to Cyborg device-profiles:
|
||||
https://docs.openstack.org/api-ref/accelerator/v2/index.html#
|
||||
device-profiles.
|
||||
returned: success
|
||||
type: str
|
||||
dns_assignment:
|
||||
description: DNS assignment for the port.
|
||||
returned: success
|
||||
type: list
|
||||
dns_domain:
|
||||
description: DNS domain assigned to the port.
|
||||
returned: success
|
||||
type: str
|
||||
dns_name:
|
||||
description: DNS name for the port.
|
||||
returned: success
|
||||
type: str
|
||||
extra_dhcp_opts:
|
||||
description: |
|
||||
A set of zero or more extra DHCP option pairs.
|
||||
An option pair consists of an option value and name.
|
||||
returned: success
|
||||
type: list
|
||||
sample: []
|
||||
fixed_ips:
|
||||
description: |
|
||||
IP addresses for the port. Includes the IP address and subnet
|
||||
ID.
|
||||
returned: success
|
||||
type: list
|
||||
id:
|
||||
description: The port ID.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de"
|
||||
ip_allocation:
|
||||
description: |
|
||||
The ip_allocation indicates when ports use deferred,
|
||||
immediate or no IP allocation.
|
||||
returned: success
|
||||
type: str
|
||||
is_admin_state_up:
|
||||
description: |
|
||||
The administrative state of the port, which is up C(True) or
|
||||
down C(False).
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
is_port_security_enabled:
|
||||
description: |
|
||||
The port security status, which is enabled C(True) or disabled
|
||||
C(False).
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
mac_address:
|
||||
description: The MAC address of an allowed address pair.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "00:00:5E:00:53:42"
|
||||
name:
|
||||
description: The port name.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "port_name"
|
||||
network_id:
|
||||
description: The ID of the attached network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d"
|
||||
numa_affinity_policy:
|
||||
description: |
|
||||
The NUMA affinity policy defined for this port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "required"
|
||||
project_id:
|
||||
description: The ID of the project who owns the network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "aa1ede4f-3952-4131-aab6-3b8902268c7d"
|
||||
propagate_uplink_status:
|
||||
description: Whether to propagate uplink status of the port.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
qos_network_policy_id:
|
||||
description: |
|
||||
The ID of the QoS policy attached to the network where the
|
||||
port is bound.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "1e4f3958-c0c9-4dec-82fa-ed2dc1c5cb34"
|
||||
qos_policy_id:
|
||||
description: The ID of the QoS policy attached to the port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b20bb47f-5d6d-45a6-8fe7-2c1b44f0db73"
|
||||
resource_request:
|
||||
description: |
|
||||
The port-resource-request exposes Placement resources
|
||||
(i.e.: minimum-bandwidth) and traits (i.e.: vnic-type, physnet)
|
||||
requested by a port to Nova and Placement.
|
||||
returned: success
|
||||
type: str
|
||||
revision_number:
|
||||
description: The revision number of the resource.
|
||||
returned: success
|
||||
type: int
|
||||
sample: 0
|
||||
security_group_ids:
|
||||
description: The IDs of any attached security groups.
|
||||
returned: success
|
||||
type: list
|
||||
status:
|
||||
description: The port status. Value is C(ACTIVE) or C(DOWN).
|
||||
returned: success
|
||||
type: str
|
||||
sample: "ACTIVE"
|
||||
tags:
|
||||
description: The list of tags on the resource.
|
||||
returned: success
|
||||
type: list
|
||||
sample: []
|
||||
tenant_id:
|
||||
description: Same as I(project_id). Deprecated.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "51fce036d7984ba6af4f6c849f65ef00"
|
||||
trunk_details:
|
||||
description: |
|
||||
The trunk referring to this parent port and its subports.
|
||||
Present for trunk parent ports if C(trunk-details) extension
|
||||
is loaded.
|
||||
returned: success
|
||||
type: dict
|
||||
updated_at:
|
||||
description: Timestamp when the port was last updated.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2022-02-03T13:28:25Z"
|
||||
'''
|
||||
|
||||
from ansible.module_utils.basic import missing_required_lib
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
try:
|
||||
from collections import OrderedDict
|
||||
HAS_ORDEREDDICT = True
|
||||
except ImportError:
|
||||
try:
|
||||
from ordereddict import OrderedDict
|
||||
HAS_ORDEREDDICT = True
|
||||
except ImportError:
|
||||
HAS_ORDEREDDICT = False
|
||||
|
||||
|
||||
class NetworkPortModule(OpenStackModule):
|
||||
class PortModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
network=dict(),
|
||||
name=dict(),
|
||||
fixed_ips=dict(type='list', elements='dict'),
|
||||
admin_state_up=dict(type='bool'),
|
||||
mac_address=dict(),
|
||||
security_groups=dict(type='list', elements='str'),
|
||||
no_security_groups=dict(default=False, type='bool'),
|
||||
allowed_address_pairs=dict(type='list', elements='dict'),
|
||||
extra_dhcp_opts=dict(type='list', elements='dict'),
|
||||
device_owner=dict(),
|
||||
device_id=dict(),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
vnic_type=dict(choices=['normal', 'direct', 'direct-physical',
|
||||
'macvtap', 'baremetal', 'virtio-forwarder']),
|
||||
port_security_enabled=dict(type='bool'),
|
||||
binding_profile=dict(type='dict'),
|
||||
binding_vnic_type=dict(choices=['normal', 'direct', 'direct-physical',
|
||||
'macvtap', 'baremetal',
|
||||
'virtio-forwarder'],
|
||||
aliases=['vnic_type']),
|
||||
description=dict(),
|
||||
device_id=dict(),
|
||||
device_owner=dict(),
|
||||
dns_domain=dict(),
|
||||
dns_name=dict(),
|
||||
dns_domain=dict()
|
||||
extra_dhcp_opts=dict(type='list', elements='dict'),
|
||||
fixed_ips=dict(type='list', elements='dict'),
|
||||
is_admin_state_up=dict(type='bool', aliases=['admin_state_up']),
|
||||
mac_address=dict(),
|
||||
name=dict(required=True),
|
||||
network=dict(),
|
||||
no_security_groups=dict(default=False, type='bool'),
|
||||
port_security_enabled=dict(type='bool'),
|
||||
security_groups=dict(type='list', elements='str'),
|
||||
state=dict(default='present', choices=['absent', 'present']),
|
||||
)
|
||||
|
||||
module_kwargs = dict(
|
||||
mutually_exclusive=[
|
||||
['no_security_groups', 'security_groups'],
|
||||
],
|
||||
required_if=[
|
||||
('state', 'present', ('network',)),
|
||||
],
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def _is_dns_integration_enabled(self):
|
||||
""" Check if dns-integraton is enabled """
|
||||
for ext in self.conn.network.extensions():
|
||||
if ext.alias == 'dns-integration':
|
||||
return True
|
||||
return False
|
||||
|
||||
def _needs_update(self, port):
|
||||
"""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',
|
||||
'binding:profile']
|
||||
compare_dns = ['dns_name', 'dns_domain']
|
||||
compare_list_dict = ['allowed_address_pairs',
|
||||
'extra_dhcp_opts']
|
||||
compare_list = ['security_groups']
|
||||
|
||||
if self.conn.has_service('dns') and \
|
||||
self._is_dns_integration_enabled():
|
||||
for key in compare_dns:
|
||||
if self.params[key] is not None and \
|
||||
self.params[key] != port[key]:
|
||||
return True
|
||||
|
||||
for key in compare_simple:
|
||||
if self.params[key] is not None and self.params[key] != port[key]:
|
||||
return True
|
||||
for key in compare_list:
|
||||
if (
|
||||
self.params[key] is not None
|
||||
and set(self.params[key]) != set(port[key])
|
||||
):
|
||||
return True
|
||||
|
||||
for key in compare_list_dict:
|
||||
if not self.params[key]:
|
||||
if port.get(key):
|
||||
return True
|
||||
|
||||
if self.params[key]:
|
||||
if not port.get(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 self.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 self.params['no_security_groups'] and port['security_groups'] != []:
|
||||
return True
|
||||
|
||||
if self.params['fixed_ips'] is not None:
|
||||
for item in self.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 self.params['fixed_ips']):
|
||||
return True
|
||||
|
||||
return False
|
||||
|
||||
def _system_state_change(self, port):
|
||||
state = self.params['state']
|
||||
if state == 'present':
|
||||
if not port:
|
||||
return True
|
||||
return self._needs_update(port)
|
||||
if state == 'absent' and port:
|
||||
return True
|
||||
return False
|
||||
|
||||
def _compose_port_args(self):
|
||||
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',
|
||||
'binding:profile']
|
||||
|
||||
if self.conn.has_service('dns') and \
|
||||
self._is_dns_integration_enabled():
|
||||
optional_parameters.extend(['dns_name', 'dns_domain'])
|
||||
|
||||
for optional_param in optional_parameters:
|
||||
if self.params[optional_param] is not None:
|
||||
port_kwargs[optional_param] = self.params[optional_param]
|
||||
|
||||
if self.params['no_security_groups']:
|
||||
port_kwargs['security_groups'] = []
|
||||
|
||||
return port_kwargs
|
||||
|
||||
def get_security_group_id(self, security_group_name_or_id):
|
||||
security_group = self.conn.get_security_group(security_group_name_or_id)
|
||||
if not security_group:
|
||||
self.fail_json(msg="Security group: %s, was not found"
|
||||
% security_group_name_or_id)
|
||||
return security_group['id']
|
||||
|
||||
def run(self):
|
||||
if not HAS_ORDEREDDICT:
|
||||
self.fail_json(msg=missing_required_lib('ordereddict'))
|
||||
|
||||
name = self.params['name']
|
||||
network_name_or_id = self.params['network']
|
||||
port_name_or_id = self.params['name']
|
||||
state = self.params['state']
|
||||
|
||||
if self.params['security_groups']:
|
||||
# translate security_groups to UUID's if names where provided
|
||||
self.params['security_groups'] = [
|
||||
self.get_security_group_id(v)
|
||||
for v in self.params['security_groups']
|
||||
]
|
||||
network = None
|
||||
if network_name_or_id:
|
||||
network = self.conn.network.find_network(
|
||||
network_name_or_id, ignore_missing=False)
|
||||
|
||||
# Neutron API accept 'binding:vnic_type' as an argument
|
||||
# for the port type.
|
||||
self.params['binding:vnic_type'] = self.params.pop('vnic_type')
|
||||
# Neutron API accept 'binding:profile' as an argument
|
||||
# for the port binding profile type.
|
||||
self.params['binding:profile'] = self.params.pop('binding_profile')
|
||||
|
||||
port = None
|
||||
network_id = None
|
||||
if name:
|
||||
port = self.conn.get_port(name)
|
||||
port = self.conn.network.find_port(
|
||||
port_name_or_id,
|
||||
# use network id in query if network parameter was specified
|
||||
**(dict(network_id=network.id) if network else dict()))
|
||||
|
||||
if self.ansible.check_mode:
|
||||
self.exit_json(changed=self._system_state_change(port))
|
||||
self.exit_json(changed=self._will_change(network, port, state))
|
||||
|
||||
changed = False
|
||||
if state == 'present':
|
||||
if not port:
|
||||
network = self.params['network']
|
||||
if not network:
|
||||
self.fail_json(
|
||||
msg="Parameter 'network' is required in Port Create"
|
||||
)
|
||||
port_kwargs = self._compose_port_args()
|
||||
network_object = self.conn.get_network(network)
|
||||
if state == 'present' and not port:
|
||||
# create port
|
||||
port = self._create(network)
|
||||
self.exit_json(changed=True,
|
||||
port=port.to_dict(computed=False))
|
||||
elif state == 'present' and port:
|
||||
# update port
|
||||
update = self._build_update(port)
|
||||
if update:
|
||||
port = self._update(port, update)
|
||||
|
||||
if network_object:
|
||||
network_id = network_object['id']
|
||||
else:
|
||||
self.fail_json(
|
||||
msg="Specified network was not found."
|
||||
)
|
||||
self.exit_json(changed=bool(update),
|
||||
port=port.to_dict(computed=False))
|
||||
elif state == 'absent' and port:
|
||||
# delete port
|
||||
self._delete(port)
|
||||
self.exit_json(changed=True)
|
||||
elif state == 'absent' and not port:
|
||||
# do nothing
|
||||
self.exit_json(changed=False)
|
||||
|
||||
port_kwargs['network_id'] = network_id
|
||||
port = self.conn.network.create_port(**port_kwargs)
|
||||
changed = True
|
||||
else:
|
||||
if self._needs_update(port):
|
||||
port_kwargs = self._compose_port_args()
|
||||
port = self.conn.network.update_port(port['id'],
|
||||
**port_kwargs)
|
||||
changed = True
|
||||
self.exit_json(changed=changed, id=port['id'], port=port)
|
||||
def _build_update(self, port):
|
||||
update = {}
|
||||
|
||||
if state == 'absent':
|
||||
if port:
|
||||
self.conn.delete_port(port['id'])
|
||||
changed = True
|
||||
self.exit_json(changed=changed)
|
||||
# A port's name cannot be updated by this module because
|
||||
# it is used to find ports by name or id.
|
||||
# If name is an id, then we do not have a name to update.
|
||||
# If name is a name actually, then it was used to find a
|
||||
# matching port hence the name is the user defined one
|
||||
# already.
|
||||
|
||||
# updateable port attributes in openstacksdk
|
||||
# (OpenStack API names in braces):
|
||||
# - allowed_address_pairs (allowed_address_pairs)
|
||||
# - binding_host_id (binding:host_id)
|
||||
# - binding_profile (binding:profile)
|
||||
# - binding_vnic_type (binding:vnic_type)
|
||||
# - data_plane_status (data_plane_status)
|
||||
# - description (description)
|
||||
# - device_id (device_id)
|
||||
# - device_owner (device_owner)
|
||||
# (- device_profile (device_profile))
|
||||
# - dns_domain (dns_domain)
|
||||
# - dns_name (dns_name)
|
||||
# - extra_dhcp_opts (extra_dhcp_opts)
|
||||
# - fixed_ips (fixed_ips)
|
||||
# - is_admin_state_up (admin_state_up)
|
||||
# - is_port_security_enabled (port_security_enabled)
|
||||
# - mac_address (mac_address)
|
||||
# - name (name)
|
||||
# - numa_affinity_policy (numa_affinity_policy)
|
||||
# - qos_policy_id (qos_policy_id)
|
||||
# - security_group_ids (security_groups)
|
||||
# Ref.: https://docs.openstack.org/api-ref/network/v2/index.html#update-port
|
||||
|
||||
# Update all known updateable attributes although
|
||||
# our module might not support them yet
|
||||
|
||||
# Update attributes which can be compared straight away
|
||||
port_attributes = dict(
|
||||
(k, self.params[k])
|
||||
for k in ['binding_host_id', 'binding_vnic_type',
|
||||
'data_plane_status', 'description', 'device_id',
|
||||
'device_owner', 'is_admin_state_up',
|
||||
'is_port_security_enabled', 'mac_address',
|
||||
'numa_affinity_policy']
|
||||
if k in self.params and self.params[k] is not None
|
||||
and self.params[k] != port[k])
|
||||
|
||||
# Compare dictionaries
|
||||
for k in ['binding_profile']:
|
||||
if self.params[k] is None:
|
||||
continue
|
||||
|
||||
if (self.params[k] or port[k]) \
|
||||
and self.params[k] != port[k]:
|
||||
port_attributes[k] = self.params[k]
|
||||
|
||||
# Attribute qos_policy_id is not supported by this module and would
|
||||
# need special handling using self.conn.network.find_qos_policy()
|
||||
|
||||
# Compare attributes which are lists of dictionaries
|
||||
for k in ['allowed_address_pairs', 'extra_dhcp_opts', 'fixed_ips']:
|
||||
if self.params[k] is None:
|
||||
continue
|
||||
|
||||
if (self.params[k] or port[k]) \
|
||||
and self.params[k] != port[k]:
|
||||
port_attributes[k] = self.params[k]
|
||||
|
||||
# Compare security groups
|
||||
if self.params['no_security_groups']:
|
||||
security_group_ids = []
|
||||
elif self.params['security_groups'] is not None:
|
||||
security_group_ids = [
|
||||
self.conn.network.find_security_group(
|
||||
security_group_name_or_id, ignore_missing=False).id
|
||||
for security_group_name_or_id in self.params['security_groups']
|
||||
]
|
||||
else:
|
||||
security_group_ids = None
|
||||
|
||||
if security_group_ids is not None \
|
||||
and set(security_group_ids) != set(port['security_group_ids']):
|
||||
port_attributes['security_group_ids'] = security_group_ids
|
||||
|
||||
# Compare dns attributes
|
||||
if self.conn.has_service('dns') and \
|
||||
self.conn.network.find_extension('dns-integration'):
|
||||
port_attributes.update(dict(
|
||||
(k, self.params[k])
|
||||
for k in ['dns_name', 'dns_domain']
|
||||
if self.params[k] is not None and self.params[k] != port[k]
|
||||
))
|
||||
|
||||
if port_attributes:
|
||||
update['port_attributes'] = port_attributes
|
||||
return update
|
||||
|
||||
def _create(self, network):
|
||||
args = {}
|
||||
args['network_id'] = network.id
|
||||
|
||||
# Fetch IDs of security groups next to fail early
|
||||
# if any security group does not exist
|
||||
if self.params['no_security_groups']:
|
||||
args['security_group_ids'] = []
|
||||
elif self.params['security_groups'] is not None:
|
||||
args['security_group_ids'] = [
|
||||
self.conn.network.find_security_group(
|
||||
security_group_name_or_id, ignore_missing=False).id
|
||||
for security_group_name_or_id in self.params['security_groups']
|
||||
]
|
||||
|
||||
for k in ['allowed_address_pairs',
|
||||
'binding_profile',
|
||||
'binding_vnic_type',
|
||||
'device_id',
|
||||
'device_owner',
|
||||
'description',
|
||||
'extra_dhcp_opts',
|
||||
'is_admin_state_up',
|
||||
'mac_address',
|
||||
'port_security_enabled',
|
||||
'fixed_ips',
|
||||
'name']:
|
||||
if self.params[k] is not None:
|
||||
args[k] = self.params[k]
|
||||
|
||||
if self.conn.has_service('dns') \
|
||||
and self.conn.network.find_extension('dns-integration'):
|
||||
for k in ['dns_domain', 'dns_name']:
|
||||
if self.params[k] is not None:
|
||||
args[k] = self.params[k]
|
||||
|
||||
return self.conn.network.create_port(**args)
|
||||
|
||||
def _delete(self, port):
|
||||
self.conn.network.delete_port(port.id)
|
||||
|
||||
def _update(self, port, update):
|
||||
port_attributes = update.get('port_attributes')
|
||||
if port_attributes:
|
||||
port = self.conn.network.update_port(port, **port_attributes)
|
||||
return port
|
||||
|
||||
def _will_change(self, port, state):
|
||||
if state == 'present' and not port:
|
||||
return True
|
||||
elif state == 'present' and port:
|
||||
return bool(self._build_update(port))
|
||||
elif state == 'absent' and port:
|
||||
return False
|
||||
else:
|
||||
# state == 'absent' and not port:
|
||||
return True
|
||||
|
||||
|
||||
def main():
|
||||
module = NetworkPortModule()
|
||||
module = PortModule()
|
||||
module()
|
||||
|
||||
|
||||
|
@ -11,10 +11,11 @@ author: OpenStack Ansible SIG
|
||||
description:
|
||||
- Retrieve information about ports from OpenStack.
|
||||
options:
|
||||
port:
|
||||
name:
|
||||
description:
|
||||
- Unique name or ID of a port.
|
||||
type: str
|
||||
aliases: ['port']
|
||||
filters:
|
||||
description:
|
||||
- A dictionary of meta data to use for further filtering. Elements
|
||||
@ -41,10 +42,10 @@ EXAMPLES = '''
|
||||
# Gather information about a single port
|
||||
- openstack.cloud.port_info:
|
||||
cloud: mycloud
|
||||
port: 6140317d-e676-31e1-8a4a-b1913814a471
|
||||
name: 6140317d-e676-31e1-8a4a-b1913814a471
|
||||
|
||||
# Gather information about all ports that have device_id set to a specific value
|
||||
# and with a status of ACTIVE.
|
||||
# Gather information about all ports that have device_id set to a specific
|
||||
# value and with a status of ACTIVE.
|
||||
- openstack.cloud.port_info:
|
||||
cloud: mycloud
|
||||
filters:
|
||||
@ -54,34 +55,37 @@ EXAMPLES = '''
|
||||
|
||||
RETURN = '''
|
||||
ports:
|
||||
description: List of port dictionaries. A subset of the dictionary keys
|
||||
listed below may be returned, depending on your cloud provider.
|
||||
description: |
|
||||
List of port dictionaries. A subset of the dictionary keys listed below
|
||||
may be returned, depending on your cloud provider.
|
||||
returned: always
|
||||
type: list
|
||||
elements: dict
|
||||
contains:
|
||||
allowed_address_pairs:
|
||||
description: A set of zero or more allowed address pairs. An
|
||||
address pair consists of an IP address and MAC address.
|
||||
description: Allowed address pairs.
|
||||
returned: success
|
||||
type: list
|
||||
sample: []
|
||||
binding_host_id:
|
||||
description: The UUID of the host where the port is allocated.
|
||||
description: |
|
||||
The ID of the host where the port is allocated. In some cases,
|
||||
different implementations can run on different hosts.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
|
||||
binding_profile:
|
||||
description: A dictionary the enables the application running on
|
||||
the host to pass and receive VIF port-specific
|
||||
information to the plug-in.
|
||||
description: |
|
||||
A dictionary the enables the application running on the
|
||||
specified host to pass and receive vif port-specific
|
||||
information to the plug-in.
|
||||
returned: success
|
||||
type: dict
|
||||
sample: {}
|
||||
binding_vif_details:
|
||||
description: A dictionary that enables the application to pass
|
||||
information about functions that the Networking API
|
||||
provides.
|
||||
description: |
|
||||
A dictionary that enables the application to pass
|
||||
information about functions that the Networking API provides.
|
||||
returned: success
|
||||
type: dict
|
||||
binding_vif_type:
|
||||
@ -89,13 +93,14 @@ ports:
|
||||
returned: success
|
||||
type: dict
|
||||
binding_vnic_type:
|
||||
description: The virtual network interface card (vNIC) type that is
|
||||
bound to the neutron port.
|
||||
description: |
|
||||
The virtual network interface card (vNIC) type that is
|
||||
bound to the neutron port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "normal"
|
||||
created_at:
|
||||
description: Date the port was created
|
||||
description: Timestamp when the port was created.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2022-02-03T13:28:25Z"
|
||||
@ -104,69 +109,78 @@ ports:
|
||||
returned: success
|
||||
type: str
|
||||
description:
|
||||
description: Description of a port
|
||||
description: The port description.
|
||||
returned: success
|
||||
type: str
|
||||
device_id:
|
||||
description: The UUID of the device that uses this port.
|
||||
description: Device ID of this port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
|
||||
device_owner:
|
||||
description: The UUID of the entity that uses this port.
|
||||
description: Device owner of this port, e.g. C(network:dhcp).
|
||||
returned: success
|
||||
type: str
|
||||
sample: "network:router_interface"
|
||||
device_profile:
|
||||
description: Device profile
|
||||
description: |
|
||||
Device profile of this port, refers to Cyborg device-profiles:
|
||||
https://docs.openstack.org/api-ref/accelerator/v2/index.html#
|
||||
device-profiles.
|
||||
returned: success
|
||||
type: str
|
||||
dns_assignment:
|
||||
description: DNS assignment information.
|
||||
description: DNS assignment for the port.
|
||||
returned: success
|
||||
type: list
|
||||
dns_domain:
|
||||
description: A valid DNS domain
|
||||
description: DNS domain assigned to the port.
|
||||
returned: success
|
||||
type: str
|
||||
dns_name:
|
||||
description: DNS name
|
||||
description: DNS name for the port.
|
||||
returned: success
|
||||
type: str
|
||||
extra_dhcp_opts:
|
||||
description: A set of zero or more extra DHCP option pairs.
|
||||
An option pair consists of an option value and name.
|
||||
description: |
|
||||
A set of zero or more extra DHCP option pairs.
|
||||
An option pair consists of an option value and name.
|
||||
returned: success
|
||||
type: list
|
||||
sample: []
|
||||
fixed_ips:
|
||||
description: The IP addresses for the port. Includes the IP address
|
||||
and UUID of the subnet.
|
||||
description: |
|
||||
IP addresses for the port. Includes the IP address and subnet
|
||||
ID.
|
||||
returned: success
|
||||
type: list
|
||||
id:
|
||||
description: The UUID of the port.
|
||||
description: The port ID.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de"
|
||||
ip_allocation:
|
||||
description: Indicates when ports use either deferred, immediate
|
||||
or no IP allocation (none).
|
||||
description: |
|
||||
The ip_allocation indicates when ports use deferred,
|
||||
immediate or no IP allocation.
|
||||
returned: success
|
||||
type: str
|
||||
is_admin_state_up:
|
||||
description: The administrative state of the router, which is
|
||||
up (true) or down (false).
|
||||
description: |
|
||||
The administrative state of the port, which is up C(True) or
|
||||
down C(False).
|
||||
returned: success
|
||||
type: bool
|
||||
sample: true
|
||||
is_port_security_enabled:
|
||||
description: The port security status. The status is enabled (true) or disabled (false).
|
||||
description: |
|
||||
The port security status, which is enabled C(True) or disabled
|
||||
C(False).
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
mac_address:
|
||||
description: The MAC address.
|
||||
description: The MAC address of an allowed address pair.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "00:00:5E:00:53:42"
|
||||
@ -176,42 +190,43 @@ ports:
|
||||
type: str
|
||||
sample: "port_name"
|
||||
network_id:
|
||||
description: The UUID of the attached network.
|
||||
description: The ID of the attached network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d"
|
||||
numa_affinity_policy:
|
||||
description: The port NUMA affinity policy requested during the
|
||||
virtual machine scheduling. Values are None, required,
|
||||
preferred or legacy.
|
||||
description: |
|
||||
The NUMA affinity policy defined for this port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "required"
|
||||
project_id:
|
||||
description: The ID of the project.
|
||||
description: The ID of the project who owns the network.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "aa1ede4f-3952-4131-aab6-3b8902268c7d"
|
||||
propagate_uplink_status:
|
||||
description: The uplink status propagation of the port.
|
||||
description: Whether to propagate uplink status of the port.
|
||||
returned: success
|
||||
type: bool
|
||||
sample: false
|
||||
qos_network_policy_id:
|
||||
description: The ID of the QoS policy of the network where this
|
||||
port is plugged.
|
||||
description: |
|
||||
The ID of the QoS policy attached to the network where the
|
||||
port is bound.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "1e4f3958-c0c9-4dec-82fa-ed2dc1c5cb34"
|
||||
qos_policy_id:
|
||||
description: The ID of the QoS policy associated with the port.
|
||||
description: The ID of the QoS policy attached to the port.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "b20bb47f-5d6d-45a6-8fe7-2c1b44f0db73"
|
||||
resource_request:
|
||||
description: Expose Placement resources i.e. minimum-bandwidth
|
||||
and traits i.e. vnic-type, physnet requested by a
|
||||
port to Nova and Placement
|
||||
description: |
|
||||
The port-resource-request exposes Placement resources
|
||||
(i.e.: minimum-bandwidth) and traits (i.e.: vnic-type, physnet)
|
||||
requested by a port to Nova and Placement.
|
||||
returned: success
|
||||
type: str
|
||||
revision_number:
|
||||
@ -220,11 +235,11 @@ ports:
|
||||
type: int
|
||||
sample: 0
|
||||
security_group_ids:
|
||||
description: The UUIDs of any attached security groups.
|
||||
description: The IDs of any attached security groups.
|
||||
returned: success
|
||||
type: list
|
||||
status:
|
||||
description: The port status.
|
||||
description: The port status. Value is C(ACTIVE) or C(DOWN).
|
||||
returned: success
|
||||
type: str
|
||||
sample: "ACTIVE"
|
||||
@ -234,47 +249,47 @@ ports:
|
||||
type: list
|
||||
sample: []
|
||||
tenant_id:
|
||||
description: The UUID of the tenant who owns the network. Deprecated.
|
||||
description: Same as I(project_id). Deprecated.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "51fce036d7984ba6af4f6c849f65ef00"
|
||||
trunk_details:
|
||||
description: The details about the trunk.
|
||||
description: |
|
||||
The trunk referring to this parent port and its subports.
|
||||
Present for trunk parent ports if C(trunk-details) extension
|
||||
is loaded.
|
||||
returned: success
|
||||
type: dict
|
||||
updated_at:
|
||||
description: Last port update
|
||||
description: Timestamp when the port was last updated.
|
||||
returned: success
|
||||
type: str
|
||||
sample: "2022-02-03T13:28:25Z"
|
||||
|
||||
'''
|
||||
|
||||
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
|
||||
|
||||
|
||||
class NetworkPortInfoModule(OpenStackModule):
|
||||
class PortInfoModule(OpenStackModule):
|
||||
argument_spec = dict(
|
||||
port=dict(),
|
||||
filters=dict(type='dict', default={}),
|
||||
name=dict(aliases=['port']),
|
||||
filters=dict(type='dict'),
|
||||
)
|
||||
module_kwargs = dict(
|
||||
supports_check_mode=True
|
||||
)
|
||||
|
||||
def run(self):
|
||||
port = self.params['port']
|
||||
filters = self.params['filters']
|
||||
|
||||
ports = self.conn.search_ports(port, filters)
|
||||
|
||||
ports = [p.to_dict(computed=False) for p in ports]
|
||||
ports = [p.to_dict(computed=False) for p in
|
||||
self.conn.search_ports(
|
||||
name_or_id=self.params['name'],
|
||||
filters=self.params['filters'])]
|
||||
|
||||
self.exit_json(changed=False, ports=ports)
|
||||
|
||||
|
||||
def main():
|
||||
module = NetworkPortInfoModule()
|
||||
module = PortInfoModule()
|
||||
module()
|
||||
|
||||
|
||||
|
Loading…
Reference in New Issue
Block a user