Java tutorial
/** * Copyright 2005-2014 Red Hat, Inc. * * Red Hat 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 io.fabric8.itests.smoke.embedded; import io.fabric8.api.BootstrapComplete; import io.fabric8.api.Constants; import io.fabric8.api.CreateEnsembleOptions; import io.fabric8.api.CreateEnsembleOptions.Builder; import io.fabric8.api.GitContext; import io.fabric8.api.Profile; import io.fabric8.api.ProfileBuilder; import io.fabric8.api.ProfileRegistry; import io.fabric8.api.ProfileService; import io.fabric8.api.RuntimeProperties; import io.fabric8.api.ZooKeeperClusterBootstrap; import io.fabric8.git.GitDataStore; import io.fabric8.git.internal.GitHelpers; import io.fabric8.git.internal.GitOperation; import io.fabric8.utils.DataStoreUtils; import java.io.File; import java.io.FileOutputStream; import java.io.IOException; import java.net.URL; import java.nio.file.FileVisitResult; import java.nio.file.Files; import java.nio.file.Path; import java.nio.file.Paths; import java.nio.file.SimpleFileVisitor; import java.nio.file.attribute.BasicFileAttributes; import java.util.Collections; import java.util.Dictionary; import java.util.Map; import java.util.Map.Entry; import java.util.Properties; import java.util.concurrent.atomic.AtomicInteger; import org.eclipse.jgit.api.Git; import org.eclipse.jgit.api.ListBranchCommand.ListMode; import org.eclipse.jgit.api.ResetCommand.ResetType; import org.eclipse.jgit.api.errors.GitAPIException; import org.eclipse.jgit.lib.Ref; import org.eclipse.jgit.revwalk.RevCommit; import org.gitective.core.CommitUtils; import org.jboss.arquillian.junit.Arquillian; import org.jboss.gravia.runtime.ServiceLocator; import org.jboss.gravia.utils.IllegalStateAssertion; import org.junit.AfterClass; import org.junit.Assert; import org.junit.Before; import org.junit.BeforeClass; import org.junit.Test; import org.junit.runner.RunWith; import org.osgi.service.cm.Configuration; import org.osgi.service.cm.ConfigurationAdmin; /** * Test the {@link ProfileService} with external git repo */ @RunWith(Arquillian.class) public class RemoteGitRepositoryTest { private static File remoteRoot; private static Git git; private ProfileRegistry profileRegistry; private GitDataStore gitDataStore; @BeforeClass public static void beforeClass() throws Exception { ServiceLocator.awaitService(BootstrapComplete.class); Builder<?> builder = CreateEnsembleOptions.builder().agentEnabled(false).clean(true) .waitForProvision(false); ServiceLocator.getRequiredService(ZooKeeperClusterBootstrap.class).create(builder.build()); Path dataPath = ServiceLocator.getRequiredService(RuntimeProperties.class).getDataPath(); Path localRepoPath = dataPath.resolve(Paths.get("git", "local", "fabric")); Path remoteRepoPath = dataPath.resolve(Paths.get("git", "remote", "fabric")); remoteRoot = remoteRepoPath.toFile(); recursiveDelete(remoteRoot.toPath()); remoteRoot.mkdirs(); URL remoteUrl = remoteRepoPath.toFile().toURI().toURL(); git = Git.cloneRepository().setURI(localRepoPath.toFile().toURI().toString()).setDirectory(remoteRoot) .setCloneAllBranches(true).setNoCheckout(true).call(); // Checkout all remote branches for (Ref ref : git.branchList().setListMode(ListMode.REMOTE).call()) { String refName = ref.getName(); String startPoint = refName.substring(refName.indexOf("origin")); String branchName = refName.substring(refName.lastIndexOf('/') + 1); git.checkout().setCreateBranch(true).setName(branchName).setStartPoint(startPoint).call(); } // Verify that we have these branches checkoutRequiredBranch("master"); checkoutRequiredBranch("1.0"); ConfigurationAdmin configAdmin = ServiceLocator.getRequiredService(ConfigurationAdmin.class); Configuration config = configAdmin.getConfiguration(Constants.DATASTORE_PID); Dictionary<String, Object> properties = config.getProperties(); properties.put(Constants.GIT_REMOTE_URL, remoteUrl.toExternalForm()); config.update(properties); // Wait for the configuredUrl to show up the {@link ProfileRegistry} ProfileRegistry profileRegistry = ServiceLocator.awaitService(ProfileRegistry.class); Map<String, String> dsprops = profileRegistry.getDataStoreProperties(); while (!dsprops.containsKey(Constants.GIT_REMOTE_URL)) { Thread.sleep(200); profileRegistry = ServiceLocator.awaitService(ProfileRegistry.class); dsprops = profileRegistry.getDataStoreProperties(); } } @AfterClass public static void afterClass() throws Exception { ConfigurationAdmin configAdmin = ServiceLocator.getRequiredService(ConfigurationAdmin.class); Configuration config = configAdmin.getConfiguration(Constants.DATASTORE_PID); Dictionary<String, Object> properties = config.getProperties(); properties.remove(Constants.GIT_REMOTE_URL); config.update(properties); // Wait for the configuredUrl to be removed from the {@link ProfileRegistry} ProfileRegistry profileRegistry = ServiceLocator.awaitService(ProfileRegistry.class); Map<String, String> dsprops = profileRegistry.getDataStoreProperties(); while (dsprops.containsKey(Constants.GIT_REMOTE_URL)) { Thread.sleep(200); profileRegistry = ServiceLocator.awaitService(ProfileRegistry.class); dsprops = profileRegistry.getDataStoreProperties(); } } @Before public void setUp() throws Exception { profileRegistry = ServiceLocator.getRequiredService(ProfileRegistry.class); gitDataStore = ServiceLocator.getRequiredService(GitDataStore.class); } /** * Test that profile changes are pushed as part of the write operation. * However, by default all write operations also do a push. */ @Test public void createProfileWithPush() throws Exception { String versionId = "1.0"; Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfA")); ProfileBuilder pbuilder = ProfileBuilder.Factory.create(versionId, "prfA"); profileRegistry.createProfile(pbuilder.addAttribute("foo", "aaa").getProfile()); Profile profile = profileRegistry.getRequiredProfile(versionId, "prfA"); Assert.assertEquals("aaa", profile.getAttributes().get("foo")); Assert.assertTrue(remoteProfileExists(versionId, "prfA")); profileRegistry.deleteProfile(versionId, "prfA"); Assert.assertFalse(remoteProfileExists("1.0", "prfA")); } /** * Test that we see the remote changes as when we do a pull as part of the a git operation. * However, read operations like getProfile() don't pull by default. */ @Test public void getProfileWithRemoteAhead() throws Exception { final String versionId = "1.0"; Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfB")); createProfileRemote(versionId, "prfB", Collections.singletonMap("foo", "bbb")); // Get the profile from local repo, doing a pull first GitOperation<Profile> gitop = new GitOperation<Profile>() { public Profile call(Git git, GitContext context) throws Exception { return profileRegistry.getProfile(versionId, "prfB"); } }; GitContext context = new GitContext().requirePull(); Profile profile = gitDataStore.gitOperation(context, gitop, null); Assert.assertEquals("1.0", profile.getVersion()); Assert.assertEquals("prfB", profile.getId()); Assert.assertEquals("bbb", profile.getAttributes().get("foo")); deleteProfileRemote(versionId, "prfB"); profile = gitDataStore.gitOperation(context, gitop, null); Assert.assertNull(profile); } /** * Test that a write operation fails if the commit cannot be pushed. * On failed pushed, the local repo is set to the state of the remote repo */ @Test public void createProfileFailOnPush() throws Exception { final String versionId = "1.0"; Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfC")); Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfD")); createProfileRemote(versionId, "prfC", null); ProfileBuilder pbuilder = ProfileBuilder.Factory.create(versionId, "prfD"); try { profileRegistry.createProfile(pbuilder.getProfile()); Assert.fail("IllegalStateException expected"); } catch (IllegalStateException ex) { Assert.assertTrue(ex.getMessage(), ex.getMessage().startsWith("Push rejected")); } Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfD")); Assert.assertTrue(profileRegistry.hasProfile(versionId, "prfC")); profileRegistry.deleteProfile(versionId, "prfC"); Assert.assertFalse(remoteProfileExists("1.0", "prfC")); } /** * Test that the remote repo can diverge in a non-conflicting way * We rebase local canges on to of remote changes in case of non-fast-forward pull */ @Test public void rebaseOnFailedPull() throws Exception { final String versionId = "1.0"; Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfE")); Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfF")); checkoutRequiredBranch(versionId); RevCommit head = CommitUtils.getHead(git.getRepository()); ProfileBuilder pbuilder = ProfileBuilder.Factory.create(versionId, "prfE"); profileRegistry.createProfile(pbuilder.getProfile()); Assert.assertTrue(remoteProfileExists("1.0", "prfE")); // Remove the last commit from the remote repository git.reset().setMode(ResetType.HARD).setRef(head.getName()).call(); Assert.assertFalse(remoteProfileExists("1.0", "prfE")); createProfileRemote(versionId, "prfF", null); Assert.assertTrue(remoteProfileExists("1.0", "prfF")); GitOperation<Profile> gitop = new GitOperation<Profile>() { public Profile call(Git git, GitContext context) throws Exception { return profileRegistry.getProfile(versionId, "prfF"); } }; GitContext context = new GitContext().requirePull(); gitDataStore.gitOperation(context, gitop, null); Assert.assertTrue(profileRegistry.hasProfile(versionId, "prfE")); Assert.assertTrue(profileRegistry.hasProfile(versionId, "prfF")); profileRegistry.deleteProfile(versionId, "prfE"); profileRegistry.deleteProfile(versionId, "prfF"); Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfE")); Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfF")); } /** * Test that repomote content overrides local content in case of a conflicting pull */ @Test public void rejectOnFailedPull() throws Exception { final String versionId = "1.0"; Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfG")); checkoutRequiredBranch(versionId); RevCommit head = CommitUtils.getHead(git.getRepository()); ProfileBuilder pbuilder = ProfileBuilder.Factory.create(versionId, "prfG"); profileRegistry.createProfile(pbuilder.addAttribute("foo", "aaa").getProfile()); Profile profile = profileRegistry.getRequiredProfile(versionId, "prfG"); Assert.assertEquals("aaa", profile.getAttributes().get("foo")); // Remove the last commit from the remote repository git.reset().setMode(ResetType.HARD).setRef(head.getName()).call(); Assert.assertFalse(remoteProfileExists("1.0", "prfG")); createProfileRemote(versionId, "prfG", Collections.singletonMap("foo", "bbb")); Assert.assertTrue(remoteProfileExists("1.0", "prfG")); GitOperation<Profile> gitop = new GitOperation<Profile>() { public Profile call(Git git, GitContext context) throws Exception { return profileRegistry.getProfile(versionId, "prfG"); } }; GitContext context = new GitContext().requirePull(); gitDataStore.gitOperation(context, gitop, null); Profile prfG = profileRegistry.getProfile(versionId, "prfG"); Assert.assertEquals("bbb", prfG.getAttributes().get("foo")); profileRegistry.deleteProfile(versionId, "prfG"); Assert.assertFalse(profileRegistry.hasProfile(versionId, "prfG")); } private static boolean remoteProfileExists(String versionId, String profileId) throws Exception { boolean success = checkoutBranch(versionId); return success && new File(remoteRoot, "fabric/profiles/" + profileId + ".profile").exists(); } private static String createProfileRemote(String versionId, String profileId, Map<String, String> attributes) throws Exception { checkoutRequiredBranch(versionId); Properties agentprops = new Properties(); if (attributes != null) { for (Entry<String, String> entry : attributes.entrySet()) { agentprops.setProperty(Profile.ATTRIBUTE_PREFIX + entry.getKey(), entry.getValue()); } } File pidFile = new File(remoteRoot, "fabric/profiles/" + profileId + ".profile/" + Constants.AGENT_PROPERTIES); pidFile.getParentFile().mkdirs(); DataStoreUtils.toBytes(agentprops); FileOutputStream fos = new FileOutputStream(pidFile); try { fos.write(DataStoreUtils.toBytes(agentprops)); } finally { fos.close(); } git.add().addFilepattern(".").call(); git.commit().setMessage("Create profile: " + profileId).call(); return profileId; } private static void deleteProfileRemote(String versionId, String profileId) throws Exception { checkoutRequiredBranch(versionId); Path profilePath = new File(remoteRoot, "fabric/profiles/" + profileId + ".profile").toPath(); if (recursiveRemove(profilePath)) { git.commit().setMessage("Delete profile: " + profileId).call(); } } private static boolean checkoutBranch(String versionId) throws GitAPIException { git.reset().setMode(ResetType.HARD).call(); // The workspace has staged files after a push ?!? return GitHelpers.checkoutBranch(git, versionId); } private static void checkoutRequiredBranch(String versionId) throws GitAPIException { boolean success = checkoutBranch(versionId); IllegalStateAssertion.assertTrue(success, "Cannot checkout branch: " + versionId); } private static boolean recursiveRemove(final Path rootPath) throws IOException { final AtomicInteger fileCount = new AtomicInteger(); if (rootPath.toFile().exists()) { Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { public FileVisitResult visitFile(Path path, BasicFileAttributes attrs) throws IOException { Path pattern = remoteRoot.toPath().relativize(path); try { git.rm().addFilepattern(pattern.toString()).call(); fileCount.incrementAndGet(); } catch (Exception ex) { throw new IllegalStateException(ex); } return FileVisitResult.CONTINUE; } }); } return fileCount.get() > 0; } private static boolean recursiveDelete(Path rootPath) throws IOException { final AtomicInteger fileCount = new AtomicInteger(); if (rootPath.toFile().exists()) { Files.walkFileTree(rootPath, new SimpleFileVisitor<Path>() { @Override public FileVisitResult visitFile(Path file, BasicFileAttributes attrs) throws IOException { if (file.toFile().delete()) { fileCount.incrementAndGet(); } return FileVisitResult.CONTINUE; } }); } return fileCount.get() > 0; } }