/*
* Copyright 2017 ThoughtWorks, 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.
*/
package com.thoughtworks.go.server.service;
import com.rits.cloning.Cloner;
import com.thoughtworks.go.config.*;
import com.thoughtworks.go.config.materials.MaterialConfigs;
import com.thoughtworks.go.config.materials.dependency.DependencyMaterial;
import com.thoughtworks.go.config.materials.mercurial.HgMaterial;
import com.thoughtworks.go.config.materials.mercurial.HgMaterialConfig;
import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry;
import com.thoughtworks.go.domain.*;
import com.thoughtworks.go.domain.activity.AgentAssignment;
import com.thoughtworks.go.domain.buildcause.BuildCause;
import com.thoughtworks.go.domain.builder.Builder;
import com.thoughtworks.go.domain.builder.FetchArtifactBuilder;
import com.thoughtworks.go.domain.materials.Material;
import com.thoughtworks.go.domain.materials.Modification;
import com.thoughtworks.go.domain.materials.svn.Subversion;
import com.thoughtworks.go.domain.materials.svn.SvnCommand;
import com.thoughtworks.go.fixture.PipelineWithTwoStages;
import com.thoughtworks.go.helper.AgentMother;
import com.thoughtworks.go.helper.SvnTestRepo;
import com.thoughtworks.go.helper.TestRepo;
import com.thoughtworks.go.remote.AgentIdentifier;
import com.thoughtworks.go.remote.work.BuildWork;
import com.thoughtworks.go.remote.work.DeniedAgentWork;
import com.thoughtworks.go.remote.work.Work;
import com.thoughtworks.go.server.cache.GoCache;
import com.thoughtworks.go.server.dao.DatabaseAccessHelper;
import com.thoughtworks.go.server.dao.JobInstanceDao;
import com.thoughtworks.go.server.dao.PipelineDao;
import com.thoughtworks.go.server.dao.StageDao;
import com.thoughtworks.go.server.domain.Username;
import com.thoughtworks.go.server.materials.DependencyMaterialUpdateNotifier;
import com.thoughtworks.go.server.persistence.MaterialRepository;
import com.thoughtworks.go.server.scheduling.ScheduleHelper;
import com.thoughtworks.go.server.service.builders.BuilderFactory;
import com.thoughtworks.go.server.service.result.HttpLocalizedOperationResult;
import com.thoughtworks.go.server.transaction.TransactionTemplate;
import com.thoughtworks.go.server.websocket.AgentRemoteHandler;
import com.thoughtworks.go.server.websocket.AgentStub;
import com.thoughtworks.go.util.*;
import com.thoughtworks.go.utils.SerializationTester;
import com.thoughtworks.go.websocket.Action;
import com.thoughtworks.go.websocket.Message;
import com.thoughtworks.go.websocket.MessageEncoding;
import org.hamcrest.Matchers;
import org.junit.*;
import org.junit.runner.RunWith;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.test.context.ContextConfiguration;
import org.springframework.test.context.junit4.SpringJUnit4ClassRunner;
import java.io.IOException;
import java.sql.SQLException;
import java.util.Date;
import java.util.List;
import java.util.UUID;
import java.util.concurrent.Semaphore;
import static com.thoughtworks.go.helper.ModificationsMother.modifyNoFiles;
import static com.thoughtworks.go.helper.ModificationsMother.modifySomeFiles;
import static com.thoughtworks.go.util.GoConstants.DEFAULT_APPROVED_BY;
import static com.thoughtworks.go.util.TestUtils.sleepQuietly;
import static org.hamcrest.Matchers.is;
import static org.hamcrest.core.IsInstanceOf.instanceOf;
import static org.hamcrest.core.IsNot.not;
import static org.hamcrest.core.IsNull.nullValue;
import static org.junit.Assert.*;
import static org.mockito.Mockito.*;
@RunWith(SpringJUnit4ClassRunner.class)
@ContextConfiguration(locations = {
"classpath:WEB-INF/applicationContext-global.xml",
"classpath:WEB-INF/applicationContext-dataLocalAccess.xml",
"classpath:WEB-INF/applicationContext-acegi-security.xml"
})
public class BuildAssignmentServiceIntegrationTest {
@Autowired private BuildAssignmentService buildAssignmentService;
@Autowired private GoConfigService goConfigService;
@Autowired private GoConfigDao goConfigDao;
@Autowired private PipelineDao pipelineDao;
@Autowired private JobInstanceDao jobInstanceDao;
@Autowired private AgentService agentService;
@Autowired private AgentAssignment agentAssignment;
@Autowired private ScheduleService scheduleService;
@Autowired private MaterialRepository materialRepository;
@Autowired private DatabaseAccessHelper dbHelper;
@Autowired private ScheduleHelper scheduleHelper;
@Autowired private GoCache goCache;
@Autowired private StageDao stageDao;
@Autowired private JobInstanceService jobInstanceService;
@Autowired private PipelineService pipelineService;
@Autowired private EnvironmentConfigService environmentConfigService;
@Autowired private TimeProvider timeProvider;
@Autowired private TransactionTemplate transactionTemplate;
@Autowired private BuilderFactory builderFactory;
@Autowired private InstanceFactory instanceFactory;
@Autowired private AgentRemoteHandler agentRemoteHandler;
@Autowired private PipelineConfigService pipelineConfigService;
@Autowired private EntityHashingService entityHashingService;
@Autowired private ElasticAgentPluginService elasticAgentPluginService;
@Autowired private DependencyMaterialUpdateNotifier notifier;
private PipelineConfig evolveConfig;
private static final String STAGE_NAME = "dev";
private GoConfigFileHelper configHelper;
private ScheduleTestUtil u;
public Subversion repository;
public static TestRepo testRepo;
private PipelineWithTwoStages fixture;
private String md5 = "md5-test";
private Username loserUser = new Username(new CaseInsensitiveString("loser"));
private AgentStub agent;
private ConfigCache configCache;
private ConfigElementImplementationRegistry registry;
@BeforeClass
public static void setupRepos() throws IOException {
testRepo = new SvnTestRepo("testSvnRepo");
}
@AfterClass
public static void tearDownConfigFileLocation() throws IOException {
TestRepo.internalTearDown();
}
@Before
public void setUp() throws Exception {
configCache = new ConfigCache();
registry = ConfigElementImplementationRegistryMother.withNoPlugins();
configHelper = new GoConfigFileHelper().usingCruiseConfigDao(goConfigDao);
configHelper.onSetUp();
dbHelper.onSetUp();
fixture = new PipelineWithTwoStages(materialRepository, transactionTemplate);
fixture.usingConfigHelper(configHelper).usingDbHelper(dbHelper).onSetUp();
repository = new SvnCommand(null, testRepo.projectRepositoryUrl());
evolveConfig = configHelper.addPipeline("evolve", STAGE_NAME, repository, "unit");
configHelper.addPipeline("anotherPipeline", STAGE_NAME, repository, "anotherTest");
configHelper.addPipeline("thirdPipeline", STAGE_NAME, repository, "yetAnotherTest");
goConfigService.forceNotifyListeners();
goCache.clear();
u = new ScheduleTestUtil(transactionTemplate, materialRepository, dbHelper, configHelper);
agent = new AgentStub();
notifier.disableUpdates();
}
@After
public void teardown() throws Exception {
notifier.enableUpdates();
goCache.clear();
agentService.clearAll();
fixture.onTearDown();
dbHelper.onTearDown();
configHelper.onTearDown();
FileUtil.deleteFolder(goConfigService.artifactsDir());
agentAssignment.clear();
agentRemoteHandler.connectedAgents().clear();
}
@Test
public void shouldRescheduleAbandonedBuild() throws SQLException {
AgentIdentifier instance = agent(AgentMother.localAgent());
Pipeline pipeline = instanceFactory.createPipelineInstance(evolveConfig, modifyNoFiles(evolveConfig), new DefaultSchedulingContext(
DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(pipeline);
buildAssignmentService.onConfigChange(goConfigService.getCurrentConfig());
buildAssignmentService.onTimer();
buildAssignmentService.assignWorkToAgent(instance);
long firstAssignedBuildId = buildOf(pipeline).getId();
//somehow agent abandoned its original build...
buildAssignmentService.assignWorkToAgent(instance);
JobInstance reloaded = jobInstanceDao.buildByIdWithTransitions(firstAssignedBuildId);
assertThat(reloaded.getState(), is(JobState.Rescheduled));
assertThat(reloaded.isIgnored(), is(true));
}
@Test
public void shouldNotAssignWorkToDeniedAgent() throws Exception {
AgentConfig deniedAgentConfig = AgentMother.localAgent();
deniedAgentConfig.disable();
Work assignedWork = buildAssignmentService.assignWorkToAgent(agent(deniedAgentConfig));
assertThat(assignedWork, instanceOf(DeniedAgentWork.class));
}
@Test
public void shouldNotAssignWorkWhenPipelineScheduledWithStaleMaterials() {
AgentIdentifier instance = agent(AgentMother.localAgent());
Pipeline pipeline = instanceFactory.createPipelineInstance(evolveConfig, modifyNoFiles(evolveConfig), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(pipeline);
evolveConfig.setMaterialConfigs(new MaterialConfigs(new HgMaterialConfig("foo", null)));
configHelper.removePipeline(CaseInsensitiveString.str(evolveConfig.name()));
configHelper.addPipeline(evolveConfig);
buildAssignmentService.onConfigChange(goConfigService.getCurrentConfig());
JobInstance job = buildOf(pipeline);
jobInstanceDao.updateStateAndResult(job);
assertThat(buildAssignmentService.assignWorkToAgent(instance), is(BuildAssignmentService.NO_WORK));
}
@Test
public void shouldNotAssignCancelledJob() throws Exception {
AgentIdentifier instance = agent(AgentMother.localAgent());
Pipeline pipeline = instanceFactory.createPipelineInstance(evolveConfig, modifyNoFiles(evolveConfig), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(pipeline);
buildAssignmentService.onConfigChange(goConfigService.getCurrentConfig());
JobInstance job = buildOf(pipeline);
job.cancel();
jobInstanceDao.updateStateAndResult(job);
assertThat(buildAssignmentService.assignWorkToAgent(instance), is(BuildAssignmentService.NO_WORK));
}
@Test
public void shouldUpdateNumberOfActiveRemoteAgentsAfterAssigned() {
AgentConfig agentConfig = AgentMother.remoteAgent();
configHelper.addAgent(agentConfig);
fixture.createPipelineWithFirstStageScheduled();
buildAssignmentService.onTimer();
AgentInstance agent = agentService.findAgent(agentConfig.getUuid());
assertFalse(agent.isBuilding());
Work work = buildAssignmentService.assignWorkToAgent(agent(agentConfig));
assertThat(work, instanceOf(BuildWork.class));
assertTrue(agent.isBuilding());
}
@Test
public void shouldCancelOutOfDateBuilds() throws Exception {
fixture.createPipelineWithFirstStageScheduled();
buildAssignmentService.onTimer();
configHelper.removeStage(fixture.pipelineName, fixture.devStage);
buildAssignmentService.onConfigChange(goConfigService.getCurrentConfig());
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance job = pipeline.getFirstStage().getJobInstances().first();
assertThat(job.getState(), is(JobState.Completed));
assertThat(job.getResult(), is(JobResult.Cancelled));
}
@Test
public void shouldCancelBuildsForDeletedStagesWhenPipelineConfigChanges() throws Exception {
buildAssignmentService.initialize();
fixture.createPipelineWithFirstStageScheduled();
buildAssignmentService.onTimer();
PipelineConfig pipelineConfig = new Cloner().deepClone(configHelper.getCachedGoConfig().currentConfig().getPipelineConfigByName(new CaseInsensitiveString(fixture.pipelineName)));
String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig);
String md5 = CachedDigestUtils.md5Hex(xml);
StageConfig devStage = pipelineConfig.findBy(new CaseInsensitiveString(fixture.devStage));
pipelineConfig.remove(devStage);
pipelineConfigService.updatePipelineConfig(loserUser, pipelineConfig, md5, new HttpLocalizedOperationResult());
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance job = pipeline.getFirstStage().getJobInstances().first();
assertThat(job.getState(), is(JobState.Completed));
assertThat(job.getResult(), is(JobResult.Cancelled));
}
@Test
public void shouldCancelBuildsForDeletedJobsWhenPipelineConfigChanges() throws Exception {
buildAssignmentService.initialize();
fixture = new PipelineWithTwoStages(materialRepository, transactionTemplate).usingTwoJobs();
fixture.usingConfigHelper(configHelper).usingDbHelper(dbHelper).onSetUp();
fixture.createPipelineWithFirstStageScheduled();
buildAssignmentService.onTimer();
PipelineConfig pipelineConfig = new Cloner().deepClone(configHelper.getCachedGoConfig().currentConfig().getPipelineConfigByName(new CaseInsensitiveString(fixture.pipelineName)));
String xml = new MagicalGoConfigXmlWriter(configCache, registry).toXmlPartial(pipelineConfig);
String md5 = CachedDigestUtils.md5Hex(xml);
StageConfig devStage = pipelineConfig.findBy(new CaseInsensitiveString(fixture.devStage));
devStage.getJobs().remove(devStage.jobConfigByConfigName(new CaseInsensitiveString(fixture.JOB_FOR_DEV_STAGE)));
pipelineConfigService.updatePipelineConfig(loserUser, pipelineConfig, md5, new HttpLocalizedOperationResult());
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance deletedJob = pipeline.getFirstStage().getJobInstances().getByName(fixture.JOB_FOR_DEV_STAGE);
assertThat(deletedJob.getState(), is(JobState.Completed));
assertThat(deletedJob.getResult(), is(JobResult.Cancelled));
JobInstance retainedJob = pipeline.getFirstStage().getJobInstances().getByName(fixture.DEV_STAGE_SECOND_JOB);
assertThat(retainedJob.getState(), is(JobState.Scheduled));
assertThat(retainedJob.getResult(), is(JobResult.Unknown));
}
@Test
public void shouldCancelBuildBelongingToNonExistentPipeline() throws Exception {
fixture.createPipelineWithFirstStageScheduled();
buildAssignmentService.onTimer();
configHelper.removePipeline(fixture.pipelineName);
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("some-other-resource"));
assertThat(buildAssignmentService.assignWorkToAgent(agent(agentConfig)), Matchers.is(BuildAssignmentService.NO_WORK));
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance job = pipeline.getFirstStage().getJobInstances().first();
assertThat(job.getState(), is(JobState.Completed));
assertThat(job.getResult(), is(JobResult.Cancelled));
Stage stage = stageDao.findStageWithIdentifier(job.getIdentifier().getStageIdentifier());
assertThat(stage.getState(), is(StageState.Cancelled));
assertThat(stage.getResult(), is(StageResult.Cancelled));
}
@Test
public void shouldNotReloadScheduledJobPlansWhenAgentWorkAssignmentIsInProgress() throws Exception {
fixture.createPipelineWithFirstStageScheduled();
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance job = pipeline.getFirstStage().getJobInstances().first();
final JobInstanceService mockJobInstanceService = mock(JobInstanceService.class);
final Pipeline pipeline1 = pipeline;
final Semaphore sem = new Semaphore(1);
sem.acquire();
when(mockJobInstanceService.orderedScheduledBuilds()).thenReturn(jobInstanceService.orderedScheduledBuilds());
when(mockJobInstanceService.buildByIdWithTransitions(job.getId())).thenReturn(jobInstanceService.buildByIdWithTransitions(job.getId()));
ScheduledPipelineLoader scheduledPipelineLoader = new ScheduledPipelineLoader(null, null, null, null, null, null, null, null) {
@Override
public Pipeline pipelineWithPasswordAwareBuildCauseByBuildId(long buildId) {
sem.release();
sleepQuietly(1000);
verify(mockJobInstanceService, times(1)).orderedScheduledBuilds();
return pipeline1;
}
};
final BuildAssignmentService buildAssignmentServiceUnderTest = new BuildAssignmentService(goConfigService, mockJobInstanceService, scheduleService,
agentService, environmentConfigService, transactionTemplate, scheduledPipelineLoader, pipelineService, builderFactory, agentRemoteHandler, elasticAgentPluginService, timeProvider);
final Throwable[] fromThread = new Throwable[1];
buildAssignmentServiceUnderTest.onTimer();
Thread assigner = new Thread(new Runnable() {
public void run() {
try {
final AgentConfig agentConfig = AgentMother.localAgentWithResources("some-other-resource");
buildAssignmentServiceUnderTest.assignWorkToAgent(agent(agentConfig));
} catch (Throwable e) {
e.printStackTrace();
fromThread[0] = e;
} finally {
}
}
}, "assignmentThread");
assigner.start();
sem.acquire();
buildAssignmentServiceUnderTest.onTimer();
assigner.join();
assertThat(fromThread[0], is(nullValue()));
}
@Test
public void shouldCancelBuildBelongingToNonExistentPipelineWhenCreatingWork() throws Exception {
fixture.createPipelineWithFirstStageScheduled();
Pipeline pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
ScheduledPipelineLoader scheduledPipelineLoader = mock(ScheduledPipelineLoader.class);
when(scheduledPipelineLoader.pipelineWithPasswordAwareBuildCauseByBuildId(pipeline.getFirstStage().getJobInstances().first().getId())).thenThrow(
new PipelineNotFoundException("thrown by mockPipelineService"));
GoConfigService mockGoConfigService = mock(GoConfigService.class);
CruiseConfig config = configHelper.currentConfig();
configHelper.removePipeline(fixture.pipelineName, config);
when(mockGoConfigService.getCurrentConfig()).thenReturn(config);
buildAssignmentService = new BuildAssignmentService(mockGoConfigService, jobInstanceService, scheduleService, agentService, environmentConfigService,
transactionTemplate, scheduledPipelineLoader, pipelineService, builderFactory, agentRemoteHandler, elasticAgentPluginService, timeProvider);
buildAssignmentService.onTimer();
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("some-other-resource"));
try {
buildAssignmentService.assignWorkToAgent(agent(agentConfig));
fail("should have thrown PipelineNotFoundException");
} catch (PipelineNotFoundException e) {
// ok
}
pipeline = pipelineDao.mostRecentPipeline(fixture.pipelineName);
JobInstance job = pipeline.getFirstStage().getJobInstances().first();
assertThat(job.getState(), is(JobState.Completed));
assertThat(job.getResult(), is(JobResult.Cancelled));
Stage stage = stageDao.findStageWithIdentifier(job.getIdentifier().getStageIdentifier());
assertThat(stage.getState(), is(StageState.Cancelled));
assertThat(stage.getResult(), is(StageResult.Cancelled));
}
@Test
public void shouldBeAbleToSerializeAndDeserializeBuildWork() throws Exception {
Pipeline pipeline1 = instanceFactory.createPipelineInstance(evolveConfig, modifySomeFiles(evolveConfig), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(pipeline1);
buildAssignmentService.onTimer();
BuildWork work = (BuildWork) buildAssignmentService.assignWorkToAgent(agent(AgentMother.localAgent()));
BuildWork deserialized = (BuildWork) SerializationTester.serializeAndDeserialize(work);
assertThat(deserialized.getAssignment().materialRevisions(), is(work.getAssignment().materialRevisions()));
assertThat(deserialized.getAssignment(), is(work.getAssignment()));
assertThat(deserialized, is(work));
}
@Test
public void shouldCreateWorkWithFetchMaterialsFlagFromStageConfig() throws Exception {
evolveConfig.getFirstStageConfig().setFetchMaterials(true);
Pipeline pipeline1 = instanceFactory.createPipelineInstance(evolveConfig, modifySomeFiles(evolveConfig), new DefaultSchedulingContext(DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(pipeline1);
buildAssignmentService.onTimer();
BuildWork work = (BuildWork) buildAssignmentService.assignWorkToAgent(agent(AgentMother.localAgent()));
assertThat("should have set fetchMaterials on assignment", work.getAssignment().getPlan().shouldFetchMaterials(), is(true));
}
/**
* (uppest/2/uppest-stage/1)
* |------------------> upper-peer -------
* | ...................................|...............................................
* | . | .
* [ uppest-stage ............................|...................... {bar.zip uppest/upper-peer/downer}
* V .
* uppest uppest-stage-2 ------> upper ------> downer ------> downest {foo.zip uppest/upper/downer}
* (uppest/1/uppest-stage-2/1)
* uppest-stage-3 ]
* <p/>
* .... :: fetch artifact call
* ---> :: material dependency
*/
@Test
public void shouldCreateWork_withAncestorFetchArtifactCalls_resolvedToRelevantStage() throws Exception {
configHelper.addPipeline("uppest", "uppest-stage");
configHelper.addStageToPipeline("uppest", "uppest-stage-2");
PipelineConfig uppest = configHelper.addStageToPipeline("uppest", "uppest-stage-3");
configHelper.addPipeline("upper", "upper-stage");
DependencyMaterial upper_sMaterial = new DependencyMaterial(new CaseInsensitiveString("uppest"), new CaseInsensitiveString("uppest-stage-2"));
PipelineConfig upper = configHelper.setMaterialConfigForPipeline("upper", upper_sMaterial.config());
configHelper.addPipeline("upper-peer", "upper-peer-stage");
DependencyMaterial upperPeer_sMaterial = new DependencyMaterial(new CaseInsensitiveString("uppest"), new CaseInsensitiveString("uppest-stage"));
PipelineConfig upperPeer = configHelper.setMaterialConfigForPipeline("upper-peer", upperPeer_sMaterial.config());
configHelper.addPipeline("downer", "downer-stage");
DependencyMaterial downer_sUpperMaterial = new DependencyMaterial(new CaseInsensitiveString("upper"), new CaseInsensitiveString("upper-stage"));
configHelper.setMaterialConfigForPipeline("downer", downer_sUpperMaterial.config());
DependencyMaterial downer_sUpperPeerMaterial = new DependencyMaterial(new CaseInsensitiveString("upper-peer"), new CaseInsensitiveString("upper-peer-stage"));
PipelineConfig downer = configHelper.addMaterialToPipeline("downer", downer_sUpperPeerMaterial.config());
configHelper.addPipeline("downest", "downest-stage");
DependencyMaterial downest_sMaterial = new DependencyMaterial(new CaseInsensitiveString("downer"), new CaseInsensitiveString("downer-stage"));
configHelper.setMaterialConfigForPipeline("downest", downest_sMaterial.config());
Tasks allFetchTasks = new Tasks();
allFetchTasks.add(new FetchTask(new CaseInsensitiveString("uppest/upper/downer"), new CaseInsensitiveString("uppest-stage"), new CaseInsensitiveString("unit"), "foo.zip", "bar"));
allFetchTasks.add(new FetchTask(new CaseInsensitiveString("uppest/upper-peer/downer"), new CaseInsensitiveString("uppest-stage"), new CaseInsensitiveString("unit"), "bar.zip", "baz"));
configHelper.replaceAllJobsInStage("downest", "downest-stage", new JobConfig(new CaseInsensitiveString("fetcher"), new Resources("fetcher"), new ArtifactPlans(), allFetchTasks));
PipelineConfig downest = goConfigService.getCurrentConfig().pipelineConfigByName(new CaseInsensitiveString("downest"));
DefaultSchedulingContext defaultSchedulingCtx = new DefaultSchedulingContext(DEFAULT_APPROVED_BY);
Pipeline uppestInstanceForUpper = instanceFactory.createPipelineInstance(uppest, modifySomeFiles(uppest), defaultSchedulingCtx, md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(uppestInstanceForUpper);
dbHelper.passStage(uppestInstanceForUpper.findStage("uppest-stage"));
Stage upper_sMaterialStage = dbHelper.scheduleStage(uppestInstanceForUpper, uppest.getStage(new CaseInsensitiveString("uppest-stage-2")));
dbHelper.passStage(upper_sMaterialStage);
Pipeline uppestInstanceForUpperPeer = instanceFactory.createPipelineInstance(uppest, modifySomeFiles(uppest), new DefaultSchedulingContext("super-hero"), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(uppestInstanceForUpperPeer);
Stage upperPeer_sMaterialStage = uppestInstanceForUpperPeer.findStage("uppest-stage");
dbHelper.passStage(upperPeer_sMaterialStage);
Pipeline upperInstance = instanceFactory.createPipelineInstance(upper, buildCauseForDependency(upper_sMaterial, upper_sMaterialStage), defaultSchedulingCtx, md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(upperInstance);
Stage downer_sUpperMaterialStage = upperInstance.findStage("upper-stage");
dbHelper.passStage(downer_sUpperMaterialStage);
Pipeline upperPeerInstance = instanceFactory.createPipelineInstance(upperPeer, buildCauseForDependency(upperPeer_sMaterial, upperPeer_sMaterialStage), defaultSchedulingCtx, md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(upperPeerInstance);
Stage downer_sUpperPeerMaterialStage = upperPeerInstance.findStage("upper-peer-stage");
dbHelper.passStage(downer_sUpperPeerMaterialStage);
MaterialRevisions downer_sMaterialRevisions = new MaterialRevisions(
materialRevisionForDownstream(downer_sUpperMaterial, downer_sUpperMaterialStage),
materialRevisionForDownstream(downer_sUpperPeerMaterial, downer_sUpperPeerMaterialStage));
Pipeline downerInstance = instanceFactory.createPipelineInstance(downer, BuildCause.createManualForced(downer_sMaterialRevisions, loserUser), defaultSchedulingCtx, md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(downerInstance);
Stage downest_sMaterialStage = downerInstance.findStage("downer-stage");
dbHelper.passStage(downest_sMaterialStage);
Pipeline downestInstance = instanceFactory.createPipelineInstance(downest, buildCauseForDependency(downest_sMaterial, downest_sMaterialStage), defaultSchedulingCtx, md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(downestInstance);
buildAssignmentService.onTimer();
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("fetcher"));
BuildWork work = (BuildWork) buildAssignmentService.assignWorkToAgent(agent(agentConfig));
List<Builder> builders = work.getAssignment().getBuilders();
FetchArtifactBuilder fooZipFetch = (FetchArtifactBuilder) builders.get(0);
assertThat(fooZipFetch.artifactLocator(), is("uppest/1/uppest-stage/latest/unit/foo.zip"));
FetchArtifactBuilder barZipFetch = (FetchArtifactBuilder) builders.get(1);
assertThat(barZipFetch.artifactLocator(), is("uppest/2/uppest-stage/1/unit/bar.zip"));
}
private BuildCause buildCauseForDependency(DependencyMaterial material, Stage upstreamStage) {
return BuildCause.createManualForced(new MaterialRevisions(materialRevisionForDownstream(material, upstreamStage)), loserUser);
}
private MaterialRevision materialRevisionForDownstream(DependencyMaterial material, Stage upstreamStage) {
StageIdentifier identifier = upstreamStage.getIdentifier();
String rev = identifier.getStageLocator();
String pipelineLabel = identifier.getPipelineLabel();
return new MaterialRevision(material, new Modification(new Date(), rev, pipelineLabel, upstreamStage.getPipelineId()));
}
private AgentIdentifier agent(AgentConfig agentConfig) {
agentService.sync(new Agents(agentConfig));
agentService.approve(agentConfig.getUuid());
return agentService.findAgent(agentConfig.getUuid()).getAgentIdentifier();
}
@Test
public void shouldNotScheduleIfAgentDoesNotHaveResources() throws Exception {
JobConfig plan = evolveConfig.findBy(new CaseInsensitiveString(STAGE_NAME)).jobConfigByInstanceName("unit", true);
plan.addResource("some-resource");
scheduleHelper.schedule(evolveConfig, modifySomeFiles(evolveConfig), DEFAULT_APPROVED_BY);
Work work = buildAssignmentService.assignWorkToAgent(agent(AgentMother.localAgent()));
Pipeline pipeline = pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(evolveConfig.name()));
JobInstance job = pipeline.findStage(STAGE_NAME).findJob("unit");
assertThat(work, is(BuildAssignmentService.NO_WORK));
assertThat(job.getState(), is(JobState.Scheduled));
assertThat(job.getAgentUuid(), is(nullValue()));
}
@Test
public void shouldNotScheduleIfAgentDoesNotHaveMatchingResources() throws Exception {
JobConfig plan = evolveConfig.findBy(new CaseInsensitiveString(STAGE_NAME)).jobConfigByInstanceName("unit", true);
plan.addResource("some-resource");
scheduleHelper.schedule(evolveConfig, modifySomeFiles(evolveConfig), DEFAULT_APPROVED_BY);
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("some-other-resource"));
Work work = buildAssignmentService.assignWorkToAgent(agent(agentConfig));
assertThat(work, is(BuildAssignmentService.NO_WORK));
Pipeline pipeline = pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(evolveConfig.name()));
JobInstance job = pipeline.findStage(STAGE_NAME).findJob("unit");
assertThat(job.getState(), is(JobState.Scheduled));
assertThat(job.getAgentUuid(), is(nullValue()));
}
@Test
public void shouldScheduleIfAgentMatchingResources() throws Exception {
JobConfig plan = evolveConfig.findBy(new CaseInsensitiveString(STAGE_NAME)).jobConfigByInstanceName("unit", true);
plan.addResource("some-resource");
scheduleHelper.schedule(evolveConfig, modifySomeFiles(evolveConfig), DEFAULT_APPROVED_BY);
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("some-resource"));
buildAssignmentService.onTimer();
Work work = buildAssignmentService.assignWorkToAgent(agent(agentConfig));
assertThat(work, is(not(BuildAssignmentService.NO_WORK)));
Pipeline pipeline = pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(evolveConfig.name()));
JobInstance job = pipeline.findStage(STAGE_NAME).findJob("unit");
JobPlan loadedPlan = jobInstanceDao.loadPlan(job.getId());
assertThat(loadedPlan.getResources(), is(plan.resources()));
assertThat(job.getState(), is(JobState.Assigned));
assertThat(job.getAgentUuid(), is(agentConfig.getUuid()));
}
@Test
public void shouldReScheduleToCorrectAgent() throws Exception {
JobConfig plan = evolveConfig.findBy(new CaseInsensitiveString(STAGE_NAME)).jobConfigByInstanceName("unit", true);
plan.addResource("some-resource");
scheduleHelper.schedule(evolveConfig, modifySomeFiles(evolveConfig), DEFAULT_APPROVED_BY);
buildAssignmentService.onTimer();
AgentConfig agentConfig = AgentMother.localAgent();
agentConfig.addResource(new Resource("some-resource"));
Work work = buildAssignmentService.assignWorkToAgent(agent(agentConfig));
assertThat(work, is(not(BuildAssignmentService.NO_WORK)));
Pipeline pipeline = pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(evolveConfig.name()));
JobInstance job = pipeline.findStage(STAGE_NAME).findJob("unit");
JobInstance runningJob = jobInstanceDao.buildByIdWithTransitions(job.getId());
scheduleService.rescheduleJob(runningJob);
pipeline = pipelineDao.mostRecentPipeline(CaseInsensitiveString.str(evolveConfig.name()));
JobInstance rescheduledJob = pipeline.findStage(STAGE_NAME).findJob("unit");
assertThat(rescheduledJob.getId(), not(runningJob.getId()));
buildAssignmentService.onTimer();
Work noResourcesWork = buildAssignmentService.assignWorkToAgent(agent(AgentMother.localAgentWithResources("WITHOUT_RESOURCES")));
assertThat(noResourcesWork, is(BuildAssignmentService.NO_WORK));
buildAssignmentService.onTimer();
Work correctAgentWork = buildAssignmentService.assignWorkToAgent(agent(agentConfig));
assertThat(correctAgentWork, is(not(BuildAssignmentService.NO_WORK)));
}
@Test
public void shouldRemoveAllJobPlansThatAreNotInConfig() {
CruiseConfig oldConfig = goConfigService.getCurrentConfig();
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWith("p1", "s1", u.m(new HgMaterial("hg", null)));
Pipeline p1_1 = instanceFactory.createPipelineInstance(p1.config, modifyNoFiles(p1.config), new DefaultSchedulingContext(
DEFAULT_APPROVED_BY), md5, new TimeProvider());
ScheduleTestUtil.AddedPipeline p2 = u.saveConfigWith("p2", "s1", u.m(new HgMaterial("hg", null)));
Pipeline p2_1 = instanceFactory.createPipelineInstance(p2.config, modifyNoFiles(p2.config), new DefaultSchedulingContext(
DEFAULT_APPROVED_BY), md5, new TimeProvider());
dbHelper.savePipelineWithStagesAndMaterials(p1_1);
dbHelper.savePipelineWithStagesAndMaterials(p2_1);
CruiseConfig cruiseConfig = goConfigService.getCurrentConfig();
buildAssignmentService.onConfigChange(cruiseConfig);
buildAssignmentService.onTimer();
List<JobPlan> plans = (List<JobPlan>) ReflectionUtil.getField(buildAssignmentService, "jobPlans");
assertThat(plans.isEmpty(), is(false));
assertThat(plans.size(), is(2));
configHelper.writeConfigFile(oldConfig);
plans = (List<JobPlan>) ReflectionUtil.getField(buildAssignmentService, "jobPlans");
assertThat("Actual size is " + plans.size(), plans.isEmpty(), is(true));
}
@Test
public void shouldCancelAScheduledJobInCaseThePipelineIsRemovedFromTheConfig_SpecificallyAPipelineRenameToADifferentCaseAndStageNameToADifferentName() throws Exception {
Material hgMaterial = new HgMaterial("url", "folder");
String[] hgRevs = new String[]{"h1"};
u.checkinInOrder(hgMaterial, hgRevs);
ScheduleTestUtil.AddedPipeline p1 = u.saveConfigWith("PIPELINE_WHICH_WILL_EVENTUALLY_CHANGE_CASE", u.m(hgMaterial));
u.scheduleWith(p1, hgRevs);
ScheduleTestUtil.AddedPipeline renamedPipeline = u.renamePipelineAndFirstStage(p1, "pipeline_which_will_eventually_change_case", "NEW_RANDOM_STAGE_NAME" + UUID.randomUUID());
Pipeline p1_2 = u.scheduleWith(renamedPipeline, hgRevs);
CruiseConfig cruiseConfig = configHelper.load();
buildAssignmentService.onTimer(); // To Reload Job Plans
buildAssignmentService.onConfigChange(cruiseConfig);
Stages allStages = stageDao.findAllStagesFor(p1_2.getName(), p1_2.getCounter());
assertThat(allStages.byName(CaseInsensitiveString.str(p1.config.first().name())).getState(), is(StageState.Cancelled));
}
@Test
public void shouldAssignMatchedJobToAgentsRegisteredInAgentRemoteHandler() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
configHelper.addAgent(agentConfig);
fixture.createPipelineWithFirstStageScheduled();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
info.setCookie("cookie");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
AgentInstance agent = agentService.findAgent(agentConfig.getUuid());
assertFalse(agent.isBuilding());
buildAssignmentService.onTimer();
assertThat(this.agent.messages.size(), is(1));
assertThat(MessageEncoding.decodeWork(this.agent.messages.get(0).getData()), instanceOf(BuildWork.class));
assertTrue(agent.isBuilding());
}
@Test
public void shouldNotAssignNoWorkToAgentsRegisteredInAgentRemoteHandler() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
configHelper.addAgent(agentConfig);
fixture.createdPipelineWithAllStagesPassed();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
info.setCookie("cookie");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
buildAssignmentService.onTimer();
assertThat(agent.messages.size(), is(0));
}
@Test
public void shouldNotAssignDeniedAgentWorkToAgentsRegisteredInAgentRemoteHandler() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
agentConfig.disable();
configHelper.addAgent(agentConfig);
fixture.createPipelineWithFirstStageScheduled();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
info.setCookie("cookie");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
buildAssignmentService.onTimer();
assertThat(agent.messages.size(), is(0));
}
@Test
public void shouldOnlyAssignWorkToIdleAgentsRegisteredInAgentRemoteHandler() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
configHelper.addAgent(agentConfig);
fixture.createPipelineWithFirstStageScheduled();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
info.setCookie("cookie");
AgentStatus[] statuses = new AgentStatus[] {
AgentStatus.Building, AgentStatus.Pending,
AgentStatus.Disabled, AgentStatus.Disabled,
AgentStatus.LostContact, AgentStatus.Missing
};
for (AgentStatus status : statuses) {
info.setStatus(status);
agent = new AgentStub();
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
buildAssignmentService.onTimer();
assertThat("Should not assign work when agent status is " + status, agent.messages.size(), is(0));
}
}
@Test
public void shouldNotAssignWorkToCanceledAgentsRegisteredInAgentRemoteHandler() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
configHelper.addAgent(agentConfig);
fixture.createPipelineWithFirstStageScheduled();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
info.setCookie("cookie");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
AgentInstance agentInstance = agentService.findAgentAndRefreshStatus(info.getUUId());
agentInstance.cancel();
buildAssignmentService.onTimer();
assertThat("Should not assign work when agent status is Canceled", agent.messages.size(), is(0));
}
@Test
public void shouldCallForReregisterIfAgentInstanceIsNotRegistered() throws Exception {
AgentConfig agentConfig = AgentMother.remoteAgent();
fixture.createPipelineWithFirstStageScheduled();
AgentRuntimeInfo info = AgentRuntimeInfo.fromServer(agentConfig, true, "location", 1000000l, "OS", false);
agentService.requestRegistration(new Username("bob"), info);
assertThat(agentService.findAgent(info.getUUId()).isRegistered(), is(false));
info.setCookie("cookie");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(info)));
buildAssignmentService.onTimer();
assertThat(agent.messages.size(), is(1));
assertThat(agent.messages.get(0).getAction(), is(Action.reregister));
}
@Test
public void shouldAssignAgentsWhenThereAreAgentsAreDisabledOrNeedReregister() throws Exception {
fixture.createPipelineWithFirstStageScheduled();
AgentConfig canceledAgentConfig = AgentMother.remoteAgent();
configHelper.addAgent(canceledAgentConfig);
AgentRuntimeInfo canceledAgentInfo = AgentRuntimeInfo.fromServer(canceledAgentConfig, true, "location", 1000000l, "OS", false);
canceledAgentInfo.setCookie("cookie1");
AgentStub canceledAgent = new AgentStub();
agentRemoteHandler.process(canceledAgent, new Message(Action.ping, MessageEncoding.encodeData(canceledAgentInfo)));
AgentInstance agentInstance = agentService.findAgentAndRefreshStatus(canceledAgentInfo.getUUId());
agentInstance.cancel();
AgentConfig needRegisterAgentConfig = AgentMother.remoteAgent();
AgentRuntimeInfo needRegisterAgentInfo = AgentRuntimeInfo.fromServer(needRegisterAgentConfig, true, "location", 1000000l, "OS", false);
agentService.requestRegistration(new Username("bob"), needRegisterAgentInfo);
needRegisterAgentInfo.setCookie("cookie2");
AgentStub needRegisterAgent = new AgentStub();
agentRemoteHandler.process(needRegisterAgent, new Message(Action.ping, MessageEncoding.encodeData(needRegisterAgentInfo)));
AgentConfig assignedAgent = AgentMother.remoteAgent();
configHelper.addAgent(assignedAgent);
AgentRuntimeInfo assignedAgentInfo = AgentRuntimeInfo.fromServer(assignedAgent, true, "location", 1000000l, "OS", false);
assignedAgentInfo.setCookie("cookie3");
agentRemoteHandler.process(agent, new Message(Action.ping, MessageEncoding.encodeData(assignedAgentInfo)));
buildAssignmentService.onTimer();
assertThat(canceledAgent.messages.size(), is(0));
assertThat(needRegisterAgent.messages.size(), is(1));
assertThat(needRegisterAgent.messages.get(0).getAction(), is(Action.reregister));
assertThat(agent.messages.size(), is(1));
assertThat(MessageEncoding.decodeWork(agent.messages.get(0).getData()), instanceOf(BuildWork.class));
}
private JobInstance buildOf(Pipeline pipeline) {
return pipeline.getStages().first().getJobInstances().first();
}
}