Java tutorial
/* * Copyright 2019 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.config; import ch.qos.logback.classic.Level; import com.rits.cloning.Cloner; import com.thoughtworks.go.CurrentGoCDVersion; import com.thoughtworks.go.config.exceptions.ConfigFileHasChangedException; import com.thoughtworks.go.config.exceptions.ConfigMergeException; import com.thoughtworks.go.config.exceptions.GoConfigInvalidException; import com.thoughtworks.go.config.materials.dependency.DependencyMaterialConfig; import com.thoughtworks.go.config.materials.svn.SvnMaterialConfig; import com.thoughtworks.go.config.materials.tfs.TfsMaterialConfig; import com.thoughtworks.go.config.parts.XmlPartialConfigProvider; import com.thoughtworks.go.config.registry.ConfigElementImplementationRegistry; import com.thoughtworks.go.config.remote.ConfigRepoConfig; import com.thoughtworks.go.config.remote.PartialConfig; import com.thoughtworks.go.config.remote.RepoConfigOrigin; import com.thoughtworks.go.domain.GoConfigRevision; import com.thoughtworks.go.helper.*; import com.thoughtworks.go.server.service.GoConfigService; import com.thoughtworks.go.service.ConfigRepository; import com.thoughtworks.go.util.*; import org.apache.commons.codec.digest.DigestUtils; import org.apache.commons.io.FileUtils; import org.eclipse.jgit.api.errors.GitAPIException; import org.hamcrest.Matchers; import org.junit.After; import org.junit.Before; import org.junit.Rule; import org.junit.Test; import org.junit.rules.ExpectedException; import org.junit.rules.TemporaryFolder; 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.File; import java.io.FileInputStream; import java.io.FileOutputStream; import java.io.IOException; import java.nio.charset.Charset; import java.util.List; import java.util.UUID; import java.util.Vector; import static com.thoughtworks.go.helper.ConfigFileFixture.DEFAULT_XML_WITH_2_AGENTS; import static com.thoughtworks.go.helper.ConfigFileFixture.VALID_XML_3169; import static com.thoughtworks.go.util.LogFixture.logFixtureFor; import static java.nio.charset.StandardCharsets.UTF_8; import static org.apache.commons.io.FileUtils.readFileToString; import static org.hamcrest.Matchers.*; import static org.junit.Assert.*; @RunWith(SpringJUnit4ClassRunner.class) @ContextConfiguration(locations = { "classpath:WEB-INF/applicationContext-global.xml", "classpath:WEB-INF/applicationContext-dataLocalAccess.xml", "classpath:testPropertyConfigurer.xml", "classpath:WEB-INF/spring-all-servlet.xml", }) public class GoFileConfigDataSourceIntegrationTest { private final String DEFAULT_CHARSET = "defaultCharset"; private final SystemEnvironment systemEnvironment = new SystemEnvironment(); @Autowired private GoFileConfigDataSource dataSource; @Autowired private GoPartialConfig goPartialConfig; @Autowired private GoConfigDao goConfigDao; @Rule public final TemporaryFolder temporaryFolder = new TemporaryFolder(); private GoConfigFileHelper configHelper; @Autowired private GoConfigService goConfigService; @Autowired private GoConfigWatchList configWatchList; private ConfigRepoConfig configRepo; @Autowired private CachedGoPartials cachedGoPartials; @Autowired private ConfigCache configCache; @Autowired private ConfigRepository configRepository; @Autowired private ConfigElementImplementationRegistry configElementImplementationRegistry; private final String remoteDownstream = "remote_downstream"; private PartialConfig partialConfig; private PipelineConfig upstreamPipeline; private ConfigRepoConfig repoConfig; @Before public void setUp() throws Exception { File configDir = temporaryFolder.newFolder(); String absolutePath = new File(configDir, "cruise-config.xml").getAbsolutePath(); systemEnvironment.setProperty(SystemEnvironment.CONFIG_FILE_PROPERTY, absolutePath); configHelper = new GoConfigFileHelper(DEFAULT_XML_WITH_2_AGENTS); configHelper.usingCruiseConfigDao(goConfigDao).initializeConfigFile(); configHelper.onSetUp(); repoConfig = new ConfigRepoConfig(MaterialConfigsMother.gitMaterialConfig("url"), XmlPartialConfigProvider.providerName); configHelper.addConfigRepo(repoConfig); configHelper.addPipeline("upstream", "upstream_stage_original"); goConfigService.forceNotifyListeners(); cachedGoPartials.clear(); configRepo = configWatchList.getCurrentConfigRepos().get(0); upstreamPipeline = goConfigService.pipelineConfigNamed(new CaseInsensitiveString("upstream")); partialConfig = PartialConfigMother.pipelineWithDependencyMaterial(remoteDownstream, upstreamPipeline, new RepoConfigOrigin(configRepo, "r1")); goPartialConfig.onSuccessPartialConfig(configRepo, partialConfig); systemEnvironment.set(SystemEnvironment.ENABLE_CONFIG_MERGE_FEATURE, true); } @After public void tearDown() throws Exception { cachedGoPartials.clear(); dataSource.reloadIfModified(); configHelper.onTearDown(); ReflectionUtil.setStaticField(Charset.class, DEFAULT_CHARSET, null); systemEnvironment.clearProperty(SystemEnvironment.CONFIG_FILE_PROPERTY); systemEnvironment.set(SystemEnvironment.ENABLE_CONFIG_MERGE_FEATURE, true); } @Test public void shouldConvertToUTF8BeforeSavingConfigToFileSystem() throws IOException { ReflectionUtil.setStaticField(Charset.class, DEFAULT_CHARSET, Charset.forName("windows-1252")); GoFileConfigDataSource.GoConfigSaveResult result = dataSource.writeWithLock(new UpdateConfigCommand() { @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { PipelineConfig pipelineConfig = PipelineConfigMother.pipelineConfig(UUID.randomUUID().toString()); JobConfig job = new JobConfig("job"); ExecTask task = new ExecTask(); task.setCommand("powershell"); task.setArgs("Get-ChildItem -Path . Recurse"); job.addTask(task); pipelineConfig.first().getJobs().add(job); cruiseConfig.addPipeline(UUID.randomUUID().toString(), pipelineConfig); return cruiseConfig; } }, new GoConfigHolder(goConfigService.currentCruiseConfig(), goConfigService.getConfigForEditing())); assertThat(result.getConfigSaveState(), is(ConfigSaveState.UPDATED)); FileInputStream inputStream = new FileInputStream(dataSource.fileLocation()); String newMd5 = CachedDigestUtils.md5Hex(inputStream); assertThat(newMd5, is(result.getConfigHolder().config.getMd5())); } @Rule public ExpectedException thrown = ExpectedException.none(); @Test public void shouldValidateMergedConfigForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames() .contains(new CaseInsensitiveString(remoteDownstream)), is(true)); updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { PipelineConfig updatedUpstream = cruiseConfig.getPipelineConfigByName(upstreamPipeline.name()); updatedUpstream.getFirstStageConfig().setName(new CaseInsensitiveString("upstream_stage_renamed")); } }); thrown.expect(org.hamcrest.Matchers.any(GoConfigInvalidException.class)); thrown.expectMessage( "Stage with name 'upstream_stage_original' does not exist on pipeline 'upstream', it is being referred to from pipeline 'remote_downstream' (url at r1)"); dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); } @Test public void shouldFallbackToValidPartialsForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames() .contains(new CaseInsensitiveString(remoteDownstream)), is(true)); String remoteInvalidPipeline = "remote_invalid_pipeline"; PartialConfig invalidPartial = PartialConfigMother.invalidPartial(remoteInvalidPipeline, new RepoConfigOrigin(configRepo, "r2")); goPartialConfig.onSuccessPartialConfig(configRepo, invalidPartial); assertThat(goConfigService.getCurrentConfig().getAllPipelineNames() .contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); final String newArtifactLocation = "some_random_change_to_config"; updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { cruiseConfig.server().setArtifactsDir(newArtifactLocation); } }); GoConfigHolder goConfigHolder = dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); assertThat(goConfigHolder.config.server().artifactsDir(), is(newArtifactLocation)); assertThat( goConfigHolder.config.getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); assertThat(goConfigHolder.config.getAllPipelineNames() .contains(new CaseInsensitiveString(remoteInvalidPipeline)), is(false)); } @Test public void shouldSaveWithKnownPartialsWhenValidationPassesForConfigChangesThroughFileSystem() throws Exception { assertThat(goConfigService.getCurrentConfig().getAllPipelineNames() .contains(new CaseInsensitiveString(remoteDownstream)), is(true)); //Introducing a change to make the latest version of remote pipeline invalid PipelineConfig remoteDownstreamPipeline = partialConfig.getGroups().first().getPipelines().get(0); DependencyMaterialConfig dependencyMaterial = remoteDownstreamPipeline.materialConfigs() .findDependencyMaterial(upstreamPipeline.name()); dependencyMaterial.setStageName(new CaseInsensitiveString("upstream_stage_renamed")); goPartialConfig.onSuccessPartialConfig(configRepo, partialConfig); DependencyMaterialConfig dependencyMaterialForRemotePipelineInConfigCache = goConfigService .getCurrentConfig().getPipelineConfigByName(new CaseInsensitiveString(remoteDownstream)) .materialConfigs().findDependencyMaterial(upstreamPipeline.name()); assertThat(dependencyMaterialForRemotePipelineInConfigCache.getStageName(), is(new CaseInsensitiveString("upstream_stage_original"))); final CaseInsensitiveString upstreamStageRenamed = new CaseInsensitiveString("upstream_stage_renamed"); updateConfigOnFileSystem(new UpdateConfig() { @Override public void update(CruiseConfig cruiseConfig) { cruiseConfig.getPipelineConfigByName(upstreamPipeline.name()).first().setName(upstreamStageRenamed); } }); GoConfigHolder goConfigHolder = dataSource.forceLoad(new File(systemEnvironment.getCruiseConfigFile())); assertThat( goConfigHolder.config.getAllPipelineNames().contains(new CaseInsensitiveString(remoteDownstream)), is(true)); assertThat(goConfigHolder.config.getPipelineConfigByName(remoteDownstreamPipeline.name()).materialConfigs() .findDependencyMaterial(upstreamPipeline.name()).getStageName(), is(upstreamStageRenamed)); assertThat( goConfigHolder.config.getPipelineConfigByName(upstreamPipeline.name()).getFirstStageConfig().name(), is(upstreamStageRenamed)); } @Test public void shouldNotSaveConfigIfValidationOfLastKnownValidPartialsMergedWithMainConfigFails() { final PipelineConfig upstream = PipelineConfigMother.createPipelineConfig(UUID.randomUUID().toString(), "s1", "j1"); configHelper.addPipeline(upstream); String remotePipeline = "remote_pipeline"; RepoConfigOrigin repoConfigOrigin = new RepoConfigOrigin(this.repoConfig, "1"); PartialConfig partialConfig = PartialConfigMother.pipelineWithDependencyMaterial(remotePipeline, upstream, repoConfigOrigin); cachedGoPartials.addOrUpdate(this.repoConfig.getMaterialConfig().getFingerprint(), partialConfig); cachedGoPartials.markAllKnownAsValid(); thrown.expect(RuntimeException.class); thrown.expectCause(Matchers.any(GoConfigInvalidException.class)); thrown.expectMessage(String.format( "Stage with name 's1' does not exist on pipeline '%s', it is being referred to from pipeline '%s' (%s)", upstream.name(), remotePipeline, repoConfigOrigin.displayName())); dataSource.writeWithLock(cruiseConfig -> { PipelineConfig pipelineConfig = cruiseConfig.getPipelineConfigByName(upstream.name()); pipelineConfig.clear(); pipelineConfig.add( new StageConfig(new CaseInsensitiveString("new_stage"), new JobConfigs(new JobConfig("job")))); return cruiseConfig; }, new GoConfigHolder(configHelper.currentConfig(), configHelper.currentConfig())); } @Test public void shouldUse_UserFromSession_asConfigModifyingUserWhenNoneGiven() throws GitAPIException, IOException { com.thoughtworks.go.server.newsecurity.SessionUtilsHelper.loginAs("loser_boozer"); goConfigDao.updateMailHost(getMailHost("mailhost.local")); CruiseConfig cruiseConfig = goConfigDao.load(); GoConfigRevision revision = configRepository.getRevision(cruiseConfig.getMd5()); assertThat(revision.getUsername(), is("loser_boozer")); } @Test public void shouldSaveTheCruiseConfigXml() throws Exception { File file = dataSource.fileLocation(); dataSource.write(ConfigMigrator.migrate(VALID_XML_3169), false); assertThat(FileUtils.readFileToString(file, UTF_8), containsString("http://hg-server/hg/connectfour")); } @Test public void shouldVersionTheCruiseConfigXmlWhenSaved() throws Exception { CachedGoConfig cachedGoConfig = configHelper.getCachedGoConfig(); CruiseConfig configForEdit = cachedGoConfig.loadForEditing(); GoConfigHolder configHolder = new GoConfigHolder(cachedGoConfig.currentConfig(), configForEdit); GoConfigHolder afterFirstSave = dataSource .writeWithLock(new UserAwarePipelineAddingCommand("foo-pipeline", "loser"), configHolder) .getConfigHolder(); GoConfigHolder afterSecondSave = dataSource .writeWithLock(new UserAwarePipelineAddingCommand("bar-pipeline", "bigger_loser"), afterFirstSave) .getConfigHolder(); String expectedMd5 = afterFirstSave.config.getMd5(); GoConfigRevision firstRev = configRepository.getRevision(expectedMd5); assertThat(firstRev.getUsername(), is("loser")); assertThat(firstRev.getGoVersion(), is(CurrentGoCDVersion.getInstance().formatted())); assertThat(firstRev.getMd5(), is(expectedMd5)); assertThat(firstRev.getSchemaVersion(), is(GoConstants.CONFIG_SCHEMA_VERSION)); assertThat(ConfigMigrator.load(firstRev.getContent()), is(afterFirstSave.configForEdit)); CruiseConfig config = afterSecondSave.config; assertThat(config.hasPipelineNamed(new CaseInsensitiveString("bar-pipeline")), is(true)); expectedMd5 = config.getMd5(); GoConfigRevision secondRev = configRepository.getRevision(expectedMd5); assertThat(secondRev.getUsername(), is("bigger_loser")); assertThat(secondRev.getGoVersion(), is(CurrentGoCDVersion.getInstance().formatted())); assertThat(secondRev.getMd5(), is(expectedMd5)); assertTrue(secondRev.getTime().after(firstRev.getTime())); assertThat(secondRev.getSchemaVersion(), is(GoConstants.CONFIG_SCHEMA_VERSION)); assertThat(ConfigMigrator.load(secondRev.getContent()), is(afterSecondSave.configForEdit)); } @Test public void shouldLoadAsUser_Filesystem_WithMd5Sum() throws Exception { GoConfigHolder configHolder = goConfigDao.loadConfigHolder(); String md5 = DigestUtils.md5Hex(FileUtils.readFileToString(dataSource.fileLocation(), UTF_8)); assertThat(configHolder.configForEdit.getMd5(), is(md5)); assertThat(configHolder.config.getMd5(), is(md5)); CruiseConfig forEdit = configHolder.configForEdit; forEdit.addPipeline("my-awesome-group", PipelineConfigMother.createPipelineConfig("pipeline-foo", "stage-bar", "job-baz")); FileOutputStream fos = new FileOutputStream(dataSource.fileLocation()); new MagicalGoConfigXmlWriter(configCache, ConfigElementImplementationRegistryMother.withNoPlugins()) .write(forEdit, fos, false); configHolder = dataSource.load(); String xmlText = FileUtils.readFileToString(dataSource.fileLocation(), UTF_8); String secondMd5 = DigestUtils.md5Hex(xmlText); assertThat(configHolder.configForEdit.getMd5(), is(secondMd5)); assertThat(configHolder.config.getMd5(), is(secondMd5)); assertThat(configHolder.configForEdit.getMd5(), is(not(md5))); GoConfigRevision commitedVersion = configRepository.getRevision(secondMd5); assertThat(commitedVersion.getContent(), is(xmlText)); assertThat(commitedVersion.getUsername(), is(GoFileConfigDataSource.FILESYSTEM)); } @Test public void shouldNotCorruptTheCruiseConfigXml() throws Exception { File file = dataSource.fileLocation(); String originalCopy = FileUtils.readFileToString(file, UTF_8); try { dataSource.write("abc", false); fail("Should not allow us to write an invalid config"); } catch (Exception e) { assertThat(e.getMessage(), containsString("Content is not allowed in prolog")); } assertThat(readFileToString(file, UTF_8), is(originalCopy)); } @Test public void shouldEncryptSvnPasswordWhenConfigIsChangedViaFileSystem() throws Exception { String configContent = ConfigFileFixture.configWithPipeline( String.format("<pipeline name='pipeline1'>" + " <materials>" + " <svn url='svnurl' username='admin' password='%s'/>" + " </materials>" + " <stage name='mingle'>" + " <jobs>" + " <job name='do-something'>" + " </job>" + " </jobs>" + " </stage>" + "</pipeline>", "hello"), GoConstants.CONFIG_SCHEMA_VERSION); FileUtils.writeStringToFile(dataSource.fileLocation(), configContent, UTF_8); GoConfigHolder configHolder = dataSource.load(); PipelineConfig pipelineConfig = configHolder.config .pipelineConfigByName(new CaseInsensitiveString("pipeline1")); SvnMaterialConfig svnMaterialConfig = (SvnMaterialConfig) pipelineConfig.materialConfigs().get(0); assertThat(svnMaterialConfig.getEncryptedPassword(), is(not(nullValue()))); } @Test public void shouldEncryptTfsPasswordWhenConfigIsChangedViaFileSystem() throws Exception { String configContent = ConfigFileFixture.configWithPipeline(String.format("<pipeline name='pipeline1'>" + " <materials>" + " <tfs url='http://some.repo.local' username='username@domain' password='password' projectPath='$/project_path' />" + " </materials>" + " <stage name='mingle'>" + " <jobs>" + " <job name='do-something'>" + " </job>" + " </jobs>" + " </stage>" + "</pipeline>", "hello"), GoConstants.CONFIG_SCHEMA_VERSION); FileUtils.writeStringToFile(dataSource.fileLocation(), configContent, UTF_8); GoConfigHolder configHolder = dataSource.load(); PipelineConfig pipelineConfig = configHolder.config .pipelineConfigByName(new CaseInsensitiveString("pipeline1")); TfsMaterialConfig tfsMaterial = (TfsMaterialConfig) pipelineConfig.materialConfigs().get(0); assertThat(tfsMaterial.getEncryptedPassword(), is(not(nullValue()))); } @Test public void shouldUpdateFileAttributesIfFileContentsHaveNotChanged() throws Exception {//so that it doesn't have to do the file content checksum computation next time dataSource.reloadIfModified(); assertThat(dataSource.load(), not(nullValue())); GoFileConfigDataSource.ReloadIfModified reloadStrategy = (GoFileConfigDataSource.ReloadIfModified) ReflectionUtil .getField(dataSource, "reloadStrategy"); ReflectionUtil.setField(reloadStrategy, "lastModified", -1); ReflectionUtil.setField(reloadStrategy, "prevSize", -1); assertThat(dataSource.load(), is(nullValue())); assertThat(ReflectionUtil.getField(reloadStrategy, "lastModified"), is(dataSource.fileLocation().lastModified())); assertThat(ReflectionUtil.getField(reloadStrategy, "prevSize"), is(dataSource.fileLocation().length())); } @Test public void shouldGetMergedConfig() throws Exception { configHelper.addMailHost(getMailHost("mailhost.local.old")); GoConfigHolder goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); CruiseConfig oldConfigForEdit = goConfigHolder.configForEdit; final String oldMD5 = oldConfigForEdit.getMd5(); MailHost oldMailHost = oldConfigForEdit.server().mailHost(); assertThat(oldMailHost.getHostName(), is("mailhost.local.old")); assertThat(oldMailHost.getHostName(), is(not("mailhost.local"))); goConfigDao.updateMailHost(getMailHost("mailhost.local")); goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); GoFileConfigDataSource.GoConfigSaveResult result = dataSource .writeWithLock(new NoOverwriteUpdateConfigCommand() { @Override public String unmodifiedMd5() { return oldMD5; } @Override public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { cruiseConfig.addPipeline("g", PipelineConfigMother.pipelineConfig("p1", StageConfigMother.custom("s", "b"))); return cruiseConfig; } }, goConfigHolder); assertThat(result.getConfigHolder().config.server().mailHost().getHostName(), is("mailhost.local")); assertThat(result.getConfigHolder().config.hasPipelineNamed(new CaseInsensitiveString("p1")), is(true)); } @Test public void shouldPropagateConfigHasChangedException() throws Exception { String originalMd5 = dataSource.forceLoad(dataSource.fileLocation()).configForEdit.getMd5(); goConfigDao.updateConfig(configHelper.addPipelineCommand(originalMd5, "p1", "s1", "b1")); GoConfigHolder goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); try { dataSource.writeWithLock(configHelper.addPipelineCommand(originalMd5, "p2", "s", "b"), goConfigHolder); fail("Should throw ConfigFileHasChanged exception"); } catch (Exception e) { assertThat(e.getCause().getClass().getName(), e.getCause() instanceof ConfigMergeException, is(true)); } } @Test public void shouldThrowConfigMergeExceptionWhenConfigMergeFeatureIsTurnedOff() throws Exception { String firstMd5 = dataSource.forceLoad(dataSource.fileLocation()).configForEdit.getMd5(); goConfigDao.updateConfig(configHelper.addPipelineCommand(firstMd5, "p0", "s0", "b0")); String originalMd5 = dataSource.forceLoad(dataSource.fileLocation()).configForEdit.getMd5(); goConfigDao.updateConfig(configHelper.addPipelineCommand(originalMd5, "p1", "s1", "j1")); GoConfigHolder goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); systemEnvironment.set(SystemEnvironment.ENABLE_CONFIG_MERGE_FEATURE, Boolean.FALSE); try { dataSource.writeWithLock(configHelper.changeJobNameCommand(originalMd5, "p0", "s0", "b0", "j0"), goConfigHolder); fail("Should throw ConfigMergeException"); } catch (RuntimeException e) { ConfigMergeException cme = (ConfigMergeException) e.getCause(); assertThat(cme.getMessage(), is(ConfigFileHasChangedException.CONFIG_CHANGED_PLEASE_REFRESH)); } } @Test public void shouldGetConfigMergedStateWhenAMergerOccurs() throws Exception { System.out.println("systemEnvironment.get(SystemEnvironment.ENABLE_CONFIG_MERGE_FEATURE) = " + systemEnvironment.get(SystemEnvironment.ENABLE_CONFIG_MERGE_FEATURE)); configHelper.addMailHost(getMailHost("mailhost.local.old")); String originalMd5 = dataSource.forceLoad(dataSource.fileLocation()).configForEdit.getMd5(); configHelper.addMailHost(getMailHost("mailhost.local")); GoConfigHolder goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); GoFileConfigDataSource.GoConfigSaveResult goConfigSaveResult = dataSource .writeWithLock(configHelper.addPipelineCommand(originalMd5, "p1", "s", "b"), goConfigHolder); assertThat(goConfigSaveResult.getConfigSaveState(), is(ConfigSaveState.MERGED)); } @Test public void shouldGetConfigUpdateStateWhenAnUpdateOccurs() throws Exception { String originalMd5 = dataSource.forceLoad(dataSource.fileLocation()).configForEdit.getMd5(); GoConfigHolder goConfigHolder = dataSource.forceLoad(dataSource.fileLocation()); GoFileConfigDataSource.GoConfigSaveResult goConfigSaveResult = dataSource .writeWithLock(configHelper.addPipelineCommand(originalMd5, "p1", "s", "b"), goConfigHolder); assertThat(goConfigSaveResult.getConfigSaveState(), is(ConfigSaveState.UPDATED)); } @Test public void shouldFallbackToLastKnownValidPartialsForValidationWhenConfigSaveWithLastKnownPartialsWithMainConfigFails() { String pipelineOneFromConfigRepo = "pipeline_one_from_config_repo"; String invalidPartial = "invalidPartial"; final String pipelineInMain = "pipeline_in_main"; PartialConfig validPartialConfig = PartialConfigMother.withPipeline(pipelineOneFromConfigRepo, new RepoConfigOrigin(repoConfig, "1")); PartialConfig invalidPartialConfig = PartialConfigMother.invalidPartial(invalidPartial, new RepoConfigOrigin(repoConfig, "2")); cachedGoPartials.addOrUpdate(repoConfig.getMaterialConfig().getFingerprint(), validPartialConfig); cachedGoPartials.markAllKnownAsValid(); cachedGoPartials.addOrUpdate(repoConfig.getMaterialConfig().getFingerprint(), invalidPartialConfig); GoFileConfigDataSource.GoConfigSaveResult result = dataSource.writeWithLock(cruiseConfig -> { cruiseConfig.addPipeline("default", PipelineConfigMother.createPipelineConfig(pipelineInMain, "stage", "job")); return cruiseConfig; }, new GoConfigHolder(configHelper.currentConfig(), configHelper.currentConfig())); assertThat(result.getConfigHolder().config.getAllPipelineNames() .contains(new CaseInsensitiveString(invalidPartial)), is(false)); assertThat(result.getConfigHolder().config.getAllPipelineNames() .contains(new CaseInsensitiveString(pipelineOneFromConfigRepo)), is(true)); assertThat(result.getConfigHolder().config.getAllPipelineNames() .contains(new CaseInsensitiveString(pipelineInMain)), is(true)); assertThat(cachedGoPartials.lastValidPartials().size(), is(1)); PartialConfig partialConfig = cachedGoPartials.lastValidPartials().get(0); assertThat(partialConfig.getGroups(), is(validPartialConfig.getGroups())); assertThat(partialConfig.getEnvironments(), is(validPartialConfig.getEnvironments())); assertThat(partialConfig.getOrigin(), is(validPartialConfig.getOrigin())); } @Test public void shouldValidateConfigRepoLastKnownPartialsWithMainConfigAndUpdateConfigToIncludePipelinesFromPartials() { String pipelineFromConfigRepo = "pipeline_from_config_repo"; final String pipelineInMain = "pipeline_in_main"; PartialConfig partialConfig = PartialConfigMother.withPipeline(pipelineFromConfigRepo, new RepoConfigOrigin(repoConfig, "r2")); cachedGoPartials.addOrUpdate(repoConfig.getMaterialConfig().getFingerprint(), partialConfig); assertThat(cachedGoPartials.lastValidPartials().size(), is(1)); assertThat(cachedGoPartials.lastValidPartials().get(0).getGroups().findGroup("group") .hasPipeline(new CaseInsensitiveString(pipelineFromConfigRepo)), is(false)); GoFileConfigDataSource.GoConfigSaveResult result = dataSource.writeWithLock(cruiseConfig -> { cruiseConfig.addPipeline("default", PipelineConfigMother.createPipelineConfig(pipelineInMain, "stage", "job")); return cruiseConfig; }, new GoConfigHolder(configHelper.currentConfig(), configHelper.currentConfig())); assertThat(result.getConfigHolder().config.getAllPipelineNames() .contains(new CaseInsensitiveString(pipelineFromConfigRepo)), is(true)); assertThat(result.getConfigHolder().config.getAllPipelineNames() .contains(new CaseInsensitiveString(pipelineInMain)), is(true)); assertThat(cachedGoPartials.lastValidPartials().size(), is(1)); PartialConfig actualPartial = cachedGoPartials.lastValidPartials().get(0); assertThat(actualPartial.getGroups().findGroup("group") .hasPipeline(new CaseInsensitiveString(pipelineFromConfigRepo)), is(true)); assertThat(actualPartial.getGroups(), is(partialConfig.getGroups())); assertThat(actualPartial.getEnvironments(), is(partialConfig.getEnvironments())); assertThat(actualPartial.getOrigin(), is(partialConfig.getOrigin())); } @Test public void shouldNotReloadIfConfigDoesNotChange() throws Exception { try (LogFixture log = logFixtureFor(GoFileConfigDataSource.class, Level.DEBUG)) { dataSource.reloadIfModified(); GoConfigHolder loadedConfig = dataSource.load(); assertThat(log.getLog(), containsString("Config file changed at")); assertThat(loadedConfig, not(nullValue())); log.clear(); loadedConfig = dataSource.load(); assertThat(log.getLog(), not(containsString("Config file changed at"))); assertThat(loadedConfig, is(nullValue())); } } @Test public void shouldBeAbleToConcurrentAccess() throws Exception { final String xml = ConfigMigrator.migrate(ConfigFileFixture.CONFIG_WITH_NANT_AND_EXEC_BUILDER); final List<Exception> errors = new Vector<>(); Thread thread1 = new Thread(() -> { for (int i = 0; i < 5; i++) { try { goConfigDao.updateMailHost(new MailHost("hostname", 9999, "user", "password", false, false, "from@local", "admin@local")); } catch (Exception e) { e.printStackTrace(); errors.add(e); } } }, "Update-license"); Thread thread2 = new Thread(() -> { for (int i = 0; i < 5; i++) { try { dataSource.write(xml, false); } catch (Exception e) { e.printStackTrace(); errors.add(e); } } }, "Modify-config"); thread1.start(); thread2.start(); thread1.join(); thread2.join(); assertThat(errors.size(), is(0)); } private static class UserAwarePipelineAddingCommand implements UpdateConfigCommand, UserAware { private final String pipelineName; private final String username; UserAwarePipelineAddingCommand(String pipelineName, String username) { this.pipelineName = pipelineName; this.username = username; } public CruiseConfig update(CruiseConfig cruiseConfig) throws Exception { cruiseConfig.addPipeline("my-grp", PipelineConfigMother.createPipelineConfig(pipelineName, "stage-other", "job-yet-another")); return cruiseConfig; } public ConfigModifyingUser user() { return new ConfigModifyingUser(username); } } private MailHost getMailHost(String hostName) { return new MailHost(hostName, 9999, "user", "password", true, false, "from@local", "admin@local"); } private interface UpdateConfig { void update(CruiseConfig cruiseConfig); } private void updateConfigOnFileSystem(UpdateConfig updateConfig) throws Exception { String cruiseConfigFile = systemEnvironment.getCruiseConfigFile(); CruiseConfig updatedConfig = new Cloner().deepClone(goConfigService.getConfigForEditing()); updateConfig.update(updatedConfig); File configFile = new File(cruiseConfigFile); FileOutputStream outputStream = new FileOutputStream(configFile); new MagicalGoConfigXmlWriter(configCache, configElementImplementationRegistry).write(updatedConfig, outputStream, true); } }