[ https://issues.apache.org/jira/browse/IGNITE-9377?page=com.atlassian.jira.plugin.system.issuetabpanels:comment-tabpanel&focusedCommentId=16612389#comment-16612389 ]
ASF GitHub Bot commented on IGNITE-9377: ---------------------------------------- asfgit closed pull request #6: IGNITE-9377 Handle print crit failures from RunAll to the JIRA ticket URL: https://github.com/apache/ignite-teamcity-bot/pull/6 This is a PR merged from a forked repository. As GitHub hides the original diff on merge, it is displayed below for the sake of provenance: As this is a foreign pull request (from a fork), the diff is supplied below (as it won't show otherwise due to GitHub magic): diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java index b3b51c7..37880c1 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/HelperConfig.java @@ -48,7 +48,14 @@ @Deprecated private static final String PASSWORD = "password"; public static final String ENCODED_PASSWORD = "encoded_password"; + + /** GitHub authorization token property name. */ public static final String GITHUB_AUTH_TOKEN = "github.auth_token"; + + /** JIRA authorization token property name. */ + public static final String JIRA_AUTH_TOKEN = "jira.auth_token"; + + /** Slack authorization token property name. */ public static final String SLACK_AUTH_TOKEN = "slack.auth_token"; public static final String SLACK_CHANNEL = "slack.channel"; public static final String LOGS = "logs"; @@ -135,6 +142,21 @@ public static File resolveWorkDir() { return pwd; } + /** + * Extract JIRA basic authorization token from properties. + * + * @param props Properties, where token is placed. + * @return Null or decoded auth token for Github. + */ + @Nullable static String prepareJiraHttpAuthToken(Properties props) { + String pwd = props.getProperty(JIRA_AUTH_TOKEN); + + if (isNullOrEmpty(pwd)) + return null; + + return pwd; + } + /** * Extract TeamCity authorization token from properties. * diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java index b2beb51..69df43d 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITcHelper.java @@ -18,6 +18,7 @@ package org.apache.ignite.ci; import java.util.List; +import org.apache.ignite.ci.observer.BuildObserver; import org.apache.ignite.ci.issue.IssueDetector; import org.apache.ignite.ci.issue.IssuesStorage; import org.apache.ignite.ci.user.ICredentialsProv; @@ -42,6 +43,11 @@ IssueDetector issueDetector(); + /** + * @return Build observer. + */ + BuildObserver buildObserver(); + IAnalyticsEnabledTeamcity server(String srvId, @Nullable ICredentialsProv prov); ITcAnalytics tcAnalytics(String serverId); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java index 7e105c7..8201f5c 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/ITeamcity.java @@ -239,7 +239,12 @@ default SingleBuildRunCtx loadTestsAndProblems(@Nonnull Build build, @Deprecated * @param cleanRebuild Rebuild all dependencies. * @param queueAtTop Put at the top of the build queue. */ - void triggerBuild(String id, String name, boolean cleanRebuild, boolean queueAtTop); + Build triggerBuild(String id, String name, boolean cleanRebuild, boolean queueAtTop); + + /** + * @param tok TeamCity authorization token. + */ + void setAuthToken(String tok); /** * @return {@code True} if TeamCity authorization token is available. @@ -247,9 +252,9 @@ default SingleBuildRunCtx loadTestsAndProblems(@Nonnull Build build, @Deprecated boolean isTeamCityTokenAvailable(); /** - * @param token TeamCity authorization token. + * @param token GitHub authorization token. */ - void setAuthToken(String token); + void setGitToken(String token); /** * @return {@code True} if GitHub authorization token is available. @@ -257,9 +262,14 @@ default SingleBuildRunCtx loadTestsAndProblems(@Nonnull Build build, @Deprecated boolean isGitTokenAvailable(); /** - * @param token GitHub authorization token. + * @param tok Jira authorization token. */ - void setGitToken(String token); + void setJiraToken(String tok); + + /** + * @return {@code True} if JIRA authorization token is available. + */ + boolean isJiraTokenAvailable(); /** * Send POST request with given body. @@ -276,6 +286,14 @@ default SingleBuildRunCtx loadTestsAndProblems(@Nonnull Build build, @Deprecated */ PullRequest getPullRequest(String branch); + /** + * @param ticket JIRA ticket name. + * @param comment Comment to be placed in the ticket conversation. + * @return {@code True} if ticket was succesfully commented. Otherwise - {@code false}. + */ + boolean commentJiraTicket(String ticket, String comment); + + default void setAuthData(String user, String password) { setAuthToken( Base64Util.encodeUtf8String(user + ":" + password)); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java index 16e3a22..1e10d78 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgnitePersistentTeamcity.java @@ -77,7 +77,6 @@ * */ public class IgnitePersistentTeamcity implements IAnalyticsEnabledTeamcity, ITeamcity, ITcAnalytics { - //V1 caches, 1024 parts //V2 caches, 32 parts @@ -817,10 +816,15 @@ public void setExecutor(ExecutorService executor) { } /** {@inheritDoc} */ - @Override public void triggerBuild(String id, String name, boolean cleanRebuild, boolean queueAtTop) { + @Override public Build triggerBuild(String id, String name, boolean cleanRebuild, boolean queueAtTop) { lastTriggerMs = System.currentTimeMillis(); - teamcity.triggerBuild(id, name, cleanRebuild, queueAtTop); + return teamcity.triggerBuild(id, name, cleanRebuild, queueAtTop); + } + + /** {@inheritDoc} */ + @Override public void setAuthToken(String tok) { + teamcity.setAuthToken(tok); } /** {@inheritDoc} */ @@ -829,8 +833,8 @@ public void setExecutor(ExecutorService executor) { } /** {@inheritDoc} */ - @Override public void setAuthToken(String token) { - teamcity.setAuthToken(token); + @Override public void setGitToken(String tok) { + teamcity.setGitToken(tok); } /** {@inheritDoc} */ @@ -839,18 +843,28 @@ public void setExecutor(ExecutorService executor) { } /** {@inheritDoc} */ - @Override public void setGitToken(String token) { - teamcity.setGitToken(token); + @Override public void setJiraToken(String tok) { + teamcity.setJiraToken(tok); } /** {@inheritDoc} */ - @Override public boolean notifyGit(String url, String body) { - return teamcity.notifyGit(url, body); + @Override public boolean isJiraTokenAvailable() { + return teamcity.isJiraTokenAvailable(); + } + + /** {@inheritDoc} */ + @Override public boolean commentJiraTicket(String ticket, String comment) { + return teamcity.commentJiraTicket(ticket, comment); } /** {@inheritDoc} */ - @Override public PullRequest getPullRequest(String branch) { - return teamcity.getPullRequest(branch); + @Override public PullRequest getPullRequest(String branchForTc) { + return teamcity.getPullRequest(branchForTc); + } + + /** {@inheritDoc} */ + @Override public boolean notifyGit(String url, String body) { + return teamcity.notifyGit(url, body); } /** {@inheritDoc} */ diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java index 0813e2d..3bbc78e 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/IgniteTeamcityHelper.java @@ -27,6 +27,7 @@ import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.io.StringReader; import java.io.UncheckedIOException; import java.util.ArrayList; import java.util.List; @@ -83,6 +84,7 @@ import static com.google.common.base.Strings.isNullOrEmpty; import static java.util.concurrent.CompletableFuture.supplyAsync; import static org.apache.ignite.ci.HelperConfig.ensureDirExist; +import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText; /** * Class for access to Teamcity REST API without any caching. @@ -106,6 +108,9 @@ /** GitHub authorization token. */ private String gitAuthTok; + /** JIRA authorization token. */ + private String jiraBasicAuthTok; + private final String configName; //main properties file name private final String tcName; @@ -131,6 +136,8 @@ public IgniteTeamcityHelper(@Nullable String tcName) { setGitToken(HelperConfig.prepareGithubHttpAuthToken(props)); + setJiraToken(HelperConfig.prepareJiraHttpAuthToken(props)); + final File logsDirFile = HelperConfig.resolveLogs(workDir, props); logsDir = ensureDirExist(logsDirFile); @@ -138,14 +145,19 @@ public IgniteTeamcityHelper(@Nullable String tcName) { this.executor = MoreExecutors.directExecutor(); } + /** {@inheritDoc} */ + @Override public void setAuthToken(String tok) { + basicAuthTok = tok; + } + /** {@inheritDoc} */ @Override public boolean isTeamCityTokenAvailable() { return basicAuthTok != null; } /** {@inheritDoc} */ - @Override public void setAuthToken(String token) { - basicAuthTok = token; + @Override public void setGitToken(String tok) { + gitAuthTok = tok; } /** {@inheritDoc} */ @@ -154,33 +166,40 @@ public IgniteTeamcityHelper(@Nullable String tcName) { } /** {@inheritDoc} */ - @Override public void setGitToken(String token) { - gitAuthTok = token; + @Override public void setJiraToken(String tok) { + jiraBasicAuthTok = tok; } /** {@inheritDoc} */ - @Override public boolean notifyGit(String url, String body) { + @Override public boolean isJiraTokenAvailable() { + return jiraBasicAuthTok != null; + } + + /** {@inheritDoc} */ + @Override public boolean commentJiraTicket(String ticket, String comment) { try { - HttpUtil.sendPostAsStringToGit(gitAuthTok, url, body); + String url = "https://issues.apache.org/jira/rest/api/2/issue/" + ticket + "/comment"; + + HttpUtil.sendPostAsStringToJira(jiraBasicAuthTok, url, "{\"body\": \"" + comment + "\"}"); return true; } catch (IOException e) { - logger.error("Failed to notify Git [errMsg="+e.getMessage()+']'); + logger.error("Failed to notify JIRA [errMsg="+e.getMessage()+']'); return false; } } /** {@inheritDoc} */ - @Override public PullRequest getPullRequest(String branch) { + @Override public PullRequest getPullRequest(String branchForTc) { String id = null; - for (int i = 5; i < branch.length(); i++) { - char c = branch.charAt(i); + for (int i = 5; i < branchForTc.length(); i++) { + char c = branchForTc.charAt(i); if (!Character.isDigit(c)) { - id = branch.substring(5, i); + id = branchForTc.substring(5, i); break; } @@ -198,6 +217,20 @@ public IgniteTeamcityHelper(@Nullable String tcName) { } } + /** {@inheritDoc} */ + @Override public boolean notifyGit(String url, String body) { + try { + HttpUtil.sendPostAsStringToGit(gitAuthTok, url, body); + + return true; + } + catch (IOException e) { + logger.error("Failed to notify Git [errMsg="+e.getMessage()+']'); + + return false; + } + } + /** {@inheritDoc} */ @Override public List<Agent> agents(boolean connected, boolean authorized) { String url = "app/rest/agents?locator=connected:" + connected + ",authorized:" + authorized; @@ -259,31 +292,13 @@ public IgniteTeamcityHelper(@Nullable String tcName) { .thenApply(LogCheckTask::getResult); } - /** - * @param t Text to process. - */ - private String xmlEscapeText(CharSequence t) { - StringBuilder sb = new StringBuilder(); - for(int i = 0; i < t.length(); i++){ - char c = t.charAt(i); - switch(c){ - case '<': sb.append("<"); break; - case '>': sb.append(">"); break; - case '\"': sb.append("""); break; - case '&': sb.append("&"); break; - case '\'': sb.append("'"); break; - default: - if(c>0x7e) - sb.append("&#").append((int)c).append(";"); - else - sb.append(c); - } - } - return sb.toString(); - } - /** {@inheritDoc} */ - @Override public void triggerBuild(String buildTypeId, String branchName, boolean cleanRebuild, boolean queueAtTop) { + @Override public Build triggerBuild( + String buildTypeId, + String branchName, + boolean cleanRebuild, + boolean queueAtTop + ) { String triggeringOptions = " <triggeringOptions" + " cleanSources=\"" + cleanRebuild + "\"" + @@ -307,11 +322,17 @@ private String xmlEscapeText(CharSequence t) { "</build>"; String url = host + "app/rest/buildQueue"; + try { logger.info("Triggering build: buildTypeId={}, branchName={}, cleanRebuild={}, queueAtTop={}", buildTypeId, branchName, cleanRebuild, queueAtTop); - HttpUtil.sendPostAsString(basicAuthTok, url, param); + try (StringReader reader = new StringReader(HttpUtil.sendPostAsString(basicAuthTok, url, param))) { + return XmlUtil.load(Build.class, reader); + } + catch (JAXBException e) { + throw Throwables.propagate(e); + } } catch (IOException e) { throw new UncheckedIOException(e); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java index 1f6bc0d..a95483b 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/TcHelper.java @@ -23,6 +23,7 @@ import java.util.List; import org.apache.ignite.Ignite; import org.apache.ignite.ci.conf.BranchesTracked; +import org.apache.ignite.ci.observer.BuildObserver; import org.apache.ignite.ci.issue.IssueDetector; import org.apache.ignite.ci.issue.IssuesStorage; import org.apache.ignite.ci.user.ICredentialsProv; @@ -56,6 +57,10 @@ private TcUpdatePool tcUpdatePool = new TcUpdatePool(); private IssuesStorage issuesStorage; private IssueDetector detector; + + /** Build observer. */ + private BuildObserver buildObserver; + private UserAndSessionsStorage userAndSessionsStorage; public TcHelper(Ignite ignite) { @@ -65,6 +70,7 @@ public TcHelper(Ignite ignite) { userAndSessionsStorage = new UserAndSessionsStorage(ignite); detector = new IssueDetector(ignite, issuesStorage, userAndSessionsStorage); + buildObserver = new BuildObserver(this); } /** {@inheritDoc} */ @@ -77,6 +83,11 @@ public TcHelper(Ignite ignite) { return detector; } + /** {@inheritDoc} */ + @Override public BuildObserver buildObserver() { + return buildObserver; + } + /** {@inheritDoc} */ @Override public IAnalyticsEnabledTeamcity server(String srvId, @Nullable ICredentialsProv prov) { if (stop.get()) @@ -150,6 +161,8 @@ public void close() { tcUpdatePool.stop(); detector.stop(); + + buildObserver.stop(); } public ExecutorService getService() { diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java index d381d12..43a6b83 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/github/PullRequest.java @@ -32,7 +32,7 @@ /** Pull Request title. */ private String title; - /** Pull Request url to get statuses. */ + /** Pull Request statuses URL. */ @SerializedName("statuses_url") private String statusesUrl; /** diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java new file mode 100644 index 0000000..445cd06 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildInfo.java @@ -0,0 +1,81 @@ +/* + * 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.ignite.ci.observer; + +import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.user.ICredentialsProv; + +/** + * + */ +class BuildInfo { + /** Build. */ + final Build build; + + /** Server id. */ + final String srvId; + + /** */ + final ICredentialsProv prov; + + /** JIRA ticket name. */ + final String ticket; + + /** + * @param build Build. + * @param srvId Server id. + * @param prov Credentials. + * @param ticket JIRA ticket name. + */ + BuildInfo(Build build, String srvId, ICredentialsProv prov, String ticket) { + this.build = build; + this.srvId = srvId; + this.prov = prov; + this.ticket = ticket; + } + + /** {@inheritDoc} */ + @Override public boolean equals(Object o) { + if (this == o) + return true; + if (o == null || getClass() != o.getClass()) + return false; + + BuildInfo info = (BuildInfo)o; + + if (!build.equals(info.build)) + return false; + if (!srvId.equals(info.srvId)) + return false; + if (!prov.equals(info.prov)) + return false; + + return ticket.equals(info.ticket); + } + + /** {@inheritDoc} */ + @Override public int hashCode() { + int res = build.hashCode(); + + res = 31 * res + srvId.hashCode(); + res = 31 * res + prov.hashCode(); + res = 31 * res + ticket.hashCode(); + + return res; + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java new file mode 100644 index 0000000..a7a7b1b --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/BuildObserver.java @@ -0,0 +1,63 @@ +/* + * 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.ignite.ci.observer; + +import java.util.Timer; +import org.apache.ignite.ci.ITcHelper; +import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.user.ICredentialsProv; + +/** + * + */ +public class BuildObserver { + /** Time between observing actions in milliseconds. */ + private final long period = 30 * 60 * 1_000; + + /** Timer. */ + private final Timer timer; + + /** Task, which should be done periodically. */ + private final ObserverTask task; + + /** + * @param helper Helper. + */ + public BuildObserver(ITcHelper helper) { + timer = new Timer(); + + timer.schedule(task = new ObserverTask(helper), period, period); + } + + /** + * Stop observer. + */ + public void stop() { + timer.cancel(); + } + + /** + * @param build Build id. + * @param srvId Server id. + * @param prov Credentials. + * @param ticket JIRA ticket name. + */ + public void observe(Build build, String srvId, ICredentialsProv prov, String ticket) { + task.builds.add(new BuildInfo(build, srvId, prov, ticket)); + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java new file mode 100644 index 0000000..7466a30 --- /dev/null +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/observer/ObserverTask.java @@ -0,0 +1,204 @@ +/* + * 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.ignite.ci.observer; + +import java.util.ArrayList; +import java.util.LinkedHashMap; +import java.util.List; +import java.util.Map; +import java.util.Queue; +import java.util.TimerTask; +import java.util.concurrent.ConcurrentLinkedQueue; +import org.apache.ignite.ci.IAnalyticsEnabledTeamcity; +import org.apache.ignite.ci.ITcHelper; +import org.apache.ignite.ci.tcmodel.result.Build; +import org.apache.ignite.ci.web.model.current.ChainAtServerCurrentStatus; +import org.apache.ignite.ci.web.model.current.SuiteCurrentStatus; +import org.apache.ignite.ci.web.model.current.TestFailure; +import org.apache.ignite.ci.web.model.current.TestFailuresSummary; +import org.apache.ignite.ci.web.model.hist.FailureSummary; +import org.apache.ignite.ci.web.rest.pr.GetPrTestFailures; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import static org.apache.ignite.ci.analysis.RunStat.MAX_LATEST_RUNS; +import static org.apache.ignite.ci.util.XmlUtil.xmlEscapeText; + +/** + * Checks observed builds for finished status and comments JIRA ticket. + */ +public class ObserverTask extends TimerTask { + /** Logger. */ + private static final Logger logger = LoggerFactory.getLogger(ObserverTask.class); + + /** Helper. */ + private final ITcHelper helper; + + /** Builds. */ + final Queue<BuildInfo> builds; + + /** + * @param helper Helper. + */ + ObserverTask(ITcHelper helper) { + this.helper = helper; + builds = new ConcurrentLinkedQueue<>(); + } + + /** {@inheritDoc} */ + @Override public void run() { + for (BuildInfo info : builds) { + IAnalyticsEnabledTeamcity teamcity = helper.server(info.srvId, info.prov); + Build build = teamcity.getBuild(info.build.getId()); + String comment; + + try { + comment = generateComment(build, info); + } + catch (RuntimeException e) { + logger.error("Exception happened during generating comment for JIRA " + + "[build=" + build.getId() + ", errMsg=" + e.getMessage() + ']'); + + continue; + } + + if (build.state.equals("finished")) { + if (teamcity.commentJiraTicket(info.ticket, comment)) + builds.remove(info); + } + } + } + + /** + * @param build Build. + * @param info Info. + */ + private String generateComment(Build build, BuildInfo info) { + StringBuilder res = new StringBuilder(); + TestFailuresSummary summary = GetPrTestFailures.getTestFailuresSummary( + helper, info.prov, info.srvId, build.getBuildType().getId(), build.branchName, "Latest", null); + + if (summary != null) { + for (ChainAtServerCurrentStatus server : summary.servers) { + if (!server.serverName().equals("apache")) + continue; + + Map<String, List<SuiteCurrentStatus>> fails = findFailures(server); + + for (List<SuiteCurrentStatus> suites : fails.values()) { + for (SuiteCurrentStatus suite : suites) { + res.append("{color:#d04437}").append(suite.name).append("{color}"); + res.append(" [[tests ").append(suite.failedTests); + + if (suite.result != null && !suite.result.equals("")) + res.append(' ').append(suite.result); + + res.append('|').append(suite.webToBuild).append("]]\\n"); + + for (TestFailure failure : suite.testFailures) { + res.append("* "); + + if (failure.suiteName != null && failure.testName != null) + res.append(failure.suiteName).append(": ").append(failure.testName); + else + res.append(failure.name); + + FailureSummary recent = failure.histBaseBranch.recent; + + if (recent != null) { + if (recent.failureRate != null) { + res.append(" - ").append(recent.failureRate).append("% fails in last ") + .append(MAX_LATEST_RUNS).append(" master runs."); + } + else if (recent.failures != null && recent.runs != null) { + res.append(" - ").append(recent.failures).append(" fails / ") + .append(recent.runs).append(" runs."); + } + } + + res.append("\\n"); + } + + res.append("\\n"); + } + } + + if (res.length() > 0) { + res.insert(0, "{panel:title=Possible Blockers|" + + "borderStyle=dashed|borderColor=#ccc|titleBGColor=#F7D6C1}\\n") + .append("{panel}"); + } + else { + res.append("{panel:title=No blockers found!|" + + "borderStyle=dashed|borderColor=#ccc|titleBGColor=#D6F7C1}{panel}"); + } + } + } + + res.append("\\n").append("[TeamCity Run All|").append(build.webUrl).append(']'); + + return xmlEscapeText(res.toString()); + } + + /** + * @param srv Server. + * @return Failures for given server. + */ + private Map<String, List<SuiteCurrentStatus>> findFailures(ChainAtServerCurrentStatus srv) { + Map<String, List<SuiteCurrentStatus>> fails = new LinkedHashMap<>(); + + fails.put("compilation", new ArrayList<>()); + fails.put("timeout", new ArrayList<>()); + fails.put("exit code", new ArrayList<>()); + fails.put("failed tests", new ArrayList<>()); + + for (SuiteCurrentStatus suite : srv.suites) { + String suiteRes = suite.result.toLowerCase(); + String failType = null; + + if (suiteRes.contains("compilation")) + failType = "compilation"; + + if (suiteRes.contains("timeout")) + failType = "timeout"; + + if (suiteRes.contains("exit code")) + failType = "exit code"; + + if (failType == null) { + List<TestFailure> failures = new ArrayList<>(); + + for (TestFailure testFailure : suite.testFailures) { + if (testFailure.isNewFailedTest()) + failures.add(testFailure); + } + + if (!failures.isEmpty()) { + suite.testFailures = failures; + + failType = "failed tests"; + } + } + + if (failType != null) + fails.get(failType).add(suite); + } + + return fails; + } +} diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java index 0e6202b..2305fc7 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/tcmodel/hist/BuildRef.java @@ -44,7 +44,8 @@ public static final String STATE_QUEUED = "queued"; - @XmlAttribute private String state; + /** Current state of build. */ + @XmlAttribute public String state; @XmlAttribute(name = "number") public String buildNumber; @@ -52,6 +53,7 @@ @XmlAttribute public Boolean composite; + /** Build page URL. */ @XmlAttribute public String webUrl; /** diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java index 8234f8a..0f2e0d1 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/HttpUtil.java @@ -81,18 +81,18 @@ public static InputStream sendGetWithBasicAuth(String basicAuthToken, String url /** * Send GET request to the GitHub url. * - * @param githubAuthToken Authorization token. + * @param githubAuthTok Authorization OAuth token. * @param url URL. * @return Input stream from connection. * @throws IOException If failed. */ - public static InputStream sendGetToGit(String githubAuthToken, String url) throws IOException { + public static InputStream sendGetToGit(String githubAuthTok, String url) throws IOException { Stopwatch started = Stopwatch.createStarted(); URL obj = new URL(url); HttpURLConnection con = (HttpURLConnection)obj.openConnection(); con.setRequestProperty("accept-charset", StandardCharsets.UTF_8.toString()); - con.setRequestProperty("Authorization", "token " + githubAuthToken); + con.setRequestProperty("Authorization", "token " + githubAuthTok); con.setRequestProperty("Connection", "Keep-Alive"); con.setRequestProperty("Keep-Alive", "header"); @@ -193,4 +193,39 @@ public static String sendPostAsStringToGit(String githubAuthToken, String url, S return readIsToString(inputStream); } } + + /** + * Send POST request to the GitHub url. + * + * @param jiraAuthTok Authorization Base64 token. + * @param url URL. + * @param body Request POST params. + * @return Response body from given url. + * @throws IOException If failed. + */ + public static String sendPostAsStringToJira(String jiraAuthTok, String url, String body) throws IOException { + URL obj = new URL(url); + HttpURLConnection con = (HttpURLConnection)obj.openConnection(); + Charset charset = StandardCharsets.UTF_8; + + con.setRequestProperty("accept-charset", charset.toString()); + con.setRequestProperty("Authorization", "Basic " + jiraAuthTok); + con.setRequestProperty("content-type", "application/json"); + con.setRequestProperty("Connection", "Keep-Alive"); + con.setRequestProperty("Keep-Alive", "header"); + + con.setRequestMethod("POST"); + + con.setDoOutput(true); + + try (OutputStreamWriter writer = new OutputStreamWriter(con.getOutputStream(), charset)){ + writer.write(body); // Write POST query string (if any needed). + } + + logger.info("\nSending 'POST' request to URL : " + url + "\n" + body); + + try (InputStream inputStream = getInputStream(con)){ + return readIsToString(inputStream); + } + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java index c636e0b..5eb1510 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/util/XmlUtil.java @@ -48,4 +48,30 @@ return unmarshal; } + + /** + * @param t Text to process. + */ + public static String xmlEscapeText(CharSequence t) { + StringBuilder sb = new StringBuilder(); + + for (int i = 0; i < t.length(); i++) { + char c = t.charAt(i); + + switch(c){ + case '<': sb.append("<"); break; + case '>': sb.append(">"); break; + case '\"': sb.append("""); break; + case '&': sb.append("&"); break; + case '\'': sb.append("'"); break; + default: + if (c>0x7e) + sb.append("&#").append((int)c).append(";"); + else + sb.append(c); + } + } + + return sb.toString(); + } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java index 795cc63..9325f60 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/TestFailure.java @@ -242,6 +242,18 @@ public void initStat(@Nullable final Function<TestInBranch, RunStat> runStatSupp } } + /** + * @return {@code True} if this failure is appeared in the current branch. + */ + public boolean isNewFailedTest() { + FailureSummary recent = histBaseBranch.recent; + + boolean lowFailureRate = recent != null && recent.failureRate != null && + Float.valueOf(recent.failureRate.replace(',', '.')) < 4.; + + return lowFailureRate && flakyComments == null; + } + /** {@inheritDoc} */ @Override public boolean equals(Object o) { if (this == o) diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java index 50b71f7..59e64ec 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/model/current/UpdateInfo.java @@ -29,6 +29,9 @@ /** GitHub auth token availability flag. */ public static int GITHUB_FLAG = 2; + /** JIRA auth token availability flag. */ + public static int JIRA_FLAG = 2; + /** Flags to use in javascript. */ public Integer javaFlags = 0; @@ -59,5 +62,8 @@ public void setJavaFlags(IAnalyticsEnabledTeamcity teamcity) { if (teamcity.isGitTokenAvailable()) javaFlags = javaFlags | GITHUB_FLAG; + + if (teamcity.isJiraTokenAvailable()) + javaFlags = javaFlags | JIRA_FLAG; } } diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java index 4986f3d..2148a35 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/TriggerBuild.java @@ -27,7 +27,10 @@ import javax.ws.rs.QueryParam; import javax.ws.rs.core.Context; import javax.ws.rs.core.MediaType; +import org.apache.ignite.ci.ITcHelper; import org.apache.ignite.ci.ITeamcity; +import org.apache.ignite.ci.github.PullRequest; +import org.apache.ignite.ci.tcmodel.result.Build; import org.apache.ignite.ci.user.ICredentialsProv; import org.apache.ignite.ci.web.CtxListener; import org.apache.ignite.ci.web.rest.login.ServiceUnauthorizedException; @@ -47,19 +50,44 @@ @GET @Path("trigger") public SimpleResult triggerBuild( - @Nullable @QueryParam("serverId") String serverId, + @Nullable @QueryParam("serverId") String srvId, @Nullable @QueryParam("branchName") String branchName, @Nullable @QueryParam("suiteId") String suiteId, - @Nullable @QueryParam("top") Boolean top) { - + @Nullable @QueryParam("top") Boolean top, + @Nullable @QueryParam("observe") Boolean observe + ) { final ICredentialsProv prov = ICredentialsProv.get(req); - if(!prov.hasAccess(serverId)) { - throw ServiceUnauthorizedException.noCreds(serverId); - } + if (!prov.hasAccess(srvId)) + throw ServiceUnauthorizedException.noCreds(srvId); - try (final ITeamcity helper = CtxListener.getTcHelper(context).server(serverId, prov)) { - helper.triggerBuild(suiteId, branchName, false, top != null && top); + ITcHelper helper = CtxListener.getTcHelper(context); + + try (final ITeamcity teamcity = helper.server(srvId, prov)) { + PullRequest pr = teamcity.getPullRequest(branchName); + + String ticketId = ""; + + if (pr.getTitle().startsWith("IGNITE-")) { + int beginIdx = 7; + int endIdx = 7; + + while (endIdx < pr.getTitle().length() && Character.isDigit(pr.getTitle().charAt(endIdx))) + endIdx++; + + ticketId = pr.getTitle().substring(beginIdx, endIdx); + } + + if (ticketId.equals("")) + return new SimpleResult("PR title \"" + pr.getTitle() + "\" should starts with \"IGNITE-XXXX\"." + + " Please, rename PR according to the" + + " <a href='https://cwiki.apache.org/confluence/display/IGNITE/How+to+Contribute" + + "#HowtoContribute-1.CreateGitHubpull-request'>contributing guide</a>."); + + Build build = teamcity.triggerBuild(suiteId, branchName, false, top != null && top); + + if (observe != null && observe) + helper.buildObserver().observe(build, srvId, prov, "ignite-" + ticketId); } return new SimpleResult("OK"); diff --git a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java index 12344b4..eba2b58 100644 --- a/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java +++ b/ignite-tc-helper-web/src/main/java/org/apache/ignite/ci/web/rest/pr/GetPrTestFailures.java @@ -103,22 +103,44 @@ public TestFailuresSummary getPrFailures( @Nonnull @QueryParam("action") String action, @Nullable @QueryParam("count") Integer count) { - final TestFailuresSummary res = new TestFailuresSummary(); - final AtomicInteger runningUpdates = new AtomicInteger(); - final ITcHelper tcHelper = CtxListener.getTcHelper(context); final ICredentialsProv creds = ICredentialsProv.get(req); + return getTestFailuresSummary(tcHelper, creds, srvId, suiteId, branchForTc, action, count); + } + + /** + * @param helper Helper. + * @param creds Credentials. + * @param srvId Server id. + * @param suiteId Suite id. + * @param branchForTc Branch name. + * @param act Action. + * @param cnt Count. + * @return Test failures summary. + */ + public static TestFailuresSummary getTestFailuresSummary( + ITcHelper helper, + ICredentialsProv creds, + String srvId, + String suiteId, + String branchForTc, + String act, + Integer cnt + ) { + final TestFailuresSummary res = new TestFailuresSummary(); + final AtomicInteger runningUpdates = new AtomicInteger(); + //using here non persistent TC allows to skip update statistic - try (IAnalyticsEnabledTeamcity teamcity = tcHelper.server(srvId, creds)) { + try (IAnalyticsEnabledTeamcity teamcity = helper.server(srvId, creds)) { res.setJavaFlags(teamcity); LatestRebuildMode rebuild; - if (FullQueryParams.HISTORY.equals(action)) + if (FullQueryParams.HISTORY.equals(act)) rebuild = LatestRebuildMode.ALL; - else if (FullQueryParams.LATEST.equals(action)) + else if (FullQueryParams.LATEST.equals(act)) rebuild = LatestRebuildMode.LATEST; - else if (FullQueryParams.CHAIN.equals(action)) + else if (FullQueryParams.CHAIN.equals(act)) rebuild = LatestRebuildMode.NONE; else rebuild = LatestRebuildMode.LATEST; @@ -129,7 +151,7 @@ else if (FullQueryParams.CHAIN.equals(action)) long limit; if (rebuild == LatestRebuildMode.ALL) - limit = count == null ? 10 : count; + limit = cnt == null ? 10 : cnt; else limit = 1; @@ -156,9 +178,10 @@ else if (FullQueryParams.CHAIN.equals(action)) if (ctx.isFakeStub()) chainStatus.setBuildNotFound(true); else { - int cnt = (int)ctx.getRunningUpdates().count(); - if (cnt > 0) - runningUpdates.addAndGet(cnt); + int cnt0 = (int)ctx.getRunningUpdates().count(); + + if (cnt0 > 0) + runningUpdates.addAndGet(cnt0); //fail rate reference is always default (master) chainStatus.initFromContext(teamcity, ctx, teamcity, failRateBranch); @@ -179,8 +202,8 @@ public String getNotifyGit( @Nullable @QueryParam("serverId") String srvId, @Nonnull @QueryParam("suiteId") String suiteId, @Nonnull @QueryParam("branchForTc") String branchForTc, - @Nonnull @QueryParam("action") String action, - @Nullable @QueryParam("count") Integer count, + @Nonnull @QueryParam("action") String act, + @Nullable @QueryParam("count") Integer cnt, @Nonnull @FormParam("notifyMsg") String msg) { if (!branchForTc.startsWith("pull/")) return "Given branch is not a pull request. Notify works only for pull requests."; diff --git a/ignite-tc-helper-web/src/main/webapp/index.html b/ignite-tc-helper-web/src/main/webapp/index.html index c642863..481706e 100644 --- a/ignite-tc-helper-web/src/main/webapp/index.html +++ b/ignite-tc-helper-web/src/main/webapp/index.html @@ -9,6 +9,7 @@ <script src="https://code.jquery.com/jquery-1.12.4.js"></script> <script src="https://code.jquery.com/ui/1.12.1/jquery-ui.js"></script> + <script src="js/common-1.6.js"></script> <script> $(document).ready(function() { @@ -21,6 +22,7 @@ function loadData() { $("#loadStatus").html("<img src='https://www.wallies.com/filebin/images/loading_apple.gif' width=20px height=20px> Please wait"); + $.ajax({ url: "rest/branches/version", success: showVersionInfo, @@ -50,6 +52,7 @@ success: function(result) { $("#loadStatus").html(""); showBuildsOnServers(result); + showRunAllForm(result); }, error: showErrInLoadStatus }); @@ -108,6 +111,55 @@ } $("#buildsCheck").html(res); } + +/** + * This form allows user to start TeamCity Run All build. + */ +function showRunAllForm(result) { + var res = ""; + + for (var i = 0; i < result.length; i++) { + var serverId = result[i]; + + res+="Server: <input type='text' name='serverId' value=" + serverId +" readonly>" ; + res+="Pull Request #<input type='text' name='prId' onkeypress='return trigBuild(event)'> "; + res+="<button onclick='trigBuild()'>Run All</button>"; + } + + $("#runAll").html(res); +} + +/** + * Start Run All build on TeamCity and comment in JIRA ticket when build will be finished. + */ +function trigBuild(event) { + if (event != null && event.key != "Enter") + return; + + var fields = document.getElementById("runAll").children; + var url = "rest/build/trigger?suiteId=IgniteTests24Java8_RunAll"; + var prId = null; + + for (let field of fields) { + if (field.name == "serverId") + url += "&serverId=" + field.value; + + if (field.name == "prId") { + url += "&branchName=pull%2F" + field.value + "%2Fhead"; + prId = field.value; + } + } + + url += "&observe=true" + + $.ajax({ + url: url, + success: function(result) { + $("#runAll").html("Run All test build was started for PR #" + prId); + }, + error: showErrInLoadStatus + }); +} </script> </head> <body> @@ -132,6 +184,9 @@ <div id="buildsCheck"></div> <br> +TeamCity Run All: <br> +<div id="runAll"></div> +<br> <a href="ignval.html">Ignite Log Values pretty-print</a><br> ---------------------------------------------------------------- This is an automated message from the Apache Git Service. To respond to the message, please log on GitHub and use the URL above to go to the specific comment. For queries about this service, please contact Infrastructure at: us...@infra.apache.org > Handle print critical failures to the PR > ---------------------------------------- > > Key: IGNITE-9377 > URL: https://issues.apache.org/jira/browse/IGNITE-9377 > Project: Ignite > Issue Type: Sub-task > Reporter: Ryabov Dmitrii > Assignee: Ryabov Dmitrii > Priority: Minor > > Create a button by clicking on which the info about critical failures will be > written in the PR. -- This message was sent by Atlassian JIRA (v7.6.3#76005)