blob: 510177870f9b80e21696e5c8ca08e77e170547ed [file] [log] [blame]
/*
* 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.jobs;
import java.text.MessageFormat;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import javax.inject.Inject;
import org.apache.ignite.ci.conf.BranchTracked;
import org.apache.ignite.ci.conf.ChainAtServerTracked;
import org.apache.ignite.ci.di.AutoProfiling;
import org.apache.ignite.ci.di.MonitoredTask;
import org.apache.ignite.ci.tcbot.conf.ITcBotConfig;
import org.apache.ignite.ci.tcmodel.agent.Agent;
import org.apache.ignite.ci.tcmodel.result.Build;
import org.apache.ignite.ci.tcmodel.result.Triggered;
import org.apache.ignite.ci.tcmodel.user.User;
import org.apache.ignite.ci.teamcity.ignited.BuildRefCompacted;
import org.apache.ignite.ci.teamcity.ignited.IStringCompactor;
import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnited;
import org.apache.ignite.ci.teamcity.ignited.ITeamcityIgnitedProvider;
import org.apache.ignite.ci.teamcity.ignited.fatbuild.FatBuildCompacted;
import org.apache.ignite.ci.user.ICredentialsProv;
import org.apache.ignite.ci.util.ExceptionUtil;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;
/**
* Trigger build if half of agents are available and there is no self-triggered builds in build queue.
*/
public class CheckQueueJob implements Runnable {
/** JVM option to disable this job. */
public static final String AUTO_TRIGGERING_BUILD_DISABLED = "AUTO_TRIGGERING_BUILD_DISABLED";
/** */
private static final Logger logger = LoggerFactory.getLogger(CheckQueueJob.class);
/** Percentage of free agents required to trigger build. */
private static final int CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT =
Integer.getInteger("CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT", 50);
/** */
private ICredentialsProv creds;
/** */
@Inject private ITeamcityIgnitedProvider tcIgnitedProv;
/** */
@Inject private IStringCompactor compactor;
/** */
@Inject private ITcBotConfig cfg;
/** */
private final Map<ChainAtServerTracked, Long> startTimes = new HashMap<>();
/**
* @param creds Background credentials provider.
*/
public void init(ICredentialsProv creds) {
this.creds = creds;
}
/** {@inheritDoc} */
@Override public void run() {
try {
runEx();
}
catch (Exception e) {
e.printStackTrace();
logger.error("Check Queue periodic check failed: " + e.getMessage(), e);
}
}
/** */
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
@AutoProfiling
@MonitoredTask(name = "Check Queue")
protected String runEx() {
if (Boolean.valueOf(System.getProperty(AUTO_TRIGGERING_BUILD_DISABLED))) {
final String msg = "Automatic build triggering was disabled.";
logger.info(msg);
return msg;
}
List<BranchTracked> tracked = cfg.getTrackedBranches().getBranches();
if (tracked == null || tracked.isEmpty()) {
final String msg = "Background check queue skipped - no config set for tracked branches.";
logger.info(msg);
return msg;
}
int srvsChecked = 0, chainsChecked = 0;
Map<String, List<ChainAtServerTracked>> chainsBySrv = mapChainsByServer(tracked);
for (Map.Entry<String, List<ChainAtServerTracked>> entry : chainsBySrv.entrySet()) {
String srvId = entry.getKey();
List<ChainAtServerTracked> chainsAll = entry.getValue();
List<ChainAtServerTracked> chains = chainsAll.stream()
.filter(c -> Objects.equals(c.serverId, srvId))
.collect(Collectors.toList());
srvsChecked++;
chainsChecked += chainsAll.size();
try {
checkQueue(srvId, chains);
}
catch (Exception e) {
logger.error("Unable to check queue: " + e.getMessage(), e);
throw ExceptionUtil.propagateException(e);
}
}
return "Checked: " + srvsChecked + "servers, " + chainsChecked + " chains, "
+ ": Trigger'able branches " + chainsBySrv.size();
}
/**
* Trigger build if half of agents is available and there is no self-triggered builds in build queue.
*/
@SuppressWarnings({"WeakerAccess", "UnusedReturnValue"})
@AutoProfiling
@MonitoredTask(name = "Check Server Queue", nameExtArgIndex = 0)
protected String checkQueue(String srvId,
List<ChainAtServerTracked> chains) {
ITeamcityIgnited tcIgn = tcIgnitedProv.server(srvId, creds);
List<Agent> agents = tcIgn.agents(true, true);
int total = agents.size();
int running = 0;
for (Agent agent : agents) {
if (agent.getBuild() != null) // || !STATE_RUNNING.equals(agent.getFatBuild().status)
++running;
}
int free = total == 0 ? -1 : (total - running) * 100 / total;
final String agentStatus = MessageFormat.format("{0}% of agents are free ({1} total, {2} running builds).", free, total, running);
logger.info(agentStatus);
if (free < CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT)
return "Min agent percent of free agents not met:" + agentStatus;
logger.info("There are more than {}% free agents (total={}, free={}).", CHECK_QUEUE_MIN_FREE_AGENTS_PERCENT,
total, total - running);
String selfLogin = creds.getUser(srvId);
StringBuilder res = new StringBuilder();
for (ChainAtServerTracked chain : chains) {
if(!Objects.equals(chain.serverId, srvId))
continue;
boolean trigger = true;
List<BuildRefCompacted> buildsForBr = tcIgn.getQueuedBuildsCompacted(chain.branchForRest);
for (BuildRefCompacted refComp : buildsForBr) {
Integer buildId = refComp.getId();
if (buildId == null)
continue; // should not occur;
final FatBuildCompacted fatBuild = tcIgn.getFatBuild(buildId);
final Build build = fatBuild.toBuild(compactor);
final Triggered triggered = build.getTriggered();
if (triggered == null) {
logger.info("Unable to get triggering info for queued build {} (type={}).", buildId, build.buildTypeId);
continue;
}
User user = build.getTriggered().getUser();
if (user == null) {
logger.info("Unable to get username for queued build {} (type={}).", buildId, build.buildTypeId);
continue;
}
String login = user.username;
if (selfLogin.equalsIgnoreCase(login)) {
final String msg
= MessageFormat.format("Queued build {0} was early triggered " +
"(user {1}, branch {2}, suite {3})." +
" Will not start Ignite build.", buildId, login, chain.branchForRest, build.buildTypeId);
logger.info(msg);
res.append(msg).append("; ");
trigger = false;
break;
}
}
if (!trigger)
continue;
long curr = System.currentTimeMillis();
long delay = chain.getTriggerBuildQuietPeriod();
if (delay > 0) {
Long lastStart = startTimes.get(chain);
long minsPassed;
if (lastStart != null &&
(minsPassed = TimeUnit.MILLISECONDS.toMinutes(curr - lastStart)) < delay) {
final String msg = MessageFormat.format("Skip triggering build, timeout has not expired " +
"(server={0}, suite={1}, branch={2}, delay={3} mins, passed={4} mins)",
chain.getServerId(), chain.getSuiteIdMandatory(), chain.getBranchForRestMandatory(),
chain.getTriggerBuildQuietPeriod(), minsPassed);
logger.info(msg);
res.append(msg).append("; ");
continue;
}
}
startTimes.put(chain, curr);
tcIgn.triggerBuild(chain.suiteId, chain.branchForRest, true, false);
res.append(chain.branchForRest).append(" ").append(chain.suiteId).append(" triggered; ");
}
return res.toString();
}
/**
* @param branchesTracked Tracked branches.
* @return Mapped chains to server identifier.
*/
private Map<String, List<ChainAtServerTracked>> mapChainsByServer(List<BranchTracked> branchesTracked) {
Map<String, List<ChainAtServerTracked>> chainsBySrv = new HashMap<>();
for(BranchTracked branchTracked: branchesTracked) {
for (ChainAtServerTracked chain : branchTracked.getChains()) {
String srv = chain.serverId;
if (!tcIgnitedProv.hasAccess(srv, creds)) {
logger.warn("Background operations credentials does not grant access to server \"{}\"," +
" build queue trigger will not work.", srv);
continue;
}
if (!chain.isTriggerBuild()) {
logger.info("Build triggering disabled for server={}, suite={}, branch={}",
srv, chain.getSuiteIdMandatory(), chain.getBranchForRestMandatory());
continue;
}
logger.info("Checking queue for server {}.", srv);
chainsBySrv.computeIfAbsent(srv, v -> new ArrayList<>()).add(chain);
}
}
return chainsBySrv;
}
}