mirror of https://github.com/ansible/ansible.git
Avi Networks Ansible modules. (#20415)
* Avi Networks Ansible modules. Avi Version: 16.3.4 * Fixed Review comments 1. Changed description to be full sentences 2. Fixed Pep8 warnings. 3. Fixed comments and descriptions. * 1. Fixed descriptions and messages as per review comments. 2. Added descriptions for the missing parameters. * Fixed the shippable break due to the incorrect description format * Removed the extra modules so that there is a single module for the first commit * Updated license to BSD based on review comments * updated comments based on review feedback * Refactored code to handle POST and PUT scenarios where playbook does not need to check whether object is present. Moved ansible helper utilities to module_utils as now roles can be patched with module_utils as well. * fixed pep8 warningspull/21148/head
parent
9f3870ddcd
commit
798972c72a
@ -0,0 +1,64 @@
|
|||||||
|
# This code is part of Ansible, but is an independent component.
|
||||||
|
# This particular file snippet, and this file snippet only, is BSD licensed.
|
||||||
|
# Modules you write using this snippet, which is embedded dynamically by Ansible
|
||||||
|
# still belong to the author of the module, and may assign their own license
|
||||||
|
# to the complete work.
|
||||||
|
#
|
||||||
|
# Copyright (c), Gaurav Rastogi <grastogi@avinetworks.com>, 2017
|
||||||
|
# All rights reserved.
|
||||||
|
#
|
||||||
|
# Redistribution and use in source and binary forms, with or without modification,
|
||||||
|
# are permitted provided that the following conditions are met:
|
||||||
|
#
|
||||||
|
# * Redistributions of source code must retain the above copyright
|
||||||
|
# notice, this list of conditions and the following disclaimer.
|
||||||
|
# * Redistributions in binary form must reproduce the above copyright notice,
|
||||||
|
# this list of conditions and the following disclaimer in the documentation
|
||||||
|
# and/or other materials provided with the distribution.
|
||||||
|
#
|
||||||
|
# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS" AND
|
||||||
|
# ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED
|
||||||
|
# WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE DISCLAIMED.
|
||||||
|
# IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
|
||||||
|
# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO,
|
||||||
|
# PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||||
|
# INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
|
||||||
|
# LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE
|
||||||
|
# USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.
|
||||||
|
#
|
||||||
|
|
||||||
|
import os
|
||||||
|
|
||||||
|
|
||||||
|
def avi_common_argument_spec():
|
||||||
|
"""
|
||||||
|
Returns common arguments for all Avi modules
|
||||||
|
:return: dict
|
||||||
|
"""
|
||||||
|
return dict(
|
||||||
|
controller=dict(default=os.environ.get('AVI_CONTROLLER', '')),
|
||||||
|
username=dict(default=os.environ.get('AVI_USERNAME', '')),
|
||||||
|
password=dict(default=os.environ.get('AVI_PASSWORD', ''), no_log=True),
|
||||||
|
tenant=dict(default='admin'),
|
||||||
|
tenant_uuid=dict(default=''))
|
||||||
|
|
||||||
|
|
||||||
|
def ansible_return(module, rsp, changed, req=None, existing_obj=None):
|
||||||
|
"""
|
||||||
|
Helper function to return the right ansible return based on the error code and
|
||||||
|
changed status.
|
||||||
|
:param module: AnsibleModule
|
||||||
|
:param rsp: ApiResponse object returned from ApiSession.
|
||||||
|
:param changed: Whether something changed in this module.
|
||||||
|
:param req: Dict data for Avi API call.
|
||||||
|
:param existing_obj: Dict representing current HTTP resource in Avi Controller.
|
||||||
|
|
||||||
|
Returns: specific ansible module exit function
|
||||||
|
"""
|
||||||
|
if rsp.status_code > 299:
|
||||||
|
return module.fail_json(msg='Error %d Msg %s req: %s' % (
|
||||||
|
rsp.status_code, rsp.text, req))
|
||||||
|
if changed and existing_obj:
|
||||||
|
return module.exit_json(
|
||||||
|
changed=changed, obj=rsp.json(), old_obj=existing_obj)
|
||||||
|
return module.exit_json(changed=changed, obj=rsp.json())
|
@ -0,0 +1,240 @@
|
|||||||
|
#!/usr/bin/python
|
||||||
|
"""
|
||||||
|
# Created on Aug 12, 2016
|
||||||
|
#
|
||||||
|
# @author: Gaurav Rastogi (grastogi@avinetworks.com) GitHub ID: grastogi23
|
||||||
|
#
|
||||||
|
# module_check: not supported
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
"""
|
||||||
|
|
||||||
|
import json
|
||||||
|
import time
|
||||||
|
from ansible.module_utils.basic import AnsibleModule
|
||||||
|
from ansible.module_utils.avi import avi_common_argument_spec, ansible_return
|
||||||
|
from copy import deepcopy
|
||||||
|
|
||||||
|
HAS_AVI = True
|
||||||
|
try:
|
||||||
|
from avi.sdk.avi_api import ApiSession
|
||||||
|
from avi.sdk.utils.ansible_utils import avi_obj_cmp, cleanup_absent_fields
|
||||||
|
except ImportError:
|
||||||
|
HAS_AVI = False
|
||||||
|
|
||||||
|
|
||||||
|
DOCUMENTATION = '''
|
||||||
|
---
|
||||||
|
module: avi_api
|
||||||
|
author: Gaurav Rastogi (grastogi@avinetworks.com)
|
||||||
|
|
||||||
|
short_description: Avi API Module
|
||||||
|
description:
|
||||||
|
- This module can be used for calling any resources defined in Avi REST API.
|
||||||
|
- This module is useful for invoking HTTP Patch methods and accessing resources that do not have an REST object associated with them.
|
||||||
|
version_added: 2.3
|
||||||
|
requirements: [ avisdk ]
|
||||||
|
options:
|
||||||
|
http_method:
|
||||||
|
description:
|
||||||
|
- Allowed HTTP methods for RESTful services and are supported by Avi Controller.
|
||||||
|
choices: ["get", "put", "post", "patch", "delete"]
|
||||||
|
required: true
|
||||||
|
data:
|
||||||
|
description:
|
||||||
|
- HTTP body in YAML or JSON format.
|
||||||
|
params:
|
||||||
|
description:
|
||||||
|
- Query parameters passed to the HTTP API.
|
||||||
|
path:
|
||||||
|
description:
|
||||||
|
- Path for Avi API resource. For example, C(path: virtualservice) will translate to C(api/virtualserivce).
|
||||||
|
timeout:
|
||||||
|
description:
|
||||||
|
- Timeout (in seconds) for Avi API calls.
|
||||||
|
extends_documentation_fragment:
|
||||||
|
- avi
|
||||||
|
'''
|
||||||
|
|
||||||
|
EXAMPLES = '''
|
||||||
|
|
||||||
|
- name: Get Pool Information using avi_api_session
|
||||||
|
avi_api_session:
|
||||||
|
controller: "{{ controller }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
http_method: get
|
||||||
|
path: pool
|
||||||
|
params:
|
||||||
|
name: "{{ pool_name }}"
|
||||||
|
register: pool_results
|
||||||
|
|
||||||
|
- name: Patch Pool with list of servers
|
||||||
|
avi_api_session:
|
||||||
|
controller: "{{ controller }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
http_method: patch
|
||||||
|
path: "{{ pool_path }}"
|
||||||
|
data:
|
||||||
|
add:
|
||||||
|
servers:
|
||||||
|
- ip:
|
||||||
|
addr: 10.10.10.10
|
||||||
|
type: V4
|
||||||
|
- ip:
|
||||||
|
addr: 20.20.20.20
|
||||||
|
type: V4
|
||||||
|
register: updated_pool
|
||||||
|
|
||||||
|
- name: Fetch Pool metrics bandwidth and connections rate
|
||||||
|
avi_api_session:
|
||||||
|
controller: "{{ controller }}"
|
||||||
|
username: "{{ username }}"
|
||||||
|
password: "{{ password }}"
|
||||||
|
http_method: get
|
||||||
|
path: analytics/metrics/pool
|
||||||
|
params:
|
||||||
|
name: "{{ pool_name }}"
|
||||||
|
metric_id: l4_server.avg_bandwidth,l4_server.avg_complete_conns
|
||||||
|
step: 300
|
||||||
|
limit: 10
|
||||||
|
register: pool_metrics
|
||||||
|
|
||||||
|
'''
|
||||||
|
|
||||||
|
|
||||||
|
RETURN = '''
|
||||||
|
obj:
|
||||||
|
description: Avi REST resource
|
||||||
|
returned: success, changed
|
||||||
|
type: dict
|
||||||
|
'''
|
||||||
|
|
||||||
|
def main():
|
||||||
|
argument_specs = dict(
|
||||||
|
http_method=dict(required=True,
|
||||||
|
choices=['get', 'put', 'post', 'patch',
|
||||||
|
'delete']),
|
||||||
|
path=dict(type='str', required=True),
|
||||||
|
params=dict(type='dict'),
|
||||||
|
data=dict(type='jsonarg'),
|
||||||
|
timeout=dict(type='int', default=60)
|
||||||
|
)
|
||||||
|
argument_specs.update(avi_common_argument_spec())
|
||||||
|
module = AnsibleModule(argument_spec=argument_specs)
|
||||||
|
|
||||||
|
if not HAS_AVI:
|
||||||
|
return module.fail_json(msg=(
|
||||||
|
'Avi python API SDK (avisdk) is not installed. '
|
||||||
|
'For more details visit https://github.com/avinetworks/sdk.'))
|
||||||
|
tenant_uuid = module.params.get('tenant_uuid', None)
|
||||||
|
api = ApiSession.get_session(
|
||||||
|
module.params['controller'], module.params['username'],
|
||||||
|
module.params['password'], tenant=module.params['tenant'],
|
||||||
|
tenant_uuid=tenant_uuid)
|
||||||
|
|
||||||
|
tenant = module.params.get('tenant', '')
|
||||||
|
timeout = int(module.params.get('timeout'))
|
||||||
|
# path is a required argument
|
||||||
|
path = module.params.get('path', '')
|
||||||
|
params = module.params.get('params', None)
|
||||||
|
data = module.params.get('data', None)
|
||||||
|
|
||||||
|
if data is not None:
|
||||||
|
data = json.loads(data)
|
||||||
|
method = module.params['http_method']
|
||||||
|
|
||||||
|
existing_obj = None
|
||||||
|
changed = method != 'get'
|
||||||
|
gparams = deepcopy(params) if params else {}
|
||||||
|
gparams.update({'include_refs': '', 'include_name': ''})
|
||||||
|
|
||||||
|
if method == 'post':
|
||||||
|
# need to check if object already exists. In that case
|
||||||
|
# change the method to be put
|
||||||
|
gparams['name'] = data['name']
|
||||||
|
rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
|
||||||
|
params=gparams)
|
||||||
|
try:
|
||||||
|
existing_obj = rsp.json()['results'][0]
|
||||||
|
except IndexError:
|
||||||
|
# object is not found
|
||||||
|
pass
|
||||||
|
else:
|
||||||
|
# object is present
|
||||||
|
method = 'put'
|
||||||
|
path += '/' + existing_obj['uuid']
|
||||||
|
|
||||||
|
if method == 'put':
|
||||||
|
# put can happen with when full path is specified or it is put + post
|
||||||
|
if existing_obj is None:
|
||||||
|
using_collection = False
|
||||||
|
if (len(path.split('/')) == 1) and ('name' in data):
|
||||||
|
gparams['name'] = data['name']
|
||||||
|
using_collection = True
|
||||||
|
rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
|
||||||
|
params=gparams)
|
||||||
|
rsp_data = rsp.json()
|
||||||
|
if using_collection:
|
||||||
|
if rsp_data['results']:
|
||||||
|
existing_obj = rsp_data['results'][0]
|
||||||
|
path += '/' + existing_obj['uuid']
|
||||||
|
else:
|
||||||
|
method = 'post'
|
||||||
|
else:
|
||||||
|
if rsp.status_code == 404:
|
||||||
|
method = 'post'
|
||||||
|
else:
|
||||||
|
existing_obj = rsp_data
|
||||||
|
if existing_obj:
|
||||||
|
changed = not avi_obj_cmp(data, existing_obj)
|
||||||
|
cleanup_absent_fields(data)
|
||||||
|
if method == 'patch':
|
||||||
|
rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
|
||||||
|
params=gparams)
|
||||||
|
existing_obj = rsp.json()
|
||||||
|
|
||||||
|
if (method == 'put' and changed) or (method != 'put'):
|
||||||
|
fn = getattr(api, method)
|
||||||
|
rsp = fn(path, tenant=tenant, tenant_uuid=tenant, timeout=timeout,
|
||||||
|
params=params, data=data)
|
||||||
|
else:
|
||||||
|
rsp = None
|
||||||
|
if method == 'delete' and rsp.status_code == 404:
|
||||||
|
changed = False
|
||||||
|
rsp.status_code = 200
|
||||||
|
if method == 'patch' and existing_obj and rsp.status_code < 299:
|
||||||
|
# Ideally the comparison should happen with the return values
|
||||||
|
# from the patch API call. However, currently Avi API are
|
||||||
|
# returning different hostname when GET is used vs Patch.
|
||||||
|
# tracked as AV-12561
|
||||||
|
if path.startswith('pool'):
|
||||||
|
time.sleep(1)
|
||||||
|
gparams = deepcopy(params) if params else {}
|
||||||
|
gparams.update({'include_refs': '', 'include_name': ''})
|
||||||
|
rsp = api.get(path, tenant=tenant, tenant_uuid=tenant_uuid,
|
||||||
|
params=gparams)
|
||||||
|
new_obj = rsp.json()
|
||||||
|
changed = not avi_obj_cmp(new_obj, existing_obj)
|
||||||
|
if rsp is None:
|
||||||
|
return module.exit_json(changed=changed, obj=existing_obj)
|
||||||
|
return ansible_return(module, rsp, changed, req=data)
|
||||||
|
|
||||||
|
|
||||||
|
if __name__ == '__main__':
|
||||||
|
main()
|
@ -0,0 +1,45 @@
|
|||||||
|
#
|
||||||
|
# Created on December 12, 2016
|
||||||
|
# @author: Gaurav Rastogi (grastogi@avinetworks.com)
|
||||||
|
# Avi Version: 16.3.4
|
||||||
|
#
|
||||||
|
#
|
||||||
|
# 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/>.
|
||||||
|
#
|
||||||
|
|
||||||
|
|
||||||
|
class ModuleDocFragment(object):
|
||||||
|
# Avi common documentation fragment
|
||||||
|
DOCUMENTATION = """
|
||||||
|
options:
|
||||||
|
controller:
|
||||||
|
description:
|
||||||
|
- IP address or hostname of the controller. The default value is the environment variable C(AVI_CONTROLLER).
|
||||||
|
username:
|
||||||
|
description:
|
||||||
|
- Username used for accessing Avi controller. The default value is the environment variable C(AVI_USERNAME).
|
||||||
|
password:
|
||||||
|
description:
|
||||||
|
- Password of Avi user in Avi controller. The default value is the environment variable C(AVI_PASSWORD).
|
||||||
|
tenant:
|
||||||
|
description:
|
||||||
|
- Name of tenant used for all Avi API calls and context of object.
|
||||||
|
default: admin
|
||||||
|
tenant_uuid:
|
||||||
|
description:
|
||||||
|
- UUID of tenant used for all Avi API calls and context of object.
|
||||||
|
default: ''
|
||||||
|
"""
|
Loading…
Reference in New Issue