ansible/network/f5/bigip_virtual_server.py

414 lines
14 KiB
Python
Raw Blame History

This file contains invisible Unicode characters

This file contains invisible Unicode characters that are indistinguishable to humans but may be processed differently by a computer. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

#!/usr/bin/python
# -*- coding: utf-8 -*-
# (c) 2015, Etienne Carriere <etienne.carriere@gmail.com>
#
# This file is part of Ansible
#
# Ansible is free software: you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation, either version 3 of the License, or
# (at your option) any later version.
#
# Ansible is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with Ansible. If not, see <http://www.gnu.org/licenses/>.
DOCUMENTATION = '''
---
module: bigip_virtual_server
short_description: "Manages F5 BIG-IP LTM virtual servers"
description:
- "Manages F5 BIG-IP LTM virtual servers via iControl SOAP API"
version_added: "2.0"
author: Etienne Carriere
notes:
- "Requires BIG-IP software version >= 11"
- "F5 developed module 'bigsuds' required (see http://devcentral.f5.com)"
- "Best run as a local_action in your playbook"
requirements:
- bigsuds
options:
server:
description:
- BIG-IP host
required: true
default: null
choices: []
aliases: []
user:
description:
- BIG-IP username
required: true
default: null
choices: []
aliases: []
password:
description:
- BIG-IP password
required: true
default: null
choices: []
aliases: []
validate_certs:
description:
- If C(no), SSL certificates will not be validated. This should only be used
on personally controlled sites using self-signed certificates.
required: false
default: 'yes'
choices: ['yes', 'no']
version_added: 2.0
state:
description:
- Pool member state
required: true
default: present
choices: ['present', 'absent', 'enabled', 'disabled']
aliases: []
partition:
description:
- Partition
required: false
default: 'Common'
choices: []
aliases: []
name:
description:
- "Virtual server name."
required: true
default: null
choices: []
aliases: ['vs']
destination:
description:
- "Destination IP of the virtual server (only host is currently supported) . Required when state=present and vs does not exist. Error when state=absent."
required: true
default: null
choices: []
aliases: ['address', 'ip']
port:
description:
- "Port of the virtual server . Required when state=present and vs does not exist"
required: true
default: null
choices: []
aliases: []
all_profiles:
description:
- "List of all Profiles (HTTP,ClientSSL,ServerSSL,etc) that must be used by the virtual server"
required: false
default: null
choices: []
aliases: []
pool:
description:
- "Default pool for the virtual server"
required: false
default: null
choices: []
aliases: []
snat:
description:
- "Source network address policy"
required: false
default: None
choices: []
aliases: []
description:
description:
- "Virtual server description."
required: false
default: null
choices: []
'''
EXAMPLES = '''
## playbook task examples:
---
# file bigip-test.yml
# ...
- name: Add VS
local_action:
module: bigip_virtual_server
server: lb.mydomain.net
user: admin
password: secret
state: present
partition: MyPartition
name: myvirtualserver
destination: "{{ ansible_default_ipv4["address"] }}"
port: 443
pool: "{{ mypool }}"
snat: Automap
description: Test Virtual Server
all_profiles:
- http
- clientssl
- name: Modify Port of the Virtual Server
local_action:
module: bigip_virtual_server
server: lb.mydomain.net
user: admin
password: secret
state: present
partition: MyPartition
name: myvirtualserver
port: 8080
- name: Delete pool
local_action:
module: bigip_virtual_server
server: lb.mydomain.net
user: admin
password: secret
state: absent
partition: MyPartition
name: myvirtualserver
'''
# ==========================
# bigip_virtual_server module specific
#
# map of state values
STATES={'enabled': 'STATE_ENABLED',
'disabled': 'STATE_DISABLED'}
STATUSES={'enabled': 'SESSION_STATUS_ENABLED',
'disabled': 'SESSION_STATUS_DISABLED',
'offline': 'SESSION_STATUS_FORCED_DISABLED'}
def vs_exists(api, vs):
# hack to determine if pool exists
result = False
try:
api.LocalLB.VirtualServer.get_object_status(virtual_servers=[vs])
result = True
except bigsuds.OperationFailed, e:
if "was not found" in str(e):
result = False
else:
# genuine exception
raise
return result
def vs_create(api,name,destination,port,pool):
_profiles=[[{'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': 'tcp'}]]
try:
api.LocalLB.VirtualServer.create(
definitions = [{'name': [name], 'address': [destination], 'port': port, 'protocol': 'PROTOCOL_TCP'}],
wildmasks = ['255.255.255.255'],
resources = [{'type': 'RESOURCE_TYPE_POOL', 'default_pool_name': pool}],
profiles = _profiles)
result = True
desc = 0
except Exception, e :
print e.args
def vs_remove(api,name):
api.LocalLB.VirtualServer.delete_virtual_server(virtual_servers = [name ])
def get_profiles(api,name):
return api.LocalLB.VirtualServer.get_profile(virtual_servers = [name])[0]
def set_profiles(api,name,profiles_list):
if profiles_list is None:
return False
current_profiles=map(lambda x:x['profile_name'], get_profiles(api,name))
to_add_profiles=[]
for x in profiles_list:
if x not in current_profiles:
to_add_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x})
to_del_profiles=[]
for x in current_profiles:
if (x not in profiles_list) and (x!= "/Common/tcp"):
to_del_profiles.append({'profile_context': 'PROFILE_CONTEXT_TYPE_ALL', 'profile_name': x})
changed=False
if len(to_del_profiles)>0:
api.LocalLB.VirtualServer.remove_profile(virtual_servers = [name],profiles = [to_del_profiles])
changed=True
if len(to_add_profiles)>0:
api.LocalLB.VirtualServer.add_profile(virtual_servers = [name],profiles= [to_add_profiles])
changed=True
return changed
def set_snat(api,name,snat):
current_state=get_snat_type(api,name)
update = False
if snat is None:
return update
if snat == 'None' and current_state != 'SRC_TRANS_NONE':
api.LocalLB.VirtualServer.set_source_address_translation_none(virtual_servers = [name])
update = True
if snat == 'Automap' and current_state != 'SRC_TRANS_AUTOMAP':
api.LocalLB.VirtualServer.set_source_address_translation_automap(virtual_servers = [name])
update = True
return update
def get_snat_type(api,name):
return api.LocalLB.VirtualServer.get_source_address_translation_type(virtual_servers = [name])[0]
def get_pool(api,name):
return api.LocalLB.VirtualServer.get_default_pool_name(virtual_servers = [name])[0]
def set_pool(api,name,pool):
current_pool = get_pool (api,name)
updated=False
if pool is not None and (pool != current_pool):
api.LocalLB.VirtualServer.set_default_pool_name(virtual_servers = [name],default_pools = [pool])
updated=True
return updated
def get_destination(api,name):
return api.LocalLB.VirtualServer.get_destination_v2(virtual_servers = [name])[0]
def set_destination(api,name,destination,port):
current_destination = get_destination(api,name)
updated=False
if (destination is not None and port is not None) and (destination != current_destination['address'] or port != current_destination['port']):
api.LocalLB.VirtualServer.set_destination_v2(virtual_servers = [name],destinations=[{'address': destination, 'port':port}])
updated=True
return updated
def get_description(api,name):
return api.LocalLB.VirtualServer.get_description(virtual_servers = [name])[0]
def set_description(api,name,description):
current_description = get_description(api,name)
updated=False
if description is not None and current_description != description:
api.LocalLB.VirtualServer.set_description(virtual_servers =[name],descriptions=[description])
updated=True
return updated
def main():
argument_spec = f5_argument_spec()
argument_spec.update( dict(
state = dict(type='str', default='present',
choices=['present', 'absent', 'disabled', 'enabled']),
name = dict(type='str', required=True,aliases=['vs']),
destination = dict(type='str', aliases=['address', 'ip']),
port = dict(type='int'),
all_profiles = dict(type='list'),
pool=dict(type='str'),
description = dict(type='str'),
snat=dict(type='str')
)
)
module = AnsibleModule(
argument_spec = argument_spec,
supports_check_mode=True
)
(server,user,password,state,partition,validate_certs) = f5_parse_arguments(module)
name = fq_name(partition,module.params['name'])
destination=module.params['destination']
port=module.params['port']
all_profiles=fq_list_names(partition,module.params['all_profiles'])
pool=fq_name(partition,module.params['pool'])
description = module.params['description']
snat = module.params['snat']
if 1 > port > 65535:
module.fail_json(msg="valid ports must be in range 1 - 65535")
try:
api = bigip_api(server, user, password)
result = {'changed': False} # default
if state == 'absent':
if not module.check_mode:
if vs_exists(api,name):
# hack to handle concurrent runs of module
# pool might be gone before we actually remove
try:
vs_remove(api,name)
result = {'changed' : True, 'deleted' : name }
except bigsuds.OperationFailed, e:
if "was not found" in str(e):
result['changed']= False
else:
raise
else:
# check-mode return value
result = {'changed': True}
elif state == 'present':
update = False
if not vs_exists(api, name):
if (not destination) or (not port):
module.fail_json(msg="both destination and port must be supplied to create a VS")
if not module.check_mode:
# a bit of a hack to handle concurrent runs of this module.
# even though we've checked the pool doesn't exist,
# it may exist by the time we run create_pool().
# this catches the exception and does something smart
# about it!
try:
vs_create(api,name,destination,port,pool)
result = {'changed': True}
except bigsuds.OperationFailed, e:
if "already exists" in str(e):
update = True
else:
raise
else:
set_profiles(api,name,all_profiles)
set_snat(api,name,snat)
set_description(api,name,description)
else:
# check-mode return value
result = {'changed': True}
else:
update = True
if update:
# VS exists
if not module.check_mode:
# Have a transaction for all the changes
api.System.Session.start_transaction()
result['changed']|=set_destination(api,name,fq_name(partition,destination),port)
result['changed']|=set_pool(api,name,pool)
result['changed']|=set_description(api,name,description)
result['changed']|=set_snat(api,name,snat)
result['changed']|=set_profiles(api,name,all_profiles)
api.System.Session.submit_transaction()
else:
# check-mode return value
result = {'changed': True}
elif state in ('disabled', 'enabled'):
if name is None:
module.fail_json(msg="name parameter required when " \
"state=enabled/disabled")
if not module.check_mode:
pass
else:
# check-mode return value
result = {'changed': True}
except Exception, e:
module.fail_json(msg="received exception: %s" % e)
module.exit_json(**result)
# import module snippets
from ansible.module_utils.basic import *
from ansible.module_utils.f5 import *
main()