Skip to content

Commit

Permalink
Make sure geogig's ConfigStore works on a clustered environment
Browse files Browse the repository at this point in the history
Geogig plugin's ConfigStore saves serialized RepositoryInfo instances
to GeoServers ResourceStore. Initially developer against the
default file based ResourceStore, it assumed it could freely
cache the RepositoryInfo instances.

On a clustered environemnt, modifications might occur on different
cluster nodes. This patch makes it so ResourceStore events are
properly handled for create/changed/deleted notifications by keeping
ConfigStore (in charge of storing the serialized RepositoryInfo instances)
and RepositoryManager (in charge of actual geogig Repository instances)
in synch, even when events come from different servers.
  • Loading branch information
Gabriel Roldan committed Jun 23, 2017
1 parent 8d01089 commit be2ee5d
Show file tree
Hide file tree
Showing 15 changed files with 608 additions and 219 deletions.

Large diffs are not rendered by default.

Original file line number Original file line Diff line number Diff line change
Expand Up @@ -93,10 +93,11 @@ public Repository load(final String repoId) throws Exception {
public Repository get(final String repositoryId) throws IOException { public Repository get(final String repositoryId) throws IOException {
try { try {
return repoCache.get(repositoryId); return repoCache.get(repositoryId);
} catch (ExecutionException e) { } catch (Throwable e) {
Throwables.propagateIfInstanceOf(e.getCause(), IOException.class); Throwables.propagateIfInstanceOf(e.getCause(), IOException.class);
throw new IOException("Error obtaining cached geogig instance for repo " + repositoryId, Throwable cause = e.getCause();
e.getCause()); throw new IOException("Error obtaining cached geogig instance for repo " + repositoryId
+ ": " + cause.getMessage(), cause);
} }
} }


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -14,8 +14,9 @@


import com.google.common.annotations.VisibleForTesting; import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Throwables;


public class RepositoryInfo implements Serializable { public class RepositoryInfo implements Serializable, Cloneable {


private static final long serialVersionUID = -5946705936987075713L; private static final long serialVersionUID = -5946705936987075713L;


Expand All @@ -36,6 +37,7 @@ public class RepositoryInfo implements Serializable {
private java.net.URI location; private java.net.URI location;


private String maskedLocation; private String maskedLocation;

/** /**
* Stores the "nice" name for a repo. This is the name that is shown in the Repository list, as * Stores the "nice" name for a repo. This is the name that is shown in the Repository list, as
* well as what is stored in the GeoGIG repository config. It is transient, as we don't want to * well as what is stored in the GeoGIG repository config. It is transient, as we don't want to
Expand All @@ -44,6 +46,8 @@ public class RepositoryInfo implements Serializable {
*/ */
private transient String repoName; private transient String repoName;


private transient long lastModified;

public RepositoryInfo() { public RepositoryInfo() {
this(null); this(null);
} }
Expand All @@ -52,6 +56,14 @@ public RepositoryInfo() {
this.id = id; this.id = id;
} }


public @Override RepositoryInfo clone() {
try {
return (RepositoryInfo) super.clone();
} catch (CloneNotSupportedException e) {
throw Throwables.propagate(e);
}
}

private Object readResolve() { private Object readResolve() {
if (parentDirectory != null && name != null) { if (parentDirectory != null && name != null) {
File file = new File(new File(parentDirectory), name).getAbsoluteFile(); File file = new File(new File(parentDirectory), name).getAbsoluteFile();
Expand Down Expand Up @@ -135,4 +147,12 @@ public String toString() {
return new StringBuilder("[id:").append(getId()).append(", URI:").append(getLocation()) return new StringBuilder("[id:").append(getId()).append(", URI:").append(getLocation())
.append("]").toString(); .append("]").toString();
} }

long getLastModified() {
return lastModified;
}

void setLastModified(long timestamp) {
this.lastModified = timestamp;
}
} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -9,7 +9,6 @@
import static org.geoserver.catalog.Predicates.equal; import static org.geoserver.catalog.Predicates.equal;


import java.io.File; import java.io.File;
import java.io.FileNotFoundException;
import java.io.IOException; import java.io.IOException;
import java.io.Serializable; import java.io.Serializable;
import java.net.URI; import java.net.URI;
Expand Down Expand Up @@ -54,11 +53,11 @@
import org.locationtech.geogig.repository.impl.GlobalContextBuilder; import org.locationtech.geogig.repository.impl.GlobalContextBuilder;
import org.opengis.filter.Filter; import org.opengis.filter.Filter;


import com.google.common.annotations.VisibleForTesting;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.base.Optional; import com.google.common.base.Optional;
import com.google.common.base.Preconditions; import com.google.common.base.Preconditions;
import com.google.common.base.Strings; import com.google.common.base.Strings;
import com.google.common.base.Supplier;
import com.google.common.base.Throwables; import com.google.common.base.Throwables;
import com.google.common.collect.Lists; import com.google.common.collect.Lists;


Expand All @@ -70,35 +69,19 @@ public class RepositoryManager implements GeoServerInitializer {
} }
} }


private static class StaticSupplier implements Supplier<RepositoryManager>, Serializable { private static final String REPO_ROOT = "geogig/repos";
private static final long serialVersionUID = 3706728433275296134L;

@Override
public RepositoryManager get() {
return RepositoryManager.get();
}
}


private final ConfigStore configStore; private ConfigStore configStore;


private final ResourceStore resourceStore; private ResourceStore resourceStore;


private final RepositoryCache repoCache; private RepositoryCache repoCache;


private static RepositoryManager INSTANCE; private static RepositoryManager INSTANCE;


private Catalog catalog; private Catalog catalog;


private static final String REPO_ROOT = "geogig/repos"; private RepositoryInfoChangedCallback REPO_CHANGED_CALLBACK;

private static final RepositoryInfoChangedCallback REPO_CHANGED_CALLBACK = new RepositoryInfoChangedCallback() {
@Override
public void repositoryInfoChanged(String repoId) {
if (INSTANCE != null && INSTANCE.repoCache != null) {
INSTANCE.repoCache.invalidate(repoId);
}
}
};


public static synchronized RepositoryManager get() { public static synchronized RepositoryManager get() {
if (INSTANCE == null) { if (INSTANCE == null) {
Expand All @@ -108,24 +91,35 @@ public static synchronized RepositoryManager get() {
return INSTANCE; return INSTANCE;
} }


public static void close() { public void dispose() {
configStore.removeRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK);
repoCache.invalidateAll();
}

public synchronized static void close() {
if (INSTANCE != null) { if (INSTANCE != null) {
INSTANCE.configStore.removeRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK); INSTANCE.dispose();
INSTANCE.repoCache.invalidateAll();
INSTANCE = null; INSTANCE = null;
} }
} }


public static Supplier<RepositoryManager> supplier() { public RepositoryManager(ConfigStore configStore, ResourceStore resourceStore) {
return new StaticSupplier(); init(configStore, resourceStore);
} }


public RepositoryManager(ConfigStore configStore, ResourceStore resourceStore) { @VisibleForTesting
RepositoryManager() {

}

@VisibleForTesting
void init(ConfigStore configStore, ResourceStore resourceStore) {
checkNotNull(configStore); checkNotNull(configStore);
checkNotNull(resourceStore); checkNotNull(resourceStore);
this.configStore = configStore; this.configStore = configStore;
this.resourceStore = resourceStore; this.resourceStore = resourceStore;
this.repoCache = new RepositoryCache(this); this.repoCache = new RepositoryCache(this);
REPO_CHANGED_CALLBACK = (repoId) -> invalidate(repoId);
this.configStore.addRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK); this.configStore.addRepositoryInfoChangedCallback(REPO_CHANGED_CALLBACK);
} }


Expand Down Expand Up @@ -153,8 +147,10 @@ public Repository createRepo(final Hints hints) {
} else { } else {
// no location set yet, generate one // no location set yet, generate one
// NOTE: If the resource store does not support a file system, the repository will be // NOTE: If the resource store does not support a file system, the repository will be
// created in a temporary directory. If this is the case, remove any repository // created
// resolvers that can resolve a 'file' URI to prevent the creation of such repos. // in a temporary directory. If this is the case, remove any repository resolvers that
// can
// resolve a 'file' URI to prevent the creation of such repos.
Resource root = resourceStore.get(REPO_ROOT); Resource root = resourceStore.get(REPO_ROOT);
File repoDir = root.get(UUID.randomUUID().toString()).dir(); File repoDir = root.get(UUID.randomUUID().toString()).dir();
if (!repoDir.exists()) { if (!repoDir.exists()) {
Expand All @@ -178,12 +174,8 @@ public Repository createRepo(final Hints hints) {
return repository; return repository;
} }


public RepositoryInfo get(final String repoId) throws IOException { public RepositoryInfo get(final String repoId) throws NoSuchElementException {
try { return configStore.get(repoId);
return configStore.get(repoId);
} catch (FileNotFoundException e) {
throw new NoSuchElementException("No repository with ID " + repoId + " exists");
}
} }


/** /**
Expand All @@ -193,11 +185,24 @@ public RepositoryInfo get(final String repoId) throws IOException {
* *
* @return a RepositoryInfo object, if found. If not found, returns null. * @return a RepositoryInfo object, if found. If not found, returns null.
*/ */
public RepositoryInfo getByRepoName(final String name) { public @Nullable RepositoryInfo getByRepoName(final String name) {
RepositoryInfo info = configStore.getByName(name); RepositoryInfo info = configStore.getByName(name);
return info; return info;
} }


public @Nullable RepositoryInfo getByRepoLocation(final URI repoURI) {
RepositoryInfo info = configStore.getByLocation(repoURI);
return info;
}

public boolean repoExistsByName(final String name) {
return configStore.repoExistsByName(name);
}

public boolean repoExistsByLocation(URI location) {
return configStore.repoExistsByLocation(location);
}

public List<DataStoreInfo> findGeogigStores() { public List<DataStoreInfo> findGeogigStores() {
return findGeogigStores(this.catalog); return findGeogigStores(this.catalog);
} }
Expand Down Expand Up @@ -231,7 +236,7 @@ public List<DataStoreInfo> findDataStores(final String repoId) {
String repoName = null; String repoName = null;
try { try {
repoName = this.get(repoId).getRepoName(); repoName = this.get(repoId).getRepoName();
} catch (IOException ioe) { } catch (NoSuchElementException ioe) {
Throwables.propagate(ioe); Throwables.propagate(ioe);
} }
Filter filter = equal("type", GeoGigDataStoreFactory.DISPLAY_NAME); Filter filter = equal("type", GeoGigDataStoreFactory.DISPLAY_NAME);
Expand Down Expand Up @@ -309,13 +314,8 @@ public RepositoryInfo save(RepositoryInfo info) {
create(info); create(info);
} else { } else {
// see if the name has changed. If so, update the repo config // see if the name has changed. If so, update the repo config
try { RepositoryInfo currentInfo = get(info.getId());
RepositoryInfo currentInfo = get(info.getId()); handleRepoRename(currentInfo, info);
handleRepoRename(currentInfo, info);
} catch (IOException ioe) {

}

} }
// so far we don't need to invalidate the GeoGIG instance from the cache here... re-evaluate // so far we don't need to invalidate the GeoGIG instance from the cache here... re-evaluate
// if any configuration option would require so in the future // if any configuration option would require so in the future
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -12,7 +12,6 @@
import java.util.Iterator; import java.util.Iterator;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.NoSuchElementException;


import org.geogig.geoserver.config.RepositoryInfo; import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager; import org.geogig.geoserver.config.RepositoryManager;
Expand Down Expand Up @@ -72,7 +71,7 @@ public Optional<RepositoryInfo> findRepository(Request request) {
} else { } else {
return Optional.absent(); return Optional.absent();
} }
} catch (NoSuchElementException | IOException e) { } catch (RuntimeException e) {
return Optional.absent(); return Optional.absent();
} }
} }
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -35,13 +35,11 @@ protected Representation runCommand(Variant variant, Request request, MediaType
// before importing the repository, extract the repository name from the request and see if // before importing the repository, extract the repository name from the request and see if
// a repository with that name already exists // a repository with that name already exists
final String repoName = RESTUtils.getStringAttribute(request, "repository"); final String repoName = RESTUtils.getStringAttribute(request, "repository");
if (repoName != null) { if (repoName != null && RepositoryManager.get().repoExistsByName(repoName)) {
if (RepositoryManager.get().getByRepoName(repoName) != null) { // repo already exists
// repo already exists throw new CommandSpecException(
throw new CommandSpecException(
"The specified repository name is already in use, please try a different name", "The specified repository name is already in use, please try a different name",
Status.CLIENT_ERROR_CONFLICT); Status.CLIENT_ERROR_CONFLICT);
}
} }
// all good // all good
return super.runCommand(variant, request, outputFormat); return super.runCommand(variant, request, outputFormat);
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -24,13 +24,11 @@ protected Representation runCommand(Variant variant, Request request, MediaType
// before running the Init command, extract the repository name from the request and see if // before running the Init command, extract the repository name from the request and see if
// a repository with that name already exists // a repository with that name already exists
final String repoName = RESTUtils.getStringAttribute(request, "repository"); final String repoName = RESTUtils.getStringAttribute(request, "repository");
if (repoName != null) { if (repoName != null && RepositoryManager.get().repoExistsByName(repoName)) {
if (RepositoryManager.get().getByRepoName(repoName) != null) { // repo already exists
// repo already exists throw new CommandSpecException(
throw new CommandSpecException( "The specified repository name is already in use, please try a different name",
"The specified repository name is already in use, please try a different name", Status.CLIENT_ERROR_CONFLICT);
Status.CLIENT_ERROR_CONFLICT);
}
} }
Representation representation = super.runCommand(variant, request, outputFormat); Representation representation = super.runCommand(variant, request, outputFormat);


Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -34,7 +34,7 @@ public class BranchSelectionPanel extends FormComponentPanel<String> {


private final IModel<String> repositoryUriModel; private final IModel<String> repositoryUriModel;


private Supplier<RepositoryManager> manager = RepositoryManager.supplier(); private Supplier<RepositoryManager> manager = () -> RepositoryManager.get();


public BranchSelectionPanel(String id, IModel<String> repositoryUriModel, public BranchSelectionPanel(String id, IModel<String> repositoryUriModel,
IModel<String> branchNameModel, Form<DataStoreInfo> storeEditForm) { IModel<String> branchNameModel, Form<DataStoreInfo> storeEditForm) {
Expand Down Expand Up @@ -103,7 +103,7 @@ public void updateChoices(boolean reportError, Form<?> form) {
if (reportError) { if (reportError) {
form.error("Could not list branches: " + e.getMessage()); form.error("Could not list branches: " + e.getMessage());
} }
} }
String current = (String) choice.getModelObject(); String current = (String) choice.getModelObject();
if (current != null && !branchNames.contains(current)) { if (current != null && !branchNames.contains(current)) {
branchNames.add(0, current); branchNames.add(0, current);
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -50,7 +50,9 @@ public void validate(IValidatable<RepositoryInfo> validatable) {
final URI location = repo.getLocation(); final URI location = repo.getLocation();
final RepositoryResolver resolver = RepositoryResolver.lookup(location); final RepositoryResolver resolver = RepositoryResolver.lookup(location);
final String scheme = location.getScheme(); final String scheme = location.getScheme();
if (isNew && repoNameExists(repo.getRepoName())) { final boolean nameExists = RepositoryManager.get()
.repoExistsByName(repo.getRepoName());
if (isNew && nameExists) {
error.addKey("errRepositoryNameExists"); error.addKey("errRepositoryNameExists");
} else if ("file".equals(scheme)) { } else if ("file".equals(scheme)) {
File repoDir = new File(location); File repoDir = new File(location);
Expand Down Expand Up @@ -98,8 +100,4 @@ public void convertInput() {
RepositoryInfo modelObject = getModelObject(); RepositoryInfo modelObject = getModelObject();
setConvertedInput(modelObject); setConvertedInput(modelObject);
} }

private boolean repoNameExists(String repoName) {
return RepositoryManager.get().getByRepoName(repoName) != null;
}
} }
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -128,20 +128,16 @@ public void validate(IValidatable<RepositoryInfo> validatable) {
ValidationError error = new ValidationError(); ValidationError error = new ValidationError();
RepositoryInfo repo = validatable.getValue(); RepositoryInfo repo = validatable.getValue();
// block duplicate names first // block duplicate names first
if (null != RepositoryManager.get().getByRepoName(repo.getRepoName())) { if (RepositoryManager.get().repoExistsByName(repo.getRepoName())) {
error.addKey("errRepositoryNameExists"); error.addKey("errRepositoryNameExists");
validatable.error(error); validatable.error(error);
return; return;
} }
final URI location = repo.getLocation(); final URI location = repo.getLocation();
// look for already configured repos // look for already configured repos
List<RepositoryInfo> existingRepos = RepositoryManager.get().getAll(); if (RepositoryManager.get().repoExistsByLocation(location)) {
for (RepositoryInfo configuredRepo : existingRepos) { error.addKey("errRepositoryAlreadyConfigured");
if (configuredRepo.getLocation().equals(location)) { return;
error.addKey("errRepositoryAlreadyConfigured");
// quit looking
break;
}
} }
if (error.getKeys().isEmpty()) { if (error.getKeys().isEmpty()) {
final RepositoryResolver resolver = RepositoryResolver.lookup(location); final RepositoryResolver resolver = RepositoryResolver.lookup(location);
Expand Down
Original file line number Original file line Diff line number Diff line change
Expand Up @@ -4,14 +4,10 @@
*/ */
package org.geogig.geoserver.web.repository; package org.geogig.geoserver.web.repository;


import java.io.IOException;

import org.apache.wicket.model.LoadableDetachableModel; import org.apache.wicket.model.LoadableDetachableModel;
import org.geogig.geoserver.config.RepositoryInfo; import org.geogig.geoserver.config.RepositoryInfo;
import org.geogig.geoserver.config.RepositoryManager; import org.geogig.geoserver.config.RepositoryManager;


import com.google.common.base.Throwables;

/** /**
* A RepositoryInfo detachable model that holds the store id to retrieve it on demand from the * A RepositoryInfo detachable model that holds the store id to retrieve it on demand from the
* catalog * catalog
Expand All @@ -29,10 +25,6 @@ public RepositoryInfoDetachableModel(RepositoryInfo repoInfo) {


@Override @Override
protected RepositoryInfo load() { protected RepositoryInfo load() {
try { return RepositoryManager.get().get(id);
return RepositoryManager.get().get(id);
} catch (IOException e) {
throw Throwables.propagate(e);
}
} }
} }

0 comments on commit be2ee5d

Please sign in to comment.