diff --git a/ansible_collections/f5networks/f5os/CHANGELOG.rst b/ansible_collections/f5networks/f5os/CHANGELOG.rst index df934d1..9f6e383 100644 --- a/ansible_collections/f5networks/f5os/CHANGELOG.rst +++ b/ansible_collections/f5networks/f5os/CHANGELOG.rst @@ -4,6 +4,19 @@ F5Networks.F5OS Release Notes .. contents:: Topics +v1.13.0 +======= + +Minor Changes +------------- + +- f5os_ntp_server - added a new parameter, prefer, iburst + +Bugfixes +-------- + +- f5os_system_image_import - bug fixed for importing system image in versions less than 1.7 + v1.12.0 ======= diff --git a/ansible_collections/f5networks/f5os/README.md b/ansible_collections/f5networks/f5os/README.md index 220a0aa..821633c 100644 --- a/ansible_collections/f5networks/f5os/README.md +++ b/ansible_collections/f5networks/f5os/README.md @@ -64,7 +64,7 @@ Please see [f5execenv] documentation for further instructions how to use and bui ansible_user: "{{ provider.user }}" ansible_httpapi_password: "{{ provider.password }}" ansible_httpapi_port: "{{ provider.server_port }}" - ansible_network_os: f5networks.f5_bigip.bigip + ansible_network_os: f5networks.f5os.f5os ansible_httpapi_use_ssl: yes ansible_httpapi_validate_certs: "{{ provider.validate_certs }}" ``` diff --git a/ansible_collections/f5networks/f5os/changelogs/.plugin-cache.yaml b/ansible_collections/f5networks/f5os/changelogs/.plugin-cache.yaml index db8bbc3..4f8620c 100644 --- a/ansible_collections/f5networks/f5os/changelogs/.plugin-cache.yaml +++ b/ansible_collections/f5networks/f5os/changelogs/.plugin-cache.yaml @@ -163,4 +163,4 @@ plugins: strategy: {} test: {} vars: {} -version: 1.12.0 +version: 1.13.0 diff --git a/ansible_collections/f5networks/f5os/changelogs/changelog.yaml b/ansible_collections/f5networks/f5os/changelogs/changelog.yaml index 578b4c5..b60e5cc 100644 --- a/ansible_collections/f5networks/f5os/changelogs/changelog.yaml +++ b/ansible_collections/f5networks/f5os/changelogs/changelog.yaml @@ -129,6 +129,17 @@ releases: - lag_bugfix.yaml - tenant_virtual_disk_size_param.yaml release_date: '2024-10-24' + 1.13.0: + changes: + bugfixes: + - f5os_system_image_import - bug fixed for importing system image in versions + less than 1.7 + minor_changes: + - f5os_ntp_server - added a new parameter, prefer, iburst + fragments: + - f5os_system_import_version_issue.yaml + - prefer_iburst_f5os_ntp_param.yaml + release_date: '2024-12-04' 1.2.0: modules: - description: Manage F5OS config backups. diff --git a/ansible_collections/f5networks/f5os/galaxy.yml b/ansible_collections/f5networks/f5os/galaxy.yml index cd21205..c7fd691 100644 --- a/ansible_collections/f5networks/f5os/galaxy.yml +++ b/ansible_collections/f5networks/f5os/galaxy.yml @@ -30,4 +30,4 @@ tags: - networking - rseries - velos -version: 1.12.0 +version: 1.13.0 diff --git a/ansible_collections/f5networks/f5os/plugins/httpapi/f5os.py b/ansible_collections/f5networks/f5os/plugins/httpapi/f5os.py index aa86d01..0480c8f 100644 --- a/ansible_collections/f5networks/f5os/plugins/httpapi/f5os.py +++ b/ansible_collections/f5networks/f5os/plugins/httpapi/f5os.py @@ -32,6 +32,7 @@ import io import json +import time from ansible.module_utils.basic import to_text from ansible.plugins.httpapi import HttpApiBase @@ -93,17 +94,26 @@ def send_request(self, **kwargs): url = url.replace(ROOT, '/api/data') if port == 443 else url # allow for empty json to be passed as payload, useful for some endpoints data = json.dumps(body) if body or body == {} else None - try: - self._display_request(method, url, body) - response, response_data = self.connection.send(url, data, method=method, **kwargs) - response_value = self._get_response_value(response_data) - return dict( - code=response.getcode(), - contents=self._response_to_json(response_value), - headers=dict(response.getheaders()) - ) - except HTTPError as e: - return dict(code=e.code, contents=handle_errors(e)) + retries = 3 + err_dict = dict() + for r1 in range(retries): + try: + self._display_request(method, url, body) + response, response_data = self.connection.send(url, data, method=method, **kwargs) + response_value = self._get_response_value(response_data) + return dict( + code=response.getcode(), + contents=self._response_to_json(response_value), + headers=dict(response.getheaders()) + ) + except HTTPError as e: + return dict(code=e.code, contents=handle_errors(e)) + except AnsibleConnectionFailure as e: + time.sleep(10) + err_dict.update({'error': str(e)}) + continue + raise F5ModuleError(f'{err_dict}') + # raise F5ModuleError('Failed to send request after {0} retries'.format(retries)) def _display_request(self, method, url, data=None): if data: diff --git a/ansible_collections/f5networks/f5os/plugins/module_utils/version.py b/ansible_collections/f5networks/f5os/plugins/module_utils/version.py index 4321aa1..cdad49e 100644 --- a/ansible_collections/f5networks/f5os/plugins/module_utils/version.py +++ b/ansible_collections/f5networks/f5os/plugins/module_utils/version.py @@ -4,4 +4,4 @@ # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # This collection version needs to be updated at each release -CURRENT_COLL_VERSION = "1.12.0" +CURRENT_COLL_VERSION = "1.13.0" diff --git a/ansible_collections/f5networks/f5os/plugins/modules/f5os_ntp_server.py b/ansible_collections/f5networks/f5os/plugins/modules/f5os_ntp_server.py index 4ce65ae..dbf10b6 100644 --- a/ansible_collections/f5networks/f5os/plugins/modules/f5os_ntp_server.py +++ b/ansible_collections/f5networks/f5os/plugins/modules/f5os_ntp_server.py @@ -26,6 +26,14 @@ description: - Specifies the key ID which identifies the key used for authentication. type: int + prefer: + description: + - Specifies that this server should be the preferred one if true. Specify false if not. + type: bool + iburst: + description: + - Specifies to enable iburst for the NTP service. Specify false to disable it. + type: bool state: description: - The NTP server state. @@ -38,6 +46,7 @@ default: present author: - Rohit Upadhyay (@rupadhyay) + - Prateek Ramani (@ramani) ''' EXAMPLES = r''' @@ -45,6 +54,8 @@ f5os_ntp_server: server: "1.2.3.4" key_id: 10 + prefer: true + iburst: true - name: Update an ntp server f5os_ntp_server: @@ -85,21 +96,26 @@ class Parameters(AnsibleF5Parameters): api_map = { - } api_attributes = [ 'server', 'key_id', + 'iburst', + 'prefer', ] returnables = [ 'server', 'key_id', + 'iburst', + 'prefer' ] updatables = [ - 'key_id' + 'key_id', + 'iburst', + 'prefer', ] @@ -112,6 +128,14 @@ def server(self): def key_id(self): return self._values['config'].get('f5-openconfig-system-ntp:key-id') + @property + def iburst(self): + return self._values['config'].get('iburst') + + @property + def prefer(self): + return self._values['config'].get('prefer') + class ModuleParameters(Parameters): pass @@ -158,6 +182,18 @@ def __default(self, param): except AttributeError: return attr1 + @property + def prefer(self): + if (self.want.prefer and self.have.prefer) or (not self.want.prefer and not self.have.prefer): + return None + return self.want.prefer + + @property + def iburst(self): + if (self.want.iburst and self.have.iburst) or (not self.want.iburst and not self.have.iburst): + return None + return self.want.iburst + class ModuleManager(object): def __init__(self, *args, **kwargs): @@ -290,6 +326,12 @@ def create_on_device(self): if params.get('key_id'): payload['server'][0]['config']['f5-openconfig-system-ntp:key-id'] = params['key_id'] + if 'prefer' in params: + payload['server'][0]['config']['prefer'] = params['prefer'] + + if 'iburst' in params: + payload['server'][0]['config']['iburst'] = params['iburst'] + uri = "/openconfig-system:system/ntp/openconfig-system:servers" response = self.client.post(uri, data=payload) @@ -306,12 +348,19 @@ def update_on_device(self): 'address': self.want.server, 'config': { 'address': self.want.server, - 'f5-openconfig-system-ntp:key-id': params['key_id'] } } ] } + if 'key_id' in params: + payload['server'][0]['config']['f5-openconfig-system-ntp:key-id'] = params['key_id'] + + if 'prefer' in params: + payload['server'][0]['config']['prefer'] = params['prefer'] + + if 'iburst' in params: + payload['server'][0]['config']['iburst'] = params['iburst'] response = self.client.patch(uri, data=payload) if response['code'] not in [200, 201, 202, 204]: raise F5ModuleError(response['contents']) @@ -341,6 +390,8 @@ def __init__(self): argument_spec = dict( server=dict(required=True), key_id=dict(type='int'), + prefer=dict(type='bool'), + iburst=dict(type='bool'), state=dict( default='present', choices=['present', 'absent'] diff --git a/ansible_collections/f5networks/f5os/plugins/modules/f5os_primarykey.py b/ansible_collections/f5networks/f5os/plugins/modules/f5os_primarykey.py index 95884f4..5f64439 100644 --- a/ansible_collections/f5networks/f5os/plugins/modules/f5os_primarykey.py +++ b/ansible_collections/f5networks/f5os/plugins/modules/f5os_primarykey.py @@ -26,6 +26,12 @@ - Specifies Salt for generating primary key. required: True type: str + force_update: + description: + - Force update the primary key on F5OS Device. + type: bool + default: False + version_added: "1.13.0" state: description: - Primary key on F5OS Device state. @@ -35,6 +41,8 @@ - present - absent default: present +notes: + - This module does not support deleting the primary key. author: - Ravinder Reddy (@chinthalapalli) ''' @@ -45,6 +53,13 @@ passphrase: "test-passphrase" salt: "test-salt" state: present + +- name: Update Primary Key on F5OS Device + f5os_primarykey: + passphrase: "test-passphrase" + salt: "test-salt" + state: present + force_update: true ''' RETURN = r''' @@ -67,7 +82,7 @@ from ansible.module_utils.connection import Connection from ansible_collections.f5networks.f5os.plugins.module_utils.client import ( - F5Client, send_teem + F5Client ) from ansible_collections.f5networks.f5os.plugins.module_utils.common import ( F5ModuleError, AnsibleF5Parameters @@ -207,11 +222,11 @@ def exec_module(self): result.update(**changes) result.update(dict(changed=changed)) self._announce_deprecations(result) - send_teem(self.client, start) + # send_teem(self.client, start) return result def present(self): - if not self.exists(): + if not self.exists() or self.want.force_update: return self.create() def absent(self): @@ -234,12 +249,13 @@ def update(self): return True def remove(self): - if self.module.check_mode: # pragma: no cover - return True - self.remove_from_device() - if self.exists(): - raise F5ModuleError("Failed to delete the resource.") - return True + pass + # if self.module.check_mode: # pragma: no cover + # return True + # self.remove_from_device() + # # if self.exists(): + # # raise F5ModuleError("Failed to delete the resource.") + # return True def create(self): self._set_changed_options() @@ -293,6 +309,7 @@ def __init__(self): argument_spec = dict( passphrase=dict(type='str', no_log=True, required=True), salt=dict(type='str', no_log=True, required=True), + force_update=dict(type='bool', default=False), state=dict( default='present', choices=['present', 'absent'] diff --git a/ansible_collections/f5networks/f5os/plugins/modules/f5os_system.py b/ansible_collections/f5networks/f5os/plugins/modules/f5os_system.py index b15cb5d..2e15687 100644 --- a/ansible_collections/f5networks/f5os/plugins/modules/f5os_system.py +++ b/ansible_collections/f5networks/f5os/plugins/modules/f5os_system.py @@ -36,6 +36,10 @@ description: - Specifies the CLI idle timeout type: int + token_lifetime: + description: + - Specifies the token lifetime length in minutes + type: int httpd_ciphersuite: description: - Specifies the httpd ciphersuite in OpenSSL format @@ -204,6 +208,7 @@ class Parameters(AnsibleF5Parameters): 'login_banner', 'hostname', 'cli_timeout', + 'token_lifetime', 'sshd_idle_timeout', 'httpd_ciphersuite', 'sshd_ciphers', @@ -219,6 +224,7 @@ class Parameters(AnsibleF5Parameters): 'login_banner', 'hostname', 'cli_timeout', + 'token_lifetime', 'sshd_idle_timeout', 'httpd_ciphersuite', 'sshd_ciphers', @@ -234,6 +240,7 @@ class Parameters(AnsibleF5Parameters): 'login_banner', 'hostname', 'cli_timeout', + 'token_lifetime', 'sshd_idle_timeout', 'httpd_ciphersuite', 'sshd_ciphers', @@ -280,6 +287,13 @@ def cli_timeout(self): except (TypeError, ValueError, KeyError): return None + @property + def token_lifetime(self): + try: + return int(self._values['token_lifetime']) + except (TypeError, ValueError, KeyError): + return None + @property def sshd_idle_timeout(self): try: @@ -495,6 +509,7 @@ def should_update(self): def update(self): self.have = self.read_current_from_device() + if not self.should_update(): return False if self.module.check_mode: # pragma: no cover @@ -544,6 +559,21 @@ def exists(self, query=None): if response['code'] not in [200, 201, 202, 404]: raise F5ModuleError(response['contents']) + aaa_attr = { + 'token_lifetime': 'lifetime' + } + for attr in aaa_attr: + if hasattr(self.want, attr) and getattr(self.want, attr) is not None: + uri = f'/openconfig-system:system/aaa/f5-aaa-confd-restconf-token:restconf-token/config/{aaa_attr[attr]}' + response = self.client.get(uri) + + if response['code'] == 200: + if query in ['any', 'still']: + return True + + if response['code'] not in [200, 201, 202, 404]: + raise F5ModuleError(response['contents']) + clock_attr = { 'timezone': 'timezone-name' } @@ -554,6 +584,8 @@ def exists(self, query=None): if response['code'] == 200: if query in ['any', 'still']: + if query == 'still': + return False return True if response['code'] not in [200, 201, 202, 404]: @@ -638,6 +670,21 @@ def update_on_device(self): if 'login_banner' in params: config['login-banner'] = params['login_banner'] + if 'token_lifetime' in params: + token_uri = '/openconfig-system:system/aaa' + token_payload = { + "openconfig-system:aaa": { + "f5-aaa-confd-restconf-token:restconf-token": { + "config": { + "lifetime": params['token_lifetime'] + } + } + } + } + settings_response = self.client.patch(token_uri, data=token_payload) + if settings_response['code'] not in [200, 201, 202, 204]: + raise F5ModuleError(settings_response['contents']) + # Settings use a different API endpoint if any(attr in ['cli_timeout', 'sshd_idle_timeout', 'gui_advisory'] for attr in params): settings_uri = '/openconfig-system:system/f5-system-settings:settings' @@ -724,13 +771,13 @@ def remove_from_device(self): continue else: raise F5ModuleError(response['contents']) - - clock_attr = { - 'timezone': 'timezone-name' + aaa_attr = { + 'token_lifetime': 'lifetime' } - for attr in clock_attr: + + for attr in aaa_attr: if hasattr(self.want, attr) and getattr(self.want, attr) is not None: - uri = f'/openconfig-system:system/clock/config/{clock_attr[attr]}' + uri = f'/openconfig-system:system/aaa/f5-aaa-confd-restconf-token:restconf-token/config/lifetime/{aaa_attr[attr]}' response = self.client.delete(uri) if response['code'] == 204: @@ -742,6 +789,37 @@ def remove_from_device(self): else: raise F5ModuleError(response['contents']) + # Deleting the clock attribute is not supported + # The following error is returned for admin users: + # { + # "ietf-restconf:errors": { + # "error": [ + # { + # "error-type": "application", + # "error-tag": "access-denied", + # "error-message": "access denied" + # } + # ] + # } + # } + # clock_attr = { + # 'timezone': 'timezone-name' + # } + + # for attr in clock_attr: + # if hasattr(self.want, attr) and getattr(self.want, attr) is not None: + # uri = f'/openconfig-system:system/clock/config/{clock_attr[attr]}' + # response = self.client.delete(uri) + + # if response['code'] == 204: + # # Deleted + # continue + # elif response['code'] == 404: + # # Not Found + # continue + # else: + # raise F5ModuleError(response['contents']) + settings_attr = { 'cli_timeout': 'idle-timeout', 'sshd_idle_timeout': 'sshd-idle-timeout' @@ -770,7 +848,7 @@ def remove_from_device(self): for attr in ciphers_attr: if hasattr(self.want, attr) and getattr(self.want, attr) is not None: if attr == 'httpd_ciphersuite': - uri = '/openconfig-system:system/f5-security-ciphers:security/services/service="httpd"/config/ssl-ciphersuite' + uri = f'/openconfig-system:system/f5-security-ciphers:security/services/service="httpd"/config/{ciphers_attr[attr]}' response = self.client.delete(uri) if response['code'] == 204: @@ -781,7 +859,6 @@ def remove_from_device(self): continue else: raise F5ModuleError(response['contents']) - else: uri = f'/openconfig-system:system/f5-security-ciphers:security/services/service="sshd"/config/{ciphers_attr[attr]}' response = self.client.delete(uri) @@ -831,6 +908,14 @@ def read_current_from_device(self): if settings_response['code'] in [200]: params['settings'] = settings_response['contents']['f5-system-settings:settings'] + # Token Lifetime + settings_uri = '/openconfig-system:system/aaa/f5-aaa-confd-restconf-token:restconf-token/state/lifetime' + settings_response = self.client.get(settings_uri) + if settings_response['code'] not in [200, 201, 202, 204]: + raise F5ModuleError(settings_response['contents']['f5-aaa-confd-restconf-token:lifetime']) + if settings_response['code'] in [200]: + params['token_lifetime'] = settings_response['contents']['f5-aaa-confd-restconf-token:lifetime'] + return ApiParameters(params=params) @@ -859,6 +944,7 @@ def __init__(self): ) ), cli_timeout=dict(type='int'), + token_lifetime=dict(type='int', no_log=False), httpd_ciphersuite=dict(type='str'), sshd_idle_timeout=dict(type='str'), sshd_ciphers=dict( diff --git a/ansible_collections/f5networks/f5os/plugins/modules/f5os_system_image_import.py b/ansible_collections/f5networks/f5os/plugins/modules/f5os_system_image_import.py index c40f19f..f321370 100644 --- a/ansible_collections/f5networks/f5os/plugins/modules/f5os_system_image_import.py +++ b/ansible_collections/f5networks/f5os/plugins/modules/f5os_system_image_import.py @@ -315,16 +315,37 @@ def create_on_device(self): del params['image-name'] payload = dict(input=[params]) response = self.client.post(uri, data=payload) - if response['code'] not in [200, 201, 202]: + + if response['code'] not in [200, 201, 202, 204]: raise F5ModuleError(f"Failed to import system image with {response['contents']}") if 'f5-utils-file-transfer:output' in response['contents'] and 'result' in response['contents']['f5-utils-file-transfer:output']: result = response['contents']['f5-utils-file-transfer:output']['result'] + if result.startswith('Aborted: local-file already exists'): raise F5ModuleError(f"Failed to import system image, error: {result}") if result.startswith('File import with same local file name is in progress'): raise F5ModuleError(f"Failed to import system image, error: {result}") - operation_id = response['contents']['f5-utils-file-transfer:output']['operation-id'] - self.changes.update({"operation_id": operation_id}) + + if 'operation-id' in response['contents']['f5-utils-file-transfer:output']: + operation_id = response['contents']['f5-utils-file-transfer:output']['operation-id'] + self.changes.update({"operation_id": operation_id}) + else: + # less than 1.7 version + uri = "/f5-utils-file-transfer:file/transfer-status" + delay, period = self.want.timeout + for x in range(0, period): + response = self.client.post(uri, data={}) + result = response['contents']['f5-utils-file-transfer:output']['result'] + if "In Progress" in result: + time.sleep(delay) + continue + elif "Completed" in result: + return True + time.sleep(delay) + raise F5ModuleError( + "Module timeout reached, state change is unknown, " + "please increase the timeout parameter for long lived actions." + ) time.sleep(20) self.changes.update({"message": f"Image {self.want.image_name} import started."}) return True diff --git a/ansible_collections/f5networks/f5os/plugins/modules/f5os_tenant.py b/ansible_collections/f5networks/f5os/plugins/modules/f5os_tenant.py index 5bda7c4..1c607d4 100644 --- a/ansible_collections/f5networks/f5os/plugins/modules/f5os_tenant.py +++ b/ansible_collections/f5networks/f5os/plugins/modules/f5os_tenant.py @@ -99,6 +99,34 @@ or C(provisioned) state. type: int version_added: "1.12.0" + type: + description: + - The size of the virtual disk in GB. + - To update this attribute the tenant must be in either C(configured) + or C(provisioned) state. + type: str + version_added: "1.13.0" + dag_ipv6_prefix_length: + description: + - DAG IPv6 prefix length is a configuration field on each tenant that is used by disaggregator algorithms as a networking mask + - To update this attribute the tenant must be in either C(configured) + or C(provisioned) state. + type: int + version_added: "1.13.0" + deployment_file: + description: + - The deployment file to use for the tenant. + - To update this attribute the tenant must be in either C(configured) + or C(provisioned) state. + type: str + version_added: "1.13.0" + mac_block_size: + description: + - The MAC block size for the tenant. + - To update this attribute the tenant must be in either C(configured) + or C(provisioned) state. + type: str + version_added: "1.13.0" running_state: description: - Desired C(running_state) of the tenant. @@ -123,6 +151,7 @@ - This module will not execute on VELOS controller. author: - Wojciech Wypior (@wojtek0806) + - Ravinder Reddy (@chinthalapalli) ''' EXAMPLES = r''' @@ -231,6 +260,10 @@ class Parameters(AnsibleF5Parameters): 'vcpu-cores-per-node': 'cpu_cores', 'cpu-cores': 'cpu_cores', 'storage': 'virtual_disk_size', + 'mac-data': 'mac_block_size', + 'type': 'type', + 'dag-ipv6-prefix-length': 'dag_ipv6_prefix_length', + 'deployment-file': 'deployment_file', } api_attributes = [ @@ -245,6 +278,10 @@ class Parameters(AnsibleF5Parameters): 'cryptos', 'running-state', 'storage', + 'mac-data', + 'dag-ipv6-prefix-length', + 'type', + 'deployment-file', ] returnables = [ @@ -259,6 +296,10 @@ class Parameters(AnsibleF5Parameters): 'cryptos', 'running_state', 'virtual_disk_size', + 'mac_block_size', + 'dag_ipv6_prefix_length', + 'type', + 'deployment_file', ] updatables = [ @@ -273,6 +314,10 @@ class Parameters(AnsibleF5Parameters): 'cryptos', 'running_state', 'virtual_disk_size', + 'mac_block_size', + 'dag_ipv6_prefix_length', + 'type', + 'deployment_file', ] @@ -299,6 +344,13 @@ def virtual_disk_size(self): except (TypeError, ValueError): return None + @property + def mac_block_size(self): + try: + return self._values.get('mac_block_size') + except (TypeError, ValueError): + return None + class ModuleParameters(Parameters): @property @@ -384,6 +436,12 @@ def virtual_disk_size(self): return None return {'size': self._values['virtual_disk_size']} + @property + def mac_block_size(self): + if self._values['mac_block_size'] is None: + return None + return {'f5-tenant-l2-inline:mac-block-size': self._values['mac_block_size']} + class Changes(Parameters): def to_return(self): # pragma: no cover @@ -438,6 +496,18 @@ def virtual_disk_size(self): return {'virtual_disk_size': want} return None + @property + def mac_block_size(self): + want = self.want.mac_block_size + have = self.have.mac_block_size + if want is None: + return None + if have is None: + return want + if want['f5-tenant-l2-inline:mac-block-size'] != have['f5-tenant-l2-inline:mac-block-size']: + return {'mac_block_size': want} + return None + class ModuleManager(object): def __init__(self, *args, **kwargs): @@ -469,6 +539,7 @@ def _update_changed_options(self): changed.update(change) else: changed[k] = change + # raise F5ModuleError(f"have: {self.have.__dict__} \n want: {self.want.__dict__} \n changed: {changed}") if changed: self.changes = UsableChanges(params=changed) return True @@ -558,6 +629,7 @@ def exists(self): def create_on_device(self): params = self.changes.api_params() + # raise F5ModuleError(f"params: {params}") payload = dict(tenant=[dict(name=self.want.name, config=params)]) uri = "/f5-tenants:tenants" @@ -569,6 +641,7 @@ def create_on_device(self): def update_on_device(self): params = self.changes.api_params() + # raise F5ModuleError(f"have: {params}") keys = list(params.keys()) if 'running-state' in keys: @@ -610,6 +683,8 @@ def __init__(self): argument_spec = dict( name=dict(required=True), image_name=dict(), + type=dict(type='str'), + deployment_file=dict(type='str'), nodes=dict(type='list', elements='int'), mgmt_ip=dict(), mgmt_prefix=dict(type='int'), @@ -621,6 +696,8 @@ def __init__(self): ), memory=dict(type='int'), virtual_disk_size=dict(type='int'), + dag_ipv6_prefix_length=dict(type='int'), + mac_block_size=dict(type='str'), cryptos=dict( choices=['enabled', 'disabled'] ), diff --git a/ansible_collections/f5networks/f5os/tests/modules/network/f5/fixtures/system_settings_lifetime.json b/ansible_collections/f5networks/f5os/tests/modules/network/f5/fixtures/system_settings_lifetime.json new file mode 100644 index 0000000..f7e4da7 --- /dev/null +++ b/ansible_collections/f5networks/f5os/tests/modules/network/f5/fixtures/system_settings_lifetime.json @@ -0,0 +1,3 @@ +{ + "f5-aaa-confd-restconf-token:lifetime": 15 +} \ No newline at end of file diff --git a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_ntp_server.py b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_ntp_server.py index 5bbaa48..e9b7b05 100644 --- a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_ntp_server.py +++ b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_ntp_server.py @@ -100,6 +100,8 @@ def test_update(self, *args): set_module_args(dict( server='10.218.33.44', key_id=32, + iburst=False, + prefer=False )) module = AnsibleModule( diff --git a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system.py b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system.py index 91cd6cb..8b14ba9 100644 --- a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system.py +++ b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system.py @@ -149,6 +149,7 @@ def test_create_system_settings(self): {'code': 200, 'contents': load_fixture('system_settings_clock.json')}, {'code': 200, 'contents': load_fixture('system_settings_ciphers.json')}, {'code': 200, 'contents': load_fixture('system_settings.json')}, + {'code': 200, 'contents': load_fixture('system_settings_lifetime.json')}, ]) mm.client.patch = Mock(return_value=dict(code=200)) @@ -192,7 +193,7 @@ def test_remove_system_settings(self): result = mm.exec_module() self.assertTrue(result['changed']) - self.assertEqual(mm.client.delete.call_count, 11) + self.assertEqual(mm.client.delete.call_count, 10) def test_update_hostname_setting(self, *args): set_module_args( @@ -210,14 +211,20 @@ def test_update_hostname_setting(self, *args): mm = ModuleManager(module=module) mm.exists = Mock(return_value=True) - mm.client.get = Mock(return_value={'code': 200, 'contents': load_fixture('system_settings_hostname.json')}) + mm.client.get = Mock(side_effect=[ + {'code': 200, 'contents': load_fixture('system_settings_hostname.json')}, + {'code': 200, 'contents': load_fixture('system_settings_clock.json')}, + {'code': 200, 'contents': load_fixture('system_settings_ciphers.json')}, + {'code': 200, 'contents': load_fixture('system_settings.json')}, + {'code': 200, 'contents': load_fixture('system_settings_lifetime.json')}, + ]) mm.client.patch = Mock(return_value=dict(code=200)) result = mm.exec_module() self.assertTrue(result['changed']) self.assertEqual(mm.client.patch.call_count, 1) - self.assertEqual(mm.client.get.call_count, 4) + self.assertEqual(mm.client.get.call_count, 5) def test_remove_hostname_setting(self, *args): set_module_args( diff --git a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system_image_import.py b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system_image_import.py index bab86a1..4434e84 100644 --- a/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system_image_import.py +++ b/ansible_collections/f5networks/f5os/tests/modules/network/f5/test_f5os_system_image_import.py @@ -104,6 +104,31 @@ def test_system_image_import(self, *args): self.assertEqual(mm.client.post.call_count, 2) # self.assertEqual(mm.client.get.call_count, 1) + def test_system_image_import_v_15(self, *args): + set_module_args(dict( + remote_image_url='https://foo.bar.baz.net/foo/bar/F5OS-A-1.8.0-14139.R5R10.CANDIDATE.iso', + local_path="images/staging", + )) + module = AnsibleModule( + argument_spec=self.spec.argument_spec, + supports_check_mode=self.spec.supports_check_mode, + ) + + mm = ModuleManager(module=module) + mm.client.platform = 'rSeries Platform' + get_data = {"f5-utils-file-transfer:output": {"entries": [{"name": "F5OS-A-1.8.0-14136.R5R10.CANDIDATE.iso", "date": "string", "size": "string"}]}} + post_data = {"f5-utils-file-transfer:output": {"result": "File transfer is initiated.(images/staging/F5OS-A-1.8.0-14139.R5R10.CANDIDATE.iso)"}} + status_data = {"f5-utils-file-transfer:output": {"result": "/F5OS-A-1.8.0-14139.R5R10.CANDIDATE.iso|Completed\n"}} + mm.client.post = Mock(return_value={'code': 201, 'contents': get_data}) + mm.client.post = Mock(side_effect=[ + dict(code=201, contents=get_data), + dict(code=201, contents=post_data), + dict(code=204, contents=status_data), + ]) + results = mm.exec_module() + self.assertTrue(results['changed']) + self.assertEqual(mm.client.post.call_count, 3) + def test_system_image_import_status(self, *args): set_module_args(dict( remote_image_url='https://foo.bar.baz.net/foo/bar/F5OS-A-1.8.0-14139.R5R10.CANDIDATE.iso',