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:
Jakob Meng
2022-07-21 12:20:57 +02:00
parent ce6193cd2f
commit d0eb83e934
7 changed files with 914 additions and 617 deletions

View File

@@ -95,7 +95,6 @@
object object
object_container object_container
port port
port_info
project project
project_info project_info
recordset recordset

View File

@@ -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: binding_profile:
"pci_slot": "0000:03:11.1" "pci_slot": "0000:03:11.1"
"physical_network": "provider" "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

View File

@@ -1,145 +1,290 @@
--- ---
- name: Create network - name: Create network
openstack.cloud.network: openstack.cloud.network:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ network_name }}" name: "{{ network_name }}"
external: "{{ network_external }}" external: "{{ network_external }}"
register: network
- name: Create subnet - name: Create subnet
openstack.cloud.subnet: openstack.cloud.subnet:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ subnet_name }}" name: "{{ subnet_name }}"
network_name: "{{ network_name }}" network_name: "{{ network_name }}"
cidr: 10.5.5.0/24 cidr: 10.5.5.0/24
register: subnet
- name: Create port (no security group or default security group) - name: Create port (no security group or default security group)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ port_name }}" name: "{{ port_name }}"
network: "{{ network_name }}" network: "{{ network_name }}"
no_security_groups: "{{ no_security_groups }}" no_security_groups: "{{ no_security_groups }}"
fixed_ips: fixed_ips:
- ip_address: 10.5.5.69 - ip_address: 10.5.5.69
register: port register: port
- debug: var=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) - name: Delete port (no security group or default security group)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ port_name }}" name: "{{ port_name }}"
- name: Create security group - name: Create security group
openstack.cloud.security_group: openstack.cloud.security_group:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ secgroup_name }}" name: "{{ secgroup_name }}"
description: Test group description: Test group
register: security_group
- name: Create port (with security group) - name: Create port (with security group)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ port_name }}" name: "{{ port_name }}"
network: "{{ network_name }}" network: "{{ network_name }}"
fixed_ips: fixed_ips:
- ip_address: 10.5.5.69 - ip_address: 10.5.5.69
security_groups: security_groups:
- "{{ secgroup_name }}" - "{{ secgroup_name }}"
register: port register: port
- debug: var=port - debug: var=port
- name: Delete port (with security group) - name: Delete port (with security group)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ port_name }}" name: "{{ port_name }}"
- name: Create port (with dns_name, dns_domain) - name: Create port (with dns_name, dns_domain)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ port_name }}" name: "{{ port_name }}"
network: "{{ network_name }}" network: "{{ network_name }}"
fixed_ips: fixed_ips:
- ip_address: 10.5.5.69 - ip_address: 10.5.5.69
dns_name: "dns-port-name" dns_name: "dns-port-name"
dns_domain: "example.com." dns_domain: "example.com."
register: port register: port
- debug: var=port - debug: var=port
- name: Delete port (with dns name,domain) - name: Delete port (with dns name,domain)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ port_name }}" name: "{{ port_name }}"
- name: Create port (with allowed_address_pairs and extra_dhcp_opts) - name: Create port (with allowed_address_pairs and extra_dhcp_opts)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: present state: present
name: "{{ port_name }}" name: "{{ port_name }}"
network: "{{ network_name }}" network: "{{ network_name }}"
no_security_groups: "{{ no_security_groups }}" no_security_groups: "{{ no_security_groups }}"
allowed_address_pairs: allowed_address_pairs:
- ip_address: 10.6.7.0/24 - ip_address: 10.6.7.0/24
extra_dhcp_opts: extra_dhcp_opts:
- opt_name: "bootfile-name" - opt_name: "bootfile-name"
opt_value: "testfile.1" opt_value: "testfile.1"
register: port register: port
- debug: var=port - debug: var=port
- name: Delete port (with allowed_address_pairs and extra_dhcp_opts) - name: Delete port (with allowed_address_pairs and extra_dhcp_opts)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ port_name }}" 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 - name: Delete security group
openstack.cloud.security_group: openstack.cloud.security_group:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ secgroup_name }}" name: "{{ secgroup_name }}"
- name: Test port binding config (runs from train release sdk > 0.28) - name: Create port (with binding profile)
block: openstack.cloud.port:
- name: Create port (with binding profile) cloud: "{{ cloud }}"
openstack.cloud.port: state: present
cloud: "{{ cloud }}" name: "{{ port_name }}"
state: present network: "{{ network_name }}"
name: "{{ port_name }}" binding_profile: "{{ binding_profile }}"
network: "{{ network_name }}" register: port
binding_profile: "{{ binding_profile }}"
register: port
- name: Assert binding:profile exists in created port - name: Assert binding_profile exists in created port
assert: assert:
that: "port.port['binding_profile']" that: "port.port['binding_profile']"
- debug: var=port - debug: var=port
- name: Delete port (with binding profile) - name: Delete port (with binding profile)
openstack.cloud.port: openstack.cloud.port:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ port_name }}" name: "{{ port_name }}"
when: sdk_version is version(0.28, '>')
- name: Delete subnet - name: Delete subnet
openstack.cloud.subnet: openstack.cloud.subnet:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ subnet_name }}" name: "{{ subnet_name }}"
- name: Delete network - name: Delete network
openstack.cloud.network: openstack.cloud.network:
cloud: "{{ cloud }}" cloud: "{{ cloud }}"
state: absent state: absent
name: "{{ network_name }}" name: "{{ network_name }}"

View File

@@ -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

View File

@@ -52,7 +52,6 @@
when: sdk_version is version(0.44, '>=') when: sdk_version is version(0.44, '>=')
- { role: object, tags: object } - { role: object, tags: object }
- { role: port, tags: port } - { role: port, tags: port }
- { role: port_info, tags: port_info }
- { role: project, tags: project } - { role: project, tags: project }
- { role: project_info, tags: project_info } - { role: project_info, tags: project_info }
- { role: recordset, tags: recordset } - { role: recordset, tags: recordset }

View File

@@ -10,128 +10,149 @@ module: port
short_description: Add/Update/Delete ports from an OpenStack cloud. short_description: Add/Update/Delete ports from an OpenStack cloud.
author: OpenStack Ansible SIG author: OpenStack Ansible SIG
description: description:
- Add, Update or Remove ports from an OpenStack cloud. A I(state) of - Add, Update or Remove ports from an OpenStack cloud.
'present' will ensure the port is created or updated if required.
options: options:
network: allowed_address_pairs:
description: description:
- Network ID or name this port belongs to. - "Allowed address pairs list. Allowed address pairs are supported
- Required when creating a new port. with dictionary structure.
type: str e.g. allowed_address_pairs:
name: - ip_address: 10.1.0.12
description: mac_address: ab:cd:ef:12:34:56
- Name that has to be given to the port. - ip_address: ..."
type: str - The port will change during update if not all suboptions are
fixed_ips: specified, e.g. when ip_address is given but mac_address is not.
description: type: list
- Desired IP and/or subnet for this port. Subnet is referenced by elements: dict
subnet_id and IP is referenced by ip_address. suboptions:
type: list ip_address:
elements: dict description: The IP address.
suboptions: type: str
ip_address: mac_address:
description: The fixed IP address to attempt to allocate. description: The MAC address.
required: true type: str
type: str binding_profile:
subnet_id: description:
description: The subnet to attach the IP address to. - Binding profile dict that the port should be created with.
type: str type: dict
admin_state_up: binding_vnic_type:
description: description:
- Sets admin state. - The type of the port that should be created
type: bool choices: [normal,
mac_address: direct,
description: direct-physical,
- MAC address of this port. macvtap,
type: str baremetal,
security_groups: virtio-forwarder]
description: type: str
- Security group(s) ID(s) or name(s) associated with the port (comma aliases: ['vnic_type']
separated string or YAML list) description:
type: list description:
elements: str - Description of the port.
no_security_groups: type: str
description: device_id:
- Do not associate a security group with this port. description:
type: bool - Device ID of device using this port.
default: 'no' type: str
allowed_address_pairs: device_owner:
description: description:
- "Allowed address pairs list. Allowed address pairs are supported with - The ID of the entity that uses this port.
dictionary structure. type: str
e.g. allowed_address_pairs: dns_domain:
- ip_address: 10.1.0.12 description:
mac_address: ab:cd:ef:12:34:56 - The dns domain of the port ( only with dns-integration enabled )
- ip_address: ..." type: str
type: list dns_name:
elements: dict description:
suboptions: - The dns name of the port ( only with dns-integration enabled )
ip_address: type: str
description: The IP address. extra_dhcp_opts:
type: str description:
mac_address: - "Extra dhcp options to be assigned to this port. Extra options are
description: The MAC address. supported with dictionary structure. Note that options cannot be
type: str removed only updated.
extra_dhcp_opts: e.g. extra_dhcp_opts:
description: - ip_version: 4
- "Extra dhcp options to be assigned to this port. Extra options are opt_name: bootfile-name
supported with dictionary structure. Note that options cannot be removed opt_value: pxelinux.0
only updated. - opt_name: ..."
e.g. extra_dhcp_opts: - The port will change during update if not all suboptions are
- opt_name: opt name1 specified, e.g. when opt_name is given but ip_version is not.
opt_value: value1 type: list
ip_version: 4 elements: dict
- opt_name: ..." suboptions:
type: list ip_version:
elements: dict description: The IP version this DHCP option is for.
suboptions: type: int
opt_name: required: true
description: The name of the DHCP option to set. opt_name:
type: str description: The name of the DHCP option to set.
required: true type: str
opt_value: required: true
description: The value of the DHCP option to set. opt_value:
type: str description: The value of the DHCP option to set.
required: true type: str
ip_version: required: true
description: The IP version this DHCP option is for. fixed_ips:
type: int description:
required: true - Desired IP and/or subnet for this port. Subnet is referenced by
device_owner: subnet_id and IP is referenced by ip_address.
description: - The port will change during update if not all suboptions are
- The ID of the entity that uses this port. specified, e.g. when ip_address is given but subnet_id is not.
type: str type: list
device_id: elements: dict
description: suboptions:
- Device ID of device using this port. ip_address:
type: str description: The fixed IP address to attempt to allocate.
state: required: true
description: type: str
- Should the resource be present or absent. subnet_id:
choices: [present, absent] description: The subnet to attach the IP address to.
default: present type: str
type: str is_admin_state_up:
vnic_type: description:
description: - Sets admin state.
- The type of the port that should be created type: bool
choices: [normal, direct, direct-physical, macvtap, baremetal, virtio-forwarder] aliases: ['admin_state_up']
type: str mac_address:
port_security_enabled: description:
description: - MAC address of this port.
- Whether to enable or disable the port security on the network. type: str
type: bool name:
binding_profile: description:
description: - Name that has to be given to the port.
- Binding profile dict that the port should be created with. - This port attribute cannot be updated.
type: dict type: str
dns_name: required: true
description: network:
- The dns name of the port ( only with dns-integration enabled ) description:
type: str - ID or name of the network this port belongs to.
dns_domain: - Required when creating a new port.
description: - Must be a name when creating a port.
- The dns domain of the port ( only with dns-integration enabled ) - This port attribute cannot be updated.
type: str 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: requirements:
- "python >= 3.6" - "python >= 3.6"
- "openstacksdk" - "openstacksdk"
@@ -211,7 +232,7 @@ EXAMPLES = '''
project_name: admin project_name: admin
name: port1 name: port1
network: foo network: foo
vnic_type: direct binding_vnic_type: direct
# Create a port with binding profile # Create a port with binding profile
- openstack.cloud.port: - openstack.cloud.port:
@@ -224,305 +245,457 @@ EXAMPLES = '''
name: port1 name: port1
network: foo network: foo
binding_profile: binding_profile:
"pci_slot": "0000:03:11.1" pci_slot: "0000:03:11.1"
"physical_network": "provider" physical_network: "provider"
''' '''
RETURN = ''' RETURN = '''
id: port:
description: Unique UUID. description: Dictionary describing the port.
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
type: dict 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 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 PortModule(OpenStackModule):
class NetworkPortModule(OpenStackModule):
argument_spec = dict( 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'), 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_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_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( module_kwargs = dict(
mutually_exclusive=[ mutually_exclusive=[
['no_security_groups', 'security_groups'], ['no_security_groups', 'security_groups'],
], ],
required_if=[
('state', 'present', ('network',)),
],
supports_check_mode=True 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): def run(self):
if not HAS_ORDEREDDICT: network_name_or_id = self.params['network']
self.fail_json(msg=missing_required_lib('ordereddict')) port_name_or_id = self.params['name']
name = self.params['name']
state = self.params['state'] state = self.params['state']
if self.params['security_groups']: network = None
# translate security_groups to UUID's if names where provided if network_name_or_id:
self.params['security_groups'] = [ network = self.conn.network.find_network(
self.get_security_group_id(v) network_name_or_id, ignore_missing=False)
for v in self.params['security_groups']
]
# Neutron API accept 'binding:vnic_type' as an argument port = self.conn.network.find_port(
# for the port type. port_name_or_id,
self.params['binding:vnic_type'] = self.params.pop('vnic_type') # use network id in query if network parameter was specified
# Neutron API accept 'binding:profile' as an argument **(dict(network_id=network.id) if network else dict()))
# 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)
if self.ansible.check_mode: 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' and not port:
if state == 'present': # create port
if not port: port = self._create(network)
network = self.params['network'] self.exit_json(changed=True,
if not network: port=port.to_dict(computed=False))
self.fail_json( elif state == 'present' and port:
msg="Parameter 'network' is required in Port Create" # update port
) update = self._build_update(port)
port_kwargs = self._compose_port_args() if update:
network_object = self.conn.get_network(network) port = self._update(port, update)
if network_object: self.exit_json(changed=bool(update),
network_id = network_object['id'] port=port.to_dict(computed=False))
else: elif state == 'absent' and port:
self.fail_json( # delete port
msg="Specified network was not found." 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 def _build_update(self, port):
port = self.conn.network.create_port(**port_kwargs) update = {}
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)
if state == 'absent': # A port's name cannot be updated by this module because
if port: # it is used to find ports by name or id.
self.conn.delete_port(port['id']) # If name is an id, then we do not have a name to update.
changed = True # If name is a name actually, then it was used to find a
self.exit_json(changed=changed) # 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(): def main():
module = NetworkPortModule() module = PortModule()
module() module()

View File

@@ -11,10 +11,11 @@ author: OpenStack Ansible SIG
description: description:
- Retrieve information about ports from OpenStack. - Retrieve information about ports from OpenStack.
options: options:
port: name:
description: description:
- Unique name or ID of a port. - Unique name or ID of a port.
type: str type: str
aliases: ['port']
filters: filters:
description: description:
- A dictionary of meta data to use for further filtering. Elements - A dictionary of meta data to use for further filtering. Elements
@@ -41,10 +42,10 @@ EXAMPLES = '''
# Gather information about a single port # Gather information about a single port
- openstack.cloud.port_info: - openstack.cloud.port_info:
cloud: mycloud 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 # Gather information about all ports that have device_id set to a specific
# and with a status of ACTIVE. # value and with a status of ACTIVE.
- openstack.cloud.port_info: - openstack.cloud.port_info:
cloud: mycloud cloud: mycloud
filters: filters:
@@ -54,34 +55,37 @@ EXAMPLES = '''
RETURN = ''' RETURN = '''
ports: ports:
description: List of port dictionaries. A subset of the dictionary keys description: |
listed below may be returned, depending on your cloud provider. List of port dictionaries. A subset of the dictionary keys listed below
may be returned, depending on your cloud provider.
returned: always returned: always
type: list type: list
elements: dict elements: dict
contains: contains:
allowed_address_pairs: allowed_address_pairs:
description: A set of zero or more allowed address pairs. An description: Allowed address pairs.
address pair consists of an IP address and MAC address.
returned: success returned: success
type: list type: list
sample: [] sample: []
binding_host_id: 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 returned: success
type: str type: str
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
binding_profile: binding_profile:
description: A dictionary the enables the application running on description: |
the host to pass and receive VIF port-specific A dictionary the enables the application running on the
information to the plug-in. specified host to pass and receive vif port-specific
information to the plug-in.
returned: success returned: success
type: dict type: dict
sample: {} sample: {}
binding_vif_details: binding_vif_details:
description: A dictionary that enables the application to pass description: |
information about functions that the Networking API A dictionary that enables the application to pass
provides. information about functions that the Networking API provides.
returned: success returned: success
type: dict type: dict
binding_vif_type: binding_vif_type:
@@ -89,13 +93,14 @@ ports:
returned: success returned: success
type: dict type: dict
binding_vnic_type: binding_vnic_type:
description: The virtual network interface card (vNIC) type that is description: |
bound to the neutron port. The virtual network interface card (vNIC) type that is
bound to the neutron port.
returned: success returned: success
type: str type: str
sample: "normal" sample: "normal"
created_at: created_at:
description: Date the port was created description: Timestamp when the port was created.
returned: success returned: success
type: str type: str
sample: "2022-02-03T13:28:25Z" sample: "2022-02-03T13:28:25Z"
@@ -104,69 +109,78 @@ ports:
returned: success returned: success
type: str type: str
description: description:
description: Description of a port description: The port description.
returned: success returned: success
type: str type: str
device_id: device_id:
description: The UUID of the device that uses this port. description: Device ID of this port.
returned: success returned: success
type: str type: str
sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759" sample: "b4bd682d-234a-4091-aa5b-4b025a6a7759"
device_owner: 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 returned: success
type: str type: str
sample: "network:router_interface" sample: "network:router_interface"
device_profile: 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 returned: success
type: str type: str
dns_assignment: dns_assignment:
description: DNS assignment information. description: DNS assignment for the port.
returned: success returned: success
type: list type: list
dns_domain: dns_domain:
description: A valid DNS domain description: DNS domain assigned to the port.
returned: success returned: success
type: str type: str
dns_name: dns_name:
description: DNS name description: DNS name for the port.
returned: success returned: success
type: str type: str
extra_dhcp_opts: extra_dhcp_opts:
description: A set of zero or more extra DHCP option pairs. description: |
An option pair consists of an option value and name. A set of zero or more extra DHCP option pairs.
An option pair consists of an option value and name.
returned: success returned: success
type: list type: list
sample: [] sample: []
fixed_ips: fixed_ips:
description: The IP addresses for the port. Includes the IP address description: |
and UUID of the subnet. IP addresses for the port. Includes the IP address and subnet
ID.
returned: success returned: success
type: list type: list
id: id:
description: The UUID of the port. description: The port ID.
returned: success returned: success
type: str type: str
sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de" sample: "3ec25c97-7052-4ab8-a8ba-92faf84148de"
ip_allocation: ip_allocation:
description: Indicates when ports use either deferred, immediate description: |
or no IP allocation (none). The ip_allocation indicates when ports use deferred,
immediate or no IP allocation.
returned: success returned: success
type: str type: str
is_admin_state_up: is_admin_state_up:
description: The administrative state of the router, which is description: |
up (true) or down (false). The administrative state of the port, which is up C(True) or
down C(False).
returned: success returned: success
type: bool type: bool
sample: true sample: true
is_port_security_enabled: 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 returned: success
type: bool type: bool
sample: false sample: false
mac_address: mac_address:
description: The MAC address. description: The MAC address of an allowed address pair.
returned: success returned: success
type: str type: str
sample: "00:00:5E:00:53:42" sample: "00:00:5E:00:53:42"
@@ -176,42 +190,43 @@ ports:
type: str type: str
sample: "port_name" sample: "port_name"
network_id: network_id:
description: The UUID of the attached network. description: The ID of the attached network.
returned: success returned: success
type: str type: str
sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d" sample: "dd1ede4f-3952-4131-aab6-3b8902268c7d"
numa_affinity_policy: numa_affinity_policy:
description: The port NUMA affinity policy requested during the description: |
virtual machine scheduling. Values are None, required, The NUMA affinity policy defined for this port.
preferred or legacy.
returned: success returned: success
type: str type: str
sample: "required" sample: "required"
project_id: project_id:
description: The ID of the project. description: The ID of the project who owns the network.
returned: success returned: success
type: str type: str
sample: "aa1ede4f-3952-4131-aab6-3b8902268c7d" sample: "aa1ede4f-3952-4131-aab6-3b8902268c7d"
propagate_uplink_status: propagate_uplink_status:
description: The uplink status propagation of the port. description: Whether to propagate uplink status of the port.
returned: success returned: success
type: bool type: bool
sample: false sample: false
qos_network_policy_id: qos_network_policy_id:
description: The ID of the QoS policy of the network where this description: |
port is plugged. The ID of the QoS policy attached to the network where the
port is bound.
returned: success returned: success
type: str type: str
sample: "1e4f3958-c0c9-4dec-82fa-ed2dc1c5cb34" sample: "1e4f3958-c0c9-4dec-82fa-ed2dc1c5cb34"
qos_policy_id: 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 returned: success
type: str type: str
sample: "b20bb47f-5d6d-45a6-8fe7-2c1b44f0db73" sample: "b20bb47f-5d6d-45a6-8fe7-2c1b44f0db73"
resource_request: resource_request:
description: Expose Placement resources i.e. minimum-bandwidth description: |
and traits i.e. vnic-type, physnet requested by a The port-resource-request exposes Placement resources
port to Nova and Placement (i.e.: minimum-bandwidth) and traits (i.e.: vnic-type, physnet)
requested by a port to Nova and Placement.
returned: success returned: success
type: str type: str
revision_number: revision_number:
@@ -220,11 +235,11 @@ ports:
type: int type: int
sample: 0 sample: 0
security_group_ids: security_group_ids:
description: The UUIDs of any attached security groups. description: The IDs of any attached security groups.
returned: success returned: success
type: list type: list
status: status:
description: The port status. description: The port status. Value is C(ACTIVE) or C(DOWN).
returned: success returned: success
type: str type: str
sample: "ACTIVE" sample: "ACTIVE"
@@ -234,47 +249,47 @@ ports:
type: list type: list
sample: [] sample: []
tenant_id: tenant_id:
description: The UUID of the tenant who owns the network. Deprecated. description: Same as I(project_id). Deprecated.
returned: success returned: success
type: str type: str
sample: "51fce036d7984ba6af4f6c849f65ef00" sample: "51fce036d7984ba6af4f6c849f65ef00"
trunk_details: 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 returned: success
type: dict type: dict
updated_at: updated_at:
description: Last port update description: Timestamp when the port was last updated.
returned: success returned: success
type: str type: str
sample: "2022-02-03T13:28:25Z" sample: "2022-02-03T13:28:25Z"
''' '''
from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule
class NetworkPortInfoModule(OpenStackModule): class PortInfoModule(OpenStackModule):
argument_spec = dict( argument_spec = dict(
port=dict(), name=dict(aliases=['port']),
filters=dict(type='dict', default={}), filters=dict(type='dict'),
) )
module_kwargs = dict( module_kwargs = dict(
supports_check_mode=True supports_check_mode=True
) )
def run(self): def run(self):
port = self.params['port'] ports = [p.to_dict(computed=False) for p in
filters = self.params['filters'] self.conn.search_ports(
name_or_id=self.params['name'],
ports = self.conn.search_ports(port, filters) filters=self.params['filters'])]
ports = [p.to_dict(computed=False) for p in ports]
self.exit_json(changed=False, ports=ports) self.exit_json(changed=False, ports=ports)
def main(): def main():
module = NetworkPortInfoModule() module = PortInfoModule()
module() module()