diff --git a/lib/ansible/modules/extras/cloud/cloudstack/cs_router.py b/lib/ansible/modules/extras/cloud/cloudstack/cs_router.py new file mode 100644 index 00000000000..7209ef19a7f --- /dev/null +++ b/lib/ansible/modules/extras/cloud/cloudstack/cs_router.py @@ -0,0 +1,383 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- +# +# (c) 2016, René Moser +# +# 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: cs_router +short_description: Manages routers on Apache CloudStack based clouds. +description: + - Start, restart, stop and destroy routers. + - C(state=present) is not able to create routers, use M(cs_network) instead. +version_added: "2.2" +author: "René Moser (@resmo)" +options: + name: + description: + - Name of the router. + required: true + service_offering: + description: + - Name or id of the service offering of the router. + required: false + default: null + domain: + description: + - Domain the router is related to. + required: false + default: null + account: + description: + - Account the router is related to. + required: false + default: null + project: + description: + - Name of the project the router is related to. + required: false + default: null + state: + description: + - State of the router. + required: false + default: 'present' + choices: [ 'present', 'absent', 'started', 'stopped', 'restarted' ] +extends_documentation_fragment: cloudstack +''' + +EXAMPLES = ''' +# Ensure the router has the desired service offering, no matter if +# the router is running or not. +- local_action: + module: cs_router + name: r-40-VM + service_offering: System Offering for Software Router + +# Ensure started +- local_action: + module: cs_router + name: r-40-VM + state: started + +# Ensure started with desired service offering. +# If the service offerings changes, router will be rebooted. +- local_action: + module: cs_router + name: r-40-VM + service_offering: System Offering for Software Router + state: started + +# Ensure stopped +- local_action: + module: cs_router + name: r-40-VM + state: stopped + +# Remove a router +- local_action: + module: cs_router + name: r-40-VM + state: absent +''' + +RETURN = ''' +--- +id: + description: UUID of the router. + returned: success + type: string + sample: 04589590-ac63-4ffc-93f5-b698b8ac38b6 +name: + description: Name of the router. + returned: success + type: string + sample: r-40-VM +created: + description: Date of the router was created. + returned: success + type: string + sample: 2014-12-01T14:57:57+0100 +template_version: + description: Version of the system VM template. + returned: success + type: string + sample: 4.5.1 +requires_upgrade: + description: Whether the router needs to be upgraded to the new template. + returned: success + type: bool + sample: false +redundant_state: + description: Redundant state of the router. + returned: success + type: string + sample: UNKNOWN +role: + description: Role of the router. + returned: success + type: string + sample: VIRTUAL_ROUTER +zone: + description: Name of zone the router is in. + returned: success + type: string + sample: ch-gva-2 +service_offering: + description: Name of the service offering the router has. + returned: success + type: string + sample: System Offering For Software Router +state: + description: State of the router. + returned: success + type: string + sample: Active +domain: + description: Domain the router is related to. + returned: success + type: string + sample: ROOT +account: + description: Account the router is related to. + returned: success + type: string + sample: admin +''' + +try: + from cs import CloudStack, CloudStackException, read_config + has_lib_cs = True +except ImportError: + has_lib_cs = False + +# import cloudstack common +from ansible.module_utils.cloudstack import * + +class AnsibleCloudStackRouter(AnsibleCloudStack): + + def __init__(self, module): + super(AnsibleCloudStackRouter, self).__init__(module) + self.returns = { + 'serviceofferingname': 'service_offering', + 'version': 'template_version', + 'requiresupgrade': 'requires_upgrade', + 'redundantstate': 'redundant_state', + 'role': 'role' + } + self.router = None + + + def get_service_offering_id(self): + service_offering = self.module.params.get('service_offering') + if not service_offering: + return None + + args = {} + args['issystem'] = True + + service_offerings = self.cs.listServiceOfferings(**args) + if service_offerings: + for s in service_offerings['serviceoffering']: + if service_offering in [ s['name'], s['id'] ]: + return s['id'] + self.module.fail_json(msg="Service offering '%s' not found" % service_offering) + + def get_router(self): + if not self.router: + router = self.module.params.get('name') + + args = {} + args['projectid'] = self.get_project(key='id') + args['account'] = self.get_account(key='name') + args['domainid'] = self.get_domain(key='id') + + routers = self.cs.listRouters(**args) + if routers: + for r in routers['router']: + if router.lower() in [ r['name'].lower(), r['id']]: + self.router = r + break + return self.router + + def start_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + if router['state'].lower() != "running": + self.result['changed'] = True + + args = {} + args['id'] = router['id'] + + if not self.module.check_mode: + res = self.cs.startRouter(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self._poll_job(res, 'router') + return router + + def stop_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + if router['state'].lower() != "stopped": + self.result['changed'] = True + + args = {} + args['id'] = router['id'] + + if not self.module.check_mode: + res = self.cs.stopRouter(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self._poll_job(res, 'router') + return router + + def reboot_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router not found") + + self.result['changed'] = True + + args = {} + args['id'] = router['id'] + + if not self.module.check_mode: + res = self.cs.rebootRouter(**args) + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + router = self._poll_job(res, 'router') + return router + + def absent_router(self): + router = self.get_router() + if router: + self.result['changed'] = True + + args = {} + args['id'] = router['id'] + + if not self.module.check_mode: + res = self.cs.destroyRouter(**args) + + if 'errortext' in res: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + + poll_async = self.module.params.get('poll_async') + if poll_async: + self._poll_job(res, 'router') + return router + + + def present_router(self): + router = self.get_router() + if not router: + self.module.fail_json(msg="Router can not be created using the API, see cs_network.") + + args = {} + args['id'] = router['id'] + args['serviceofferingid'] = self.get_service_offering_id() + + state = self.module.params.get('state') + + if self.has_changed(args, router): + self.result['changed'] = True + + if not self.module.check_mode: + current_state = router['state'].lower() + + self.stop_router() + router = self.cs.changeServiceForRouter(**args) + + if 'errortext' in router: + self.module.fail_json(msg="Failed: '%s'" % res['errortext']) + + if state in [ 'restarted', 'started' ]: + router = self.start_router() + + # if state=present we get to the state before the service + # offering change. + elif state == "present" and current_state == "running": + router = self.start_router() + + elif state == "started": + router = self.start_router() + + elif state == "stopped": + router = self.stop_router() + + elif state == "restarted": + router = self.reboot_router() + + return router + + +def main(): + argument_spec = cs_argument_spec() + argument_spec.update(dict( + name = dict(required=True), + service_offering = dict(default=None), + state = dict(choices=['present', 'started', 'stopped', 'restarted', 'absent'], default="present"), + domain = dict(default=None), + account = dict(default=None), + project = dict(default=None), + poll_async = dict(type='bool', default=True), + )) + + module = AnsibleModule( + argument_spec=argument_spec, + required_together=cs_required_together(), + supports_check_mode=True + ) + + if not has_lib_cs: + module.fail_json(msg="python library cs required: pip install cs") + + try: + acs_router = AnsibleCloudStackRouter(module) + + state = module.params.get('state') + if state in ['absent']: + router = acs_router.absent_router() + else: + router = acs_router.present_router() + + result = acs_router.get_result(router) + + except CloudStackException as e: + module.fail_json(msg='CloudStackException: %s' % str(e)) + + module.exit_json(**result) + +# import module snippets +from ansible.module_utils.basic import * +if __name__ == '__main__': + main()