diff --git a/.zuul.yaml b/.zuul.yaml index 88f65392..402ab700 100644 --- a/.zuul.yaml +++ b/.zuul.yaml @@ -103,6 +103,7 @@ router security_group security_group_rule + server subnet subnet_pool user @@ -113,7 +114,6 @@ # floating_ip # orchestrate # neutron_rbac - # server - job: name: ansible-collections-openstack-functional-devstack-octavia-base diff --git a/ci/roles/floating_ip/tasks/main.yml b/ci/roles/floating_ip/tasks/main.yml index df324778..ebb41ec7 100644 --- a/ci/roles/floating_ip/tasks/main.yml +++ b/ci/roles/floating_ip/tasks/main.yml @@ -162,11 +162,11 @@ # that no floating ip on public network is associated with "10.7.7.100" before running this role assert: that: - - info.openstack_servers|length == 1 - - info.openstack_servers.0.public_v4|length == 0 - - info.openstack_servers.0.public_v6|length == 0 - - info.openstack_servers.0.addresses.ansible_internal|length == 1 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == ["10.7.7.100"] + - info.servers|length == 1 + - info.servers.0.public_v4|length == 0 + - info.servers.0.public_v6|length == 0 + - info.servers.0.addresses.ansible_internal|length == 1 + - info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == ["10.7.7.100"] - name: Create server with two nics openstack.cloud.server: @@ -190,11 +190,11 @@ - name: Assert two internal ports and no floating ips on server 2 assert: that: - - info.openstack_servers|length == 1 - - info.openstack_servers.0.public_v4|length == 0 - - info.openstack_servers.0.public_v6|length == 0 - - info.openstack_servers.0.addresses.ansible_internal|length == 2 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == + - info.servers|length == 1 + - info.servers.0.public_v4|length == 0 + - info.servers.0.public_v6|length == 0 + - info.servers.0.addresses.ansible_internal|length == 2 + - info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list == ["10.7.7.101", "10.7.7.102"] # Tests @@ -214,8 +214,8 @@ - name: Assert one internal port and one floating ip on server 1 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 2 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == + - info.servers.0.addresses.ansible_internal|length == 2 + - info.servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == ["fixed", "floating"] - name: Detach floating IP from server @@ -224,7 +224,7 @@ state: absent server: ansible_server1 network: public - floating_ip_address: "{{ (info.openstack_servers.0.addresses.ansible_internal| + floating_ip_address: "{{ (info.servers.0.addresses.ansible_internal| selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}" - name: Get info about server @@ -233,16 +233,16 @@ server: ansible_server1 register: info # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary. + # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. retries: 10 delay: 3 - until: info.openstack_servers.0.addresses.ansible_internal|length == 1 + until: info.servers.0.addresses.ansible_internal|length == 1 - name: Assert one internal port on server 1 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 1 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.100"] + - info.servers.0.addresses.ansible_internal|length == 1 + - info.servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.100"] - name: Assign floating IP to server openstack.cloud.floating_ip: @@ -263,8 +263,8 @@ - name: Assert two internal ports and one floating ip on server 2 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 3 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == + - info.servers.0.addresses.ansible_internal|length == 3 + - info.servers.0.addresses.ansible_internal|map(attribute="OS-EXT-IPS:type")|sort|list == ["fixed", "fixed", "floating"] - name: Assign a second, specific floating IP to server @@ -288,13 +288,13 @@ # retry because we cannot wait for second floating ip retries: 10 delay: 3 - until: info.openstack_servers.0.addresses.ansible_internal|length == 4 + until: info.servers.0.addresses.ansible_internal|length == 4 - name: Assert two internal ports and two floating ips on server 2 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 4 - - ("10.6.6.150" in info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list) + - info.servers.0.addresses.ansible_internal|length == 4 + - ("10.6.6.150" in info.servers.0.addresses.ansible_internal|map(attribute="addr")|sort|list) - name: Detach second floating IP from server openstack.cloud.floating_ip: @@ -310,15 +310,15 @@ server: ansible_server2 register: info # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary. + # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. retries: 10 delay: 3 - until: info.openstack_servers.0.addresses.ansible_internal|length == 3 + until: info.servers.0.addresses.ansible_internal|length == 3 - name: Assert two internal ports and one floating ip on server 2 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 3 + - info.servers.0.addresses.ansible_internal|length == 3 - name: Detach remaining floating IP from server openstack.cloud.floating_ip: @@ -326,7 +326,7 @@ state: absent server: ansible_server2 network: public - floating_ip_address: "{{ (info.openstack_servers.0.addresses.ansible_internal| + floating_ip_address: "{{ (info.servers.0.addresses.ansible_internal| selectattr('OS-EXT-IPS:type', '==', 'floating')|map(attribute='addr')|list)[0] }}" - name: Get info about server @@ -335,16 +335,16 @@ server: ansible_server2 register: info # When detaching a floating ip from an instance there might be a delay until openstack.cloud.server_info - # does not list it any more in info.openstack_servers.0.addresses.ansible_internal, so retry if necessary. + # does not list it any more in info.servers.0.addresses.ansible_internal, so retry if necessary. retries: 10 delay: 3 - until: info.openstack_servers.0.addresses.ansible_internal|length == 2 + until: info.servers.0.addresses.ansible_internal|length == 2 - name: Assert two internal ports on server 2 assert: that: - - info.openstack_servers.0.addresses.ansible_internal|length == 2 - - info.openstack_servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.101", "10.7.7.102"] + - info.servers.0.addresses.ansible_internal|length == 2 + - info.servers.0.addresses.ansible_internal|map(attribute="addr")|list == ["10.7.7.101", "10.7.7.102"] # Clean environment - name: Delete server with two nics diff --git a/ci/roles/server/defaults/main.yaml b/ci/roles/server/defaults/main.yaml deleted file mode 100644 index 0b0f2769..00000000 --- a/ci/roles/server/defaults/main.yaml +++ /dev/null @@ -1,8 +0,0 @@ -server_network: private -server_name: ansible_server -server_alt_network: private_alt -server_alt_subnet: subnet_alt -server_alt_name: ansible_server_alt -flavor: m1.tiny -floating_ip_pool_name: public -boot_volume_size: 5 diff --git a/ci/roles/server/defaults/main.yml b/ci/roles/server/defaults/main.yml new file mode 100644 index 00000000..47063ff8 --- /dev/null +++ b/ci/roles/server/defaults/main.yml @@ -0,0 +1,66 @@ +boot_volume_size: 5 +expected_fields: + - access_ipv4 + - access_ipv6 + - addresses + - admin_password + - attached_volumes + - availability_zone + - block_device_mapping + - compute_host + - config_drive + - created_at + - description + - disk_config + - flavor + - flavor_id + - has_config_drive + - host_id + - host_status + - hostname + - hypervisor_hostname + - id + - image + - image_id + - instance_name + - is_locked + - kernel_id + - key_name + - launch_index + - launched_at + - links + - max_count + - metadata + - min_count + - name + - networks + - power_state + - progress + - project_id + - ramdisk_id + - reservation_id + - root_device_name + - scheduler_hints + - security_groups + - server_groups + - status + - tags + - task_state + - terminated_at + - trusted_image_certificates + - updated_at + - user_data + - user_id + - vm_state + - volumes +flavor: m1.tiny +floating_ip_pool_name: public +server_alt_name: ansible_server_alt +server_alt_network: ansible_server_network_alt +server_alt_security_group: ansible_server_security_group_alt +server_alt_subnet: ansible_server_subnet_alt +server_name: ansible_server +server_network: ansible_server_network +server_port: ansible_server_port +server_security_group: ansible_server_security_group +server_subnet: ansible_server_subnet diff --git a/ci/roles/server/tasks/main.yml b/ci/roles/server/tasks/main.yml index 6b78dd1a..b10b3df4 100644 --- a/ci/roles/server/tasks/main.yml +++ b/ci/roles/server/tasks/main.yml @@ -1,19 +1,79 @@ --- +- name: Create network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + name: "{{ server_network }}" + state: present + register: network + +- name: Create subnet for server + openstack.cloud.subnet: + cidr: 192.168.0.0/24 + cloud: "{{ cloud }}" + name: "{{ server_subnet }}" + network_name: "{{ server_network }}" + state: present + register: subnet + +- name: Create second network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + name: "{{ server_alt_network }}" + state: present + +- name: Create second subnet for server + openstack.cloud.subnet: + cidr: 192.168.1.0/24 + cloud: "{{ cloud }}" + name: "{{ server_alt_subnet }}" + network_name: "{{ server_alt_network }}" + state: present + +- name: Create security group for server + openstack.cloud.security_group: + cloud: "{{ cloud }}" + state: present + name: "{{ server_security_group }}" + register: security_group + +- name: Create second security group for server + openstack.cloud.security_group: + cloud: "{{ cloud }}" + state: present + name: "{{ server_alt_security_group }}" + register: security_group_alt + - name: Create server with meta as CSV openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: "{{ server_name }}" - image: "{{ image }}" - flavor: "{{ flavor }}" - network: "{{ server_network }}" - auto_floating_ip: false - meta: "key1=value1,key2=value2" - wait: true + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "{{ image }}" + flavor: "{{ flavor }}" + network: "{{ server_network }}" + auto_ip: false + metadata: "key1=value1,key2=value2" + wait: true register: server - debug: var=server +- name: assert return values of server module + assert: + that: + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(server.server.keys())|length == 0 + +- name: Assert server + assert: + that: + - server.server.name == server_name + - server.server.metadata.keys()|sort == ['key1', 'key2'] + - server.server.metadata['key1'] == 'value1' + - server.server.metadata['key2'] == 'value2' + - server_network in server.server.addresses + - server.server.security_groups|map(attribute='name')|list == ['default'] + - name: Get info about all servers openstack.cloud.server_info: cloud: "{{ cloud }}" @@ -22,14 +82,16 @@ - name: Check info about servers assert: that: - info.openstack_servers|length > 0 + - info.servers|length > 0 + # allow new fields to be introduced but prevent fields from being removed + - expected_fields|difference(info.servers[0].keys())|length == 0 - name: Delete server with meta as CSV openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: "{{ server_name }}" - wait: true + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true - name: Get info about all servers openstack.cloud.server_info: @@ -39,21 +101,21 @@ - name: Check info about no servers assert: that: - info.openstack_servers|length == 0 + - info.servers|length == 0 - name: Create server with meta as dict openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: "{{ server_name }}" - image: "{{ image }}" - flavor: "{{ flavor }}" - auto_floating_ip: false - network: "{{ server_network }}" - meta: - key1: value1 - key2: value2 - wait: true + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "{{ image }}" + flavor: "{{ flavor }}" + auto_ip: false + network: "{{ server_network }}" + metadata: + key1: value1 + key2: value2 + wait: true register: server - debug: var=server @@ -67,26 +129,66 @@ - name: Check info about server name assert: that: - info.openstack_servers[0].name == "{{ server_name }}" + - info.servers[0].name == "{{ server_name }}" + - info.servers[0].id == server.server.id + +- name: Filter servers + openstack.cloud.server_info: + cloud: "{{ cloud }}" + filters: + id: "{{ server.server.id }}" + metadata: + key1: value1 + key2: value2 + register: info + +- name: Check filter results + assert: + that: info.servers|map(attribute='id')|list == [server.server.id] + +- name: Filter servers with partial data + openstack.cloud.server_info: + cloud: "{{ cloud }}" + filters: + id: "{{ server.server.id }}" + metadata: + key1: value1 + # intentially left out parts of metadata here + register: info + +- name: Check filter results + assert: + that: info.servers|map(attribute='id')|list == [server.server.id] + +- name: Filter servers which should not return results + openstack.cloud.server_info: + cloud: "{{ cloud }}" + filters: + id: "THIS_IS_NOT_A_VALID_ID" + register: info + +- name: Check filter results + assert: + that: info.servers|length == 0 - name: Delete server with meta as dict openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: "{{ server_name }}" - wait: true + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true - name: Create server (FIP from pool/network) openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: "{{ server_name }}" - image: "{{ image }}" - flavor: "{{ flavor }}" - network: "{{ server_network }}" - floating_ip_pools: - - "{{ floating_ip_pool_name }}" - wait: true + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "{{ image }}" + flavor: "{{ flavor }}" + network: "private" + floating_ip_pools: + - "{{ floating_ip_pool_name }}" + wait: true register: server - debug: var=server @@ -97,52 +199,59 @@ server: "{{ server_name }}" detailed: true register: info + # TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved. + # Ref.: https://storyboard.openstack.org/#!/story/2010135 + ignore_errors: yes - name: Check info about server image name assert: that: - info.openstack_servers[0].image.name == "{{ image }}" + - info.servers[0].image.name == "{{ image }}" + # TODO: Drop ignore_errors once openstacksdk's bug #2010135 has been solved. + # Ref.: https://storyboard.openstack.org/#!/story/2010135 + ignore_errors: yes - name: Delete server (FIP from pool/network) openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: "{{ server_name }}" - wait: true + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true - name: Create server from volume openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: "{{ server_name }}" - image: "{{ image }}" - flavor: "{{ flavor }}" - network: "{{ server_network }}" - auto_floating_ip: false - boot_from_volume: true - volume_size: "{{ boot_volume_size }}" - terminate_volume: true - wait: true + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "{{ image }}" + flavor: "{{ flavor }}" + network: "{{ server_network }}" + auto_ip: false + boot_from_volume: true + volume_size: "{{ boot_volume_size }}" + terminate_volume: true + wait: true register: server - debug: var=server - name: Delete server with volume openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: "{{ server_name }}" - wait: true + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true + - name: Create a minimal server openstack.cloud.server: - cloud: "{{ cloud }}" - state: present - name: "{{ server_name }}" - image: "{{ image }}" - flavor: "{{ flavor }}" - network: "{{ server_network }}" - auto_floating_ip: false - wait: true + cloud: "{{ cloud }}" + state: present + name: "{{ server_name }}" + image: "{{ image }}" + flavor: "{{ flavor }}" + network: "{{ server_network }}" + auto_ip: false + wait: true register: server - debug: var=server @@ -155,8 +264,7 @@ - name: Check info about servers in all projects assert: - that: - info.openstack_servers|length > 0 + that: info.servers|length > 0 - name: Get info about one server in all projects openstack.cloud.server_info: @@ -167,14 +275,174 @@ - name: Check info about one server in all projects assert: - that: - info.openstack_servers|length > 0 + that: info.servers|length > 0 - name: Delete minimal server openstack.cloud.server: - cloud: "{{ cloud }}" - state: absent - name: "{{ server_name }}" - wait: true + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true -- include_tasks: server_actions.yml +- name: Create port to be attached to server + openstack.cloud.port: + cloud: "{{ cloud }}" + state: present + name: "{{ server_port }}" + network: "{{ server_network }}" + no_security_groups: yes + fixed_ips: + - ip_address: 192.168.0.42 + register: port + +- name: Create server which will be updated + openstack.cloud.server: + auto_ip: false + cloud: "{{ cloud }}" + # TODO: Uncomment once openstacksdk with support for + # description parameter has been released to PyPI. + # Ref.: https://review.opendev.org/c/openstack/openstacksdk/+/850671 + #description: "This is a server" + flavor: "{{ flavor }}" + image: "{{ image }}" + metadata: + key1: value1 + key2: value2 + name: "{{ server_name }}" + nics: + - net-name: 'public' + - net-name: "{{ server_network }}" + - port-id: '{{ port.port.id }}' + state: present + wait: true + register: server + +- debug: var=server + +- name: Update server + openstack.cloud.server: + auto_ip: true + cloud: "{{ cloud }}" + description: "This server got updated" + # flavor cannot be updated but must be present + flavor: "{{ flavor }}" + # image cannot be updated but must be present + image: "{{ image }}" + metadata: + key2: value2 + key3: value3 + name: "{{ server_name }}" + # nics cannot be updated + nics: + - net-name: 'public' + - net-name: "{{ server_network }}" + - port-id: '{{ port.port.id }}' + security_groups: + - '{{ server_security_group }}' + - '{{ server_alt_security_group }}' + state: present + wait: true + register: server_updated + +- debug: var=server_updated + +- name: Assert updated server + assert: + that: + - server.server.id == server_updated.server.id + - server_updated is changed + - server_updated.server.description == "This server got updated" + - "'key1' not in server_updated.server.metadata" + - server_updated.server.metadata['key2'] == 'value2' + - server_updated.server.metadata['key3'] == 'value3' + - server_updated.server.addresses.keys()|sort == [server_network,'public'] + - server_updated.server.addresses[server_network]|length == 2 + - server_updated.server.addresses.public|length > 0 + - port.port.fixed_ips[0].ip_address in + server_updated.server.addresses[server_network]|map(attribute='addr') + - server_updated.server.security_groups|map(attribute='name')|unique|length == 2 + - security_group.secgroup.name in server_updated.server.security_groups|map(attribute='name') + - security_group_alt.secgroup.name in server_updated.server.security_groups|map(attribute='name') + +- name: Update server again + openstack.cloud.server: + auto_ip: true + cloud: "{{ cloud }}" + description: "This server got updated" + # flavor cannot be updated but must be present + flavor: "{{ flavor }}" + # image cannot be updated but must be present + image: "{{ image }}" + metadata: + key2: value2 + key3: value3 + name: "{{ server_name }}" + # nics cannot be updated + nics: + - net-name: 'public' + - net-name: "{{ server_network }}" + - port-id: '{{ port.port.id }}' + security_groups: + - '{{ server_security_group }}' + - '{{ server_alt_security_group }}' + state: present + wait: true + register: server_again + +- name: Assert server did not change + assert: + that: + - server.server.id == server_again.server.id + - server_again is not changed + +- name: Delete updated server + openstack.cloud.server: + cloud: "{{ cloud }}" + delete_ips: yes + name: "{{ server_name }}" + state: absent + wait: true + +- name: Delete port which was attached to server + openstack.cloud.port: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_port }}" + +- name: Delete second security group for server + openstack.cloud.security_group: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_alt_security_group }}" + +- name: Delete security group for server + openstack.cloud.security_group: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_security_group }}" + +- name: Delete second subnet for server + openstack.cloud.subnet: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_alt_subnet }}" + +- name: Delete second network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_alt_network }}" + +- name: Delete subnet for server + openstack.cloud.subnet: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_subnet }}" + +- name: Delete network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_network }}" + +- import_tasks: server_actions.yml diff --git a/ci/roles/server/tasks/server_actions.yml b/ci/roles/server/tasks/server_actions.yml index 7d0d33a1..629ed5e2 100644 --- a/ci/roles/server/tasks/server_actions.yml +++ b/ci/roles/server/tasks/server_actions.yml @@ -1,3 +1,20 @@ +--- +- name: Create network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + name: "{{ server_network }}" + state: present + register: network + +- name: Create subnet for server + openstack.cloud.subnet: + cidr: 192.168.0.0/24 + cloud: "{{ cloud }}" + name: "{{ server_subnet }}" + network_name: "{{ server_network }}" + state: present + register: subnet + - name: Create server openstack.cloud.server: cloud: "{{ cloud }}" @@ -19,7 +36,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info1.openstack_servers.0.status == 'ACTIVE' + - info1.servers.0.status == 'ACTIVE' - name: Stop server openstack.cloud.server_action: @@ -38,7 +55,7 @@ - name: Ensure status for server is SHUTOFF assert: that: - - info2.openstack_servers.0.status == 'SHUTOFF' + - info2.servers.0.status == 'SHUTOFF' - server is changed - name: Stop server again @@ -58,7 +75,7 @@ - name: Ensure status for server is SHUTOFF assert: that: - - info3.openstack_servers.0.status == 'SHUTOFF' + - info3.servers.0.status == 'SHUTOFF' - server is not changed - name: Start server @@ -78,7 +95,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info4.openstack_servers.0.status == 'ACTIVE' + - info4.servers.0.status == 'ACTIVE' - server is changed - name: Start server again @@ -98,7 +115,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info5.openstack_servers.0.status == 'ACTIVE' + - info5.servers.0.status == 'ACTIVE' - server is not changed - name: Pause server @@ -118,7 +135,7 @@ - name: Ensure status for server is PAUSED assert: that: - - info6.openstack_servers.0.status == 'PAUSED' + - info6.servers.0.status == 'PAUSED' - server is changed - name: Pause server again @@ -138,7 +155,7 @@ - name: Ensure status for server is PAUSED assert: that: - - info7.openstack_servers.0.status == 'PAUSED' + - info7.servers.0.status == 'PAUSED' - server is not changed - name: Unpause server @@ -158,7 +175,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info8.openstack_servers.0.status == 'ACTIVE' + - info8.servers.0.status == 'ACTIVE' - server is changed - name: Unpause server again @@ -178,7 +195,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info9.openstack_servers.0.status == 'ACTIVE' + - info9.servers.0.status == 'ACTIVE' - server is not changed - name: Lock server @@ -198,7 +215,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info10.openstack_servers.0.status == 'ACTIVE' + - info10.servers.0.status == 'ACTIVE' # not in all versions 'locked' is supported - >- (info10.openstack_server[0]['locked'] is defined and @@ -223,7 +240,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info11.openstack_servers.0.status == 'ACTIVE' + - info11.servers.0.status == 'ACTIVE' # not in all versions 'locked' is supported - >- (info11.openstack_server[0]['locked'] is defined and @@ -248,7 +265,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info12.openstack_servers.0.status == 'ACTIVE' + - info12.servers.0.status == 'ACTIVE' # not in all versions 'locked' is supported - >- (info12.openstack_server[0]['locked'] is defined and @@ -273,7 +290,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info13.openstack_servers.0.status == 'ACTIVE' + - info13.servers.0.status == 'ACTIVE' - server is changed # no support for unlock idempotency # not in all versions 'locked' is supported - >- @@ -298,7 +315,7 @@ - name: Ensure status for server is SUSPENDED assert: that: - - info14.openstack_servers.0.status == 'SUSPENDED' + - info14.servers.0.status == 'SUSPENDED' - server is changed - name: Suspend server again @@ -318,7 +335,7 @@ - name: Ensure status for server is SUSPENDED assert: that: - - info15.openstack_servers.0.status == 'SUSPENDED' + - info15.servers.0.status == 'SUSPENDED' - server is not changed - name: Resume server @@ -338,7 +355,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info16.openstack_servers.0.status == 'ACTIVE' + - info16.servers.0.status == 'ACTIVE' - server is changed - name: Resume server again @@ -358,7 +375,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info17.openstack_servers.0.status == 'ACTIVE' + - info17.servers.0.status == 'ACTIVE' - server is not changed - name: Rebuild server - error @@ -394,7 +411,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info18.openstack_servers.0.status in ('ACTIVE', 'REBUILD') + - info18.servers.0.status in ('ACTIVE', 'REBUILD') - server is changed - name: Rebuild server with admin password @@ -416,7 +433,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info19.openstack_servers.0.status in ('ACTIVE', 'REBUILD') + - info19.servers.0.status in ('ACTIVE', 'REBUILD') - server is changed - name: Shelve server @@ -436,7 +453,7 @@ - name: Ensure status for server is SHELVED or SHELVED_OFFLOADED assert: that: - - info20.openstack_servers.0.status in ['SHELVED', 'SHELVED_OFFLOADED'] + - info20.servers.0.status in ['SHELVED', 'SHELVED_OFFLOADED'] - server is changed - name: Shelve offload server @@ -457,7 +474,7 @@ # no change if server has been offloaded automatically after first shelve command assert: that: - - info21.openstack_servers.0.status == 'SHELVED_OFFLOADED' + - info21.servers.0.status == 'SHELVED_OFFLOADED' - name: Shelve offload server again openstack.cloud.server_action: @@ -476,7 +493,7 @@ - name: Ensure status for server is SHELVED_OFFLOADED assert: that: - - info22.openstack_servers.0.status == 'SHELVED_OFFLOADED' + - info22.servers.0.status == 'SHELVED_OFFLOADED' - server is not changed - name: Unshelve server @@ -496,7 +513,7 @@ - name: Ensure status for server is ACTIVE assert: that: - - info23.openstack_servers.0.status == 'ACTIVE' + - info23.servers.0.status == 'ACTIVE' - server is changed - name: Unshelve server again @@ -516,9 +533,16 @@ - name: Ensure status for server is ACTIVE assert: that: - - info24.openstack_servers.0.status == 'ACTIVE' + - info24.servers.0.status == 'ACTIVE' - server is not changed +- name: Delete server + openstack.cloud.server: + cloud: "{{ cloud }}" + name: "{{ server_name }}" + state: absent + wait: true + - name: Create network for alternate server openstack.cloud.network: cloud: "{{ cloud_alt }}" @@ -554,7 +578,7 @@ - name: Ensure status for server in alternate project is ACTIVE assert: that: - - info25.openstack_servers.0.status == 'ACTIVE' + - info25.servers.0.status == 'ACTIVE' - name: Try to stop server in alternate project openstack.cloud.server_action: @@ -589,7 +613,7 @@ - name: Ensure status for server is SHUTOFF assert: that: - - info26.openstack_servers.0.status == 'SHUTOFF' + - info26.servers.0.status == 'SHUTOFF' - server_alt is changed - name: Delete server in alternate project @@ -610,3 +634,15 @@ cloud: "{{ cloud_alt }}" name: "{{ server_alt_network }}" state: absent + +- name: Delete subnet for server + openstack.cloud.subnet: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_subnet }}" + +- name: Delete network for server + openstack.cloud.network: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_network }}" diff --git a/plugins/doc_fragments/openstack.py b/plugins/doc_fragments/openstack.py index 6cb13ee5..c8df14a2 100644 --- a/plugins/doc_fragments/openstack.py +++ b/plugins/doc_fragments/openstack.py @@ -86,10 +86,6 @@ options: choices: [ admin, internal, public ] default: public aliases: [ endpoint_type ] - availability_zone: - description: - - Ignored. Present for backwards compatibility - type: str sdk_log_path: description: - Path to the logfile of the OpenStackSDK. If empty no log is written diff --git a/plugins/module_utils/openstack.py b/plugins/module_utils/openstack.py index 609e20fe..b837f555 100644 --- a/plugins/module_utils/openstack.py +++ b/plugins/module_utils/openstack.py @@ -46,7 +46,6 @@ import importlib import os from ansible.module_utils.basic import AnsibleModule -from ansible.module_utils.six import iteritems OVERRIDES = {} @@ -73,7 +72,6 @@ def openstack_argument_spec(): login_username=dict(default=OS_USERNAME), auth_url=dict(default=OS_AUTH_URL), region_name=dict(default=OS_REGION_NAME), - availability_zone=dict(), ) if OS_PASSWORD: spec['login_password'] = dict(default=OS_PASSWORD) @@ -86,26 +84,12 @@ def openstack_argument_spec(): return spec -def openstack_find_nova_addresses(addresses, ext_tag, key_name=None): - - ret = [] - for (k, v) in iteritems(addresses): - if key_name and k == key_name: - ret.extend([addrs['addr'] for addrs in v]) - else: - for interface_spec in v: - if 'OS-EXT-IPS:type' in interface_spec and interface_spec['OS-EXT-IPS:type'] == ext_tag: - ret.append(interface_spec['addr']) - return ret - - def openstack_full_argument_spec(**kwargs): spec = dict( cloud=dict(type='raw'), auth_type=dict(), auth=dict(type='dict', no_log=True), region_name=dict(), - availability_zone=dict(), validate_certs=dict(type='bool', aliases=['verify']), ca_cert=dict(aliases=['cacert']), client_cert=dict(aliases=['cert']), diff --git a/plugins/modules/server.py b/plugins/modules/server.py index c3e79943..cd09a8fe 100644 --- a/plugins/modules/server.py +++ b/plugins/modules/server.py @@ -15,184 +15,210 @@ author: OpenStack Ansible SIG description: - Create or Remove compute instances from OpenStack. options: - name: - description: - - Name that has to be given to the instance. It is also possible to - specify the ID of the instance instead of its name if I(state) is I(absent). - required: true - type: str - image: - description: - - The name or id of the base image to boot. - - Required when I(boot_from_volume=true) - type: str - image_exclude: - description: - - Text to use to filter image names, for the case, such as HP, where - there are multiple image names matching the common identifying - portions. image_exclude is a negative match filter - it is text that - may not exist in the image name. - type: str - default: "(deprecated)" - flavor: - description: + auto_ip: + description: + - Ensure instance has public ip however the cloud wants to do that. + type: bool + default: 'yes' + aliases: ['auto_floating_ip', 'public_ip'] + availability_zone: + description: + - Availability zone in which to create the server. + - This server attribute cannot be updated. + type: str + boot_from_volume: + description: + - Should the instance boot from a persistent volume created based on + the image given. Mutually exclusive with boot_volume. + - This server attribute cannot be updated. + type: bool + default: 'no' + boot_volume: + description: + - Volume name or id to use as the volume to boot from. Implies + boot_from_volume. Mutually exclusive with image and boot_from_volume. + - This server attribute cannot be updated. + aliases: ['root_volume'] + type: str + config_drive: + description: + - Whether to boot the server with config drive enabled. + - This server attribute cannot be updated. + type: bool + default: 'no' + delete_ips: + description: + - When I(state) is C(absent) and this option is true, any floating IP + address associated with this server will be deleted along with it. + type: bool + aliases: ['delete_fip'] + default: 'no' + description: + description: + - Description of the server. + type: str + flavor: + description: - The name or id of the flavor in which the new instance has to be created. - Exactly one of I(flavor) and I(flavor_ram) must be defined when I(state=present). - type: str - flavor_ram: - description: - - The minimum amount of ram in MB that the flavor in which the new - instance has to be created must have. - - Exactly one of I(flavor) and I(flavor_ram) must be defined when - I(state=present). - type: int - flavor_include: - description: + - This server attribute cannot be updated. + type: str + flavor_include: + description: - Text to use to filter flavor names, for the case, such as Rackspace, where there are multiple flavors that have the same ram count. flavor_include is a positive match filter - it must exist in the flavor name. - type: str - key_name: - description: - - The key pair name to be used when creating a instance - type: str - security_groups: - description: - - Names of the security groups to which the instance should be - added. This may be a YAML list or a comma separated string. - type: list - default: ['default'] - elements: str - network: - description: + - This server attribute cannot be updated. + type: str + flavor_ram: + description: + - The minimum amount of ram in MB that the flavor in which the new + instance has to be created must have. + - Exactly one of I(flavor) and I(flavor_ram) must be defined when + I(state=present). + - This server attribute cannot be updated. + type: int + floating_ip_pools: + description: + - Name of floating IP pool from which to choose a floating IP. + type: list + elements: str + floating_ips: + description: + - list of valid floating IPs that pre-exist to assign to this node. + type: list + elements: str + image: + description: + - The name or id of the base image to boot. + - Required when I(boot_from_volume=true). + - This server attribute cannot be updated. + type: str + image_exclude: + description: + - Text to use to filter image names, for the case, such as HP, where + there are multiple image names matching the common identifying + portions. image_exclude is a negative match filter - it is text that + may not exist in the image name. + - This server attribute cannot be updated. + type: str + default: "(deprecated)" + key_name: + description: + - The key pair name to be used when creating a instance. + - This server attribute cannot be updated. + type: str + metadata: + description: + - 'A list of key value pairs that should be provided as a metadata to + the new instance or a string containing a list of key-value pairs. + Example: metadata: "key1=value1,key2=value2"' + aliases: ['meta'] + type: raw + name: + description: + - Name that has to be given to the instance. It is also possible to + specify the ID of the instance instead of its name if I(state) is + I(absent). + - This server attribute cannot be updated. + required: true + type: str + network: + description: - Name or ID of a network to attach this instance to. A simpler - version of the nics parameter, only one of network or nics should - be supplied. - type: str - nics: - description: + version of the I(nics) parameter, only one of I(network) or I(nics) + should be supplied. + - This server attribute cannot be updated. + type: str + nics: + description: - A list of networks to which the instance's interface should be attached. Networks may be referenced by net-id/net-name/port-id or port-name. - 'Also this accepts a string containing a list of (net/port)-(id/name) - Eg: nics: "net-id=uuid-1,port-name=myport" - Only one of network or nics should be supplied.' - type: list - elements: raw - suboptions: - tag: - description: - - 'A "tag" for the specific port to be passed via metadata. - Eg: tag: test_tag' - auto_ip: - description: - - Ensure instance has public ip however the cloud wants to do that - type: bool - default: 'yes' - aliases: ['auto_floating_ip', 'public_ip'] - floating_ips: - description: - - list of valid floating IPs that pre-exist to assign to this node - type: list - elements: str - floating_ip_pools: - description: - - Name of floating IP pool from which to choose a floating IP - type: list - elements: str - meta: - description: - - 'A list of key value pairs that should be provided as a metadata to - the new instance or a string containing a list of key-value pairs. - Eg: meta: "key1=value1,key2=value2"' - type: raw - wait: - description: - - If the module should wait for the instance to be created. - type: bool - default: 'yes' - timeout: - description: + Example: C(nics: "net-id=uuid-1,port-name=myport")' + - Only one of I(network) or I(nics) should be supplied. + - This server attribute cannot be updated. + type: list + elements: raw + suboptions: + tag: + description: + - 'A I(tag) for the specific port to be passed via metadata. + Eg: C(tag: test_tag)' + reuse_ips: + description: + - When I(auto_ip) is true and this option is true, the I(auto_ip) code + will attempt to re-use unassigned floating ips in the project before + creating a new one. It is important to note that it is impossible + to safely do this concurrently, so if your use case involves + concurrent server creation, it is highly recommended to set this to + false and to delete the floating ip associated with a server when + the server is deleted using I(delete_ips). + - This server attribute cannot be updated. + type: bool + default: 'yes' + scheduler_hints: + description: + - Arbitrary key/value pairs to the scheduler for custom use. + - This server attribute cannot be updated. + type: dict + security_groups: + description: + - Names or IDs of the security groups to which the instance should be + added. + - On server creation, if I(security_groups) is omitted, the API creates + the server in the default security group. + - Requested security groups are not applied to pre-existing ports. + type: list + elements: str + default: [] + state: + description: + - Should the resource be C(present) or C(absent). + choices: [present, absent] + default: present + type: str + terminate_volume: + description: + - If C(yes), delete volume when deleting the instance and if it has + been booted from volume(s). + - This server attribute cannot be updated. + type: bool + default: 'no' + timeout: + description: - The amount of time the module should wait for the instance to get into active state. - default: 180 - type: int - config_drive: - description: - - Whether to boot the server with config drive enabled - type: bool - default: 'no' - userdata: - description: - - Opaque blob of data which is made available to the instance - type: str - aliases: ['user_data'] - boot_from_volume: - description: - - Should the instance boot from a persistent volume created based on - the image given. Mutually exclusive with boot_volume. - type: bool - default: 'no' - volume_size: - description: + default: 180 + type: int + user_data: + description: + - Opaque blob of data which is made available to the instance. + - This server attribute cannot be updated. + type: str + aliases: ['userdata'] + volume_size: + description: - The size of the volume to create in GB if booting from volume based on an image. - type: int - boot_volume: - description: - - Volume name or id to use as the volume to boot from. Implies - boot_from_volume. Mutually exclusive with image and boot_from_volume. - aliases: ['root_volume'] - type: str - terminate_volume: - description: - - If C(yes), delete volume when deleting instance (if booted from volume) - type: bool - default: 'no' - volumes: - description: - - A list of preexisting volumes names or ids to attach to the instance - default: [] - type: list - elements: str - scheduler_hints: - description: - - Arbitrary key/value pairs to the scheduler for custom use - type: dict - state: - description: - - Should the resource be present or absent. - choices: [present, absent] - default: present - type: str - delete_fip: - description: - - When I(state) is absent and this option is true, any floating IP - associated with the instance will be deleted along with the instance. - type: bool - default: 'no' - reuse_ips: - description: - - When I(auto_ip) is true and this option is true, the I(auto_ip) code - will attempt to re-use unassigned floating ips in the project before - creating a new one. It is important to note that it is impossible - to safely do this concurrently, so if your use case involves - concurrent server creation, it is highly recommended to set this to - false and to delete the floating ip associated with a server when - the server is deleted using I(delete_fip). - type: bool - default: 'yes' - availability_zone: - description: - - Availability zone in which to create the server. - type: str - description: - description: - - Description of the server. - type: str + - This server attribute cannot be updated. + type: int + volumes: + description: + - A list of preexisting volumes names or ids to attach to the instance + - This server attribute cannot be updated. + default: [] + type: list + elements: str + wait: + description: + - If the module should wait for the instance to be created. + type: bool + default: 'yes' requirements: - "python >= 3.6" - "openstacksdk" @@ -202,7 +228,7 @@ extends_documentation_fragment: ''' EXAMPLES = ''' -- name: Create a new instance and attaches to a network and passes metadata to the instance +- name: Create a new instance with metadata and attaches it to a network openstack.cloud.server: state: present auth: @@ -242,7 +268,8 @@ EXAMPLES = ''' key_name: test timeout: 200 flavor: 101 - security_groups: default + security_groups: + - default auto_ip: yes # Create a new instance in named cloud mordred availability zone az2 @@ -307,9 +334,11 @@ EXAMPLES = ''' key_name: ansible_key timeout: 200 flavor: 4 - nics: "net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f,net-id=542f0430-62fe-11e5-9d70-feff819cdc9f..." + nics: >- + net-id=4cb08b20-62fe-11e5-9d70-feff819cdc9f, + net-id=542f0430-62fe-11e5-9d70-feff819cdc9f -- name: Creates a new instance and attaches to a network and passes metadata to the instance +- name: Creates a new instance with metadata and attaches it to a network openstack.cloud.server: state: present auth: @@ -384,7 +413,7 @@ EXAMPLES = ''' image: "Ubuntu Server 14.04" flavor: "P-1" network: "Production" - userdata: | + user_data: | #cloud-config chpasswd: list: | @@ -402,15 +431,13 @@ EXAMPLES = ''' openstack.cloud.server: name: vm1 state: present - image: "Ubuntu Server 14.04" + image: "Ubuntu Server 22.04" flavor: "P-1" network: "Production" - userdata: | - {%- raw -%}#!/bin/bash - echo " up ip route add 10.0.0.0/8 via {% endraw -%}{{ intra_router }}{%- raw -%}" >> /etc/network/interfaces.d/eth0.conf - echo " down ip route del 10.0.0.0/8" >> /etc/network/interfaces.d/eth0.conf - ifdown eth0 && ifup eth0 - {% endraw %} + user_data: | + #!/bin/sh + apt update + apt -y full-upgrade # Create a new instance with server group for (anti-)affinity # server group ID is returned from openstack.cloud.server_group module. @@ -455,66 +482,339 @@ EXAMPLES = ''' ''' -from ansible_collections.openstack.cloud.plugins.module_utils.openstack import ( - openstack_find_nova_addresses, OpenStackModule) - - -def _parse_nics(nics): - for net in nics: - if isinstance(net, str): - for nic in net.split(','): - yield dict((nic.split('='),)) - else: - yield net - - -def _parse_meta(meta): - if isinstance(meta, str): - metas = {} - for kv_str in meta.split(","): - k, v = kv_str.split("=") - metas[k] = v - return metas - if not meta: - return {} - return meta +RETURN = ''' +server: + description: Dictionary describing the server. + type: dict + returned: On success when I(state) is 'present'. + contains: + access_ipv4: + description: | + IPv4 address that should be used to access this server. + May be automatically set by the provider. + returned: success + type: str + access_ipv6: + description: | + IPv6 address that should be used to access this + server. May be automatically set by the provider. + returned: success + type: str + addresses: + description: | + A dictionary of addresses this server can be accessed through. + The dictionary contains keys such as 'private' and 'public', + each containing a list of dictionaries for addresses of that + type. The addresses are contained in a dictionary with keys + 'addr' and 'version', which is either 4 or 6 depending on the + protocol of the IP address. + returned: success + type: dict + admin_password: + description: | + When a server is first created, it provides the administrator + password. + returned: success + type: str + attached_volumes: + description: | + A list of an attached volumes. Each item in the list contains + at least an 'id' key to identify the specific volumes. + returned: success + type: list + availability_zone: + description: | + The name of the availability zone this server is a part of. + returned: success + type: str + block_device_mapping: + description: | + Enables fine grained control of the block device mapping for an + instance. This is typically used for booting servers from + volumes. + returned: success + type: str + compute_host: + description: | + The name of the compute host on which this instance is running. + Appears in the response for administrative users only. + returned: success + type: str + config_drive: + description: | + Indicates whether or not a config drive was used for this + server. + returned: success + type: str + created_at: + description: Timestamp of when the server was created. + returned: success + type: str + description: + description: | + The description of the server. Before microversion + 2.19 this was set to the server name. + returned: success + type: str + disk_config: + description: The disk configuration. Either AUTO or MANUAL. + returned: success + type: str + flavor: + description: The flavor property as returned from server. + returned: success + type: dict + flavor_id: + description: | + The flavor reference, as a ID or full URL, for the flavor to + use for this server. + returned: success + type: str + has_config_drive: + description: | + Indicates whether a configuration drive enables metadata + injection. Not all cloud providers enable this feature. + returned: success + type: str + host_id: + description: An ID representing the host of this server. + returned: success + type: str + host_status: + description: The host status. + returned: success + type: str + hostname: + description: | + The hostname set on the instance when it is booted. + By default, it appears in the response for administrative users + only. + returned: success + type: str + hypervisor_hostname: + description: | + The hypervisor host name. Appears in the response for + administrative users only. + returned: success + type: str + id: + description: ID of the server. + returned: success + type: str + image: + description: The image property as returned from server. + returned: success + type: dict + image_id: + description: | + The image reference, as a ID or full URL, for the image to use + for this server. + returned: success + type: str + instance_name: + description: | + The instance name. The Compute API generates the instance name + from the instance name template. Appears in the response for + administrative users only. + returned: success + type: str + is_locked: + description: The locked status of the server + returned: success + type: bool + kernel_id: + description: | + The UUID of the kernel image when using an AMI. Will be null if + not. By default, it appears in the response for administrative + users only. + returned: success + type: str + key_name: + description: The name of an associated keypair. + returned: success + type: str + launch_index: + description: | + When servers are launched via multiple create, this is the + sequence in which the servers were launched. By default, it + appears in the response for administrative users only. + returned: success + type: int + launched_at: + description: The timestamp when the server was launched. + returned: success + type: str + links: + description: | + A list of dictionaries holding links relevant to this server. + returned: success + type: str + max_count: + description: The maximum number of servers to create. + returned: success + type: str + metadata: + description: List of tag strings. + returned: success + type: dict + min_count: + description: The minimum number of servers to create. + returned: success + type: str + name: + description: Name of the server + returned: success + type: str + networks: + description: | + A networks object. Required parameter when there are multiple + networks defined for the tenant. When you do not specify the + networks parameter, the server attaches to the only network + created for the current tenant. + returned: success + type: str + power_state: + description: The power state of this server. + returned: success + type: str + progress: + description: | + While the server is building, this value represents the + percentage of completion. Once it is completed, it will be 100. + returned: success + type: int + project_id: + description: The ID of the project this server is associated with. + returned: success + type: str + ramdisk_id: + description: | + The UUID of the ramdisk image when using an AMI. Will be null + if not. By default, it appears in the response for + administrative users only. + returned: success + type: str + reservation_id: + description: | + The reservation id for the server. This is an id that can be + useful in tracking groups of servers created with multiple + create, that will all have the same reservation_id. By default, + it appears in the response for administrative users only. + returned: success + type: str + root_device_name: + description: | + The root device name for the instance By default, it appears in + the response for administrative users only. + returned: success + type: str + scheduler_hints: + description: The dictionary of data to send to the scheduler. + returned: success + type: dict + security_groups: + description: | + A list of applicable security groups. Each group contains keys + for: description, name, id, and rules. + returned: success + type: list + elements: dict + server_groups: + description: | + The UUIDs of the server groups to which the server belongs. + Currently this can contain at most one entry. + returned: success + type: list + status: + description: | + The state this server is in. Valid values include 'ACTIVE', + 'BUILDING', 'DELETED', 'ERROR', 'HARD_REBOOT', 'PASSWORD', + 'PAUSED', 'REBOOT', 'REBUILD', 'RESCUED', 'RESIZED', + 'REVERT_RESIZE', 'SHUTOFF', 'SOFT_DELETED', 'STOPPED', + 'SUSPENDED', 'UNKNOWN', or 'VERIFY_RESIZE'. + returned: success + type: str + tags: + description: A list of associated tags. + returned: success + type: list + task_state: + description: The task state of this server. + returned: success + type: str + terminated_at: + description: | + The timestamp when the server was terminated (if it has been). + returned: success + type: str + trusted_image_certificates: + description: | + A list of trusted certificate IDs, that were used during image + signature verification to verify the signing certificate. + returned: success + type: list + updated_at: + description: Timestamp of when this server was last updated. + returned: success + type: str + user_data: + description: | + Configuration information or scripts to use upon launch. + Base64 encoded. + returned: success + type: str + user_id: + description: The ID of the owners of this server. + returned: success + type: str + vm_state: + description: The VM state of this server. + returned: success + type: str + volumes: + description: Same as attached_volumes. + returned: success + type: list +''' +from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule class ServerModule(OpenStackModule): argument_spec = dict( - name=dict(required=True), - image=dict(), - image_exclude=dict(default='(deprecated)'), - flavor=dict(), - flavor_ram=dict(type='int'), - flavor_include=dict(), - key_name=dict(), - security_groups=dict(default=['default'], type='list', elements='str'), - network=dict(), - nics=dict(default=[], type='list', elements='raw'), - meta=dict(type='raw'), - userdata=dict(aliases=['user_data']), - config_drive=dict(default=False, type='bool'), - auto_ip=dict(default=True, type='bool', aliases=['auto_floating_ip', 'public_ip']), - floating_ips=dict(type='list', elements='str'), - floating_ip_pools=dict(type='list', elements='str'), - volume_size=dict(type='int'), + auto_ip=dict(default=True, type='bool', + aliases=['auto_floating_ip', 'public_ip']), + availability_zone=dict(), boot_from_volume=dict(default=False, type='bool'), boot_volume=dict(aliases=['root_volume']), - terminate_volume=dict(default=False, type='bool'), - volumes=dict(default=[], type='list', elements='str'), - scheduler_hints=dict(type='dict'), - state=dict(default='present', choices=['absent', 'present']), - delete_fip=dict(default=False, type='bool'), - reuse_ips=dict(default=True, type='bool'), + config_drive=dict(default=False, type='bool'), + delete_ips=dict(default=False, type='bool', aliases=['delete_fip']), description=dict(), + flavor=dict(), + flavor_include=dict(), + flavor_ram=dict(type='int'), + floating_ip_pools=dict(type='list', elements='str'), + floating_ips=dict(type='list', elements='str'), + image=dict(), + image_exclude=dict(default='(deprecated)'), + key_name=dict(), + metadata=dict(type='raw', aliases=['meta']), + name=dict(required=True), + network=dict(), + nics=dict(default=[], type='list', elements='raw'), + reuse_ips=dict(default=True, type='bool'), + scheduler_hints=dict(type='dict'), + security_groups=dict(default=[], type='list', elements='str'), + state=dict(default='present', choices=['absent', 'present']), + terminate_volume=dict(default=False, type='bool'), + user_data=dict(aliases=['userdata']), + volume_size=dict(type='int'), + volumes=dict(default=[], type='list', elements='str'), ) + module_kwargs = dict( mutually_exclusive=[ - ['auto_ip', 'floating_ips'], - ['auto_ip', 'floating_ip_pools'], - ['floating_ips', 'floating_ip_pools'], + ['auto_ip', 'floating_ips', 'floating_ip_pools'], ['flavor', 'flavor_ram'], ['image', 'boot_volume'], ['boot_from_volume', 'boot_volume'], @@ -522,277 +822,394 @@ class ServerModule(OpenStackModule): ], required_if=[ ('boot_from_volume', True, ['volume_size', 'image']), + ('state', 'present', ('image', 'boot_volume'), True), + ('state', 'present', ('flavor', 'flavor_ram'), True), ], + supports_check_mode=True, ) def run(self): - state = self.params['state'] - image = self.params['image'] - boot_volume = self.params['boot_volume'] - flavor = self.params['flavor'] - flavor_ram = self.params['flavor_ram'] - if state == 'present': - if not (image or boot_volume): - self.fail( - msg="Parameter 'image' or 'boot_volume' is required " - "if state == 'present'" - ) - if not flavor and not flavor_ram: - self.fail( - msg="Parameter 'flavor' or 'flavor_ram' is required " - "if state == 'present'" - ) - - if state == 'present': - self._get_server_state() - self._create_server() - elif state == 'absent': - self._get_server_state() - self._delete_server() - - def _exit_hostvars(self, server, changed=True): - hostvars = self.conn.get_openstack_vars(server) - self.exit( - changed=changed, server=server, id=server.id, openstack=hostvars) - - def _get_server_state(self): - state = self.params['state'] + # self.conn.get_server is required for server.addresses and + # server.interface_ip which self.conn.compute.find_server + # does not return server = self.conn.get_server(self.params['name']) - if server and state == 'present': - if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'): - self.fail( - msg="The instance is available but not Active state: " + server.status) - (ip_changed, server) = self._check_ips(server) - (sg_changed, server) = self._check_security_groups(server) - (server_changed, server) = self._update_server(server) - self._exit_hostvars(server, ip_changed or sg_changed or server_changed) - if server and state == 'absent': - return True - if state == 'absent': - self.exit(changed=False, result="not present") - return True - def _create_server(self): - flavor = self.params['flavor'] - flavor_ram = self.params['flavor_ram'] - flavor_include = self.params['flavor_include'] + if self.ansible.check_mode: + self.exit_json(changed=self._will_change(state, server)) + + if state == 'present' and not server: + # Create server + server = self._create() + self.exit_json(changed=True, + server=server.to_dict(computed=False)) + + elif state == 'present' and server: + # Update server + update = self._build_update(server) + if update: + server = self._update(server, update) + else: + # drop attributes added in function + # openstacksdk.meta.add_server_interfaces() + # because all other branches do not return them + # + # addresses will be expanded by get_server's call to + # openstacksdk.meta.add_server_interfaces() but we + # cannot easily undo this so we ignore it + for k in ['public_v4', 'public_v6', 'interface_ip']: + del server[k] + + self.exit_json(changed=bool(update), + server=server.to_dict(computed=False)) + + elif state == 'absent' and server: + # Delete server + self._delete(server) + self.exit_json(changed=True) + + elif state == 'absent' and not server: + # Do nothing + self.exit_json(changed=False) + + def _build_update(self, server): + if server.status not in ('ACTIVE', 'SHUTOFF', 'PAUSED', 'SUSPENDED'): + self.fail_json(msg="The instance is available but not " + "active state: {0}".format(server.status)) + + return { + **self._build_update_ips(server), + **self._build_update_security_groups(server), + **self._build_update_server(server)} + + def _build_update_ips(self, server): + auto_ip = self.params['auto_ip'] + floating_ips = self.params['floating_ips'] + floating_ip_pools = self.params['floating_ip_pools'] + + if not (auto_ip or floating_ips or floating_ip_pools): + # No floating ip has been requested, so + # do not add or remove any floating ip. + return {} + + if (auto_ip and server['interface_ip'] + and not (floating_ip_pools or floating_ips)): + # Server has a floating ip address attached and + # no specific floating ip has been requested, + # so nothing to change. + return {} + + # Get floating ip addresses attached to the server + ips = [interface_spec['addr'] + for v in server.addresses.values() + for interface_spec in v + if ('OS-EXT-IPS:type' in interface_spec + and interface_spec['OS-EXT-IPS:type'] == 'floating')] + + if not ips: + # One or multiple floating ips have been requested, + # but none have been attached, so attach them. + return dict(ips=dict( + auto_ip=auto_ip, + ips=floating_ips, + ip_pool=floating_ip_pools)) + + if auto_ip or not floating_ips: + # Nothing do to because either any floating ip address + # or no specific floating ips have been requested + # and any floating ip has been attached. + return {} + + # A specific set of floating ips has been requested + update = {} + add_ips = [ip for ip in floating_ips if ip not in ips] + if add_ips: + # add specific ips which have not been added + update['add_ips'] = add_ips + + remove_ips = [ip for ip in ips if ip not in floating_ips] + if remove_ips: + # Detach ips which are not supposed to be attached + update['remove_ips'] = remove_ips + + def _build_update_security_groups(self, server): + update = {} + + required_security_groups = dict( + (sg['id'], sg) for sg in [ + self.conn.network.find_security_group( + security_group_name_or_id, ignore_missing=False) + for security_group_name_or_id in self.params['security_groups'] + ]) + + # Retrieve IDs of security groups attached to the server + server = self.conn.compute.fetch_server_security_groups(server) + assigned_security_groups = dict( + (sg['id'], self.conn.network.get_security_group(sg['id'])) + for sg in server.security_groups) + + # openstacksdk adds security groups to server using resources + add_security_groups = [ + sg for (sg_id, sg) in required_security_groups.items() + if sg_id not in assigned_security_groups] + + if add_security_groups: + update['add_security_groups'] = add_security_groups + + # openstacksdk removes security groups from servers using resources + remove_security_groups = [ + sg for (sg_id, sg) in assigned_security_groups.items() + if sg_id not in required_security_groups] + + if remove_security_groups: + update['remove_security_groups'] = remove_security_groups + + return update + + def _build_update_server(self, server): + update = {} + + # Process metadata + required_metadata = self._parse_metadata(self.params['metadata']) + assigned_metadata = server.metadata + + add_metadata = dict() + for (k, v) in required_metadata.items(): + if k not in assigned_metadata or assigned_metadata[k] != v: + add_metadata[k] = v + + if add_metadata: + update['add_metadata'] = add_metadata + + remove_metadata = dict() + for (k, v) in assigned_metadata.items(): + if k not in required_metadata or required_metadata[k] != v: + remove_metadata[k] = v + + if remove_metadata: + update['remove_metadata'] = remove_metadata + + # Process server attributes + + # Updateable server attributes in openstacksdk + # (OpenStack API names in braces): + # - access_ipv4 (accessIPv4) + # - access_ipv6 (accessIPv6) + # - name (name) + # - hostname (hostname) + # - disk_config (OS-DCF:diskConfig) + # - description (description) + # Ref.: https://docs.openstack.org/api-ref/compute/#update-server + + # A server's name cannot be updated by this module because + # it is used to find servers 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 server hence the name is the user defined one + # already. + + # Update all known updateable attributes although + # our module might not support them yet + server_attributes = dict( + (k, self.params[k]) + for k in ['access_ipv4', 'access_ipv6', 'hostname', 'disk_config', + 'description'] + if k in self.params and self.params[k] is not None + and self.params[k] != server[k]) + + if server_attributes: + update['server_attributes'] = server_attributes + + return update + + def _create(self): + flavor_name_or_id = self.params['flavor'] image_id = None if not self.params['boot_volume']: image_id = self.conn.get_image_id( self.params['image'], self.params['image_exclude']) if not image_id: - self.fail( - msg="Could not find image %s" % self.params['image']) + self.fail_json( + msg="Could not find image {0} with exclude {1}".format( + self.params['image'], self.params['image_exclude'])) - if flavor: - flavor_dict = self.conn.get_flavor(flavor) - if not flavor_dict: - self.fail(msg="Could not find flavor %s" % flavor) + if flavor_name_or_id: + flavor = self.conn.compute.find_flavor(flavor_name_or_id, + ignore_missing=False) else: - flavor_dict = self.conn.get_flavor_by_ram(flavor_ram, flavor_include) - if not flavor_dict: - self.fail(msg="Could not find any matching flavor") + flavor = self.conn.get_flavor_by_ram(self.params['flavor_ram'], + self.params['flavor_include']) + if not flavor: + self.fail_json(msg="Could not find any matching flavor") - nics = self._network_args() - - self.params['meta'] = _parse_meta(self.params['meta']) - - bootkwargs = self.check_versioned( - name=self.params['name'], + args = dict( + flavor=flavor.id, image=image_id, - flavor=flavor_dict['id'], - nics=nics, - meta=self.params['meta'], - security_groups=self.params['security_groups'], - userdata=self.params['userdata'], - config_drive=self.params['config_drive'], - ) - for optional_param in ( - 'key_name', 'availability_zone', 'network', - 'scheduler_hints', 'volume_size', 'volumes', - 'description'): - if self.params[optional_param]: - bootkwargs[optional_param] = self.params[optional_param] - - server = self.conn.create_server( ip_pool=self.params['floating_ip_pools'], ips=self.params['floating_ips'], - auto_ip=self.params['auto_ip'], - boot_volume=self.params['boot_volume'], - boot_from_volume=self.params['boot_from_volume'], - terminate_volume=self.params['terminate_volume'], - reuse_ips=self.params['reuse_ips'], - wait=self.params['wait'], timeout=self.params['timeout'], - **bootkwargs + meta=self._parse_metadata(self.params['metadata']), + nics=self._parse_nics(), ) - self._exit_hostvars(server) + for k in ['auto_ip', 'availability_zone', 'boot_from_volume', + 'boot_volume', 'config_drive', 'description', 'key_name', + 'name', 'network', 'reuse_ips', 'scheduler_hints', + 'security_groups', 'terminate_volume', 'timeout', + 'user_data', 'volume_size', 'volumes', 'wait']: + if self.params[k] is not None: + args[k] = self.params[k] - def _update_server(self, server): - changed = False + server = self.conn.create_server(**args) - self.params['meta'] = _parse_meta(self.params['meta']) + return server - # self.conn.set_server_metadata only updates the key=value pairs, it doesn't - # touch existing ones - update_meta = {} - for (k, v) in self.params['meta'].items(): - if k not in server.metadata or server.metadata[k] != v: - update_meta[k] = v + def _delete(self, server): + self.conn.delete_server( + server.id, + **dict((k, self.params[k]) + for k in ['wait', 'timeout', 'delete_ips'])) - if update_meta: - self.conn.set_server_metadata(server, update_meta) - changed = True - # Refresh server vars - server = self.conn.get_server(self.params['name']) + def _update(self, server, update): + server = self._update_ips(server, update) + server = self._update_security_groups(server, update) + server = self._update_server(server, update) + # Refresh server attributes after security groups etc. have changed + # + # self.conn.get_server() is unnecessary because server.addresses and + # server.interface_ip are computed and hence not returned anyway + return self.conn.compute.find_server(name_or_id=server.id) - return (changed, server) + def _update_ips(self, server, update): + args = dict((k, self.params[k]) for k in ['wait', 'timeout']) + ips = update.get('ips') + if ips: + return self.conn.add_ips_to_server(server, **ips, **args) - def _delete_server(self): - try: - self.conn.delete_server( - self.params['name'], wait=self.params['wait'], - timeout=self.params['timeout'], - delete_ips=self.params['delete_fip']) - except Exception as e: - self.fail(msg="Error in deleting vm: %s" % e) - self.exit(changed=True, result='deleted') + add_ips = update.get('add_ips') + if add_ips: + # Add specific ips which have not been added + server = self.conn.add_ip_list(server, add_ips, **args) - def _network_args(self): - args = [] - nics = self.params['nics'] + remove_ips = update.get('remove_ips') + if remove_ips: + # Detach ips which are not supposed to be attached + for ip in remove_ips: + ip_id = self.conn.network.find_ip(name_or_id=ip, + ignore_missing=False).id + # self.network.update_ip(ip_id, port_id=None) would not handle + # nova network which self.conn.detach_ip_from_server() does + self.conn.detach_ip_from_server(server_id=server.id, + floating_ip_id=ip_id) + return server - if not isinstance(nics, list): - self.fail(msg='The \'nics\' parameter must be a list.') + def _update_security_groups(self, server, update): + add_security_groups = update.get('add_security_groups') + if add_security_groups: + for sg in add_security_groups: + self.conn.compute.add_security_group_to_server(server, sg) - for num, net in enumerate(_parse_nics(nics)): + remove_security_groups = update.get('remove_security_groups') + if remove_security_groups: + for sg in remove_security_groups: + self.conn.compute.remove_security_group_from_server(server, sg) + + # Whenever security groups of a server have changed, + # the server object has to be refreshed. This will + # be postponed until all updates have been applied. + return server + + def _update_server(self, server, update): + add_metadata = update.get('add_metadata') + if add_metadata: + self.conn.compute.set_server_metadata(server.id, + **add_metadata) + + remove_metadata = update.get('remove_metadata') + if remove_metadata: + self.conn.compute.delete_server_metadata(server.id, + remove_metadata.keys()) + + server_attributes = update.get('server_attributes') + if server_attributes: + # Server object cannot passed to self.conn.compute.update_server() + # entirely because its security_groups attribute was expanded by + # self.conn.compute.fetch_server_security_groups() previously which + # thus will no longer have a valid value for OpenStack API. + server = self.conn.compute.update_server(server['id'], + **server_attributes) + + # Whenever server attributes such as metadata have changed, + # the server object has to be refreshed. This will + # be postponed until all updates have been applied. + return server + + def _parse_metadata(self, metadata): + if not metadata: + return {} + + if isinstance(metadata, str): + metas = {} + for kv_str in metadata.split(","): + k, v = kv_str.split("=") + metas[k] = v + return metas + + return metadata + + def _parse_nics(self): + nics = [] + stringified_nets = self.params['nics'] + + if not isinstance(stringified_nets, list): + self.fail_json(msg="The 'nics' parameter must be a list.") + + nets = [(dict((nested_net.split('='),)) + for nested_net in net.split(',')) + if isinstance(net, str) else net + for net in stringified_nets] + + for net in nets: if not isinstance(net, dict): - self.fail( - msg='Each entry in the \'nics\' parameter must be a dict.') + self.fail_json( + msg="Each entry in the 'nics' parameter must be a dict.") if net.get('net-id'): - args.append(net) + nics.append(net) elif net.get('net-name'): - by_name = self.conn.get_network(net['net-name']) - if not by_name: - self.fail( - msg='Could not find network by net-name: %s' % - net['net-name']) - resolved_net = net.copy() - del resolved_net['net-name'] - resolved_net['net-id'] = by_name['id'] - args.append(resolved_net) + network_id = self.conn.network.find_network( + net['net-name'], ignore_missing=False).id + # Replace net-name with net-id and keep optional nic args + # Ref.: https://github.com/ansible/ansible/pull/20969 + del net['net-name'] + net['net-id'] = network_id + nics.append(net) elif net.get('port-id'): - args.append(net) + nics.append(net) elif net.get('port-name'): - by_name = self.conn.get_port(net['port-name']) - if not by_name: - self.fail( - msg='Could not find port by port-name: %s' % - net['port-name']) - resolved_net = net.copy() - del resolved_net['port-name'] - resolved_net['port-id'] = by_name['id'] - args.append(resolved_net) + port_id = self.conn.network.find_port( + net['port-name'], ignore_missing=False).id + # Replace net-name with net-id and keep optional nic args + # Ref.: https://github.com/ansible/ansible/pull/20969 + del net['port-name'] + net['port-id'] = port_id + nics.append(net) if 'tag' in net: - args[num]['tag'] = net['tag'] - return args + nics[-1]['tag'] = net['tag'] + return nics - def _detach_ip_list(self, server, extra_ips): - for ip in extra_ips: - ip_id = self.conn.get_floating_ip( - id=None, filters={'floating_ip_address': ip}) - self.conn.detach_ip_from_server( - server_id=server.id, floating_ip_id=ip_id) - - def _check_ips(self, server): - changed = False - - auto_ip = self.params['auto_ip'] - floating_ips = self.params['floating_ips'] - floating_ip_pools = self.params['floating_ip_pools'] - - if floating_ip_pools or floating_ips: - ips = openstack_find_nova_addresses(server.addresses, 'floating') - if not ips: - # If we're configured to have a floating but we don't have one, - # let's add one - server = self.conn.add_ips_to_server( - server, - auto_ip=auto_ip, - ips=floating_ips, - ip_pool=floating_ip_pools, - wait=self.params['wait'], - timeout=self.params['timeout'], - ) - changed = True - elif floating_ips: - # we were configured to have specific ips, let's make sure we have - # those - missing_ips = [] - for ip in floating_ips: - if ip not in ips: - missing_ips.append(ip) - if missing_ips: - server = self.conn.add_ip_list(server, missing_ips, - wait=self.params['wait'], - timeout=self.params['timeout']) - changed = True - extra_ips = [] - for ip in ips: - if ip not in floating_ips: - extra_ips.append(ip) - if extra_ips: - self._detach_ip_list(server, extra_ips) - changed = True - elif auto_ip: - if server['interface_ip']: - changed = False - else: - # We're configured for auto_ip but we're not showing an - # interface_ip. Maybe someone deleted an IP out from under us. - server = self.conn.add_ips_to_server( - server, - auto_ip=auto_ip, - ips=floating_ips, - ip_pool=floating_ip_pools, - wait=self.params['wait'], - timeout=self.params['timeout'], - ) - changed = True - return (changed, server) - - def _check_security_groups(self, server): - changed = False - - # server security groups were added to shade in 1.19. Until then this - # module simply ignored trying to update security groups and only set them - # on newly created hosts. - if not ( - hasattr(self.conn, 'add_server_security_groups') - and hasattr(self.conn, 'remove_server_security_groups') - ): - return changed, server - - module_security_groups = set(self.params['security_groups']) - server_security_groups = set(sg['name'] for sg in server.security_groups) - - add_sgs = module_security_groups - server_security_groups - remove_sgs = server_security_groups - module_security_groups - - if add_sgs: - self.conn.add_server_security_groups(server, list(add_sgs)) - changed = True - - if remove_sgs: - self.conn.remove_server_security_groups(server, list(remove_sgs)) - changed = True - - return (changed, server) + def _will_change(self, state, server): + if state == 'present' and not server: + return True + elif state == 'present' and server: + return bool(self._build_update(server)) + elif state == 'absent' and server: + return False + else: + # state == 'absent' and not server: + return True def main(): diff --git a/plugins/modules/server_info.py b/plugins/modules/server_info.py index 66aa7765..a3ef7920 100644 --- a/plugins/modules/server_info.py +++ b/plugins/modules/server_info.py @@ -14,10 +14,11 @@ description: notes: - The result contains a list of servers. options: - server: + name: description: - restrict results to servers with names or UUID matching - this glob expression (e.g., ). + this glob expression such as web*. + aliases: ['server'] type: str detailed: description: @@ -26,9 +27,10 @@ options: type: bool default: 'no' filters: - description: - - restrict results to servers matching a dictionary of - filters + description: | + Used for further filtering of results. Either a string containing a + JMESPath expression or a dictionary of meta data. Elements of the latter + may, themselves, be dictionaries. type: dict all_projects: description: @@ -45,15 +47,316 @@ extends_documentation_fragment: ''' EXAMPLES = ''' -# Gather information about all servers named that are in an active state: -- openstack.cloud.server_info: - cloud: rax-dfw - server: web* +- name: Gather information about all 'web*' servers in active state + openstack.cloud.server_info: + cloud: devstack + name: web* filters: vm_state: active - register: result -- debug: - msg: "{{ result.openstack_servers }}" + +- name: Filter servers with nested dictionaries + openstack.cloud.server_info: + cloud: devstack + filters: + metadata: + key1: value1 + key2: value2 +''' + +RETURN = ''' +servers: + description: List of servers matching the filters + elements: dict + type: list + returned: always + contains: + access_ipv4: + description: | + IPv4 address that should be used to access this server. + May be automatically set by the provider. + returned: success + type: str + access_ipv6: + description: | + IPv6 address that should be used to access this + server. May be automatically set by the provider. + returned: success + type: str + addresses: + description: | + A dictionary of addresses this server can be accessed through. + The dictionary contains keys such as 'private' and 'public', + each containing a list of dictionaries for addresses of that + type. The addresses are contained in a dictionary with keys + 'addr' and 'version', which is either 4 or 6 depending on the + protocol of the IP address. + returned: success + type: dict + admin_password: + description: | + When a server is first created, it provides the administrator + password. + returned: success + type: str + attached_volumes: + description: | + A list of an attached volumes. Each item in the list contains + at least an 'id' key to identify the specific volumes. + returned: success + type: list + availability_zone: + description: | + The name of the availability zone this server is a part of. + returned: success + type: str + block_device_mapping: + description: | + Enables fine grained control of the block device mapping for an + instance. This is typically used for booting servers from + volumes. + returned: success + type: str + compute_host: + description: | + The name of the compute host on which this instance is running. + Appears in the response for administrative users only. + returned: success + type: str + config_drive: + description: | + Indicates whether or not a config drive was used for this + server. + returned: success + type: str + created_at: + description: Timestamp of when the server was created. + returned: success + type: str + description: + description: | + The description of the server. Before microversion + 2.19 this was set to the server name. + returned: success + type: str + disk_config: + description: The disk configuration. Either AUTO or MANUAL. + returned: success + type: str + flavor: + description: The flavor property as returned from server. + returned: success + type: dict + flavor_id: + description: | + The flavor reference, as a ID or full URL, for the flavor to + use for this server. + returned: success + type: str + has_config_drive: + description: | + Indicates whether a configuration drive enables metadata + injection. Not all cloud providers enable this feature. + returned: success + type: str + host_id: + description: An ID representing the host of this server. + returned: success + type: str + host_status: + description: The host status. + returned: success + type: str + hostname: + description: | + The hostname set on the instance when it is booted. + By default, it appears in the response for administrative users + only. + returned: success + type: str + hypervisor_hostname: + description: | + The hypervisor host name. Appears in the response for + administrative users only. + returned: success + type: str + id: + description: ID of the server. + returned: success + type: str + image: + description: The image property as returned from server. + returned: success + type: dict + image_id: + description: | + The image reference, as a ID or full URL, for the image to use + for this server. + returned: success + type: str + instance_name: + description: | + The instance name. The Compute API generates the instance name + from the instance name template. Appears in the response for + administrative users only. + returned: success + type: str + is_locked: + description: The locked status of the server + returned: success + type: bool + kernel_id: + description: | + The UUID of the kernel image when using an AMI. Will be null if + not. By default, it appears in the response for administrative + users only. + returned: success + type: str + key_name: + description: The name of an associated keypair. + returned: success + type: str + launch_index: + description: | + When servers are launched via multiple create, this is the + sequence in which the servers were launched. By default, it + appears in the response for administrative users only. + returned: success + type: int + launched_at: + description: The timestamp when the server was launched. + returned: success + type: str + links: + description: | + A list of dictionaries holding links relevant to this server. + returned: success + type: str + max_count: + description: The maximum number of servers to create. + returned: success + type: str + metadata: + description: List of tag strings. + returned: success + type: dict + min_count: + description: The minimum number of servers to create. + returned: success + type: str + name: + description: Name of the server + returned: success + type: str + networks: + description: | + A networks object. Required parameter when there are multiple + networks defined for the tenant. When you do not specify the + networks parameter, the server attaches to the only network + created for the current tenant. + returned: success + type: str + power_state: + description: The power state of this server. + returned: success + type: str + progress: + description: | + While the server is building, this value represents the + percentage of completion. Once it is completed, it will be 100. + returned: success + type: int + project_id: + description: The ID of the project this server is associated with. + returned: success + type: str + ramdisk_id: + description: | + The UUID of the ramdisk image when using an AMI. Will be null + if not. By default, it appears in the response for + administrative users only. + returned: success + type: str + reservation_id: + description: | + The reservation id for the server. This is an id that can be + useful in tracking groups of servers created with multiple + create, that will all have the same reservation_id. By default, + it appears in the response for administrative users only. + returned: success + type: str + root_device_name: + description: | + The root device name for the instance By default, it appears in + the response for administrative users only. + returned: success + type: str + scheduler_hints: + description: The dictionary of data to send to the scheduler. + returned: success + type: dict + security_groups: + description: | + A list of applicable security groups. Each group contains keys + for: description, name, id, and rules. + returned: success + type: list + elements: dict + server_groups: + description: | + The UUIDs of the server groups to which the server belongs. + Currently this can contain at most one entry. + returned: success + type: list + status: + description: | + The state this server is in. Valid values include 'ACTIVE', + 'BUILDING', 'DELETED', 'ERROR', 'HARD_REBOOT', 'PASSWORD', + 'PAUSED', 'REBOOT', 'REBUILD', 'RESCUED', 'RESIZED', + 'REVERT_RESIZE', 'SHUTOFF', 'SOFT_DELETED', 'STOPPED', + 'SUSPENDED', 'UNKNOWN', or 'VERIFY_RESIZE'. + returned: success + type: str + tags: + description: A list of associated tags. + returned: success + type: list + task_state: + description: The task state of this server. + returned: success + type: str + terminated_at: + description: | + The timestamp when the server was terminated (if it has been). + returned: success + type: str + trusted_image_certificates: + description: | + A list of trusted certificate IDs, that were used during image + signature verification to verify the signing certificate. + returned: success + type: list + updated_at: + description: Timestamp of when this server was last updated. + returned: success + type: str + user_data: + description: | + Configuration information or scripts to use upon launch. + Base64 encoded. + returned: success + type: str + user_id: + description: The ID of the owners of this server. + returned: success + type: str + vm_state: + description: The VM state of this server. + returned: success + type: str + volumes: + description: Same as attached_volumes. + returned: success + type: list ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule @@ -62,7 +365,7 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O class ServerInfoModule(OpenStackModule): argument_spec = dict( - server=dict(), + name=dict(aliases=['server']), detailed=dict(type='bool', default=False), filters=dict(type='dict'), all_projects=dict(type='bool', default=False), @@ -72,16 +375,14 @@ class ServerInfoModule(OpenStackModule): ) def run(self): + kwargs = dict((k, self.params[k]) + for k in ['detailed', 'filters', 'all_projects'] + if self.params[k] is not None) + kwargs['name_or_id'] = self.params['name'] - kwargs = self.check_versioned( - detailed=self.params['detailed'], - filters=self.params['filters'], - all_projects=self.params['all_projects'] - ) - if self.params['server']: - kwargs['name_or_id'] = self.params['server'] - openstack_servers = self.conn.search_servers(**kwargs) - self.exit(changed=False, openstack_servers=openstack_servers) + self.exit(changed=False, + servers=[server.to_dict(computed=False) for server in + self.conn.search_servers(**kwargs)]) def main(): diff --git a/plugins/modules/server_metadata.py b/plugins/modules/server_metadata.py index ae20e98e..11554474 100644 --- a/plugins/modules/server_metadata.py +++ b/plugins/modules/server_metadata.py @@ -31,11 +31,6 @@ options: choices: [present, absent] default: present type: str - availability_zone: - description: - - Availability zone in which to create the snapshot. - required: false - type: str requirements: - "python >= 3.6" - "openstacksdk" diff --git a/plugins/modules/volume.py b/plugins/modules/volume.py index 67c6f681..e3e07837 100644 --- a/plugins/modules/volume.py +++ b/plugins/modules/volume.py @@ -12,6 +12,10 @@ author: OpenStack Ansible SIG description: - Create or Remove cinder block storage volumes options: + availability_zone: + description: + - The availability zone. + type: str size: description: - Size of volume in GB. This parameter is required when the @@ -106,6 +110,7 @@ from ansible_collections.openstack.cloud.plugins.module_utils.openstack import O class VolumeModule(OpenStackModule): argument_spec = dict( + availability_zone=dict(type='str'), size=dict(type='int'), volume_type=dict(), display_name=dict(required=True, aliases=['name']),