org.apache.hive.ptest.execution.JIRAService.java Source code

Java tutorial

Introduction

Here is the source code for org.apache.hive.ptest.execution.JIRAService.java

Source

/*
 * Licensed to the Apache Software Foundation (ASF) under one
 * or more contributor license agreements.  See the NOTICE file
 * distributed with this work for additional information
 * regarding copyright ownership.  The ASF licenses this file
 * to you 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.
 */
package org.apache.hive.ptest.execution;

import java.io.File;
import java.io.IOException;
import java.net.URL;
import java.util.*;

import com.google.common.collect.Sets;
import org.apache.commons.cli.*;
import org.apache.commons.io.FilenameUtils;
import org.apache.hive.ptest.api.server.TestLogger;
import org.apache.hive.ptest.execution.conf.Context;
import org.apache.hive.ptest.execution.conf.TestConfiguration;
import org.apache.http.HttpException;
import org.apache.http.HttpHost;
import org.apache.http.HttpRequest;
import org.apache.http.HttpRequestInterceptor;
import org.apache.http.HttpResponse;
import org.apache.http.StatusLine;
import org.apache.http.auth.AuthScheme;
import org.apache.http.auth.AuthScope;
import org.apache.http.auth.AuthState;
import org.apache.http.auth.Credentials;
import org.apache.http.auth.UsernamePasswordCredentials;
import org.apache.http.client.CredentialsProvider;
import org.apache.http.client.methods.HttpPost;
import org.apache.http.client.protocol.ClientContext;
import org.apache.http.entity.StringEntity;
import org.apache.http.impl.auth.BasicScheme;
import org.apache.http.impl.client.DefaultHttpClient;
import org.apache.http.protocol.BasicHttpContext;
import org.apache.http.protocol.ExecutionContext;
import org.apache.http.protocol.HttpContext;
import org.codehaus.jackson.map.ObjectMapper;
import org.codehaus.jackson.JsonFactory;
import org.codehaus.jackson.JsonParser;
import org.codehaus.jackson.JsonToken;
import org.slf4j.Logger;

import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Joiner;
import com.google.common.collect.Lists;
import com.google.common.collect.Maps;

class JIRAService {
    static final int MAX_MESSAGES = 200;
    static final String TRIMMED_MESSAGE = "**** This message was trimmed, see log for full details ****";
    private final Logger mLogger;
    private final String mName;
    private final String mBuildTag;
    private final String mPatch;
    private final String mUrl;
    private final String mUser;
    private final String mPassword;
    private final String mJenkinsURL;
    private final String mLogsURL;

    private static final String OPT_HELP_SHORT = "h";
    private static final String OPT_HELP_LONG = "help";
    private static final String OPT_USER_SHORT = "u";
    private static final String OPT_USER_LONG = "user";
    private static final String OPT_PASS_SHORT = "p";
    private static final String OPT_PASS_LONG = "password";
    private static final String OPT_FILE_SHORT = "f";
    private static final String OPT_FILE_LONG = "file";

    public JIRAService(Logger logger, TestConfiguration configuration, String buildTag) {
        mLogger = logger;
        mName = configuration.getJiraName();
        mBuildTag = buildTag;
        mPatch = configuration.getPatch();
        mUrl = configuration.getJiraUrl();
        mUser = configuration.getJiraUser();
        mPassword = configuration.getJiraPassword();
        mJenkinsURL = configuration.getJenkinsURL();
        mLogsURL = configuration.getLogsURL();
    }

    void postComment(boolean error, int numTestsExecuted, SortedSet<String> failedTests, List<String> messages) {
        postComment(error, numTestsExecuted, failedTests, messages, new HashSet<String>());
    }

    void postComment(boolean error, int numTestsExecuted, SortedSet<String> failedTests, List<String> messages,
            Set<String> addedTests) {
        String comments = generateComments(error, numTestsExecuted, failedTests, messages, addedTests);
        publishComments(comments);
    }

    @VisibleForTesting
    String generateComments(boolean error, int numTestsExecuted, SortedSet<String> failedTests,
            List<String> messages, Set<String> addedTests) {
        BuildInfo buildInfo = formatBuildTag(mBuildTag);
        String buildTagForLogs = formatBuildTagForLogs(mBuildTag);
        List<String> comments = Lists.newArrayList();
        comments.add("");
        comments.add("");
        if (!mPatch.isEmpty()) {
            comments.add("Here are the results of testing the latest attachment:");
            comments.add(mPatch);
        }
        comments.add("");
        if (error && numTestsExecuted == 0) {
            comments.add(formatError("-1 due to build exiting with an error"));
        } else {
            if (addedTests.size() > 0) {
                comments.add(formatSuccess("+1 due to " + addedTests.size() + " test(s) being added or modified."));
            } else {
                comments.add(formatError("-1 due to no test(s) being added or modified."));
            }
            comments.add("");
            if (numTestsExecuted == 0) {
                comments.add(formatError("-1 due to no tests executed"));
            } else {
                if (failedTests.isEmpty()) {
                    comments.add(formatSuccess("+1 due to " + numTestsExecuted + " tests passed"));
                } else {
                    comments.add(formatError("-1 due to " + failedTests.size() + " failed/errored test(s), "
                            + numTestsExecuted + " tests executed"));
                    comments.add("*Failed tests:*");
                    comments.add("{noformat}");
                    comments.addAll(failedTests);
                    comments.add("{noformat}");
                }
            }
        }
        comments.add("");
        comments.add("Test results: " + mJenkinsURL + "/" + buildInfo.getFormattedBuildTag() + "/testReport");
        comments.add("Console output: " + mJenkinsURL + "/" + buildInfo.getFormattedBuildTag() + "/console");
        comments.add("Test logs: " + mLogsURL + buildTagForLogs);
        comments.add("");
        if (!messages.isEmpty()) {
            comments.add("Messages:");
            comments.add("{noformat}");
            comments.addAll(trimMessages(messages));
            comments.add("{noformat}");
            comments.add("");
        }
        comments.add("This message is automatically generated.");
        String attachmentId = parseAttachementId(mPatch);
        comments.add("");
        comments.add("ATTACHMENT ID: " + attachmentId + " - " + buildInfo.getBuildName());
        mLogger.info("Comment: " + Joiner.on("\n").join(comments));
        return Joiner.on("\n").join(comments);
    }

    void publishComments(String comments) {
        DefaultHttpClient httpClient = new DefaultHttpClient();
        try {
            String url = String.format("%s/rest/api/2/issue/%s/comment", mUrl, mName);
            URL apiURL = new URL(mUrl);
            httpClient.getCredentialsProvider().setCredentials(
                    new AuthScope(apiURL.getHost(), apiURL.getPort(), AuthScope.ANY_REALM),
                    new UsernamePasswordCredentials(mUser, mPassword));
            BasicHttpContext localcontext = new BasicHttpContext();
            localcontext.setAttribute("preemptive-auth", new BasicScheme());
            httpClient.addRequestInterceptor(new PreemptiveAuth(), 0);
            HttpPost request = new HttpPost(url);
            ObjectMapper mapper = new ObjectMapper();
            StringEntity params = new StringEntity(mapper.writeValueAsString(new Body(comments)));
            request.addHeader("Content-Type", "application/json");
            request.setEntity(params);
            HttpResponse httpResponse = httpClient.execute(request, localcontext);
            StatusLine statusLine = httpResponse.getStatusLine();
            if (statusLine.getStatusCode() != 201) {
                throw new RuntimeException(statusLine.getStatusCode() + " " + statusLine.getReasonPhrase());
            }
            mLogger.info("JIRA Response Metadata: " + httpResponse);
        } catch (Exception e) {
            mLogger.error("Encountered error attempting to post comment to " + mName, e);
        } finally {
            httpClient.getConnectionManager().shutdown();
        }
    }

    static List<String> trimMessages(List<String> messages) {
        int size = messages.size();
        if (size > MAX_MESSAGES) {
            messages = messages.subList(size - MAX_MESSAGES, size);
            messages.add(0, TRIMMED_MESSAGE);
        }
        return messages;
    }

    @SuppressWarnings("unused")
    private static class Body {
        private String body;

        public Body() {

        }

        public Body(String body) {
            this.body = body;
        }

        public String getBody() {
            return body;
        }

        public void setBody(String body) {
            this.body = body;
        }
    }

    public static class BuildInfo {
        private String buildName;
        private String formattedBuildTag;

        public BuildInfo(String buildName, String formattedBuildTag) {
            this.buildName = buildName;
            this.formattedBuildTag = formattedBuildTag;
        }

        public String getBuildName() {
            return buildName;
        }

        public String getFormattedBuildTag() {
            return formattedBuildTag;
        }
    }

    /**
     * Hive-Build-123 to Hive-Build/123
     */
    @VisibleForTesting
    static BuildInfo formatBuildTag(String buildTag) {
        if (buildTag.contains("-")) {
            int lastDashIndex = buildTag.lastIndexOf("-");
            String buildName = buildTag.substring(0, lastDashIndex);
            String buildId = buildTag.substring(lastDashIndex + 1);
            String formattedBuildTag = buildName + "/" + buildId;
            return new BuildInfo(buildName, formattedBuildTag);
        }
        throw new IllegalArgumentException("Build tag '" + buildTag + "' must contain a -");
    }

    static String formatBuildTagForLogs(String buildTag) {
        if (buildTag.endsWith("/")) {
            return buildTag;
        } else {
            return buildTag + "/";
        }
    }

    private static String formatError(String msg) {
        return String.format("{color:red}ERROR:{color} %s", msg);
    }

    private static String formatSuccess(String msg) {
        return String.format("{color:green}SUCCESS:{color} %s", msg);
    }

    static class PreemptiveAuth implements HttpRequestInterceptor {

        public void process(final HttpRequest request, final HttpContext context)
                throws HttpException, IOException {
            AuthState authState = (AuthState) context.getAttribute(ClientContext.TARGET_AUTH_STATE);
            if (authState.getAuthScheme() == null) {
                AuthScheme authScheme = (AuthScheme) context.getAttribute("preemptive-auth");
                CredentialsProvider credsProvider = (CredentialsProvider) context
                        .getAttribute(ClientContext.CREDS_PROVIDER);
                HttpHost targetHost = (HttpHost) context.getAttribute(ExecutionContext.HTTP_TARGET_HOST);
                if (authScheme != null) {
                    Credentials creds = credsProvider
                            .getCredentials(new AuthScope(targetHost.getHostName(), targetHost.getPort()));
                    if (creds == null) {
                        throw new HttpException("No credentials for preemptive authentication");
                    }
                    authState.update(authScheme, creds);
                }
            }
        }
    }

    private static String parseAttachementId(String patch) {
        if (patch == null) {
            return "";
        }
        String result = FilenameUtils.getPathNoEndSeparator(patch.trim());
        if (result == null) {
            return "";
        }
        result = FilenameUtils.getName(result.trim());
        if (result == null) {
            return "";
        }
        return result.trim();
    }

    private static void assertRequired(CommandLine commandLine, String[] requiredOptions)
            throws IllegalArgumentException {
        for (String requiredOption : requiredOptions) {
            if (!commandLine.hasOption(requiredOption)) {
                throw new IllegalArgumentException("--" + requiredOption + " is required");
            }
        }
    }

    private static final String FIELD_BUILD_STATUS = "buildStatus";
    private static final String FIELD_BUILD_TAG = "buildTag";
    private static final String FIELD_LOGS_URL = "logsURL";
    private static final String FIELD_JENKINS_URL = "jenkinsURL";
    private static final String FIELD_PATCH_URL = "patchUrl";
    private static final String FIELD_JIRA_NAME = "jiraName";
    private static final String FIELD_JIRA_URL = "jiraUrl";
    private static final String FIELD_REPO = "repository";
    private static final String FIELD_REPO_NAME = "repositoryName";
    private static final String FIELD_REPO_TYPE = "repositoryType";
    private static final String FIELD_REPO_BRANCH = "branch";
    private static final String FIELD_NUM_TESTS_EXECUTED = "numTestsExecuted";
    private static final String FIELD_FAILED_TESTS = "failedTests";
    private static final String FIELD_MESSAGES = "messages";
    private static final String FIELD_JIRA_USER = "jiraUser";
    private static final String FIELD_JIRA_PASS = "jiraPassword";

    private static Map<String, Class> supportedJsonFields = new HashMap<String, Class>() {
        {
            put(FIELD_BUILD_STATUS, Integer.class);
            put(FIELD_BUILD_TAG, String.class);
            put(FIELD_LOGS_URL, String.class);
            put(FIELD_JENKINS_URL, String.class);
            put(FIELD_PATCH_URL, String.class);
            put(FIELD_JIRA_NAME, String.class);
            put(FIELD_JIRA_URL, String.class);
            put(FIELD_REPO, String.class);
            put(FIELD_REPO_NAME, String.class);
            put(FIELD_REPO_TYPE, String.class);
            put(FIELD_REPO_BRANCH, String.class);
            put(FIELD_NUM_TESTS_EXECUTED, Integer.class);
            put(FIELD_FAILED_TESTS, SortedSet.class);
            put(FIELD_MESSAGES, List.class);
        }
    };

    private static Map<String, Object> parseJsonFile(String jsonFile) throws IOException {
        JsonFactory jsonFactory = new JsonFactory();
        JsonParser jsonParser = jsonFactory.createJsonParser(new File(jsonFile));
        Map<String, Object> values = new HashMap<String, Object>();

        while (jsonParser.nextToken() != JsonToken.END_OBJECT) {
            String fieldName = jsonParser.getCurrentName();
            if (supportedJsonFields.containsKey(fieldName)) {
                jsonParser.nextToken();

                Class clazz = supportedJsonFields.get(fieldName);
                if (clazz == String.class) {
                    values.put(fieldName, jsonParser.getText());
                } else if (clazz == Integer.class) {
                    values.put(fieldName, Integer.valueOf(jsonParser.getText()));
                } else if (clazz == SortedSet.class) {
                    SortedSet<String> failedTests = new TreeSet<String>();
                    while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                        failedTests.add(jsonParser.getText());
                    }

                    values.put(fieldName, failedTests);
                } else if (clazz == List.class) {
                    List<String> messages = new ArrayList<String>();
                    while (jsonParser.nextToken() != JsonToken.END_ARRAY) {
                        messages.add(jsonParser.getText());
                    }

                    values.put(fieldName, messages);
                }
            }
        }

        jsonParser.close();
        return values;
    }

    private static CommandLine parseCommandLine(String[] args) throws ParseException {
        CommandLineParser parser = new GnuParser();

        Options options = new Options();
        options.addOption(OPT_HELP_SHORT, OPT_HELP_LONG, false, "Display help text and exit");
        options.addOption(OPT_USER_SHORT, OPT_USER_LONG, true, "Jira username.");
        options.addOption(OPT_PASS_SHORT, OPT_PASS_LONG, true, "Jira password.");
        options.addOption(OPT_FILE_SHORT, OPT_FILE_LONG, true,
                "Pathname to file (JSON format) that will be post as Jira comment.");

        CommandLine cmd = parser.parse(options, args);

        // If help option is requested, then display help and exit
        if (cmd.hasOption(OPT_HELP_LONG)) {
            new HelpFormatter().printHelp(JIRAService.class.getName(), options, true);
            return null;
        }

        assertRequired(cmd, new String[] { OPT_USER_LONG, OPT_PASS_LONG, OPT_FILE_LONG });

        return cmd;
    }

    public static void main(String[] args) throws Exception {
        CommandLine cmd = null;

        try {
            cmd = parseCommandLine(args);
        } catch (ParseException e) {
            System.out.println("Error parsing command arguments: " + e.getMessage());
            System.exit(1);
        }

        // If null is returned, then help message was displayed in parseCommandLine method
        if (cmd == null) {
            System.exit(0);
        }

        Map<String, Object> jsonValues = parseJsonFile(cmd.getOptionValue(OPT_FILE_LONG));

        Map<String, String> context = Maps.newHashMap();
        context.put(FIELD_JIRA_URL, (String) jsonValues.get(FIELD_JIRA_URL));
        context.put(FIELD_JIRA_USER, cmd.getOptionValue(OPT_USER_LONG));
        context.put(FIELD_JIRA_PASS, cmd.getOptionValue(OPT_PASS_LONG));
        context.put(FIELD_LOGS_URL, (String) jsonValues.get(FIELD_LOGS_URL));
        context.put(FIELD_REPO, (String) jsonValues.get(FIELD_REPO));
        context.put(FIELD_REPO_NAME, (String) jsonValues.get(FIELD_REPO_NAME));
        context.put(FIELD_REPO_TYPE, (String) jsonValues.get(FIELD_REPO_TYPE));
        context.put(FIELD_REPO_BRANCH, (String) jsonValues.get(FIELD_REPO_BRANCH));
        context.put(FIELD_JENKINS_URL, (String) jsonValues.get(FIELD_JENKINS_URL));

        TestLogger logger = new TestLogger(System.err, TestLogger.LEVEL.TRACE);
        TestConfiguration configuration = new TestConfiguration(new Context(context), logger);
        configuration.setJiraName((String) jsonValues.get(FIELD_JIRA_NAME));
        configuration.setPatch((String) jsonValues.get(FIELD_PATCH_URL));

        JIRAService service = new JIRAService(logger, configuration, (String) jsonValues.get(FIELD_BUILD_TAG));
        List<String> messages = (List) jsonValues.get(FIELD_MESSAGES);
        SortedSet<String> failedTests = (SortedSet) jsonValues.get(FIELD_FAILED_TESTS);
        boolean error = (Integer) jsonValues.get(FIELD_BUILD_STATUS) == 0 ? false : true;
        service.postComment(error, (Integer) jsonValues.get(FIELD_NUM_TESTS_EXECUTED), failedTests, messages);
    }
}