diff --git a/ci/roles/server/tasks/main.yml b/ci/roles/server/tasks/main.yml index b10b3df4..d5f0eef3 100644 --- a/ci/roles/server/tasks/main.yml +++ b/ci/roles/server/tasks/main.yml @@ -29,6 +29,18 @@ network_name: "{{ server_alt_network }}" state: present +- name: Create router 1 (for attaching floating ips) + openstack.cloud.router: + cloud: "{{ cloud }}" + state: present + name: ansible_router1 + network: public + interfaces: + - net: "{{ server_network }}" + subnet: "{{ server_subnet }}" + - net: "{{ server_alt_network }}" + subnet: "{{ server_alt_subnet }}" + - name: Create security group for server openstack.cloud.security_group: cloud: "{{ cloud }}" @@ -284,6 +296,68 @@ name: "{{ server_name }}" wait: true +- name: Create server on private network with auto_ip + openstack.cloud.server: + auto_ip: true + cloud: "{{ cloud }}" + flavor: "{{ flavor }}" + image: "{{ image }}" + name: "{{ server_name }}" + nics: + - net-name: "{{ server_network }}" + reuse_ips: false + state: present + wait: true + register: server + +- name: Assert server on private network with auto_ip + assert: + that: + - server.server.addresses.values() + |flatten(levels=1) + |selectattr('OS-EXT-IPS:type', 'equalto', 'floating') + |map(attribute='addr') + |list|length == 1 + +- name: Delete server on private network with auto_ip + openstack.cloud.server: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true + +- name: Create server on public network + openstack.cloud.server: + auto_ip: false + cloud: "{{ cloud }}" + flavor: "{{ flavor }}" + image: "{{ image }}" + name: "{{ server_name }}" + nics: + - net-name: 'public' + reuse_ips: false + state: present + wait: true + register: server + +- debug: var=server + +- name: Assert server on public network + assert: + that: + - server.server.addresses.values() + |flatten(levels=1) + |selectattr('OS-EXT-IPS:type', 'equalto', 'floating') + |map(attribute='addr') + |list|length == 0 + +- name: Delete server on public network + openstack.cloud.server: + cloud: "{{ cloud }}" + state: absent + name: "{{ server_name }}" + wait: true + - name: Create port to be attached to server openstack.cloud.port: cloud: "{{ cloud }}" @@ -310,18 +384,46 @@ key2: value2 name: "{{ server_name }}" nics: - - net-name: 'public' - net-name: "{{ server_network }}" - - port-id: '{{ port.port.id }}' + - port-id: "{{ port.port.id }}" + reuse_ips: false state: present wait: true register: server - debug: var=server +- name: Assert server is not on public network and does not have a floating ip + assert: + that: + - server.server.addresses.keys()|sort == [server_network]|sort + - server.server.addresses.values() + |flatten(levels=1) + |selectattr('OS-EXT-IPS:type', 'equalto', 'floating') + |map(attribute='addr') + |list|length == 0 + +- name: Find all floating ips for debugging + openstack.cloud.floating_ip_info: + cloud: "{{ cloud }}" + register: fips + +- name: Print all floating ips for debugging + debug: var=fips + +- name: Find all servers for debugging + openstack.cloud.server_info: + cloud: "{{ cloud }}" + register: servers + +- name: Print all servers for debugging + debug: var=servers + - name: Update server openstack.cloud.server: - auto_ip: true + # TODO: Change auto_ip to true once openstacksdk's issue #2010352 has been solved + # Ref.: https://storyboard.openstack.org/#!/story/2010352 + auto_ip: false cloud: "{{ cloud }}" description: "This server got updated" # flavor cannot be updated but must be present @@ -334,9 +436,9 @@ name: "{{ server_name }}" # nics cannot be updated nics: - - net-name: 'public' - net-name: "{{ server_network }}" - - port-id: '{{ port.port.id }}' + - port-id: "{{ port.port.id }}" + reuse_ips: false security_groups: - '{{ server_security_group }}' - '{{ server_alt_security_group }}' @@ -355,16 +457,59 @@ - "'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') + - server_network in server_updated.server.addresses.keys()|list|sort + - server_updated.server.addresses[server_network]|length == 2 + - port.port.fixed_ips[0].ip_address in + server_updated.server.addresses[server_network]|map(attribute='addr') + # TODO: Verify networks once openstacksdk's issue #2010352 has been solved + # Ref.: https://storyboard.openstack.org/#!/story/2010352 + #- server_updated.server.addresses.public|length > 0 + #- (server_updated.server.addresses.keys()|sort == ([server_network, 'public']|sort)) + # or (server_updated.server.addresses.values() + # |flatten(levels=1) + # |selectattr('OS-EXT-IPS:type', 'equalto', 'floating') + # |map(attribute='addr') + # |list|length > 0) - name: Update server again + openstack.cloud.server: + # TODO: Change auto_ip to true once openstacksdk's issue #2010352 has been solved + # Ref.: https://storyboard.openstack.org/#!/story/2010352 + auto_ip: false + 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: "{{ server_network }}" + - port-id: "{{ port.port.id }}" + reuse_ips: false + security_groups: + - '{{ server_security_group }}' + - '{{ server_alt_security_group }}' + state: present + wait: true + register: server_updated_again + +- name: Assert server did not change + assert: + that: + - server.server.id == server_updated_again.server.id + - server_updated_again is not changed + +# TODO: Drop failure test once openstacksdk's issue #2010352 has been solved +# Ref.: https://storyboard.openstack.org/#!/story/2010352 +- name: Update server again with auto_ip set to true openstack.cloud.server: auto_ip: true cloud: "{{ cloud }}" @@ -379,21 +524,22 @@ name: "{{ server_name }}" # nics cannot be updated nics: - - net-name: 'public' - net-name: "{{ server_network }}" - - port-id: '{{ port.port.id }}' + - port-id: "{{ port.port.id }}" + reuse_ips: false security_groups: - '{{ server_security_group }}' - '{{ server_alt_security_group }}' state: present wait: true - register: server_again + register: server_updated_again + ignore_errors: true -- name: Assert server did not change +- name: Assert server update succeeded or failed with expected error assert: that: - - server.server.id == server_again.server.id - - server_again is not changed + - not server_updated_again.failed + or ('was found matching your NAT destination network' in server_updated_again.msg) - name: Delete updated server openstack.cloud.server: @@ -421,6 +567,12 @@ state: absent name: "{{ server_security_group }}" +- name: Delete router 1 + openstack.cloud.router: + cloud: "{{ cloud }}" + state: absent + name: ansible_router1 + - name: Delete second subnet for server openstack.cloud.subnet: cloud: "{{ cloud }}" diff --git a/plugins/modules/server.py b/plugins/modules/server.py index cd09a8fe..fabfcbbd 100644 --- a/plugins/modules/server.py +++ b/plugins/modules/server.py @@ -18,6 +18,10 @@ options: auto_ip: description: - Ensure instance has public ip however the cloud wants to do that. + - For example, the cloud could add a floating ip for the server or + attach the server to a public network. + - Requires I(wait) to be C(True) during server creation. + - Floating IP support is unstable in this module, use with caution. type: bool default: 'yes' aliases: ['auto_floating_ip', 'public_ip'] @@ -50,6 +54,7 @@ options: 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. + - Floating IP support is unstable in this module, use with caution. type: bool aliases: ['delete_fip'] default: 'no' @@ -84,11 +89,15 @@ options: floating_ip_pools: description: - Name of floating IP pool from which to choose a floating IP. + - Requires I(wait) to be C(True) during server creation. + - Floating IP support is unstable in this module, use with caution. type: list elements: str floating_ips: description: - list of valid floating IPs that pre-exist to assign to this node. + - Requires I(wait) to be C(True) during server creation. + - Floating IP support is unstable in this module, use with caution. type: list elements: str image: @@ -158,6 +167,7 @@ options: 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). + - Floating IP support is unstable in this module, use with caution. - This server attribute cannot be updated. type: bool default: 'yes' @@ -777,6 +787,7 @@ server: type: list ''' from ansible_collections.openstack.cloud.plugins.module_utils.openstack import OpenStackModule +import copy class ServerModule(OpenStackModule): @@ -831,10 +842,10 @@ class ServerModule(OpenStackModule): def run(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']) + server = self.conn.compute.find_server(self.params['name']) + if server: + # fetch server details such as server['addresses'] + server = self.conn.compute.get_server(server) if self.ansible.check_mode: self.exit_json(changed=self._will_change(state, server)) @@ -850,16 +861,6 @@ class ServerModule(OpenStackModule): 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)) @@ -893,20 +894,18 @@ class ServerModule(OpenStackModule): # do not add or remove any floating ip. return {} - if (auto_ip and server['interface_ip'] - and not (floating_ip_pools or floating_ips)): + # Get floating ip addresses attached to the server + ips = [interface_spec['addr'] + for v in server['addresses'].values() + for interface_spec in v + if interface_spec.get('OS-EXT-IPS:type', None) == 'floating'] + + if (auto_ip and ips and not floating_ip_pools and not 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. @@ -1024,6 +1023,15 @@ class ServerModule(OpenStackModule): return update def _create(self): + for k in ['auto_ip', 'floating_ips', 'floating_ip_pools']: + if self.params[k] is not None \ + and self.params['wait'] is False: + # floating ip addresses will only be added if + # we wait until the server has been created + # Ref.: https://opendev.org/openstack/openstacksdk/src/commit/3f81d0001dd994cde990d38f6e2671ee0694d7d5/openstack/cloud/_compute.py#L945 + self.fail_json( + msg="Option '{0}' requires 'wait: yes'".format(k)) + flavor_name_or_id = self.params['flavor'] image_id = None @@ -1063,7 +1071,12 @@ class ServerModule(OpenStackModule): server = self.conn.create_server(**args) - return server + # openstacksdk's create_server() might call meta.add_server_interfaces( + # ) which alters server attributes such as server['addresses']. So we + # do an extra call to compute.get_server() to return a clean server + # resource. + # Ref.: https://opendev.org/openstack/openstacksdk/src/commit/3f81d0001dd994cde990d38f6e2671ee0694d7d5/openstack/cloud/_compute.py#L942 + return self.conn.compute.get_server(server) def _delete(self, server): self.conn.delete_server( @@ -1077,15 +1090,15 @@ class ServerModule(OpenStackModule): 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) + # Use compute.get_server() instead of compute.find_server() + # to include server details + return self.conn.compute.get_server(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) + server = self.conn.add_ips_to_server(server, **ips, **args) add_ips = update.get('add_ips') if add_ips: @@ -1182,6 +1195,10 @@ class ServerModule(OpenStackModule): 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 + # + # Delete net-name from a copy else it will + # disappear from Ansible's debug output + net = copy.deepcopy(net) del net['net-name'] net['net-id'] = network_id nics.append(net) @@ -1192,6 +1209,10 @@ class ServerModule(OpenStackModule): 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 + # + # Delete net-name from a copy else it will + # disappear from Ansible's debug output + net = copy.deepcopy(net) del net['port-name'] net['port-id'] = port_id nics.append(net)