From 5e02737fa5f23451bbb443bbf9d67cbac603dc46 Mon Sep 17 00:00:00 2001 From: Harry Huang Date: Thu, 1 Feb 2018 15:56:03 +0800 Subject: Ansible callback support both V2.3 and V2.4 JIRA: COMPASS-585 1. Add conditional branch to support both ansible versions 2. Keep callback in ansible_plugins directory which will contains all plugins upcoming in the future Change-Id: I11f3d9700e07ad5bd7f03ad56f4e0bc061df05ec Signed-off-by: Harry Huang --- deploy/ansible_plugins/callback/playbook_done.py | 111 +++++++++++ deploy/ansible_plugins/callback/status_callback.py | 203 +++++++++++++++++++++ .../ansible_cfg/HA-ansible-multinodes.tmpl | 2 +- deploy/playbook_done.py | 106 ----------- deploy/status_callback.py | 198 -------------------- 5 files changed, 315 insertions(+), 305 deletions(-) create mode 100644 deploy/ansible_plugins/callback/playbook_done.py create mode 100644 deploy/ansible_plugins/callback/status_callback.py delete mode 100644 deploy/playbook_done.py delete mode 100644 deploy/status_callback.py (limited to 'deploy') diff --git a/deploy/ansible_plugins/callback/playbook_done.py b/deploy/ansible_plugins/callback/playbook_done.py new file mode 100644 index 00000000..4784ff63 --- /dev/null +++ b/deploy/ansible_plugins/callback/playbook_done.py @@ -0,0 +1,111 @@ +#!/usr/bin/env python +# +# Copyright 2014 Huawei Technologies Co. Ltd +# +# Licensed under the Apache License, Version 2.0 (the "License"); +# you may not use this file except in compliance with the License. +# You may obtain a copy of the License at +# +# http://www.apache.org/licenses/LICENSE-2.0 +# +# Unless required by applicable law or agreed to in writing, software +# distributed under the License is distributed on an "AS IS" BASIS, +# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +# See the License for the specific language governing permissions and +# limitations under the License. + +"""Ansible playbook callback after a playbook run has completed.""" +import sys + +from distutils.version import LooseVersion +from ansible import __version__ as __ansible_version__ +from ansible.plugins.callback import CallbackBase + +compass_bin = "/opt/compass/bin" +sys.path.append(compass_bin) + +import switch_virtualenv # noqa: F401 + +from compass.apiclient.restful import Client # noqa: E402 +from compass.utils import flags # noqa: E402 + + +flags.add('compass_server', + help='compass server url', + default='http://compass-deck/api') +flags.add('compass_user_email', + help='compass user email', + default='admin@huawei.com') +flags.add('compass_user_password', + help='compass user password', + default='admin') + + +class CallbackModule(CallbackBase): + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'notification' + CALLBACK_NAME = 'playbook_done' + CALLBACK_NEEDS_WHITELIST = True + + def __init__(self): + super(CallbackModule, self).__init__() + + self.play = None + self.loader = None + self.disabled = False + try: + self.client = self._get_client() + except Exception: + self.disabled = True + self._display.error("No compass server found" + "disabling this plugin") + + def _get_client(self): + return Client(flags.OPTIONS.compass_server) + + def _login(self, client): + """get apiclient token.""" + status, resp = client.get_token( + flags.OPTIONS.compass_user_email, + flags.OPTIONS.compass_user_password + ) + self._display.warning( + 'login status: %s, resp: %s' % + (status, resp) + ) + if status >= 400: + raise Exception( + 'failed to login %s with user %s', + flags.OPTIONS.compass_server, + flags.OPTIONS.compass_user_email + ) + return resp['token'] + + def v2_playbook_on_play_start(self, play): + self.play = play + self.loader = self.play.get_loader() + return + + def v2_playbook_on_stats(self, stats): + if LooseVersion(__ansible_version__) < LooseVersion("2.4"): + all_vars = self.play.get_variable_manager().get_vars(self.loader) + else: + all_vars = self.play.get_variable_manager().get_vars() + host_vars = all_vars["hostvars"] + hosts = sorted(stats.processed.keys()) + cluster_name = host_vars[hosts[0]]['cluster_name'] + self._display.warning("cluster_name %s" % cluster_name) + + failures = False + unreachable = False + + for host in hosts: + summary = stats.summarize(host) + + if summary['failures'] > 0: + failures = True + if summary['unreachable'] > 0: + unreachable = True + + if failures or unreachable: + return diff --git a/deploy/ansible_plugins/callback/status_callback.py b/deploy/ansible_plugins/callback/status_callback.py new file mode 100644 index 00000000..b87d2094 --- /dev/null +++ b/deploy/ansible_plugins/callback/status_callback.py @@ -0,0 +1,203 @@ +############################################################################## +# Copyright (c) 2016 HUAWEI TECHNOLOGIES CO.,LTD and others. +# +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Apache License, Version 2.0 +# which accompanies this distribution, and is available at +# http://www.apache.org/licenses/LICENSE-2.0 +############################################################################## + +import httplib +import simplejson as json +import sys # noqa:F401 + +from distutils.version import LooseVersion +from ansible import __version__ as __ansible_version__ +from ansible.plugins.callback import CallbackBase + +COMPASS_HOST = "compass-deck" + + +# def task_error(display, host, data): +# display.display("task_error: host=%s,data=%s" % (host, data)) +# +# if isinstance(data, dict): +# invocation = data.pop('invocation', {}) +# +# notify_host(display, COMPASS_HOST, host, "failed") + + +class CallbackModule(CallbackBase): + """ + logs playbook results, per host, in /var/log/ansible/hosts + """ + CALLBACK_VERSION = 2.0 + CALLBACK_TYPE = 'notification' + CALLBACK_NAME = 'status_callback' + CALLBACK_NEEDS_WHITELIST = True + + def __init__(self): + super(CallbackModule, self).__init__() + + def v2_on_any(self, *args, **kwargs): + pass + + def v2_runner_on_failed(self, res, ignore_errors=False): + # task_error(self._display, host, res) + pass + + def v2_runner_on_ok(self, res): + pass + + def v2_runner_on_skipped(self, host, item=None): + pass + + def v2_runner_on_unreachable(self, host, res): + pass + + def v2_runner_on_no_hosts(self): + pass + + def v2_runner_on_async_poll(self, host, res, jid, clock): + pass + + def v2_runner_on_async_ok(self, host, res, jid): + pass + + def v2_runner_on_async_failed(self, host, res, jid): + # task_error(self._display, host, res) + pass + + def v2_playbook_on_start(self): + pass + + def v2_playbook_on_notify(self, host, handler): + pass + + def v2_playbook_on_no_hosts_matched(self): + pass + + def v2_playbook_on_no_hosts_remaining(self): + pass + + def v2_playbook_on_task_start(self, name, is_conditional): + pass + + def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None, + encrypt=None, confirm=False, salt_size=None, salt=None, default=None): # noqa + pass + + def v2_playbook_on_setup(self): + pass + + def v2_playbook_on_import_for_host(self, host, imported_file): + pass + + def v2_playbook_on_not_import_for_host(self, host, missing_file): + pass + + def v2_playbook_on_play_start(self, play): + self.play = play + self.loader = self.play.get_loader() + return + + def v2_playbook_on_stats(self, stats): + self._display.display("playbook_on_stats enter") + if LooseVersion(__ansible_version__) < LooseVersion("2.4"): + all_vars = self.play.get_variable_manager().get_vars(self.loader) + else: + all_vars = self.play.get_variable_manager().get_vars() + host_vars = all_vars["hostvars"] + hosts = sorted(stats.processed.keys()) + cluster_name = host_vars[hosts[0]]['cluster_name'] + + headers = {"Content-type": "application/json", + "Accept": "*/*"} + conn = httplib.HTTPConnection(COMPASS_HOST, 80) + token = auth(conn) + headers["X-Auth-Token"] = token + get_url = "/api/clusterhosts" + conn.request("GET", get_url, "", headers) + resp = conn.getresponse() + raise_for_status(resp) + clusterhost_data = json.loads(resp.read()) + clusterhost_mapping = {} + for item in clusterhost_data: + if item["clustername"] == cluster_name: + clusterhost_mapping.update({item["hostname"]: + item["clusterhost_id"]}) + + force_error = False + if "localhost" in hosts: + summary = stats.summarize("localhost") + if summary['failures'] > 0 or summary['unreachable'] > 0: + force_error = True + + for hostname, hostid in clusterhost_mapping.iteritems(): + if hostname not in hosts: + continue + + summary = stats.summarize(hostname) + # self._display.display("host: %s \nsummary: %s\n" % (host, summary)) # noqa + + if summary['failures'] > 0 or summary['unreachable'] > 0 \ + or force_error: + status = "error" + else: + status = "succ" + self._display.display("hostname: %s" % hostname) + notify_host(self._display, COMPASS_HOST, hostid, status) + + +def raise_for_status(resp): + if resp.status < 200 or resp.status > 300: + raise RuntimeError( + "%s, %s, %s" % + (resp.status, resp.reason, resp.read())) + + +def auth(conn): + credential = {} + credential['email'] = "admin@huawei.com" + credential['password'] = "admin" + url = "/api/users/token" + headers = {"Content-type": "application/json", + "Accept": "*/*"} + conn.request("POST", url, json.dumps(credential), headers) + resp = conn.getresponse() + + raise_for_status(resp) + return json.loads(resp.read())["token"] + + +def notify_host(display, compass_host, hostid, status): + url = "/api/clusterhosts/%s/state" % hostid + if status == "succ": + body = {"state": "SUCCESSFUL"} + elif status == "error": + body = {"state": "ERROR"} + else: + display.error("notify_host: hostid %s with status %s is not supported" + % (hostid, status)) + return + + headers = {"Content-type": "application/json", + "Accept": "*/*"} + + conn = httplib.HTTPConnection(compass_host, 80) + token = auth(conn) + headers["X-Auth-Token"] = token + display.display("host=%s,url=%s,body=%s,headers=%s" % + (compass_host, url, json.dumps(body), headers)) + conn.request("POST", url, json.dumps(body), headers) + resp = conn.getresponse() + try: + raise_for_status(resp) + display.display( + "notify host status success!!! status=%s, body=%s" % + (resp.status, resp.read())) + except Exception as e: + display.error("http request failed %s" % str(e)) + raise + finally: + conn.close() diff --git a/deploy/compass_conf/templates/ansible_installer/openstack_pike/ansible_cfg/HA-ansible-multinodes.tmpl b/deploy/compass_conf/templates/ansible_installer/openstack_pike/ansible_cfg/HA-ansible-multinodes.tmpl index cd8c8d30..1d0d6478 100755 --- a/deploy/compass_conf/templates/ansible_installer/openstack_pike/ansible_cfg/HA-ansible-multinodes.tmpl +++ b/deploy/compass_conf/templates/ansible_installer/openstack_pike/ansible_cfg/HA-ansible-multinodes.tmpl @@ -3,7 +3,7 @@ log_path = /var/ansible/run/openstack_pike-$cluster_name/ansible.log host_key_checking = False callback_whitelist = playbook_done, status_callback -callback_plugins = /opt/ansible_callbacks +callback_plugins = /opt/ansible_plugins/callback forks=100 [ssh_connection] diff --git a/deploy/playbook_done.py b/deploy/playbook_done.py deleted file mode 100644 index 6b1043d4..00000000 --- a/deploy/playbook_done.py +++ /dev/null @@ -1,106 +0,0 @@ -#!/usr/bin/env python -# -# Copyright 2014 Huawei Technologies Co. Ltd -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -"""Ansible playbook callback after a playbook run has completed.""" -import sys - -from ansible.plugins.callback import CallbackBase - -compass_bin = "/opt/compass/bin" -sys.path.append(compass_bin) - -import switch_virtualenv # noqa: F401 - -from compass.apiclient.restful import Client # noqa: E402 -from compass.utils import flags # noqa: E402 - - -flags.add('compass_server', - help='compass server url', - default='http://compass-deck/api') -flags.add('compass_user_email', - help='compass user email', - default='admin@huawei.com') -flags.add('compass_user_password', - help='compass user password', - default='admin') - - -class CallbackModule(CallbackBase): - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'notification' - CALLBACK_NAME = 'playbook_done' - CALLBACK_NEEDS_WHITELIST = True - - def __init__(self): - super(CallbackModule, self).__init__() - - self.play = None - self.loader = None - self.disabled = False - try: - self.client = self._get_client() - except Exception: - self.disabled = True - self._display.error("No compass server found" - "disabling this plugin") - - def _get_client(self): - return Client(flags.OPTIONS.compass_server) - - def _login(self, client): - """get apiclient token.""" - status, resp = client.get_token( - flags.OPTIONS.compass_user_email, - flags.OPTIONS.compass_user_password - ) - self._display.warning( - 'login status: %s, resp: %s' % - (status, resp) - ) - if status >= 400: - raise Exception( - 'failed to login %s with user %s', - flags.OPTIONS.compass_server, - flags.OPTIONS.compass_user_email - ) - return resp['token'] - - def v2_playbook_on_play_start(self, play): - self.play = play - self.loader = self.play.get_loader() - return - - def v2_playbook_on_stats(self, stats): - all_vars = self.play.get_variable_manager().get_vars(self.loader) - host_vars = all_vars["hostvars"] - hosts = sorted(stats.processed.keys()) - cluster_name = host_vars[hosts[0]]['cluster_name'] - self._display.warning("cluster_name %s" % cluster_name) - - failures = False - unreachable = False - - for host in hosts: - summary = stats.summarize(host) - - if summary['failures'] > 0: - failures = True - if summary['unreachable'] > 0: - unreachable = True - - if failures or unreachable: - return diff --git a/deploy/status_callback.py b/deploy/status_callback.py deleted file mode 100644 index 6169b87f..00000000 --- a/deploy/status_callback.py +++ /dev/null @@ -1,198 +0,0 @@ -############################################################################## -# Copyright (c) 2016 HUAWEI TECHNOLOGIES CO.,LTD and others. -# -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Apache License, Version 2.0 -# which accompanies this distribution, and is available at -# http://www.apache.org/licenses/LICENSE-2.0 -############################################################################## - -import httplib -import simplejson as json -import sys # noqa:F401 - -from ansible.plugins.callback import CallbackBase - -COMPASS_HOST = "compass-deck" - - -# def task_error(display, host, data): -# display.display("task_error: host=%s,data=%s" % (host, data)) -# -# if isinstance(data, dict): -# invocation = data.pop('invocation', {}) -# -# notify_host(display, COMPASS_HOST, host, "failed") - - -class CallbackModule(CallbackBase): - """ - logs playbook results, per host, in /var/log/ansible/hosts - """ - CALLBACK_VERSION = 2.0 - CALLBACK_TYPE = 'notification' - CALLBACK_NAME = 'status_callback' - CALLBACK_NEEDS_WHITELIST = True - - def __init__(self): - super(CallbackModule, self).__init__() - - def v2_on_any(self, *args, **kwargs): - pass - - def v2_runner_on_failed(self, res, ignore_errors=False): - # task_error(self._display, host, res) - pass - - def v2_runner_on_ok(self, res): - pass - - def v2_runner_on_skipped(self, host, item=None): - pass - - def v2_runner_on_unreachable(self, host, res): - pass - - def v2_runner_on_no_hosts(self): - pass - - def v2_runner_on_async_poll(self, host, res, jid, clock): - pass - - def v2_runner_on_async_ok(self, host, res, jid): - pass - - def v2_runner_on_async_failed(self, host, res, jid): - # task_error(self._display, host, res) - pass - - def v2_playbook_on_start(self): - pass - - def v2_playbook_on_notify(self, host, handler): - pass - - def v2_playbook_on_no_hosts_matched(self): - pass - - def v2_playbook_on_no_hosts_remaining(self): - pass - - def v2_playbook_on_task_start(self, name, is_conditional): - pass - - def v2_playbook_on_vars_prompt(self, varname, private=True, prompt=None, - encrypt=None, confirm=False, salt_size=None, salt=None, default=None): # noqa - pass - - def v2_playbook_on_setup(self): - pass - - def v2_playbook_on_import_for_host(self, host, imported_file): - pass - - def v2_playbook_on_not_import_for_host(self, host, missing_file): - pass - - def v2_playbook_on_play_start(self, play): - self.play = play - self.loader = self.play.get_loader() - return - - def v2_playbook_on_stats(self, stats): - self._display.display("playbook_on_stats enter") - all_vars = self.play.get_variable_manager().get_vars(self.loader) - host_vars = all_vars["hostvars"] - hosts = sorted(stats.processed.keys()) - cluster_name = host_vars[hosts[0]]['cluster_name'] - - headers = {"Content-type": "application/json", - "Accept": "*/*"} - conn = httplib.HTTPConnection(COMPASS_HOST, 80) - token = auth(conn) - headers["X-Auth-Token"] = token - get_url = "/api/clusterhosts" - conn.request("GET", get_url, "", headers) - resp = conn.getresponse() - raise_for_status(resp) - clusterhost_data = json.loads(resp.read()) - clusterhost_mapping = {} - for item in clusterhost_data: - if item["clustername"] == cluster_name: - clusterhost_mapping.update({item["hostname"]: - item["clusterhost_id"]}) - - force_error = False - if "localhost" in hosts: - summary = stats.summarize("localhost") - if summary['failures'] > 0 or summary['unreachable'] > 0: - force_error = True - - for hostname, hostid in clusterhost_mapping.iteritems(): - if hostname not in hosts: - continue - - summary = stats.summarize(hostname) - # self._display.display("host: %s \nsummary: %s\n" % (host, summary)) # noqa - - if summary['failures'] > 0 or summary['unreachable'] > 0 \ - or force_error: - status = "error" - else: - status = "succ" - self._display.display("hostname: %s" % hostname) - notify_host(self._display, COMPASS_HOST, hostid, status) - - -def raise_for_status(resp): - if resp.status < 200 or resp.status > 300: - raise RuntimeError( - "%s, %s, %s" % - (resp.status, resp.reason, resp.read())) - - -def auth(conn): - credential = {} - credential['email'] = "admin@huawei.com" - credential['password'] = "admin" - url = "/api/users/token" - headers = {"Content-type": "application/json", - "Accept": "*/*"} - conn.request("POST", url, json.dumps(credential), headers) - resp = conn.getresponse() - - raise_for_status(resp) - return json.loads(resp.read())["token"] - - -def notify_host(display, compass_host, hostid, status): - url = "/api/clusterhosts/%s/state" % hostid - if status == "succ": - body = {"state": "SUCCESSFUL"} - elif status == "error": - body = {"state": "ERROR"} - else: - display.error("notify_host: hostid %s with status %s is not supported" - % (hostid, status)) - return - - headers = {"Content-type": "application/json", - "Accept": "*/*"} - - conn = httplib.HTTPConnection(compass_host, 80) - token = auth(conn) - headers["X-Auth-Token"] = token - display.display("host=%s,url=%s,body=%s,headers=%s" % - (compass_host, url, json.dumps(body), headers)) - conn.request("POST", url, json.dumps(body), headers) - resp = conn.getresponse() - try: - raise_for_status(resp) - display.display( - "notify host status success!!! status=%s, body=%s" % - (resp.status, resp.read())) - except Exception as e: - display.error("http request failed %s" % str(e)) - raise - finally: - conn.close() -- cgit 1.2.3-korg