Adds new parameters to bigip_vlan (#47777)

Also fixes unit tests to work in ansible 2.8
pull/47779/head
Tim Rupp 6 years ago committed by GitHub
parent 7c84ba8051
commit 8edbfb488c
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23

@ -1,7 +1,7 @@
#!/usr/bin/python #!/usr/bin/python
# -*- coding: utf-8 -*- # -*- coding: utf-8 -*-
# #
# Copyright (c) 2017 F5 Networks Inc. # Copyright: (c) 2017, F5 Networks Inc.
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt) # GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
from __future__ import absolute_import, division, print_function from __future__ import absolute_import, division, print_function
@ -28,12 +28,16 @@ options:
- Specifies a list of tagged interfaces and trunks that you want to - Specifies a list of tagged interfaces and trunks that you want to
configure for the VLAN. Use tagged interfaces or trunks when configure for the VLAN. Use tagged interfaces or trunks when
you want to assign a single interface or trunk to multiple VLANs. you want to assign a single interface or trunk to multiple VLANs.
- This parameter is mutually exclusive with the C(untagged_interfaces)
and C(interfaces) parameters.
aliases: aliases:
- tagged_interface - tagged_interface
untagged_interfaces: untagged_interfaces:
description: description:
- Specifies a list of untagged interfaces and trunks that you want to - Specifies a list of untagged interfaces and trunks that you want to
configure for the VLAN. configure for the VLAN.
- This parameter is mutually exclusive with the C(tagged_interfaces)
and C(interfaces) parameters.
aliases: aliases:
- untagged_interface - untagged_interface
name: name:
@ -111,6 +115,57 @@ options:
- Device partition to manage resources on. - Device partition to manage resources on.
default: Common default: Common
version_added: 2.5 version_added: 2.5
source_check:
description:
- When C(yes), specifies that the system verifies that the return route to an initial
packet is the same VLAN from which the packet originated.
- The system performs this verification only if the C(auto_last_hop) option is C(no).
type: bool
version_added: 2.8
fail_safe:
description:
- When C(yes), specifies that the VLAN takes the specified C(fail_safe_action) if the
system detects a loss of traffic on this VLAN's interfaces.
type: bool
version_added: 2.8
fail_safe_timeout:
description:
- Specifies the number of seconds that a system can run without detecting network
traffic on this VLAN before it takes the C(fail_safe_action).
version_added: 2.8
fail_safe_action:
description:
- Specifies the action that the system takes when it does not detect any traffic on
this VLAN, and the C(fail_safe_timeout) has expired.
choices:
- reboot
- restart-all
version_added: 2.8
sflow_poll_interval:
description:
- Specifies the maximum interval in seconds between two pollings.
version_added: 2.8
sflow_sampling_rate:
description:
- Specifies the ratio of packets observed to the samples generated.
version_added: 2.8
interfaces:
description:
- Interfaces that you want added to the VLAN. This can include both tagged
and untagged interfaces as the C(tagging) parameter specifies.
- This parameter is mutually exclusive with the C(untagged_interfaces) and
C(tagged_interfaces) parameters.
suboptions:
interface:
description:
- The name of the interface
tagging:
description:
- Whether the interface is C(tagged) or C(untagged).
choices:
- tagged
- untagged
version_added: 2.8
notes: notes:
- Requires BIG-IP versions >= 12.0.0 - Requires BIG-IP versions >= 12.0.0
extends_documentation_fragment: f5 extends_documentation_fragment: f5
@ -122,45 +177,45 @@ author:
EXAMPLES = r''' EXAMPLES = r'''
- name: Create VLAN - name: Create VLAN
bigip_vlan: bigip_vlan:
name: "net1" name: net1
password: "secret" provider:
server: "lb.mydomain.com" password: secret
user: "admin" server: lb.mydomain.com
validate_certs: "no" user: admin
delegate_to: localhost delegate_to: localhost
- name: Set VLAN tag - name: Set VLAN tag
bigip_vlan: bigip_vlan:
name: "net1" name: net1
password: "secret" tag: 2345
server: "lb.mydomain.com" provider:
tag: "2345" user: admin
user: "admin" password: secret
validate_certs: "no" server: lb.mydomain.com
delegate_to: localhost delegate_to: localhost
- name: Add VLAN 2345 as tagged to interface 1.1 - name: Add VLAN 2345 as tagged to interface 1.1
bigip_vlan: bigip_vlan:
tagged_interface: 1.1 tagged_interface: 1.1
name: "net1" name: net1
password: "secret" tag: 2345
server: "lb.mydomain.com" provider:
tag: "2345" password: secret
user: "admin" server: lb.mydomain.com
validate_certs: "no" user: admin
delegate_to: localhost delegate_to: localhost
- name: Add VLAN 1234 as tagged to interfaces 1.1 and 1.2 - name: Add VLAN 1234 as tagged to interfaces 1.1 and 1.2
bigip_vlan: bigip_vlan:
tagged_interfaces: tagged_interfaces:
- 1.1 - 1.1
- 1.2 - 1.2
name: "net1" name: net1
password: "secret" tag: 1234
server: "lb.mydomain.com" provider:
tag: "1234" user: admin
user: "admin" password: secret
validate_certs: "no" server: lb.mydomain.com
delegate_to: localhost delegate_to: localhost
''' '''
@ -195,86 +250,217 @@ dag_tunnel:
returned: changed returned: changed
type: string type: string
sample: outer sample: outer
source_check:
description: The new Source Check setting.
returned: changed
type: bool
sample: yes
fail_safe:
description: The new Fail Safe setting.
returned: changed
type: bool
sample: no
fail_safe_timeout:
description: The new Fail Safe Timeout setting.
returned: changed
type: int
sample: 90
fail_safe_action:
description: The new Fail Safe Action setting.
returned: changed
type: string
sample: reboot
sflow_poll_interval:
description: The new sFlow Polling Interval setting.
returned: changed
type: int
sample: 10
sflow_sampling_rate:
description: The new sFlow Sampling Rate setting.
returned: changed
type: int
sample: 20
''' '''
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
from ansible.module_utils.basic import env_fallback from ansible.module_utils.basic import env_fallback
try: try:
from library.module_utils.network.f5.bigip import HAS_F5SDK from library.module_utils.network.f5.bigip import F5RestClient
from library.module_utils.network.f5.bigip import F5Client
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import AnsibleF5Parameters from library.module_utils.network.f5.common import AnsibleF5Parameters
from library.module_utils.network.f5.common import cleanup_tokens from library.module_utils.network.f5.common import cleanup_tokens
from library.module_utils.network.f5.common import fq_name
from library.module_utils.network.f5.common import f5_argument_spec from library.module_utils.network.f5.common import f5_argument_spec
try: from library.module_utils.network.f5.common import transform_name
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError from library.module_utils.network.f5.common import exit_json
except ImportError: from library.module_utils.network.f5.common import fail_json
HAS_F5SDK = False from library.module_utils.network.f5.common import flatten_boolean
from library.module_utils.network.f5.common import compare_complex_list
except ImportError: except ImportError:
from ansible.module_utils.network.f5.bigip import HAS_F5SDK from ansible.module_utils.network.f5.bigip import F5RestClient
from ansible.module_utils.network.f5.bigip import F5Client
from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import F5ModuleError
from ansible.module_utils.network.f5.common import AnsibleF5Parameters from ansible.module_utils.network.f5.common import AnsibleF5Parameters
from ansible.module_utils.network.f5.common import cleanup_tokens from ansible.module_utils.network.f5.common import cleanup_tokens
from ansible.module_utils.network.f5.common import fq_name
from ansible.module_utils.network.f5.common import f5_argument_spec from ansible.module_utils.network.f5.common import f5_argument_spec
try: from ansible.module_utils.network.f5.common import transform_name
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError from ansible.module_utils.network.f5.common import exit_json
except ImportError: from ansible.module_utils.network.f5.common import fail_json
HAS_F5SDK = False from ansible.module_utils.network.f5.common import flatten_boolean
from ansible.module_utils.network.f5.common import compare_complex_list
class Parameters(AnsibleF5Parameters): class Parameters(AnsibleF5Parameters):
api_map = { api_map = {
'cmpHash': 'cmp_hash', 'cmpHash': 'cmp_hash',
'dagTunnel': 'dag_tunnel', 'dagTunnel': 'dag_tunnel',
'dagRoundRobin': 'dag_round_robin' 'dagRoundRobin': 'dag_round_robin',
'interfacesReference': 'interfaces',
'sourceChecking': 'source_check',
'failsafe': 'fail_safe',
'failsafeAction': 'fail_safe_action',
'failsafeTimeout': 'fail_safe_timeout',
} }
api_attributes = [
'description',
'interfaces',
'tag',
'mtu',
'cmpHash',
'dagTunnel',
'dagRoundRobin',
'sourceChecking',
'failsafe',
'failsafeAction',
'failsafeTimeout',
'sflow',
]
updatables = [ updatables = [
'tagged_interfaces', 'untagged_interfaces', 'tag', 'interfaces',
'description', 'mtu', 'cmp_hash', 'dag_tunnel', 'tagged_interfaces',
'dag_round_robin' 'untagged_interfaces',
'tag',
'description',
'mtu',
'cmp_hash',
'dag_tunnel',
'dag_round_robin',
'source_check',
'fail_safe',
'fail_safe_action',
'fail_safe_timeout',
'sflow_poll_interval',
'sflow_sampling_rate',
'sflow',
] ]
returnables = [ returnables = [
'description', 'partition', 'tag', 'interfaces', 'description',
'tagged_interfaces', 'untagged_interfaces', 'mtu', 'partition',
'cmp_hash', 'dag_tunnel', 'dag_round_robin' 'tag',
'interfaces',
'tagged_interfaces',
'untagged_interfaces',
'mtu',
'cmp_hash',
'dag_tunnel',
'dag_round_robin',
'source_check',
'fail_safe',
'fail_safe_action',
'fail_safe_timeout',
'sflow_poll_interval',
'sflow_sampling_rate',
'sflow',
] ]
api_attributes = [ @property
'description', 'interfaces', 'tag', 'mtu', 'cmpHash', def source_check(self):
'dagTunnel', 'dagRoundRobin' return flatten_boolean(self._values['source_check'])
]
def to_return(self): @property
result = {} def fail_safe(self):
for returnable in self.returnables: return flatten_boolean(self._values['fail_safe'])
result[returnable] = getattr(self, returnable)
result = self._filter_params(result)
return result
class ApiParameters(Parameters): class ApiParameters(Parameters):
@property @property
def tagged_interfaces(self): def interfaces(self):
if self._values['interfaces'] is None: if self._values['interfaces'] is None:
return None return None
result = [str(x.name) for x in self._values['interfaces'] if x.tagged is True] if 'items' not in self._values['interfaces']:
return None
result = []
for item in self._values['interfaces']['items']:
name = item['name']
if 'tagged' in item:
tagged = item['tagged']
result.append(dict(name=name, tagged=tagged))
if 'untagged' in item:
untagged = item['untagged']
result.append(dict(name=name, untagged=untagged))
return result
@property
def tagged_interfaces(self):
if self.interfaces is None:
return None
result = [str(x['name']) for x in self.interfaces if 'tagged' in x and x['tagged'] is True]
result = sorted(result) result = sorted(result)
return result return result
@property @property
def untagged_interfaces(self): def untagged_interfaces(self):
if self._values['interfaces'] is None: if self.interfaces is None:
return None return None
result = [str(x.name) for x in self._values['interfaces'] if x.untagged is True] result = [str(x['name']) for x in self.interfaces if 'untagged' in x and x['untagged'] is True]
result = sorted(result) result = sorted(result)
return result return result
@property
def sflow_poll_interval(self):
try:
return self._values['sflow']['pollInterval']
except (KeyError, TypeError):
return None
@property
def sflow_sampling_rate(self):
try:
return self._values['sflow']['samplingRate']
except (KeyError, TypeError):
return None
class ModuleParameters(Parameters): class ModuleParameters(Parameters):
@property
def interfaces(self):
if self._values['interfaces'] is None:
return None
elif len(self._values['interfaces']) == 1 and self._values['interfaces'][0] in ['', 'none']:
return ''
result = []
for item in self._values['interfaces']:
if 'interface' not in item:
raise F5ModuleError(
"An 'interface' key must be provided when specifying a list of interfaces."
)
if 'tagging' not in item:
raise F5ModuleError(
"A 'tagging' key must be provided when specifying a list of interfaces."
)
name = str(item['interface'])
tagging = item['tagging']
if tagging == 'tagged':
result.append(dict(name=name, tagged=True))
else:
result.append(dict(name=name, untagged=True))
return result
@property @property
def untagged_interfaces(self): def untagged_interfaces(self):
if self._values['untagged_interfaces'] is None: if self._values['untagged_interfaces'] is None:
@ -341,26 +527,66 @@ class Changes(Parameters):
class UsableChanges(Changes): class UsableChanges(Changes):
pass @property
def source_check(self):
if self._values['source_check'] is None:
return None
if self._values['source_check'] == 'yes':
return 'enabled'
return 'disabled'
@property
def fail_safe(self):
if self._values['fail_safe'] is None:
return None
if self._values['fail_safe'] == 'yes':
return 'enabled'
return 'disabled'
class ReportableChanges(Changes): class ReportableChanges(Changes):
@property @property
def tagged_interfaces(self): def tagged_interfaces(self):
if self._values['interfaces'] is None: if self.interfaces is None:
return None return None
result = [str(x['name']) for x in self._values['interfaces'] if 'tagged' in x and x['tagged'] is True] result = [str(x['name']) for x in self.interfaces if 'tagged' in x and x['tagged'] is True]
result = sorted(result) result = sorted(result)
return result return result
@property @property
def untagged_interfaces(self): def untagged_interfaces(self):
if self._values['interfaces'] is None: if self.interfaces is None:
return None return None
result = [str(x['name']) for x in self._values['interfaces'] if 'untagged' in x and x['untagged'] is True] result = [str(x['name']) for x in self.interfaces if 'untagged' in x and x['untagged'] is True]
result = sorted(result) result = sorted(result)
return result return result
@property
def source_check(self):
return flatten_boolean(self._values['source_check'])
@property
def fail_safe(self):
return flatten_boolean(self._values['fail_safe'])
@property
def sflow(self):
return None
@property
def sflow_poll_interval(self):
try:
return self._values['sflow']['pollInterval']
except (KeyError, TypeError):
return None
@property
def sflow_sampling_rate(self):
try:
return self._values['sflow']['samplingRate']
except (KeyError, TypeError):
return None
class Difference(object): class Difference(object):
def __init__(self, want, have=None): def __init__(self, want, have=None):
@ -383,6 +609,20 @@ class Difference(object):
except AttributeError: except AttributeError:
return attr1 return attr1
@property
def interfaces(self):
if self.want.interfaces is None:
return None
if self.have.interfaces is None and self.want.interfaces in ['', 'none']:
return None
if self.have.interfaces is not None and self.want.interfaces in ['', 'none']:
return []
if self.have.interfaces is None:
return dict(
interfaces=self.want.interfaces
)
return compare_complex_list(self.want.interfaces, self.have.interfaces)
@property @property
def untagged_interfaces(self): def untagged_interfaces(self):
result = self.cmp_interfaces(self.want.untagged_interfaces, self.have.untagged_interfaces, False) result = self.cmp_interfaces(self.want.untagged_interfaces, self.have.untagged_interfaces, False)
@ -417,6 +657,38 @@ class Difference(object):
return None return None
return result return result
@property
def sflow(self):
result = {}
s = self.sflow_poll_interval
if s:
result.update(s)
s = self.sflow_sampling_rate
if s:
result.update(s)
if result:
return dict(
sflow=result
)
@property
def sflow_poll_interval(self):
if self.want.sflow_poll_interval is None:
return None
if self.want.sflow_poll_interval != self.have.sflow_poll_interval:
return dict(
pollInterval=self.want.sflow_poll_interval
)
@property
def sflow_sampling_rate(self):
if self.want.sflow_sampling_rate is None:
return None
if self.want.sflow_sampling_rate != self.have.sflow_sampling_rate:
return dict(
samplingRate=self.want.sflow_sampling_rate
)
class ModuleManager(object): class ModuleManager(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
@ -449,14 +721,10 @@ class ModuleManager(object):
result = dict() result = dict()
state = self.want.state state = self.want.state
try: if state == "present":
if state == "present": changed = self.present()
changed = self.present() elif state == "absent":
elif state == "absent": changed = self.absent()
changed = self.absent()
except iControlUnexpectedHTTPError as e:
raise F5ModuleError(str(e))
reportable = ReportableChanges(params=self.changes.to_return()) reportable = ReportableChanges(params=self.changes.to_return())
changes = reportable.to_return() changes = reportable.to_return()
result.update(**changes) result.update(**changes)
@ -518,42 +786,88 @@ class ModuleManager(object):
def create_on_device(self): def create_on_device(self):
params = self.changes.api_params() params = self.changes.api_params()
self.client.api.tm.net.vlans.vlan.create( params['name'] = self.want.name
name=self.want.name, params['partition'] = self.want.partition
partition=self.want.partition, uri = "https://{0}:{1}/mgmt/tm/net/vlan".format(
**params self.client.provider['server'],
self.client.provider['server_port']
) )
resp = self.client.api.post(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] in [400, 403]:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
return response['selfLink']
def update_on_device(self): def update_on_device(self):
params = self.changes.api_params() params = self.changes.api_params()
resource = self.client.api.tm.net.vlans.vlan.load( uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
name=self.want.name, self.client.provider['server'],
partition=self.want.partition self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
) )
resource.modify(**params) resp = self.client.api.patch(uri, json=params)
try:
response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
def exists(self): def exists(self):
return self.client.api.tm.net.vlans.vlan.exists( uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
name=self.want.name, self.client.provider['server'],
partition=self.want.partition self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
) )
resp = self.client.api.get(uri)
try:
response = resp.json()
except ValueError:
return False
if resp.status == 404 or 'code' in response and response['code'] == 404:
return False
return True
def remove_from_device(self): def remove_from_device(self):
resource = self.client.api.tm.net.vlans.vlan.load( uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
name=self.want.name, self.client.provider['server'],
partition=self.want.partition self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
) )
if resource: resp = self.client.api.delete(uri)
resource.delete() if resp.status == 200:
return True
def read_current_from_device(self): def read_current_from_device(self):
resource = self.client.api.tm.net.vlans.vlan.load( uri = "https://{0}:{1}/mgmt/tm/net/vlan/{2}".format(
name=self.want.name, partition=self.want.partition self.client.provider['server'],
self.client.provider['server_port'],
transform_name(self.want.partition, self.want.name)
) )
interfaces = resource.interfaces_s.get_collection() query = '?expandSubcollections=true'
result = resource.attrs resp = self.client.api.get(uri + query)
result['interfaces'] = interfaces try:
return ApiParameters(params=result) response = resp.json()
except ValueError as ex:
raise F5ModuleError(str(ex))
if 'code' in response and response['code'] == 400:
if 'message' in response:
raise F5ModuleError(response['message'])
else:
raise F5ModuleError(resp.content)
return ApiParameters(params=response)
class ArgumentSpec(object): class ArgumentSpec(object):
@ -571,6 +885,15 @@ class ArgumentSpec(object):
type='list', type='list',
aliases=['untagged_interface'] aliases=['untagged_interface']
), ),
interfaces=dict(
type='list',
options=dict(
interface=dict(),
tagging=dict(
choice=['tagged', 'untagged']
)
)
),
description=dict(), description=dict(),
tag=dict( tag=dict(
type='int' type='int'
@ -587,6 +910,14 @@ class ArgumentSpec(object):
choices=['inner', 'outer'] choices=['inner', 'outer']
), ),
dag_round_robin=dict(type='bool'), dag_round_robin=dict(type='bool'),
source_check=dict(type='bool'),
fail_safe=dict(type='bool'),
fail_safe_timeout=dict(type='int'),
fail_safe_action=dict(
choices=['reboot', 'restart-all']
),
sflow_poll_interval=dict(type='int'),
sflow_sampling_rate=dict(type='int'),
state=dict( state=dict(
default='present', default='present',
choices=['present', 'absent'] choices=['present', 'absent']
@ -594,13 +925,13 @@ class ArgumentSpec(object):
partition=dict( partition=dict(
default='Common', default='Common',
fallback=(env_fallback, ['F5_PARTITION']) fallback=(env_fallback, ['F5_PARTITION'])
) ),
) )
self.argument_spec = {} self.argument_spec = {}
self.argument_spec.update(f5_argument_spec) self.argument_spec.update(f5_argument_spec)
self.argument_spec.update(argument_spec) self.argument_spec.update(argument_spec)
self.mutually_exclusive = [ self.mutually_exclusive = [
['tagged_interfaces', 'untagged_interfaces'] ['tagged_interfaces', 'untagged_interfaces', 'interfaces'],
] ]
@ -612,18 +943,16 @@ def main():
supports_check_mode=spec.supports_check_mode, supports_check_mode=spec.supports_check_mode,
mutually_exclusive=spec.mutually_exclusive mutually_exclusive=spec.mutually_exclusive
) )
if not HAS_F5SDK: client = F5RestClient(**module.params)
module.fail_json(msg="The python f5-sdk module is required")
try: try:
client = F5Client(**module.params)
mm = ModuleManager(module=module, client=client) mm = ModuleManager(module=module, client=client)
results = mm.exec_module() results = mm.exec_module()
cleanup_tokens(client) cleanup_tokens(client)
module.exit_json(**results) exit_json(module, results, client)
except F5ModuleError as e: except F5ModuleError as ex:
cleanup_tokens(client) cleanup_tokens(client)
module.fail_json(msg=str(e)) fail_json(module, ex, client)
if __name__ == '__main__': if __name__ == '__main__':

@ -14,9 +14,6 @@ from nose.plugins.skip import SkipTest
if sys.version_info < (2, 7): if sys.version_info < (2, 7):
raise SkipTest("F5 Ansible modules require Python >= 2.7") raise SkipTest("F5 Ansible modules require Python >= 2.7")
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
@ -24,17 +21,25 @@ try:
from library.modules.bigip_vlan import ModuleParameters from library.modules.bigip_vlan import ModuleParameters
from library.modules.bigip_vlan import ModuleManager from library.modules.bigip_vlan import ModuleManager
from library.modules.bigip_vlan import ArgumentSpec from library.modules.bigip_vlan import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError
from library.module_utils.network.f5.common import iControlUnexpectedHTTPError # In Ansible 2.8, Ansible changed import paths.
from test.unit.modules.utils import set_module_args from test.units.compat import unittest
from test.units.compat.mock import Mock
from test.units.compat.mock import patch
from test.units.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_vlan import ApiParameters from ansible.modules.network.f5.bigip_vlan import ApiParameters
from ansible.modules.network.f5.bigip_vlan import ModuleParameters from ansible.modules.network.f5.bigip_vlan import ModuleParameters
from ansible.modules.network.f5.bigip_vlan import ModuleManager from ansible.modules.network.f5.bigip_vlan import ModuleManager
from ansible.modules.network.f5.bigip_vlan import ArgumentSpec from ansible.modules.network.f5.bigip_vlan import ArgumentSpec
from ansible.module_utils.network.f5.common import F5ModuleError
from ansible.module_utils.network.f5.common import iControlUnexpectedHTTPError # Ansible 2.8 imports
from units.compat import unittest
from units.compat.mock import Mock
from units.compat.mock import patch
from units.modules.utils import set_module_args from units.modules.utils import set_module_args
except ImportError: except ImportError:
raise SkipTest("F5 Ansible modules require the f5-sdk Python library") raise SkipTest("F5 Ansible modules require the f5-sdk Python library")

Loading…
Cancel
Save