Grab failure details from pipeline jobs

- if a job fails, try to get the stages from the job
- if there are stages, get from non-SUCCESS stages all the
  workflow 'nodes' (the commands performed in the stage, like
  'running shell script' or 'echo'), and get the log from the
  failed node only.
- if there are no stages found (assuming that this is
  a pipeline job), get the complete job console output

When a pipeline job failed, the parent job is failed throwing
the exception contaied the found details.

Change-Id: Ie5c2171e5373345b1951de55ba604b5d484340d3
diff --git a/tcp_tests/managers/jenkins/client.py b/tcp_tests/managers/jenkins/client.py
index 2628e7a..fbd5c43 100644
--- a/tcp_tests/managers/jenkins/client.py
+++ b/tcp_tests/managers/jenkins/client.py
@@ -2,6 +2,7 @@
 import time
 
 import jenkins
+import json
 import requests
 
 from devops.helpers import helpers
@@ -35,10 +36,8 @@
 
 class JenkinsClient(object):
 
-    def __init__(self, host=None, username=None, password=None):
+    def __init__(self, host=None, username='admin', password='r00tme'):
         host = host or 'http://172.16.44.33:8081'
-        username = username or 'admin'
-        password = password or 'r00tme'
         # self.__client = jenkins.Jenkins(
         self.__client = JenkinsWrapper(
             host,
@@ -167,3 +166,27 @@
                 'GET',
                 self.__client._build_url(PROGRESSIVE_CONSOLE_OUTPUT, locals()))
         return(self.__client.jenkins_request(req))
+
+    def get_workflow(self, name, build_id, enode=None, mode='describe'):
+        '''Get workflow results from pipeline job
+
+        :param name: job name
+        :param build_id: str, build number or 'lastBuild'
+        :param enode: int, execution node in the workflow
+        :param mode: the stage or execution node description if 'describe',
+                     the execution node log if 'log'
+        '''
+        folder_url, short_name = self.__client._get_job_folder(name)
+
+        if enode:
+            WORKFLOW_DESCRIPTION = (
+                '%(folder_url)sjob/%(short_name)s/%(build_id)s/'
+                'execution/node/%(enode)d/wfapi/%(mode)s')
+        else:
+            WORKFLOW_DESCRIPTION = (
+                '%(folder_url)sjob/%(short_name)s/%(build_id)s/wfapi/%(mode)s')
+        req = requests.Request(
+                'GET',
+                self.__client._build_url(WORKFLOW_DESCRIPTION, locals()))
+        response = self.__client.jenkins_open(req)
+        return json.loads(response)
diff --git a/tcp_tests/utils/get_jenkins_job_stages.py b/tcp_tests/utils/get_jenkins_job_stages.py
new file mode 100755
index 0000000..143e1a2
--- /dev/null
+++ b/tcp_tests/utils/get_jenkins_job_stages.py
@@ -0,0 +1,136 @@
+#    Copyright 2017 Mirantis, Inc.
+#
+#    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.
+
+import argparse
+import os
+import sys
+
+sys.path.append(os.getcwd())
+try:
+    from tcp_tests.managers.jenkins import client
+except ImportError:
+    print("ImportError: Run the application from the tcp-qa directory or "
+          "set the PYTHONPATH environment variable to directory which contains"
+          " ./tcp_tests")
+    sys.exit(1)
+
+
+def load_params():
+    """
+    Parse CLI arguments and environment variables
+
+    Returns: ArgumentParser instance
+    """
+    env_host = os.environ.get('JENKINS_URL', None)
+    env_username = os.environ.get('JENKINS_USER', None)
+    env_password = os.environ.get('JENKINS_PASS', None)
+    env_job_name = os.environ.get('JOB_NAME', 'deploy_openstack')
+    env_build_number = os.environ.get('BUILD_NUMBER', 'lastBuild')
+
+    parser = argparse.ArgumentParser(description=(
+        'Host, username and password may be specified either by the command '
+        'line arguments or using environment variables: JENKINS_URL, '
+        'JENKINS_USER, JENKINS_PASS, JENKINS_START_TIMEOUT, '
+        'JENKINS_BUILD_TIMEOUT. \nCommand line arguments have the highest '
+        'priority, after that the environment variables are used as defaults.'
+    ))
+    parser.add_argument('--host',
+                        metavar='JENKINS_URL',
+                        help='Jenkins Host',
+                        default=env_host)
+    parser.add_argument('--username',
+                        metavar='JENKINS_USER',
+                        help='Jenkins Username',
+                        default=env_username)
+    parser.add_argument('--password',
+                        metavar='JENKINS_PASS',
+                        help='Jenkins Password or API token',
+                        default=env_password)
+    parser.add_argument('--job-name',
+                        metavar='JOB_NAME',
+                        help='Jenkins job name',
+                        default=env_job_name)
+    parser.add_argument('--build-number',
+                        metavar='BUILD_NUMBER',
+                        help='Jenkins job build number',
+                        default=env_build_number)
+    return parser
+
+
+def get_deployment_result(opts):
+    """Get the pipeline job result from Jenkins
+
+    Get all the stages resutls from the specified job,
+    show error message if present.
+    """
+    jenkins = client.JenkinsClient(host=opts.host,
+                                   username=opts.username,
+                                   password=opts.password)
+
+    def get_stages(nodes, indent=0, show_status=True):
+        res = []
+        for node in nodes:
+            if show_status:
+                msg = " " * indent + "{}: {}".format(node['name'],
+                                                     node['status'])
+                if 'error' in node and 'message' in node['error']:
+                    msg += ", " + node['error']['message']
+                res.append(msg)
+
+            if node['status'] != 'SUCCESS':
+                wf = jenkins.get_workflow(opts.job_name, opts.build_number,
+                                          int(node['id']))
+                if wf is not None:
+                    if 'stageFlowNodes' in wf:
+                        res += get_stages(wf['stageFlowNodes'], indent + 2,
+                                          show_status=False)
+                    elif '_links' in wf and 'log' in wf['_links']:
+                        log = jenkins.get_workflow(opts.job_name,
+                                                   opts.build_number,
+                                                   int(node['id']),
+                                                   mode='log')
+                        if "text" in log:
+                            prefix = " " * (indent + 2)
+                            res.append("\n".join(
+                                prefix + line
+                                for line in log["text"].splitlines()))
+        return res
+
+    wf = jenkins.get_workflow(opts.job_name, opts.build_number)
+    info = jenkins.build_info(opts.job_name, int(wf['id']))
+    build_description = ("[" + info['fullDisplayName'] + "] " +
+                         info['url'] + " : " + info['result'])
+    stages = get_stages(wf['stages'], 0)
+    if not stages:
+        msg = wf['status'] + ":\n\n"
+        stages = [msg + jenkins.get_build_output(opts.job_name, int(wf['id']))]
+    return (build_description, stages)
+
+
+def main(args=None):
+    parser = load_params()
+    opts = parser.parse_args()
+
+    if opts.host is None:
+        print("JENKINS_URL is required!")
+        parser.print_help()
+        return 10
+    else:
+        (build_description, stages) = get_deployment_result(opts)
+        print(build_description)
+        print('\n'.join(stages))
+
+
+if __name__ == "__main__":
+    sys.exit(main())