2017-06-14 17:28:12 +00:00
|
|
|
#!/usr/bin/python
|
|
|
|
# -*- coding: utf-8 -*-
|
|
|
|
#
|
2018-11-09 05:46:56 +00:00
|
|
|
# Copyright: (c) 2017, F5 Networks Inc.
|
2017-10-04 19:16:17 +00:00
|
|
|
# GNU General Public License v3.0 (see COPYING or https://www.gnu.org/licenses/gpl-3.0.txt)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
from __future__ import absolute_import, division, print_function
|
|
|
|
__metaclass__ = type
|
|
|
|
|
|
|
|
|
2017-08-16 03:16:38 +00:00
|
|
|
ANSIBLE_METADATA = {'metadata_version': '1.1',
|
2018-06-20 19:16:43 +00:00
|
|
|
'status': ['stableinterface'],
|
2018-10-12 15:57:28 +00:00
|
|
|
'supported_by': 'certified'}
|
2017-06-14 17:28:12 +00:00
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
DOCUMENTATION = r'''
|
2017-06-14 17:28:12 +00:00
|
|
|
---
|
|
|
|
module: bigip_provision
|
2017-10-14 05:19:46 +00:00
|
|
|
short_description: Manage BIG-IP module provisioning
|
2017-06-14 17:28:12 +00:00
|
|
|
description:
|
|
|
|
- Manage BIG-IP module provisioning. This module will only provision at the
|
|
|
|
standard levels of Dedicated, Nominal, and Minimum.
|
2018-04-25 02:49:52 +00:00
|
|
|
version_added: 2.4
|
2017-06-14 17:28:12 +00:00
|
|
|
options:
|
2018-04-25 02:49:52 +00:00
|
|
|
module:
|
2017-06-14 17:28:12 +00:00
|
|
|
description:
|
|
|
|
- The module to provision in BIG-IP.
|
|
|
|
required: true
|
|
|
|
choices:
|
|
|
|
- am
|
|
|
|
- afm
|
|
|
|
- apm
|
|
|
|
- asm
|
|
|
|
- avr
|
2018-04-25 02:49:52 +00:00
|
|
|
- cgnat
|
2017-06-14 17:28:12 +00:00
|
|
|
- fps
|
|
|
|
- gtm
|
|
|
|
- ilx
|
|
|
|
- lc
|
|
|
|
- ltm
|
|
|
|
- pem
|
|
|
|
- sam
|
|
|
|
- swg
|
2017-10-14 05:19:46 +00:00
|
|
|
- vcmp
|
2017-11-03 00:40:41 +00:00
|
|
|
aliases:
|
2018-04-25 02:49:52 +00:00
|
|
|
- name
|
2017-06-14 17:28:12 +00:00
|
|
|
level:
|
|
|
|
description:
|
|
|
|
- Sets the provisioning level for the requested modules. Changing the
|
|
|
|
level for one module may require modifying the level of another module.
|
|
|
|
For example, changing one module to C(dedicated) requires setting all
|
|
|
|
others to C(none). Setting the level of a module to C(none) means that
|
2017-12-02 05:53:54 +00:00
|
|
|
the module is not activated.
|
2018-04-25 02:49:52 +00:00
|
|
|
- This parameter is not relevant to C(cgnat) and will not be applied to the
|
|
|
|
C(cgnat) module.
|
2017-06-14 17:28:12 +00:00
|
|
|
default: nominal
|
|
|
|
choices:
|
|
|
|
- dedicated
|
|
|
|
- nominal
|
|
|
|
- minimum
|
|
|
|
state:
|
|
|
|
description:
|
|
|
|
- The state of the provisioned module on the system. When C(present),
|
|
|
|
guarantees that the specified module is provisioned at the requested
|
|
|
|
level provided that there are sufficient resources on the device (such
|
|
|
|
as physical RAM) to support the provisioned module. When C(absent),
|
|
|
|
de-provision the module.
|
|
|
|
default: present
|
|
|
|
choices:
|
|
|
|
- present
|
|
|
|
- absent
|
|
|
|
extends_documentation_fragment: f5
|
|
|
|
author:
|
|
|
|
- Tim Rupp (@caphrim007)
|
|
|
|
'''
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
EXAMPLES = r'''
|
2017-06-14 17:28:12 +00:00
|
|
|
- name: Provision PEM at "nominal" level
|
|
|
|
bigip_provision:
|
2017-10-14 05:19:46 +00:00
|
|
|
module: pem
|
|
|
|
level: nominal
|
2018-12-04 18:31:25 +00:00
|
|
|
provider:
|
|
|
|
server: lb.mydomain.com
|
|
|
|
password: secret
|
|
|
|
user: admin
|
2017-06-14 17:28:12 +00:00
|
|
|
delegate_to: localhost
|
|
|
|
|
|
|
|
- name: Provision a dedicated SWG. This will unprovision every other module
|
|
|
|
bigip_provision:
|
2017-10-14 05:19:46 +00:00
|
|
|
module: swg
|
|
|
|
level: dedicated
|
2018-12-04 18:31:25 +00:00
|
|
|
provider:
|
|
|
|
server: lb.mydomain.com
|
|
|
|
password: secret
|
|
|
|
user: admin
|
2017-06-14 17:28:12 +00:00
|
|
|
delegate_to: localhost
|
|
|
|
'''
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
RETURN = r'''
|
2017-06-14 17:28:12 +00:00
|
|
|
level:
|
2017-10-14 05:19:46 +00:00
|
|
|
description: The new provisioning level of the module.
|
|
|
|
returned: changed
|
|
|
|
type: string
|
|
|
|
sample: minimum
|
2017-06-14 17:28:12 +00:00
|
|
|
'''
|
|
|
|
|
|
|
|
import time
|
|
|
|
|
2018-01-13 05:49:12 +00:00
|
|
|
from ansible.module_utils.basic import AnsibleModule
|
|
|
|
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
from library.module_utils.network.f5.bigip import F5RestClient
|
2018-01-13 05:49:12 +00:00
|
|
|
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 cleanup_tokens
|
|
|
|
from library.module_utils.network.f5.common import f5_argument_spec
|
2018-11-09 05:46:56 +00:00
|
|
|
from library.module_utils.network.f5.common import exit_json
|
|
|
|
from library.module_utils.network.f5.common import fail_json
|
|
|
|
from library.module_utils.network.f5.icontrol import TransactionContextManager
|
2018-01-13 05:49:12 +00:00
|
|
|
except ImportError:
|
2018-11-09 05:46:56 +00:00
|
|
|
from ansible.module_utils.network.f5.bigip import F5RestClient
|
2018-01-13 05:49:12 +00:00
|
|
|
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 cleanup_tokens
|
|
|
|
from ansible.module_utils.network.f5.common import f5_argument_spec
|
2018-11-09 05:46:56 +00:00
|
|
|
from ansible.module_utils.network.f5.common import exit_json
|
|
|
|
from ansible.module_utils.network.f5.common import fail_json
|
|
|
|
from ansible.module_utils.network.f5.icontrol import TransactionContextManager
|
2017-10-14 05:19:46 +00:00
|
|
|
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
class Parameters(AnsibleF5Parameters):
|
|
|
|
api_attributes = ['level']
|
|
|
|
|
|
|
|
returnables = ['level']
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
updatables = ['level', 'cgnat']
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
@property
|
|
|
|
def level(self):
|
|
|
|
if self._values['level'] is None:
|
|
|
|
return None
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.state == 'absent':
|
|
|
|
return 'none'
|
2017-06-14 17:28:12 +00:00
|
|
|
return str(self._values['level'])
|
|
|
|
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
class ApiParameters(Parameters):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class ModuleParameters(Parameters):
|
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Changes(Parameters):
|
2018-11-09 05:46:56 +00:00
|
|
|
def to_return(self):
|
|
|
|
result = {}
|
|
|
|
try:
|
|
|
|
for returnable in self.returnables:
|
|
|
|
result[returnable] = getattr(self, returnable)
|
|
|
|
result = self._filter_params(result)
|
|
|
|
return result
|
|
|
|
except Exception:
|
|
|
|
return result
|
2018-04-25 02:49:52 +00:00
|
|
|
|
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
class UsableChanges(Changes):
|
2018-04-25 02:49:52 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
class ReportableChanges(Changes):
|
2018-04-25 02:49:52 +00:00
|
|
|
pass
|
|
|
|
|
|
|
|
|
|
|
|
class Difference(object):
|
|
|
|
def __init__(self, want, have=None):
|
|
|
|
self.want = want
|
|
|
|
self.have = have
|
|
|
|
|
|
|
|
def compare(self, param):
|
|
|
|
try:
|
|
|
|
result = getattr(self, param)
|
|
|
|
return result
|
|
|
|
except AttributeError:
|
|
|
|
result = self.__default(param)
|
|
|
|
return result
|
|
|
|
|
|
|
|
def __default(self, param):
|
|
|
|
attr1 = getattr(self.want, param)
|
|
|
|
try:
|
|
|
|
attr2 = getattr(self.have, param)
|
|
|
|
if attr1 != attr2:
|
|
|
|
return attr1
|
|
|
|
except AttributeError:
|
|
|
|
return attr1
|
|
|
|
|
|
|
|
@property
|
|
|
|
def cgnat(self):
|
|
|
|
if self.want.module == 'cgnat':
|
|
|
|
if self.want.state == 'absent' and self.have.enabled is True:
|
|
|
|
return True
|
|
|
|
if self.want.state == 'present' and self.have.disabled is True:
|
|
|
|
return True
|
|
|
|
|
|
|
|
|
2017-06-14 17:28:12 +00:00
|
|
|
class ModuleManager(object):
|
2018-01-13 05:49:12 +00:00
|
|
|
def __init__(self, *args, **kwargs):
|
|
|
|
self.module = kwargs.get('module', None)
|
|
|
|
self.client = kwargs.get('client', None)
|
2017-06-14 17:28:12 +00:00
|
|
|
self.have = None
|
2018-04-25 02:49:52 +00:00
|
|
|
self.want = ModuleParameters(params=self.module.params)
|
|
|
|
self.changes = UsableChanges()
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
def _update_changed_options(self):
|
2018-04-25 02:49:52 +00:00
|
|
|
diff = Difference(self.want, self.have)
|
|
|
|
updatables = Parameters.updatables
|
|
|
|
changed = dict()
|
|
|
|
for k in updatables:
|
|
|
|
change = diff.compare(k)
|
|
|
|
if change is None:
|
|
|
|
continue
|
|
|
|
else:
|
|
|
|
if isinstance(change, dict):
|
|
|
|
changed.update(change)
|
|
|
|
else:
|
|
|
|
changed[k] = change
|
2017-06-14 17:28:12 +00:00
|
|
|
if changed:
|
2018-04-25 02:49:52 +00:00
|
|
|
self.changes = UsableChanges(params=changed)
|
2017-06-14 17:28:12 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def exec_module(self):
|
|
|
|
changed = False
|
|
|
|
result = dict()
|
|
|
|
state = self.want.state
|
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
if state == "present":
|
|
|
|
changed = self.present()
|
|
|
|
elif state == "absent":
|
|
|
|
changed = self.absent()
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
changes = self.changes.to_return()
|
|
|
|
result.update(**changes)
|
|
|
|
result.update(dict(changed=changed))
|
|
|
|
return result
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def present(self):
|
|
|
|
if self.exists():
|
2017-06-14 17:28:12 +00:00
|
|
|
return False
|
2018-04-25 02:49:52 +00:00
|
|
|
return self.update()
|
|
|
|
|
|
|
|
def exists(self):
|
|
|
|
if self.want.module == 'cgnat':
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/feature-module/cgnat/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
if 'disabled' in response and response['disabled'] is True:
|
2018-04-25 02:49:52 +00:00
|
|
|
return False
|
2018-11-09 05:46:56 +00:00
|
|
|
elif 'enabled' in response and response['enabled'] is True:
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
|
|
|
try:
|
|
|
|
for x in range(0, 5):
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/provision/{2}".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
self.want.module
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
|
|
|
|
if str(response['level']) != 'none' and self.want.level == 'none':
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
2018-11-09 05:46:56 +00:00
|
|
|
if str(response['level']) == 'none' and self.want.level == 'none':
|
2018-04-25 02:49:52 +00:00
|
|
|
return False
|
2018-11-09 05:46:56 +00:00
|
|
|
if str(response['level']) == self.want.level:
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
|
|
|
return False
|
|
|
|
except Exception as ex:
|
|
|
|
if 'not registered' in str(ex):
|
|
|
|
return False
|
|
|
|
time.sleep(1)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
def update(self):
|
|
|
|
self.have = self.read_current_from_device()
|
|
|
|
if not self.should_update():
|
|
|
|
return False
|
2018-01-13 05:49:12 +00:00
|
|
|
if self.module.check_mode:
|
2017-06-14 17:28:12 +00:00
|
|
|
return True
|
2017-11-03 00:40:41 +00:00
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
result = self.update_on_device()
|
|
|
|
if self.want.module == 'cgnat':
|
|
|
|
return result
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
self._wait_for_module_provisioning()
|
2017-11-03 00:40:41 +00:00
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
if self.want.module == 'vcmp':
|
|
|
|
self._wait_for_reboot()
|
|
|
|
self._wait_for_module_provisioning()
|
2017-11-03 00:40:41 +00:00
|
|
|
|
|
|
|
if self.want.module == 'asm':
|
|
|
|
self._wait_for_asm_ready()
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.want.module == 'afm':
|
|
|
|
self._wait_for_afm_ready()
|
2017-06-14 17:28:12 +00:00
|
|
|
return True
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def should_reboot(self):
|
|
|
|
for x in range(0, 24):
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/db/{2}".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
'provision.action'
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
|
|
|
|
if response['value'] == 'reboot':
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
2018-11-09 05:46:56 +00:00
|
|
|
elif response['value'] == 'none':
|
2018-04-25 02:49:52 +00:00
|
|
|
time.sleep(5)
|
|
|
|
except Exception:
|
|
|
|
time.sleep(5)
|
|
|
|
return False
|
|
|
|
|
|
|
|
def reboot_device(self):
|
|
|
|
nops = 0
|
|
|
|
last_reboot = self._get_last_reboot()
|
|
|
|
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(
|
|
|
|
command="run",
|
2018-04-25 02:49:52 +00:00
|
|
|
utilCmdArgs='-c "/sbin/reboot"'
|
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
|
|
|
|
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)
|
|
|
|
if 'commandResult' in response:
|
|
|
|
return str(response['commandResult'])
|
2018-04-25 02:49:52 +00:00
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
|
|
|
|
# Sleep a little to let rebooting take effect
|
|
|
|
time.sleep(20)
|
|
|
|
|
2018-08-23 16:50:30 +00:00
|
|
|
while nops < 3:
|
2018-04-25 02:49:52 +00:00
|
|
|
try:
|
|
|
|
self.client.reconnect()
|
|
|
|
next_reboot = self._get_last_reboot()
|
|
|
|
if next_reboot is None:
|
|
|
|
nops = 0
|
|
|
|
if next_reboot == last_reboot:
|
|
|
|
nops = 0
|
|
|
|
else:
|
|
|
|
nops += 1
|
|
|
|
except Exception as ex:
|
|
|
|
# This can be caused by restjavad restarting.
|
|
|
|
pass
|
|
|
|
time.sleep(10)
|
|
|
|
return None
|
|
|
|
|
2017-06-14 17:28:12 +00:00
|
|
|
def should_update(self):
|
|
|
|
result = self._update_changed_options()
|
|
|
|
if result:
|
|
|
|
return True
|
|
|
|
return False
|
|
|
|
|
|
|
|
def update_on_device(self):
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.want.module == 'cgnat':
|
|
|
|
if self.changes.cgnat:
|
|
|
|
return self.provision_cgnat_on_device()
|
|
|
|
return False
|
|
|
|
elif self.want.level == 'dedicated':
|
2017-12-02 05:53:54 +00:00
|
|
|
self.provision_dedicated_on_device()
|
|
|
|
else:
|
|
|
|
self.provision_non_dedicated_on_device()
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def provision_cgnat_on_device(self):
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/feature-module/cgnat/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
2018-04-25 02:49:52 +00:00
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(enabled=True)
|
|
|
|
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)
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
|
|
|
|
2017-12-02 05:53:54 +00:00
|
|
|
def provision_dedicated_on_device(self):
|
|
|
|
params = self.want.api_params()
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/provision/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
|
|
|
|
resources = [x['name'] for x in response['items'] if x['name'] != self.want.module]
|
|
|
|
|
|
|
|
with TransactionContextManager(self.client) as transact:
|
2017-12-02 05:53:54 +00:00
|
|
|
for resource in resources:
|
2018-11-09 05:46:56 +00:00
|
|
|
target = uri + resource
|
|
|
|
resp = transact.api.patch(target, json=dict(level='none'))
|
|
|
|
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)
|
|
|
|
|
|
|
|
target = uri + self.want.module
|
|
|
|
resp = transact.api.patch(target, 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)
|
2017-12-02 05:53:54 +00:00
|
|
|
|
|
|
|
def provision_non_dedicated_on_device(self):
|
2017-06-14 17:28:12 +00:00
|
|
|
params = self.want.api_params()
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/provision/{2}".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
self.want.module
|
|
|
|
)
|
|
|
|
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)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
def read_current_from_device(self):
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.want.module == 'cgnat':
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/feature-module/cgnat/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
else:
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/provision/{2}".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
self.want.module
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
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)
|
|
|
|
return ApiParameters(params=response)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
def absent(self):
|
|
|
|
if self.exists():
|
|
|
|
return self.remove()
|
|
|
|
return False
|
|
|
|
|
|
|
|
def remove(self):
|
2018-01-13 05:49:12 +00:00
|
|
|
if self.module.check_mode:
|
2017-06-14 17:28:12 +00:00
|
|
|
return True
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.want.module == 'cgnat':
|
2018-11-09 05:46:56 +00:00
|
|
|
return self.deprovision_cgnat_on_device()
|
2017-10-14 05:19:46 +00:00
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
self.remove_from_device()
|
|
|
|
self._wait_for_module_provisioning()
|
2017-10-14 05:19:46 +00:00
|
|
|
# For vCMP, because it has to reboot, we also wait for mcpd to become available
|
|
|
|
# before "moving on", or else the REST API would not be available and subsequent
|
|
|
|
# Tasks would fail.
|
|
|
|
if self.want.module == 'vcmp':
|
|
|
|
self._wait_for_reboot()
|
|
|
|
self._wait_for_module_provisioning()
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
if self.should_reboot():
|
|
|
|
self.save_on_device()
|
|
|
|
self.reboot_device()
|
|
|
|
self._wait_for_module_provisioning()
|
|
|
|
|
2017-06-14 17:28:12 +00:00
|
|
|
if self.exists():
|
|
|
|
raise F5ModuleError("Failed to de-provision the module")
|
|
|
|
return True
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def save_on_device(self):
|
|
|
|
command = 'tmsh save sys config'
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(
|
|
|
|
command="run",
|
2018-04-25 02:49:52 +00:00
|
|
|
utilCmdArgs='-c "{0}"'.format(command)
|
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port']
|
|
|
|
)
|
|
|
|
resp = self.client.api.post(uri, json=params)
|
2018-04-25 02:49:52 +00:00
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
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)
|
2018-04-25 02:49:52 +00:00
|
|
|
|
2018-11-09 05:46:56 +00:00
|
|
|
def remove_from_device(self):
|
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/provision/{2}".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
self.want.module
|
|
|
|
)
|
|
|
|
resp = self.client.api.patch(uri, json=dict(level='none'))
|
|
|
|
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)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def deprovision_cgnat_on_device(self):
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/sys/feature-module/cgnat/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
2018-04-25 02:49:52 +00:00
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(disabled=True)
|
|
|
|
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)
|
2018-04-25 02:49:52 +00:00
|
|
|
return True
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
def _wait_for_module_provisioning(self):
|
2017-06-14 17:28:12 +00:00
|
|
|
# To prevent things from running forever, the hack is to check
|
|
|
|
# for mprov's status twice. If mprov is finished, then in most
|
|
|
|
# cases (not ASM) the provisioning is probably ready.
|
|
|
|
nops = 0
|
|
|
|
|
|
|
|
# Sleep a little to let provisioning settle and begin properly
|
|
|
|
time.sleep(5)
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
while nops < 3:
|
2017-06-14 17:28:12 +00:00
|
|
|
try:
|
|
|
|
if not self._is_mprov_running_on_device():
|
|
|
|
nops += 1
|
|
|
|
else:
|
|
|
|
nops = 0
|
|
|
|
except Exception:
|
|
|
|
# This can be caused by restjavad restarting.
|
2017-12-02 05:53:54 +00:00
|
|
|
try:
|
|
|
|
self.client.reconnect()
|
|
|
|
except Exception:
|
|
|
|
pass
|
2017-10-14 05:19:46 +00:00
|
|
|
time.sleep(5)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
def _is_mprov_running_on_device(self):
|
2017-12-02 05:53:54 +00:00
|
|
|
# /usr/libexec/qemu-kvm is added here to prevent vcmp provisioning
|
|
|
|
# from never allowing the mprov provisioning to succeed.
|
|
|
|
#
|
|
|
|
# It turns out that the 'mprov' string is found when enabling vcmp. The
|
|
|
|
# qemu-kvm command that is run includes it.
|
|
|
|
#
|
|
|
|
# For example,
|
|
|
|
# /usr/libexec/qemu-kvm -rt-usecs 880 ... -mem-path /dev/mprov/vcmp -f5-tracing ...
|
|
|
|
#
|
2017-10-14 05:19:46 +00:00
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
command = "ps aux | grep \'[m]prov\' | grep -v /usr/libexec/qemu-kvm"
|
|
|
|
params = dict(
|
|
|
|
command="run",
|
|
|
|
utilCmdArgs='-c "{0}"'.format(command)
|
|
|
|
)
|
|
|
|
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port']
|
2017-12-02 05:53:54 +00:00
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
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)
|
|
|
|
|
|
|
|
if 'commandResult' in response:
|
2017-10-14 05:19:46 +00:00
|
|
|
return True
|
2017-12-02 05:53:54 +00:00
|
|
|
except Exception:
|
2017-10-14 05:19:46 +00:00
|
|
|
pass
|
2017-06-14 17:28:12 +00:00
|
|
|
return False
|
|
|
|
|
2017-11-03 00:40:41 +00:00
|
|
|
def _wait_for_asm_ready(self):
|
|
|
|
"""Waits specifically for ASM
|
|
|
|
|
|
|
|
On older versions, ASM can take longer to actually start up than
|
|
|
|
all the previous checks take. This check here is specifically waiting for
|
|
|
|
the Policies API to stop raising errors
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
nops = 0
|
2017-12-07 16:47:35 +00:00
|
|
|
restarted_asm = False
|
2017-11-03 00:40:41 +00:00
|
|
|
while nops < 3:
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/asm/policies/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
if len(response['items']) >= 0:
|
2017-11-03 00:40:41 +00:00
|
|
|
nops += 1
|
|
|
|
else:
|
|
|
|
nops = 0
|
2017-12-02 05:53:54 +00:00
|
|
|
except Exception as ex:
|
2017-12-07 16:47:35 +00:00
|
|
|
if not restarted_asm:
|
|
|
|
self._restart_asm()
|
|
|
|
restarted_asm = True
|
2017-11-03 00:40:41 +00:00
|
|
|
time.sleep(5)
|
|
|
|
|
2018-04-25 02:49:52 +00:00
|
|
|
def _wait_for_afm_ready(self):
|
|
|
|
"""Waits specifically for AFM
|
|
|
|
|
|
|
|
AFM can take longer to actually start up than all the previous checks take.
|
|
|
|
This check here is specifically waiting for the Security API to stop raising
|
|
|
|
errors.
|
|
|
|
:return:
|
|
|
|
"""
|
|
|
|
nops = 0
|
|
|
|
while nops < 3:
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/security/".format(
|
|
|
|
self.client.provider['server'],
|
|
|
|
self.client.provider['server_port'],
|
|
|
|
)
|
|
|
|
resp = self.client.api.get(uri)
|
|
|
|
|
|
|
|
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)
|
|
|
|
|
|
|
|
if len(response['items']) >= 0:
|
2018-04-25 02:49:52 +00:00
|
|
|
nops += 1
|
|
|
|
else:
|
|
|
|
nops = 0
|
|
|
|
except Exception as ex:
|
|
|
|
pass
|
|
|
|
time.sleep(5)
|
|
|
|
|
2017-12-07 16:47:35 +00:00
|
|
|
def _restart_asm(self):
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(
|
|
|
|
command="run",
|
2017-12-07 16:47:35 +00:00
|
|
|
utilCmdArgs='-c "bigstart restart asm"'
|
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
|
|
|
|
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)
|
2017-12-07 16:47:35 +00:00
|
|
|
time.sleep(60)
|
|
|
|
return True
|
|
|
|
except Exception:
|
|
|
|
pass
|
|
|
|
return None
|
|
|
|
|
2017-10-14 05:19:46 +00:00
|
|
|
def _get_last_reboot(self):
|
|
|
|
try:
|
2018-11-09 05:46:56 +00:00
|
|
|
params = dict(
|
|
|
|
command="run",
|
2017-12-02 05:53:54 +00:00
|
|
|
utilCmdArgs='-c "/usr/bin/last reboot | head -1"'
|
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
uri = "https://{0}:{1}/mgmt/tm/util/bash".format(
|
|
|
|
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)
|
|
|
|
|
|
|
|
if 'commandResult' in response:
|
|
|
|
return str(response['commandResult'])
|
2017-12-02 05:53:54 +00:00
|
|
|
except Exception:
|
2017-10-14 05:19:46 +00:00
|
|
|
pass
|
|
|
|
return None
|
|
|
|
|
|
|
|
def _wait_for_reboot(self):
|
|
|
|
nops = 0
|
|
|
|
|
|
|
|
last_reboot = self._get_last_reboot()
|
|
|
|
|
|
|
|
# Sleep a little to let provisioning settle and begin properly
|
|
|
|
time.sleep(5)
|
|
|
|
|
|
|
|
while nops < 6:
|
|
|
|
try:
|
2017-12-02 05:53:54 +00:00
|
|
|
self.client.reconnect()
|
2017-10-14 05:19:46 +00:00
|
|
|
next_reboot = self._get_last_reboot()
|
|
|
|
if next_reboot is None:
|
|
|
|
nops = 0
|
|
|
|
if next_reboot == last_reboot:
|
|
|
|
nops = 0
|
|
|
|
else:
|
|
|
|
nops += 1
|
2017-12-02 05:53:54 +00:00
|
|
|
except Exception as ex:
|
2017-10-14 05:19:46 +00:00
|
|
|
# This can be caused by restjavad restarting.
|
|
|
|
pass
|
|
|
|
time.sleep(10)
|
|
|
|
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
class ArgumentSpec(object):
|
|
|
|
def __init__(self):
|
|
|
|
self.supports_check_mode = True
|
2018-01-13 05:49:12 +00:00
|
|
|
argument_spec = dict(
|
2017-06-14 17:28:12 +00:00
|
|
|
module=dict(
|
|
|
|
required=True,
|
|
|
|
choices=[
|
|
|
|
'afm', 'am', 'sam', 'asm', 'avr', 'fps',
|
|
|
|
'gtm', 'lc', 'ltm', 'pem', 'swg', 'ilx',
|
2018-04-25 02:49:52 +00:00
|
|
|
'apm', 'vcmp', 'cgnat'
|
2017-11-03 00:40:41 +00:00
|
|
|
],
|
|
|
|
aliases=['name']
|
2017-06-14 17:28:12 +00:00
|
|
|
),
|
|
|
|
level=dict(
|
|
|
|
default='nominal',
|
2017-12-02 05:53:54 +00:00
|
|
|
choices=['nominal', 'dedicated', 'minimum']
|
2017-06-14 17:28:12 +00:00
|
|
|
),
|
|
|
|
state=dict(
|
|
|
|
default='present',
|
|
|
|
choices=['present', 'absent']
|
|
|
|
)
|
|
|
|
)
|
2018-01-13 05:49:12 +00:00
|
|
|
self.argument_spec = {}
|
|
|
|
self.argument_spec.update(f5_argument_spec)
|
|
|
|
self.argument_spec.update(argument_spec)
|
2017-06-14 17:28:12 +00:00
|
|
|
self.mutually_exclusive = [
|
|
|
|
['parameters', 'parameters_src']
|
|
|
|
]
|
|
|
|
|
|
|
|
|
|
|
|
def main():
|
|
|
|
spec = ArgumentSpec()
|
|
|
|
|
2018-01-13 05:49:12 +00:00
|
|
|
module = AnsibleModule(
|
2017-06-14 17:28:12 +00:00
|
|
|
argument_spec=spec.argument_spec,
|
|
|
|
supports_check_mode=spec.supports_check_mode,
|
2018-01-13 05:49:12 +00:00
|
|
|
mutually_exclusive=spec.mutually_exclusive
|
2017-06-14 17:28:12 +00:00
|
|
|
)
|
2018-11-09 05:46:56 +00:00
|
|
|
|
|
|
|
client = F5RestClient(**module.params)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
try:
|
2018-01-13 05:49:12 +00:00
|
|
|
mm = ModuleManager(module=module, client=client)
|
2017-06-14 17:28:12 +00:00
|
|
|
results = mm.exec_module()
|
2018-11-09 05:46:56 +00:00
|
|
|
exit_json(module, results, client)
|
2018-01-13 05:49:12 +00:00
|
|
|
except F5ModuleError as ex:
|
2018-11-09 05:46:56 +00:00
|
|
|
fail_json(module, ex, client)
|
2017-06-14 17:28:12 +00:00
|
|
|
|
|
|
|
|
|
|
|
if __name__ == '__main__':
|
|
|
|
main()
|