diff --git a/net_infrastructure/a10_server b/net_infrastructure/a10_server
index 993ca7e8ce3..65410536eef 100644
--- a/net_infrastructure/a10_server
+++ b/net_infrastructure/a10_server
@@ -24,16 +24,13 @@ along with Ansible. If not, see .
DOCUMENTATION = '''
---
module: a10_server
-version_added: 1.0
+version_added: 1.8
short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
description:
- Manage slb server objects on A10 Networks devices via aXAPI
author: Mischa Peters
notes:
- Requires A10 Networks aXAPI 2.1
-requirements:
- - urllib2
- - re
options:
host:
description:
@@ -70,27 +67,23 @@ options:
default: null
aliases: ['ip', 'address']
choices: []
- server_port:
+ server_status:
description:
- - slb server port
+ - slb virtual server status
required: false
- default: null
- aliases: ['port']
- choices: []
- server_protocol:
+ default: enable
+ aliases: ['status']
+ choices: ['enabled', 'disabled']
+ server_ports:
description:
- - slb server protocol
+ - A list of ports to create for the server. Each list item should be a
+ dictionary which specifies the C(port:) and C(protocol:), but can also optionally
+ specify the C(status:). See the examples below for details. This parameter is
+ required when C(state) is C(present).
required: false
default: null
- aliases: ['proto', 'protocol']
- choices: ['tcp', 'udp']
- server_status:
- description:
- - slb server status
- required: false
- default: enabled
- aliases: ['status']
- choices: ['enable', 'disable']
+ aliases: []
+ choices: []
state:
description:
- create, update or remove slb server
@@ -102,124 +95,175 @@ options:
EXAMPLES = '''
# Create a new server
-ansible host -m a10_server -a "host=a10adc.example.com username=axapiuser password=axapipass server_name=realserver1 server_ip=192.168.1.23"
+- a10_server:
+ host: a10.mydomain.com
+ username: myadmin
+ password: mypassword
+ server: test
+ server_ip: 1.1.1.100
+ server_ports:
+ - port_num: 8080
+ protocol: tcp
+ - port_num: 8443
+ protocol: TCP
-# Add a port
-ansible host -m a10_server -a "host=a10adc.example.com username=axapiuser password=axapipass server_name=realserver1 server_port=80 server_protocol=tcp"
-
-# Disable a server
-ansible host -m a10_server -a "host=a10adc.example.com username=axapiuser password=axapipass server_name=realserver1 server_status=disable"
'''
-import urllib2
+VALID_PORT_FIELDS = ['port_num', 'protocol', 'status']
+def validate_ports(module, ports):
+ for item in ports:
+ for key in item:
+ if key not in VALID_PORT_FIELDS:
+ module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS)))
-def axapi_call(url, post=None):
- result = urllib2.urlopen(url, post).read()
- return result
+ # validate the port number is present and an integer
+ if 'port_num' in item:
+ try:
+ item['port_num'] = int(item['port_num'])
+ except:
+ module.fail_json(msg="port_num entries in the port definitions must be integers")
+ else:
+ module.fail_json(msg="port definitions must define the port_num field")
+ # validate the port protocol is present, and convert it to
+ # the internal API integer value (and validate it)
+ if 'protocol' in item:
+ protocol = axapi_get_port_protocol(item['protocol'])
+ if not protocol:
+ module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_PORT_PROTOCOLS))
+ else:
+ item['protocol'] = protocol
+ else:
+ module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_PORT_PROTOCOLS))
-def axapi_authenticate(base_url, user, pwd):
- url = base_url + '&method=authenticate&username=' + user + \
- '&password=' + pwd
- result = json.loads(axapi_call(url))
- if 'response' in result:
- return module.fail_json(msg=result['response']['err']['msg'])
- sessid = result['session_id']
- return base_url + '&session_id=' + sessid
+ # convert the status to the internal API integer value
+ if 'status' in item:
+ item['status'] = axapi_enabled_disabled(item['status'])
+ else:
+ item['status'] = 1
def main():
- global module
- module = AnsibleModule(
- argument_spec=dict(
- host=dict(type='str', required=True),
- username=dict(type='str', aliases=['user', 'admin'],
- required=True),
- password=dict(type='str', aliases=['pass', 'pwd'], required=True),
+ argument_spec = a10_argument_spec()
+ argument_spec.update(url_argument_spec())
+ argument_spec.update(
+ dict(
+ state=dict(type='str', default='present', choices=['present', 'absent']),
server_name=dict(type='str', aliases=['server'], required=True),
server_ip=dict(type='str', aliases=['ip', 'address']),
- server_port=dict(type='int', aliases=['port']),
- server_protocol=dict(type='str', aliases=['proto', 'protocol'],
- choices=['tcp', 'udp']),
- server_status=dict(type='str', default='enable',
- aliases=['status'],
- choices=['enable', 'disable']),
- state=dict(type='str', default='present',
- choices=['present', 'absent']),
- ),
+ server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']),
+ server_ports=dict(type='list', aliases=['port'], default=[]),
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
supports_check_mode=False
)
host = module.params['host']
- user = module.params['username']
- pwd = module.params['password']
+ username = module.params['username']
+ password = module.params['password']
+ state = module.params['state']
+ write_config = module.params['write_config']
slb_server = module.params['server_name']
slb_server_ip = module.params['server_ip']
- slb_server_port = module.params['server_port']
- slb_server_proto = module.params['server_protocol']
slb_server_status = module.params['server_status']
- state = module.params['state']
+ slb_server_ports = module.params['server_ports']
- axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json'
+ if slb_server is None:
+ module.fail_json(msg='server_name is required')
- if slb_server_proto == 'tcp' or slb_server_proto == 'TCP' or \
- slb_server_proto is None:
- protocol = '2'
- else:
- protocol = '3'
+ axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host
+ session_url = axapi_authenticate(module, axapi_base_url, username, password)
- if slb_server_status == 'enable':
- status = '1'
- else:
- status = '0'
+ # validate the ports data structure
+ validate_ports(module, slb_server_ports)
- if slb_server is None:
- module.fail_json(msg='server_name is required')
+ json_post = {
+ 'server': {
+ 'name': slb_server,
+ 'host': slb_server_ip,
+ 'status': axapi_enabled_disabled(slb_server_status),
+ 'port_list': slb_server_ports,
+ }
+ }
- if slb_server_port is None:
- json_post = {'server': {'name': slb_server,
- 'host': slb_server_ip, 'status': status}}
- else:
- json_post = {'server': {'name': slb_server, 'host': slb_server_ip,
- 'status': status, 'port_list':
- [{'port_num': slb_server_port,
- 'protocol': protocol}]}}
-
- try:
- session_url = axapi_authenticate(axapi_base_url, user, pwd)
-
- if state == 'present':
- response = axapi_call(session_url + '&method=slb.server.search',
- json.dumps({'name': slb_server}))
- slb_server_exist = re.search(slb_server, response, re.I)
-
- if slb_server_exist is None:
- if slb_server_ip is None:
- module.fail_json(msg='IP address is required')
- response = axapi_call(session_url +
- '&method=slb.server.create',
- json.dumps(json_post))
- else:
- response = axapi_call(session_url +
- '&method=slb.server.update',
- json.dumps(json_post))
+ slb_server_data = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server}))
+ slb_server_exists = not axapi_failure(slb_server_data)
- if state == 'absent':
- response = axapi_call(session_url +
- '&method=slb.server.delete',
- json.dumps({'name': slb_server}))
+ changed = False
+ if state == 'present':
+ if not slb_server_ip:
+ module.fail_json(msg='you must specify an IP address when creating a server')
- result = json.loads(response)
- axapi_call(session_url + '&method=session.close')
+ if not slb_server_exists:
+ result = axapi_call(module, session_url + '&method=slb.server.create', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg="failed to create the server: %s" % result['response']['err']['msg'])
+ changed = True
+ else:
+ def needs_update(src_ports, dst_ports):
+ '''
+ Checks to determine if the port definitions of the src_ports
+ array are in or different from those in dst_ports. If there is
+ a difference, this function returns true, otherwise false.
+ '''
+ for src_port in src_ports:
+ found = False
+ different = False
+ for dst_port in dst_ports:
+ if src_port['port_num'] == dst_port['port_num']:
+ found = True
+ for valid_field in VALID_PORT_FIELDS:
+ if src_port[valid_field] != dst_port[valid_field]:
+ different = True
+ break
+ if found or different:
+ break
+ if not found or different:
+ return True
+ # every port from the src exists in the dst, and none of them were different
+ return False
- except Exception, e:
- return module.fail_json(msg='received exception: %s' % e)
+ defined_ports = slb_server_data.get('server', {}).get('port_list', [])
- if 'respone' in result and 'err' in result['response']:
- return module.fail_json(msg=result['response']['err']['msg'])
+ # we check for a needed update both ways, in case ports
+ # are missing from either the ones specified by the user
+ # or from those on the device
+ if needs_update(defined_ports, slb_server_ports) or needs_update(slb_server_ports, defined_ports):
+ result = axapi_call(module, session_url + '&method=slb.server.update', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg="failed to update the server: %s" % result['response']['err']['msg'])
+ changed = True
- module.exit_json(changed=True, content=result)
+ # if we changed things, get the full info regarding
+ # the service group for the return data below
+ if changed:
+ result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': slb_server}))
+ else:
+ result = slb_server_data
+ elif state == 'absent':
+ if slb_server_exists:
+ result = axapi_call(module, session_url + '&method=slb.server.delete', json.dumps({'name': slb_server}))
+ changed = True
+ else:
+ result = dict(msg="the server was not present")
+ # if the config has changed, save the config unless otherwise requested
+ if changed and write_config:
+ write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
+ if axapi_failure(write_result):
+ module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
+
+ # log out of the session nicely and exit
+ axapi_call(module, session_url + '&method=session.close')
+ module.exit_json(changed=changed, content=result)
+
+# standard ansible module imports
from ansible.module_utils.basic import *
+from ansible.module_utils.urls import *
+from ansible.module_utils.a10 import *
+
main()
diff --git a/net_infrastructure/a10_service_group b/net_infrastructure/a10_service_group
index 0a9f02f76a1..3627e2d12b8 100644
--- a/net_infrastructure/a10_service_group
+++ b/net_infrastructure/a10_service_group
@@ -24,7 +24,7 @@ along with Ansible. If not, see .
DOCUMENTATION = '''
---
module: a10_service_group
-version_added: 1.0
+version_added: 1.8
short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
description:
- Manage slb service-group objects on A10 Networks devices via aXAPI
@@ -32,9 +32,6 @@ author: Mischa Peters
notes:
- Requires A10 Networks aXAPI 2.1
- When a server doesn't exist and is added to the service-group the server will be created
-requirements:
- - urllib2
- - re
options:
host:
description:
@@ -78,79 +75,91 @@ options:
default: round-robin
aliases: ['method']
choices: ['round-robin', 'weighted-rr', 'least-connection', 'weighted-least-connection', 'service-least-connection', 'service-weighted-least-connection', 'fastest-response', 'least-request', 'round-robin-strict', 'src-ip-only-hash', 'src-ip-hash']
- server_name:
+ servers:
description:
- - slb server name
+ - A list of servers to add to the service group. Each list item should be a
+ dictionary which specifies the C(server:) and C(port:), but can also optionally
+ specify the C(status:). See the examples below for details.
required: false
default: null
- aliases: ['server', 'member']
- choices: []
- server_port:
- description:
- - slb server port
- required: false
- default: null
- aliases: ['port']
+ aliases: []
choices: []
- server_status:
+ write_config:
description:
- - slb server status
+ - If C(yes), any changes will cause a write of the running configuration
+ to non-volatile memory. This will save I(all) configuration changes,
+ including those that may have been made manually or through other modules,
+ so care should be taken when specifying C(yes).
required: false
- default: enabled
- aliases: ['status']
- choices: ['enable', 'disable']
- state:
+ default: "no"
+ choices: ["yes", "no"]
+ validate_certs:
description:
- - create, remove or update slb service-group
+ - If C(no), SSL certificates will not be validated. This should only be used
+ on personally controlled devices using self-signed certificates.
required: false
- default: present
- aliases: []
- choices: ['present', 'absent']
+ default: 'yes'
+ choices: ['yes', 'no']
+
'''
EXAMPLES = '''
# Create a new service-group
-ansible host -m a10_service_group -a "host=a10adc.example.com username=axapiuser password=axapipass service_group=sg-80-tcp"
-
-# Add a server
-ansible host -m a10_service_group -a "host=a10adc.example.com username=axapiuser password=axapipass service_group=sg-80-tcp server_name=realserver1 server_port=80"
+- a10_service_group:
+ host: a10.mydomain.com
+ username: myadmin
+ password: mypassword
+ service_group: sg-80-tcp
+ servers:
+ - server: foo1.mydomain.com
+ port: 8080
+ - server: foo2.mydomain.com
+ port: 8080
+ - server: foo3.mydomain.com
+ port: 8080
+ - server: foo4.mydomain.com
+ port: 8080
+ status: disabled
-# Disable a server
-ansible host -m a10_service_group -a "host=a10adc.example.com username=axapiuser password=axapipass service_group=sg-80-tcp server_name=realserver1 server_port=80 status=disable"
'''
-import urllib2
+VALID_SERVICE_GROUP_FIELDS = ['name', 'protocol', 'lb_method']
+VALID_SERVER_FIELDS = ['server', 'port', 'status']
+def validate_servers(module, servers):
+ for item in servers:
+ for key in item:
+ if key not in VALID_SERVER_FIELDS:
+ module.fail_json(msg="invalid server field (%s), must be one of: %s" % (key, ','.join(VALID_SERVER_FIELDS)))
-def axapi_call(url, post=None):
- result = urllib2.urlopen(url, post).read()
- return result
+ # validate the server name is present
+ if 'server' not in item:
+ module.fail_json(msg="server definitions must define the server field")
+ # validate the port number is present and an integer
+ if 'port' in item:
+ try:
+ item['port'] = int(item['port'])
+ except:
+ module.fail_json(msg="server port definitions must be integers")
+ else:
+ module.fail_json(msg="server definitions must define the port field")
-def axapi_authenticate(base_url, user, pwd):
- url = base_url + '&method=authenticate&username=' + user + \
- '&password=' + pwd
- result = json.loads(axapi_call(url))
- if 'response' in result:
- return module.fail_json(msg=result['response']['err']['msg'])
- sessid = result['session_id']
- return base_url + '&session_id=' + sessid
+ # convert the status to the internal API integer value
+ if 'status' in item:
+ item['status'] = axapi_enabled_disabled(item['status'])
+ else:
+ item['status'] = 1
def main():
- global module
- module = AnsibleModule(
- argument_spec=dict(
- host=dict(type='str', required=True),
- username=dict(type='str', aliases=['user', 'admin'],
- required=True),
- password=dict(type='str', aliases=['pass', 'pwd'], required=True),
- service_group=dict(type='str',
- aliases=['service', 'pool', 'group'],
- required=True),
- service_group_protocol=dict(type='str', default='tcp',
- aliases=['proto', 'protocol'],
- choices=['tcp', 'udp']),
+ argument_spec = a10_argument_spec()
+ argument_spec.update(url_argument_spec())
+ argument_spec.update(
+ dict(
+ state=dict(type='str', default='present', choices=['present', 'absent']),
+ service_group=dict(type='str', aliases=['service', 'pool', 'group'], required=True),
+ service_group_protocol=dict(type='str', default='tcp', aliases=['proto', 'protocol'], choices=['tcp', 'udp']),
service_group_method=dict(type='str', default='round-robin',
aliases=['method'],
choices=['round-robin',
@@ -164,27 +173,27 @@ def main():
'round-robin-strict',
'src-ip-only-hash',
'src-ip-hash']),
- server_name=dict(type='str', aliases=['server', 'member']),
- server_port=dict(type='int', aliases=['port']),
- server_status=dict(type='str', default='enable',
- aliases=['status'],
- choices=['enable', 'disable']),
- state=dict(type='str', default='present',
- choices=['present', 'absent']),
- ),
+ servers=dict(type='list', aliases=['server', 'member'], default=[]),
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
supports_check_mode=False
)
host = module.params['host']
- user = module.params['username']
- pwd = module.params['password']
+ username = module.params['username']
+ password = module.params['password']
+ state = module.params['state']
+ write_config = module.params['write_config']
slb_service_group = module.params['service_group']
slb_service_group_proto = module.params['service_group_protocol']
slb_service_group_method = module.params['service_group_method']
- slb_server = module.params['server_name']
- slb_server_port = module.params['server_port']
- slb_server_status = module.params['server_status']
- state = module.params['state']
+ slb_servers = module.params['servers']
+
+ if slb_service_group is None:
+ module.fail_json(msg='service_group is required')
axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json'
load_balancing_methods = {'round-robin': 0,
@@ -199,69 +208,134 @@ def main():
'src-ip-only-hash': 14,
'src-ip-hash': 15}
- if slb_service_group_proto == 'tcp' or slb_service_group_proto == 'TCP':
- protocol = '2'
+ if not slb_service_group_proto or slb_service_group_proto.lower() == 'tcp':
+ protocol = 2
else:
- protocol = '3'
+ protocol = 3
- if slb_server_status == 'enable':
- status = '1'
- else:
- status = '0'
+ # validate the server data list structure
+ validate_servers(module, slb_servers)
- if slb_service_group is None:
- module.fail_json(msg='service_group is required')
+ json_post = {
+ 'service_group': {
+ 'name': slb_service_group,
+ 'protocol': protocol,
+ 'lb_method': load_balancing_methods[slb_service_group_method],
+ }
+ }
- if slb_server is None and slb_server_port is None:
- json_post = {'service_group': {'name': slb_service_group,
- 'protocol': protocol,
- 'lb_method': load_balancing_methods[slb_service_group_method]}}
- elif slb_server is not None and slb_server_port is not None:
- json_post = {'service_group': {'name': slb_service_group,
- 'protocol': protocol,
- 'lb_method': load_balancing_methods[slb_service_group_method],
- 'member_list':
- [{'server': slb_server,
- 'port': slb_server_port,
- 'status': status}]}}
- else:
- module.fail_json(msg='server_name and server_name_port are \
- required to add to the service-group')
+ # first we authenticate to get a session id
+ session_url = axapi_authenticate(module, axapi_base_url, username, password)
- try:
- session_url = axapi_authenticate(axapi_base_url, user, pwd)
+ # then we check to see if the specified group exists
+ slb_result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group}))
+ slb_service_group_exist = not axapi_failure(slb_result)
- if state == 'present':
- response = axapi_call(session_url +
- '&method=slb.service_group.search',
- json.dumps({'name': slb_service_group}))
- slb_service_group_exist = re.search(slb_service_group,
- response, re.I)
+ changed = False
+ if state == 'present':
+ # before creating/updating we need to validate that servers
+ # defined in the servers list exist to prevent errors
+ checked_servers = []
+ for server in slb_servers:
+ result = axapi_call(module, session_url + '&method=slb.server.search', json.dumps({'name': server['server']}))
+ if axapi_failure(result):
+ module.fail_json(msg="the server %s specified in the servers list does not exist" % server['server'])
+ checked_servers.append(server['server'])
- if slb_service_group_exist is None:
- response = axapi_call(session_url +
- '&method=slb.service_group.create',
- json.dumps(json_post))
- else:
- response = axapi_call(session_url +
- '&method=slb.service_group.update',
- json.dumps(json_post))
+ if not slb_service_group_exist:
+ result = axapi_call(module, session_url + '&method=slb.service_group.create', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg=result['response']['err']['msg'])
+ changed = True
+ else:
+ # check to see if the service group definition without the
+ # server members is different, and update that individually
+ # if it needs it
+ do_update = False
+ for field in VALID_SERVICE_GROUP_FIELDS:
+ if json_post['service_group'][field] != slb_result['service_group'][field]:
+ do_update = True
+ break
- if state == 'absent':
- response = axapi_call(session_url +
- '&method=slb.service_group.delete',
- json.dumps({'name': slb_service_group}))
+ if do_update:
+ result = axapi_call(module, session_url + '&method=slb.service_group.update', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg=result['response']['err']['msg'])
+ changed = True
- result = json.loads(response)
- axapi_call(session_url + '&method=session.close')
+ # next we pull the defined list of servers out of the returned
+ # results to make it a bit easier to iterate over
+ defined_servers = slb_result.get('service_group', {}).get('member_list', [])
- except Exception, e:
- return module.fail_json(msg='received exception: %s' % e)
+ # next we add/update new member servers from the user-specified
+ # list if they're different or not on the target device
+ for server in slb_servers:
+ found = False
+ different = False
+ for def_server in defined_servers:
+ if server['server'] == def_server['server']:
+ found = True
+ for valid_field in VALID_SERVER_FIELDS:
+ if server[valid_field] != def_server[valid_field]:
+ different = True
+ break
+ if found or different:
+ break
+ # add or update as required
+ server_data = {
+ "name": slb_service_group,
+ "member": server,
+ }
+ if not found:
+ result = axapi_call(module, session_url + '&method=slb.service_group.member.create', json.dumps(server_data))
+ changed = True
+ elif different:
+ result = axapi_call(module, session_url + '&method=slb.service_group.member.update', json.dumps(server_data))
+ changed = True
- if 'respone' in result and 'err' in result['response']:
- return module.fail_json(msg=result['response']['err']['msg'])
+ # finally, remove any servers that are on the target
+ # device but were not specified in the list given
+ for server in defined_servers:
+ found = False
+ for slb_server in slb_servers:
+ if server['server'] == slb_server['server']:
+ found = True
+ break
+ # remove if not found
+ server_data = {
+ "name": slb_service_group,
+ "member": server,
+ }
+ if not found:
+ result = axapi_call(module, session_url + '&method=slb.service_group.member.delete', json.dumps(server_data))
+ changed = True
- module.exit_json(changed=True, content=result)
+ # if we changed things, get the full info regarding
+ # the service group for the return data below
+ if changed:
+ result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': slb_service_group}))
+ else:
+ result = slb_result
+ elif state == 'absent':
+ if slb_service_group_exist:
+ result = axapi_call(module, session_url + '&method=slb.service_group.delete', json.dumps({'name': slb_service_group}))
+ changed = True
+ else:
+ result = dict(msg="the service group was not present")
+ # if the config has changed, save the config unless otherwise requested
+ if changed and write_config:
+ write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
+ if axapi_failure(write_result):
+ module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
+
+ # log out of the session nicely and exit
+ axapi_call(module, session_url + '&method=session.close')
+ module.exit_json(changed=changed, content=result)
+
+# standard ansible module imports
from ansible.module_utils.basic import *
+from ansible.module_utils.urls import *
+from ansible.module_utils.a10 import *
+
main()
diff --git a/net_infrastructure/a10_virtual b/net_infrastructure/a10_virtual
deleted file mode 100644
index aed49ce3ea5..00000000000
--- a/net_infrastructure/a10_virtual
+++ /dev/null
@@ -1,352 +0,0 @@
-#!/usr/bin/python
-# -*- coding: utf-8 -*-
-
-"""
-Ansible module to manage A10 Networks slb virtual server objects
-(c) 2014, Mischa Peters
-
-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 .
-"""
-
-DOCUMENTATION = '''
----
-module: a10_virtual_server
-version_added: 1.0
-short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
-description:
- - Manage slb virtual server objects on A10 Networks devices via aXAPI
-author: Mischa Peters
-notes:
- - Requires A10 Networks aXAPI 2.1
-requirements:
- - urllib2
- - re
-options:
- host:
- description:
- - hostname or ip of your A10 Networks device
- required: true
- default: null
- aliases: []
- choices: []
- username:
- description:
- - admin account of your A10 Networks device
- required: true
- default: null
- aliases: ['user', 'admin']
- choices: []
- password:
- description:
- - admin password of your A10 Networks device
- required: true
- default: null
- aliases: ['pass', 'pwd']
- choices: []
- virtual_server:
- description:
- - slb virtual server name
- required: true
- default: null
- aliases: ['vip', 'virtual']
- choices: []
- virtual_server_ip:
- description:
- - slb virtual server ip address
- required: false
- default: null
- aliases: ['ip', 'address']
- choices: []
- virtual_server_status:
- description:
- - slb virtual server status
- required: false
- default: enable
- aliases: ['status']
- choices: ['enabled', 'disabled']
- virtual_server_port:
- description:
- - slb virtual server port
- required: false
- default: round-robin
- aliases: ['port', 'vport']
- choices: []
- virtual_server_type:
- description:
- - slb virtual server port type
- required: false
- default: null
- aliases: ['proto', 'protocol']
- choices: ['tcp', 'udp', 'fast-http', 'http', 'https']
- virtual_server_port_status:
- description:
- - slb virtual server port status
- required: false
- default: enable
- aliases: ['status']
- choices: ['enabled', 'disabled']
- service_group:
- description:
- - slb virtual server service-group
- required: false
- default: enabled
- aliases: ['pool', 'group']
- choices: []
- state:
- description:
- - create, update or remove slb virtual server
- required: false
- default: present
- aliases: []
- choices: ['present', 'absent']
-'''
-
-EXAMPLES = '''
-# Create a new virtual server
-ansible host -m a10_virtual -a "host=a10adc.example.com username=axapiuser password=axapipass virtual_server=vip1 virtual_server_ip=192.168.1.20"
-
-# Add a virtual port
-ansible host -m a10_virtual -a "host=a10adc.example.com username=axapiuser password=axapipass virtual_server=vip1 virtual_server_ip=192.168.1.20 virtual_server_port=80 virtual_server_port_type=http service_group=sg-80-tcp"
-
-# Disable a virtual server
-ansible host -m a10_virtual -a "host=a10adc.example.com username=axapiuser password=axapipass virtual_server=vip1 status=disable"
-
-# Disable a virtual server port
-ansible host -m a10_virtual -a "host=a10adc.example.com username=axapiuser password=axapipass virtual_server=vip1 virtual_server_port=80 virtual_server_port_type=http virtual_server_port_status=disable"
-'''
-
-import urllib2
-
-
-def axapi_call(url, post=None):
- result = urllib2.urlopen(url, post).read()
- return result
-
-
-def axapi_authenticate(base_url, user, pwd):
- url = base_url + '&method=authenticate&username=' + user + \
- '&password=' + pwd
- result = json.loads(axapi_call(url))
- if 'response' in result:
- return module.fail_json(msg=result['response']['err']['msg'])
- sessid = result['session_id']
- return base_url + '&session_id=' + sessid
-
-
-def main():
- global module
- module = AnsibleModule(
- argument_spec=dict(
- host=dict(type='str', required=True),
- username=dict(type='str', aliases=['user', 'admin'],
- required=True),
- password=dict(type='str', aliases=['pass', 'pwd'],
- required=True),
- virtual_server=dict(type='str', aliases=['vip', 'virtual'],
- required=True),
- virtual_server_ip=dict(type='str',
- aliases=['ip', 'address']),
- virtual_server_status=dict(type='str', default='enabled',
- aliases=['status'],
- choices=['enabled', 'disabled']),
- virtual_server_port=dict(type='int',
- aliases=['port', 'vport']),
- virtual_server_port_type=dict(type='str',
- aliases=['proto', 'protocol'],
- choices=['tcp', 'udp', 'fast-http',
- 'http', 'https']),
- virtual_server_port_status=dict(type='str', default='enabled',
- aliases=['portstatus',
- 'port_status'],
- choices=['enabled', 'disabled']),
- service_group=dict(type='str', aliases=['pool', 'group']),
- state=dict(type='str', default='present',
- choices=['present', 'absent']),
- ),
- supports_check_mode=False
- )
-
- host = module.params['host']
- user = module.params['username']
- pwd = module.params['password']
- slb_virtual = module.params['virtual_server']
- slb_virtual_ip = module.params['virtual_server_ip']
- slb_virtual_status = module.params['virtual_server_status']
- slb_virtual_port = module.params['virtual_server_port']
- slb_virtual_port_type = module.params['virtual_server_port_type']
- slb_virtual_port_status = module.params['virtual_server_port_status']
- slb_service_group = module.params['service_group']
- state = module.params['state']
-
- axapi_base_url = 'https://' + host + '/services/rest/V2.1/?format=json'
- vport_types = {'tcp': 2,
- 'udp': 3,
- 'fast-http': 9,
- 'http': 11,
- 'https': 12}
-
- if slb_virtual_status == 'enabled':
- status = '1'
- else:
- status = '0'
-
- if slb_virtual_port_status == 'enabled':
- port_status = '1'
- else:
- port_status = '0'
-
- if slb_virtual is None:
- module.fail_json(msg='virtual_server is required')
-
- try:
- session_url = axapi_authenticate(axapi_base_url, user, pwd)
-
- if state == 'present':
- find_slb_virtual = axapi_call(session_url +
- '&method=slb.virtual_server.search',
- json.dumps({'name': slb_virtual}))
- slb_virtual_fail = re.search('status": "fail',
- find_slb_virtual, re.I)
-
- if slb_virtual_fail:
- if slb_virtual_port is None and slb_virtual_port_type is None \
- and slb_service_group is None:
- json_post = {'virtual_server': {'name': slb_virtual,
- 'address': slb_virtual_ip,
- 'status': status}}
- elif slb_virtual_port is not None and \
- slb_virtual_port_type is not None and \
- slb_service_group is None:
- json_post = {'virtual_server':
- {'name': slb_virtual,
- 'address': slb_virtual_ip,
- 'status': status,
- 'vport_list':
- [{'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port}]}}
- elif slb_virtual_port is not None and \
- slb_virtual_port_type is not None and \
- slb_service_group is not None:
- json_post = {'virtual_server':
- {'name': slb_virtual,
- 'address': slb_virtual_ip,
- 'status': status, 'vport_list':
- [{'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port,
- 'service_group': slb_service_group}]}}
- else:
- module.fail_json(msg='virtual_server_port and
- virtual_server_type are required to
- create the virtual port')
-
- response = axapi_call(session_url +
- '&method=slb.virtual_server.create',
- json.dumps(json_post))
- else:
- response = axapi_call(session_url +
- '&method=slb.virtual_server.search',
- json.dumps({'name': slb_virtual}))
- slb_virtual_port_exist = re.search('"port":' +
- str(slb_virtual_port)
- response, re.I)
- current_status = json.loads(response)['virtual_server']['status']
- current_port_status = 1
-
- if slb_virtual_port_exist:
- vport_list = json.loads(response)['virtual_server']['vport_list']
- if vport_list:
- for port in range(len(vport_list)):
- if slb_virtual_port == str(vport_list[port]['port']):
- current_port_status = vport_list[port]['port']
-
- json_post = {'address': slb_virtual_ip,
- 'vport':
- {'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port,
- 'service_group': slb_service_group},
- 'status': port_status}
- response = axapi_call(session_url +
- '&method=slb.virtual_server.\
- vport.update', json.dumps(json_post))
- else:
- if slb_service_group is None:
- module.fail_json(msg='service_group is required')
- json_post = {'name': slb_virtual,
- 'vport':
- {'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port,
- 'service_group': slb_service_group},
- 'status': port_status}
- response = axapi_call(session_url +
- '&method=slb.virtual_server.\
- vport.create', json.dumps(json_post))
-
- if current_status != status:
- json_post = {'virtual_server':
- {'name': slb_virtual,
- 'address': slb_virtual_ip,
- 'status': status}}
- response = axapi_call(session_url +
- '&method=slb.virtual_server.update',
- json.dumps(json_post))
-
- if current_port_status != port_status:
- json_post = {'address': slb_virtual_ip, 'vport':
- {'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port},
- 'status': port_status}
- response = axapi_call(session_url +
- '&method=slb.virtual_server.\
- vport.update', json.dumps(json_post))
-
- if state == 'absent':
- if slb_virtual_port is not None and \
- slb_virtual_port_type is not None:
- response = axapi_call(session_url +
- '&method=slb.virtual_server.\
- vport.delete',
- json.dumps({'name': slb_virtual,
- 'vport':
- {'protocol':
- vport_types[slb_virtual_port_type],
- 'port': slb_virtual_port}}))
- elif slb_virtual_port is None and slb_virtual_port_type is None:
- response = axapi_call(session_url +
- '&method=slb.virtual_server.delete',
- json.dumps({'name': slb_virtual}))
- else:
- module.fail_json(msg='virtual_server_port and \
- virtual_server_type are required to remove \
- the virtual port')
-
- result = json.loads(response)
- axapi_call(session_url + '&method=session.close')
-
- except Exception, e:
- return module.fail_json(msg='received exception: %s' % e)
-
- if 'respone' in result and 'err' in result['response']:
- return module.fail_json(msg=result['response']['err']['msg'])
-
- module.exit_json(changed=True, content=result)
-
-from ansible.module_utils.basic import *
-main()
diff --git a/net_infrastructure/a10_virtual_server b/net_infrastructure/a10_virtual_server
new file mode 100644
index 00000000000..3d807c098cf
--- /dev/null
+++ b/net_infrastructure/a10_virtual_server
@@ -0,0 +1,299 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+"""
+Ansible module to manage A10 Networks slb virtual server objects
+(c) 2014, Mischa Peters
+
+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 .
+"""
+
+DOCUMENTATION = '''
+---
+module: a10_virtual_server
+version_added: 1.8
+short_description: Manage A10 Networks AX/SoftAX/Thunder/vThunder devices
+description:
+ - Manage slb virtual server objects on A10 Networks devices via aXAPI
+author: Mischa Peters
+notes:
+ - Requires A10 Networks aXAPI 2.1
+requirements:
+ - urllib2
+ - re
+options:
+ host:
+ description:
+ - hostname or ip of your A10 Networks device
+ required: true
+ default: null
+ aliases: []
+ choices: []
+ username:
+ description:
+ - admin account of your A10 Networks device
+ required: true
+ default: null
+ aliases: ['user', 'admin']
+ choices: []
+ password:
+ description:
+ - admin password of your A10 Networks device
+ required: true
+ default: null
+ aliases: ['pass', 'pwd']
+ choices: []
+ virtual_server:
+ description:
+ - slb virtual server name
+ required: true
+ default: null
+ aliases: ['vip', 'virtual']
+ choices: []
+ virtual_server_ip:
+ description:
+ - slb virtual server ip address
+ required: false
+ default: null
+ aliases: ['ip', 'address']
+ choices: []
+ virtual_server_status:
+ description:
+ - slb virtual server status
+ required: false
+ default: enable
+ aliases: ['status']
+ choices: ['enabled', 'disabled']
+ virtual_server_ports:
+ description:
+ - A list of ports to create for the virtual server. Each list item should be a
+ dictionary which specifies the C(port:) and C(type:), but can also optionally
+ specify the C(service_group:) as well as the C(status:). See the examples
+ below for details. This parameter is required when C(state) is C(present).
+ required: false
+ write_config:
+ description:
+ - If C(yes), any changes will cause a write of the running configuration
+ to non-volatile memory. This will save I(all) configuration changes,
+ including those that may have been made manually or through other modules,
+ so care should be taken when specifying C(yes).
+ required: false
+ default: "no"
+ choices: ["yes", "no"]
+ validate_certs:
+ description:
+ - If C(no), SSL certificates will not be validated. This should only be used
+ on personally controlled devices using self-signed certificates.
+ required: false
+ default: 'yes'
+ choices: ['yes', 'no']
+
+'''
+
+EXAMPLES = '''
+# Create a new virtual server
+- a10_virtual_server:
+ host: a10.mydomain.com
+ username: myadmin
+ password: mypassword
+ virtual_server: vserver1
+ virtual_server_ip: 1.1.1.1
+ virtual_server_ports:
+ - port: 80
+ protocol: TCP
+ service_group: sg-80-tcp
+ - port: 443
+ protocol: HTTPS
+ service_group: sg-443-https
+ - port: 8080
+ protocol: http
+ status: disabled
+
+'''
+
+VALID_PORT_FIELDS = ['port', 'protocol', 'service_group', 'status']
+
+def validate_ports(module, ports):
+ for item in ports:
+ for key in item:
+ if key not in VALID_PORT_FIELDS:
+ module.fail_json(msg="invalid port field (%s), must be one of: %s" % (key, ','.join(VALID_PORT_FIELDS)))
+
+ # validate the port number is present and an integer
+ if 'port' in item:
+ try:
+ item['port'] = int(item['port'])
+ except:
+ module.fail_json(msg="port definitions must be integers")
+ else:
+ module.fail_json(msg="port definitions must define the port field")
+
+ # validate the port protocol is present, and convert it to
+ # the internal API integer value (and validate it)
+ if 'protocol' in item:
+ protocol = axapi_get_vport_protocol(item['protocol'])
+ if not protocol:
+ module.fail_json(msg="invalid port protocol, must be one of: %s" % ','.join(AXAPI_VPORT_PROTOCOLS))
+ else:
+ item['protocol'] = protocol
+ else:
+ module.fail_json(msg="port definitions must define the port protocol (%s)" % ','.join(AXAPI_VPORT_PROTOCOLS))
+
+ # convert the status to the internal API integer value
+ if 'status' in item:
+ item['status'] = axapi_enabled_disabled(item['status'])
+ else:
+ item['status'] = 1
+
+ # ensure the service_group field is at least present
+ if 'service_group' not in item:
+ item['service_group'] = ''
+
+def main():
+ argument_spec = a10_argument_spec()
+ argument_spec.update(url_argument_spec())
+ argument_spec.update(
+ dict(
+ state=dict(type='str', default='present', choices=['present', 'absent']),
+ virtual_server=dict(type='str', aliases=['vip', 'virtual'], required=True),
+ virtual_server_ip=dict(type='str', aliases=['ip', 'address'], required=True),
+ virtual_server_status=dict(type='str', default='enabled', aliases=['status'], choices=['enabled', 'disabled']),
+ virtual_server_ports=dict(type='list', required=True),
+ )
+ )
+
+ module = AnsibleModule(
+ argument_spec=argument_spec,
+ supports_check_mode=False
+ )
+
+ host = module.params['host']
+ username = module.params['username']
+ password = module.params['password']
+ state = module.params['state']
+ write_config = module.params['write_config']
+ slb_virtual = module.params['virtual_server']
+ slb_virtual_ip = module.params['virtual_server_ip']
+ slb_virtual_status = module.params['virtual_server_status']
+ slb_virtual_ports = module.params['virtual_server_ports']
+
+ if slb_virtual is None:
+ module.fail_json(msg='virtual_server is required')
+
+ validate_ports(module, slb_virtual_ports)
+
+ axapi_base_url = 'https://%s/services/rest/V2.1/?format=json' % host
+ session_url = axapi_authenticate(module, axapi_base_url, username, password)
+
+ slb_virtual_data = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual}))
+ slb_virtual_exists = not axapi_failure(slb_virtual_data)
+
+ changed = False
+ if state == 'present':
+ json_post = {
+ 'virtual_server': {
+ 'name': slb_virtual,
+ 'address': slb_virtual_ip,
+ 'status': axapi_enabled_disabled(slb_virtual_status),
+ 'vport_list': slb_virtual_ports,
+ }
+ }
+
+ # before creating/updating we need to validate that any
+ # service groups defined in the ports list exist since
+ # since the API will still create port definitions for
+ # them while indicating a failure occurred
+ checked_service_groups = []
+ for port in slb_virtual_ports:
+ if 'service_group' in port and port['service_group'] not in checked_service_groups:
+ # skip blank service group entries
+ if port['service_group'] == '':
+ continue
+ result = axapi_call(module, session_url + '&method=slb.service_group.search', json.dumps({'name': port['service_group']}))
+ if axapi_failure(result):
+ module.fail_json(msg="the service group %s specified in the ports list does not exist" % port['service_group'])
+ checked_service_groups.append(port['service_group'])
+
+ if not slb_virtual_exists:
+ result = axapi_call(module, session_url + '&method=slb.virtual_server.create', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg'])
+ changed = True
+ else:
+ def needs_update(src_ports, dst_ports):
+ '''
+ Checks to determine if the port definitions of the src_ports
+ array are in or different from those in dst_ports. If there is
+ a difference, this function returns true, otherwise false.
+ '''
+ for src_port in src_ports:
+ found = False
+ different = False
+ for dst_port in dst_ports:
+ if src_port['port'] == dst_port['port']:
+ found = True
+ for valid_field in VALID_PORT_FIELDS:
+ if src_port[valid_field] != dst_port[valid_field]:
+ different = True
+ break
+ if found or different:
+ break
+ if not found or different:
+ return True
+ # every port from the src exists in the dst, and none of them were different
+ return False
+
+ defined_ports = slb_virtual_data.get('virtual_server', {}).get('vport_list', [])
+
+ # we check for a needed update both ways, in case ports
+ # are missing from either the ones specified by the user
+ # or from those on the device
+ if needs_update(defined_ports, slb_virtual_ports) or needs_update(slb_virtual_ports, defined_ports):
+ result = axapi_call(module, session_url + '&method=slb.virtual_server.update', json.dumps(json_post))
+ if axapi_failure(result):
+ module.fail_json(msg="failed to create the virtual server: %s" % result['response']['err']['msg'])
+ changed = True
+
+ # if we changed things, get the full info regarding
+ # the service group for the return data below
+ if changed:
+ result = axapi_call(module, session_url + '&method=slb.virtual_server.search', json.dumps({'name': slb_virtual}))
+ else:
+ result = slb_virtual_data
+ elif state == 'absent':
+ if slb_virtual_exists:
+ result = axapi_call(module, session_url + '&method=slb.virtual_server.delete', json.dumps({'name': slb_virtual}))
+ changed = True
+ else:
+ result = dict(msg="the virtual server was not present")
+
+ # if the config has changed, save the config unless otherwise requested
+ if changed and write_config:
+ write_result = axapi_call(module, session_url + '&method=system.action.write_memory')
+ if axapi_failure(write_result):
+ module.fail_json(msg="failed to save the configuration: %s" % write_result['response']['err']['msg'])
+
+ # log out of the session nicely and exit
+ axapi_call(module, session_url + '&method=session.close')
+ module.exit_json(changed=changed, content=result)
+
+# standard ansible module imports
+from ansible.module_utils.basic import *
+from ansible.module_utils.urls import *
+from ansible.module_utils.a10 import *
+
+main()
+