More bug fixes before 2.5 (#35260)

This patch includes more fixes for bugs that were identified in
the upcoming 2.5 release.
This commit is contained in:
Tim Rupp 2018-01-24 08:15:37 -08:00 committed by GitHub
commit bd09e67438
No known key found for this signature in database
GPG key ID: 4AEE18F83AFDEB23
6 changed files with 101 additions and 115 deletions

View file

@ -84,6 +84,7 @@ f5_top_spec = {
), ),
'transport': dict( 'transport': dict(
removed_in_version=2.9, removed_in_version=2.9,
default='rest',
choices=['cli', 'rest'] choices=['cli', 'rest']
) )
} }
@ -94,6 +95,14 @@ def get_provider_argspec():
return f5_provider_spec return f5_provider_spec
def load_params(params):
provider = params.get('provider') or dict()
for key, value in iteritems(provider):
if key in f5_argument_spec:
if params.get(key) is None and value is not None:
params[key] = value
# Fully Qualified name (with the partition) # Fully Qualified name (with the partition)
def fqdn_name(partition, value): def fqdn_name(partition, value):
if value is not None and not value.startswith('/'): if value is not None and not value.startswith('/'):
@ -143,7 +152,8 @@ def cleanup_tokens(client):
def is_cli(module): def is_cli(module):
transport = module.params['transport'] transport = module.params['transport']
provider_transport = (module.params['provider'] or {}).get('transport') provider_transport = (module.params['provider'] or {}).get('transport')
return 'cli' in (transport, provider_transport) result = 'cli' in (transport, provider_transport)
return result
class Noop(object): class Noop(object):
@ -163,6 +173,7 @@ class Noop(object):
class F5BaseClient(object): class F5BaseClient(object):
def __init__(self, *args, **kwargs): def __init__(self, *args, **kwargs):
self.params = kwargs self.params = kwargs
load_params(self.params)
@property @property
def api(self): def api(self):

View file

@ -163,7 +163,6 @@ import re
import time import time
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.six import string_types from ansible.module_utils.six import string_types
from ansible.module_utils.network.common.parsing import FailedConditionsError from ansible.module_utils.network.common.parsing import FailedConditionsError
from ansible.module_utils.network.common.parsing import Conditional from ansible.module_utils.network.common.parsing import Conditional
@ -259,7 +258,8 @@ class ModuleManager(object):
self.module = kwargs.get('module', None) self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None) self.client = kwargs.get('client', None)
self.want = Parameters(params=self.module.params) self.want = Parameters(params=self.module.params)
self.changes = Parameters() self.want.update({'module': self.module})
self.changes = Parameters(module=self.module)
def _to_lines(self, stdout): def _to_lines(self, stdout):
lines = list() lines = list()
@ -346,7 +346,7 @@ class ModuleManager(object):
'stdout_lines': self._to_lines(responses), 'stdout_lines': self._to_lines(responses),
'warnings': warnings 'warnings': warnings
} }
self.changes = Parameters(params=changes) self.changes = Parameters(params=changes, module=self.module)
if any(x for x in self.want.user_commands if x.startswith(changed)): if any(x for x in self.want.user_commands if x.startswith(changed)):
return True return True
return False return False

View file

@ -287,10 +287,8 @@ class ModuleManager(object):
def remove_from_device(self): def remove_from_device(self):
result = self.client.api.tm.cm.remove_from_trust.exec_cmd( result = self.client.api.tm.cm.remove_from_trust.exec_cmd(
'run', deviceName=self.want.peer_hostname 'run', deviceName=self.want.peer_hostname, name=self.want.peer_hostname
) )
if result:
result.delete()
class ArgumentSpec(object): class ArgumentSpec(object):

View file

@ -24,6 +24,7 @@ options:
description: description:
- The IP addresses for the new self IP. This value is ignored upon update - The IP addresses for the new self IP. This value is ignored upon update
as addresses themselves cannot be changed after they are created. as addresses themselves cannot be changed after they are created.
- This value is required when creating new self IPs.
allow_service: allow_service:
description: description:
- Configure port lockdown for the Self IP. By default, the Self IP has a - Configure port lockdown for the Self IP. By default, the Self IP has a
@ -62,6 +63,7 @@ options:
description: description:
- The route domain id of the system. When creating a new Self IP, if - The route domain id of the system. When creating a new Self IP, if
this value is not specified, a default value of C(0) will be used. this value is not specified, a default value of C(0) will be used.
- This value cannot be changed after it is set.
version_added: 2.3 version_added: 2.3
partition: partition:
description: description:
@ -249,7 +251,7 @@ except ImportError:
HAS_F5SDK = False HAS_F5SDK = False
try: try:
from netaddr import IPNetwork, AddrFormatError, IPAddress import netaddr
HAS_NETADDR = True HAS_NETADDR = True
except ImportError: except ImportError:
HAS_NETADDR = False HAS_NETADDR = False
@ -262,11 +264,11 @@ class Parameters(AnsibleF5Parameters):
} }
updatables = [ updatables = [
'traffic_group', 'allow_service', 'vlan', 'route_domain', 'netmask' 'traffic_group', 'allow_service', 'vlan', 'netmask', 'address'
] ]
returnables = [ returnables = [
'traffic_group', 'allow_service', 'vlan', 'route_domain', 'netmask' 'traffic_group', 'allow_service', 'vlan', 'route_domain', 'netmask', 'address'
] ]
api_attributes = [ api_attributes = [
@ -280,6 +282,19 @@ class Parameters(AnsibleF5Parameters):
result = self._filter_params(result) result = self._filter_params(result)
return result return result
def _fqdn_name(self, value):
if value is not None and not value.startswith('/'):
return '/{0}/{1}'.format(self.partition, value)
return value
@property
def vlan(self):
if self._values['vlan'] is None:
return None
return self._fqdn_name(self._values['vlan'])
class ModuleParameters(Parameters):
@property @property
def address(self): def address(self):
address = "{0}%{1}/{2}".format( address = "{0}%{1}/{2}".format(
@ -287,18 +302,14 @@ class Parameters(AnsibleF5Parameters):
) )
return address return address
@address.setter
def address(self, value):
self._values['ip'] = value
@property @property
def ip(self): def ip(self):
if self._values['ip'] is None: if self._values['address'] is None:
return None return None
try: try:
ip = str(IPAddress(self._values['ip'])) ip = str(netaddr.IPAddress(self._values['address']))
return ip return ip
except AddrFormatError: except netaddr.AddrFormatError:
raise F5ModuleError( raise F5ModuleError(
'The provided address is not a valid IP address' 'The provided address is not a valid IP address'
) )
@ -333,13 +344,13 @@ class Parameters(AnsibleF5Parameters):
try: try:
# IPv4 netmask # IPv4 netmask
address = '0.0.0.0/' + self._values['netmask'] address = '0.0.0.0/' + self._values['netmask']
ip = IPNetwork(address) ip = netaddr.IPNetwork(address)
except AddrFormatError as ex: except netaddr.AddrFormatError as ex:
try: try:
# IPv6 netmask # IPv6 netmask
address = '::/' + self._values['netmask'] address = '::/' + self._values['netmask']
ip = IPNetwork(address) ip = netaddr.IPNetwork(address)
except AddrFormatError as ex: except netaddr.AddrFormatError as ex:
raise F5ModuleError( raise F5ModuleError(
'The provided netmask {0} is neither in IP or CIDR format'.format(self._values['netmask']) 'The provided netmask {0} is neither in IP or CIDR format'.format(self._values['netmask'])
) )
@ -405,70 +416,57 @@ class Parameters(AnsibleF5Parameters):
result = sorted(list(set(result))) result = sorted(list(set(result)))
return result return result
def _fqdn_name(self, value):
if value is not None and not value.startswith('/'):
return '/{0}/{1}'.format(self.partition, value)
return value
@property
def vlan(self):
if self._values['vlan'] is None:
return None
return self._fqdn_name(self._values['vlan'])
class ApiParameters(Parameters): class ApiParameters(Parameters):
api_map = {}
@property
def address(self):
if self.ip and self.route_domain and self.netmask:
return '{0}%{1}/{2}'.format(self.ip, self.route_domain, self.netmask)
elif self.ip and self.netmask:
return '{0}/{1}'.format(self.ip, self.netmask)
@address.setter
def address(self, value):
pattern = r'^(?P<ip>[0-9A-Fa-f:.]+)%?(?P<rd>\d+)?\/(?P<nm>\d+)$'
matches = re.match(pattern, value)
if not matches:
raise F5ModuleError(
"The specified address is malformed. Please see documentation."
)
try:
ip = matches.group('ip')
self._values['ip'] = str(IPAddress(ip))
except AddrFormatError:
raise F5ModuleError(
'The provided address is not a valid IP address'
)
self._values['route_domain'] = matches.group('rd')
self._values['netmask'] = matches.group('nm')
@property @property
def allow_service(self): def allow_service(self):
if self._values['allow_service'] is None: if self._values['allow_service'] is None:
return None return None
if self._values['allow_service'] == 'all':
self._values['allow_service'] = ['all']
return sorted(self._values['allow_service']) return sorted(self._values['allow_service'])
@property @property
def trafficGroup(self): def destination_ip(self):
return self.traffic_group if self._values['address'] is None:
return None
@trafficGroup.setter try:
def trafficGroup(self, value): pattern = r'(?P<rd>%[0-9]+)'
self._values['traffic_group'] = value addr = re.sub(pattern, '', self._values['address'])
ip = netaddr.IPNetwork(addr)
return '{0}/{1}'.format(ip.ip, ip.prefixlen)
except netaddr.AddrFormatError:
raise F5ModuleError(
"The provided destination is not an IP address"
)
@property @property
def allowService(self): def netmask(self):
return self._values['allow_service'] ip = netaddr.IPNetwork(self.destination_ip)
return int(ip.prefixlen)
@allowService.setter @property
def allowService(self, value): def ip(self):
if value == 'all': result = netaddr.IPNetwork(self.destination_ip)
self._values['allow_service'] = ['all'] return str(result.ip)
else:
self._values['allow_service'] = sorted([str(x) for x in value])
class Changes(Parameters):
pass
class UsableChanges(Changes):
@property
def allow_service(self):
if self._values['allow_service'] is None:
return None
if self._values['allow_service'] == ['all']:
return 'all'
return sorted(self._values['allow_service'])
class ReportableChanges(Changes):
pass
class ModuleManager(object): class ModuleManager(object):
@ -476,8 +474,8 @@ class ModuleManager(object):
self.module = kwargs.get('module', None) self.module = kwargs.get('module', None)
self.client = kwargs.get('client', None) self.client = kwargs.get('client', None)
self.have = None self.have = None
self.want = Parameters(params=self.module.params) self.want = ModuleParameters(params=self.module.params)
self.changes = ApiParameters() self.changes = UsableChanges()
def _set_changed_options(self): def _set_changed_options(self):
changed = {} changed = {}
@ -485,7 +483,7 @@ class ModuleManager(object):
if getattr(self.want, key) is not None: if getattr(self.want, key) is not None:
changed[key] = getattr(self.want, key) changed[key] = getattr(self.want, key)
if changed: if changed:
self.changes = Parameters(params=changed) self.changes = UsableChanges(params=changed)
def _update_changed_options(self): def _update_changed_options(self):
diff = Difference(self.want, self.have) diff = Difference(self.want, self.have)
@ -496,12 +494,12 @@ class ModuleManager(object):
if change is None: if change is None:
continue continue
else: else:
if k in ['netmask', 'route_domain']: if k in ['netmask']:
changed['address'] = change changed['address'] = change
else: else:
changed[k] = change changed[k] = change
if changed: if changed:
self.changes = ApiParameters(params=changed) self.changes = UsableChanges(params=changed)
return True return True
return False return False
@ -583,11 +581,12 @@ class ModuleManager(object):
self.want.update({'route_domain': 0}) self.want.update({'route_domain': 0})
if self.want.allow_service: if self.want.allow_service:
if 'all' in self.want.allow_service: if 'all' in self.want.allow_service:
self.want.update(dict(allow_service='all')) self.want.update(dict(allow_service=['all']))
elif 'none' in self.want.allow_service: elif 'none' in self.want.allow_service:
self.want.update(dict(allow_service=[])) self.want.update(dict(allow_service=[]))
elif 'default' in self.want.allow_service: elif 'default' in self.want.allow_service:
self.want.update(dict(allow_service=['default'])) self.want.update(dict(allow_service=['default']))
self._set_changed_options()
if self.want.check_mode: if self.want.check_mode:
return True return True
self.create_on_device() self.create_on_device()
@ -597,7 +596,7 @@ class ModuleManager(object):
raise F5ModuleError("Failed to create the Self IP") raise F5ModuleError("Failed to create the Self IP")
def create_on_device(self): def create_on_device(self):
params = self.want.api_params() params = self.changes.api_params()
self.client.api.tm.net.selfips.selfip.create( self.client.api.tm.net.selfips.selfip.create(
name=self.want.name, name=self.want.name,
partition=self.want.partition, partition=self.want.partition,
@ -648,6 +647,10 @@ class Difference(object):
except AttributeError: except AttributeError:
return attr1 return attr1
@property
def address(self):
pass
@property @property
def allow_service(self): def allow_service(self):
"""Returns services formatted for consumption by f5-sdk update """Returns services formatted for consumption by f5-sdk update
@ -670,7 +673,7 @@ class Difference(object):
if result[0] == 'none' and self.have.allow_service is None: if result[0] == 'none' and self.have.allow_service is None:
return None return None
elif result[0] == 'all' and self.have.allow_service[0] != 'all': elif result[0] == 'all' and self.have.allow_service[0] != 'all':
return 'all' return ['all']
elif result[0] == 'none': elif result[0] == 'none':
return [] return []
elif self.have.allow_service is None: elif self.have.allow_service is None:
@ -683,7 +686,7 @@ class Difference(object):
if self.want.netmask is None: if self.want.netmask is None:
return None return None
try: try:
address = IPNetwork(self.have.ip) address = netaddr.IPNetwork(self.have.ip)
if self.want.route_domain is not None: if self.want.route_domain is not None:
nipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.want.netmask) nipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.want.netmask)
cipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.have.netmask) cipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.have.netmask)
@ -695,32 +698,11 @@ class Difference(object):
cipnet = "{0}/{1}".format(address.ip, self.have.netmask) cipnet = "{0}/{1}".format(address.ip, self.have.netmask)
if nipnet != cipnet: if nipnet != cipnet:
return nipnet return nipnet
except AddrFormatError: except netaddr.AddrFormatError:
raise F5ModuleError( raise F5ModuleError(
'The provided address/netmask value "{0}" was invalid'.format(self.have.ip) 'The provided address/netmask value "{0}" was invalid'.format(self.have.ip)
) )
@property
def route_domain(self):
if self.want.route_domain is None:
return None
try:
address = IPNetwork(self.have.ip)
if self.want.netmask is not None:
nipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.want.netmask)
cipnet = "{0}%{1}/{2}".format(address.ip, self.have.route_domain, self.want.netmask)
elif self.have.netmask is not None:
nipnet = "{0}%{1}/{2}".format(address.ip, self.want.route_domain, self.have.netmask)
cipnet = "{0}%{1}/{2}".format(address.ip, self.have.route_domain, self.have.netmask)
if nipnet != cipnet:
return nipnet
except AddrFormatError:
raise F5ModuleError(
'The provided address/netmask value was invalid'
)
@property @property
def traffic_group(self): def traffic_group(self):
if self.want.traffic_group != self.have.traffic_group: if self.want.traffic_group != self.have.traffic_group:
@ -737,7 +719,7 @@ class ArgumentSpec(object):
netmask=dict(), netmask=dict(),
traffic_group=dict(), traffic_group=dict(),
vlan=dict(), vlan=dict(),
route_domain=dict(), route_domain=dict(type='int'),
state=dict( state=dict(
default='present', default='present',
choices=['present', 'absent'] choices=['present', 'absent']

View file

@ -43,10 +43,6 @@ except ImportError:
class ActionModule(_ActionModule): class ActionModule(_ActionModule):
def run(self, tmp=None, task_vars=None): def run(self, tmp=None, task_vars=None):
transport = self._task.args.get('transport', 'rest')
display.vvvv('connection transport is %s' % transport, self._play_context.remote_addr)
if self._play_context.connection == 'network_cli': if self._play_context.connection == 'network_cli':
provider = self._task.args.get('provider', {}) provider = self._task.args.get('provider', {})
if any(provider.values()): if any(provider.values()):
@ -67,7 +63,7 @@ class ActionModule(_ActionModule):
pc.remote_user = provider.get('user', self._play_context.connection_user) pc.remote_user = provider.get('user', self._play_context.connection_user)
pc.password = provider.get('password', self._play_context.password) pc.password = provider.get('password', self._play_context.password)
pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file pc.private_key_file = provider['ssh_keyfile'] or self._play_context.private_key_file
pc.timeout = int(provider.get('timeout', C.PERSISTENT_COMMAND_TIMEOUT)) pc.timeout = int(provider['timeout'] or C.PERSISTENT_COMMAND_TIMEOUT)
display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr) display.vvv('using connection plugin %s' % pc.connection, pc.remote_addr)
connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin) connection = self._shared_loader_obj.connection_loader.get('persistent', pc, sys.stdin)

View file

@ -21,8 +21,8 @@ from ansible.compat.tests.mock import patch
from ansible.module_utils.basic import AnsibleModule from ansible.module_utils.basic import AnsibleModule
try: try:
from library.bigip_selfip import Parameters
from library.bigip_selfip import ApiParameters from library.bigip_selfip import ApiParameters
from library.bigip_selfip import ModuleParameters
from library.bigip_selfip import ModuleManager from library.bigip_selfip import ModuleManager
from library.bigip_selfip import ArgumentSpec from library.bigip_selfip import ArgumentSpec
from library.module_utils.network.f5.common import F5ModuleError from library.module_utils.network.f5.common import F5ModuleError
@ -30,8 +30,8 @@ try:
from test.unit.modules.utils import set_module_args from test.unit.modules.utils import set_module_args
except ImportError: except ImportError:
try: try:
from ansible.modules.network.f5.bigip_selfip import Parameters
from ansible.modules.network.f5.bigip_selfip import ApiParameters from ansible.modules.network.f5.bigip_selfip import ApiParameters
from ansible.modules.network.f5.bigip_selfip import ModuleParameters
from ansible.modules.network.f5.bigip_selfip import ModuleManager from ansible.modules.network.f5.bigip_selfip import ModuleManager
from ansible.modules.network.f5.bigip_selfip import ArgumentSpec from ansible.modules.network.f5.bigip_selfip import ArgumentSpec
from ansible.module_utils.network.f5.common import F5ModuleError from ansible.module_utils.network.f5.common import F5ModuleError
@ -79,7 +79,7 @@ class TestParameters(unittest.TestCase):
traffic_group='traffic-group-local-only', traffic_group='traffic-group-local-only',
vlan='net1' vlan='net1'
) )
p = Parameters(params=args) p = ModuleParameters(params=args)
assert p.address == '10.10.10.10%1/24' assert p.address == '10.10.10.10%1/24'
assert p.allow_service == ['gre:0', 'tcp:80', 'udp:53'] assert p.allow_service == ['gre:0', 'tcp:80', 'udp:53']
assert p.name == 'net1' assert p.name == 'net1'
@ -96,7 +96,7 @@ class TestParameters(unittest.TestCase):
'grp' 'grp'
] ]
) )
p = Parameters(params=args) p = ModuleParameters(params=args)
with pytest.raises(F5ModuleError) as ex: with pytest.raises(F5ModuleError) as ex:
assert p.allow_service == ['grp', 'tcp:80', 'udp:53'] assert p.allow_service == ['grp', 'tcp:80', 'udp:53']
assert 'The provided protocol' in str(ex) assert 'The provided protocol' in str(ex)
@ -119,7 +119,6 @@ class TestParameters(unittest.TestCase):
assert p.allow_service == ['gre', 'tcp:80', 'udp:53'] assert p.allow_service == ['gre', 'tcp:80', 'udp:53']
assert p.name == 'net1' assert p.name == 'net1'
assert p.netmask == 24 assert p.netmask == 24
assert p.route_domain == 1
assert p.traffic_group == '/Common/traffic-group-local-only' assert p.traffic_group == '/Common/traffic-group-local-only'
assert p.vlan == '/Common/net1' assert p.vlan == '/Common/net1'