From 3ffa8bdaba1676cf0350a95e6e02cac00955ba0a Mon Sep 17 00:00:00 2001 From: Richard Millet Date: Fri, 8 May 2020 22:56:56 -0700 Subject: [PATCH] DRYD-871: Fixed issue where single tenant deployments had incorrect Nuxeo datasource config files. --- .../config/proto-datasource-config.xml | 26 +-- .../services/common/ServiceMain.java | 164 +++++++++++++++++- .../services/common/storage/JDBCTools.java | 2 +- .../services/common/config/ConfigUtils.java | 21 ++- services/config/src/main/resources/tenant.xsd | 1 + 5 files changed, 186 insertions(+), 28 deletions(-) diff --git a/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-datasource-config.xml b/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-datasource-config.xml index 4887c1673..1df6521fe 100644 --- a/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-datasource-config.xml +++ b/3rdparty/nuxeo/nuxeo-server/9.10-HF30/config/proto-datasource-config.xml @@ -2,31 +2,21 @@ - - - - - - - - - - - - + - - @DB_SERVER_HOSTNAME@ @DB_JDBC_OPTIONS@ diff --git a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java index 4c8af4694..8c1d1a048 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java +++ b/services/common/src/main/java/org/collectionspace/services/common/ServiceMain.java @@ -60,7 +60,9 @@ import org.collectionspace.services.nuxeo.listener.CSEventListener; import org.collectionspace.services.nuxeo.listener.AbstractCSEventListenerImpl; import org.jboss.resteasy.spi.ResteasyProviderFactory; +import org.dom4j.Attribute; import org.dom4j.Document; +import org.dom4j.tree.DefaultElement; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -1249,6 +1251,11 @@ public class ServiceMain { logger.info(String.format("Using prototype Nuxeo server configuration file at path %s", prototypeNuxeoDatasourceFile.getAbsolutePath())); } + + // + // If multiple active tenants, set the "default" repository for Nuxeo services to use. + // + setDefaultNuxeoRepository(); // // For each tenant config we find, create the xml datasource config file and fill in the correct values. @@ -1278,7 +1285,7 @@ public class ServiceMain { datasourceConfigDoc = (Document) prototypeConfigDoc.clone(); // Update this config file by inserting values pertinent to the // current repository. - datasourceConfigDoc = updateRepositoryDatasourceDoc(datasourceConfigDoc, repositoryName, this.getCspaceInstanceId()); + datasourceConfigDoc = updateRepositoryDatasourceDoc(datasourceConfigDoc, repositoryName, tbt.getRepositoryDomain(), this.getCspaceInstanceId()); logger.debug("Updated Nuxeo datasource config file contents=\n" + datasourceConfigDoc.asXML()); // Write this config file to the Nuxeo server config directory. @@ -1291,6 +1298,137 @@ public class ServiceMain { } } + private boolean isImpliedDefaultRepository(RepositoryDomainType repositoryDomain) { + boolean result = false; + + String repoName = repositoryDomain.getRepositoryName(); + if (repoName == null || repoName.equals(ConfigUtils.DEFAULT_NUXEO_REPOSITORY_NAME)) { + result = true; + } + + return result; + } + + private void setDefaultNuxeoRepository() throws Exception { + boolean moreThanOne = false; + boolean defaultIsSet = false; + String defaultRepositoryName = null; + + Hashtable tenantBindingTypeMap = tenantBindingConfigReader.getTenantBindings(); + + // + // Ensure we have at least one tenant binding and at least one corresponding repository domain + // + if (tenantBindingTypeMap.values().size() == 0) { + String msg = "At least one tenant binding must be configured."; + throw new Exception(msg); + } + + // + // If we have just one tenant, make its (or one of its) repository domain(s) the default one. + // + if (tenantBindingTypeMap.values().size() == 1) { + TenantBindingType tbt = (TenantBindingType) tenantBindingTypeMap.values().toArray()[0]; + List repositoryDomainList = tbt.getRepositoryDomain(); + for (RepositoryDomainType repositoryDomain : repositoryDomainList) { + if (repositoryDomain.isDefaultRepository() && defaultIsSet == false) { + defaultIsSet = true; + defaultRepositoryName = repositoryDomain.getRepositoryName(); + } else if (repositoryDomain.isDefaultRepository() && defaultIsSet == true) { + moreThanOne = true; + String msg = String.format("The tenant '%s' configuration is declaring '%s' the default repository. However, '%s' is already the default repository. Please ensure only one tenant is configured to be the default repository.", + tbt.getName(), repositoryDomain.getRepositoryName(), defaultRepositoryName); + logger.error(msg); + } + } + + if (moreThanOne == true) { + String msg = String.format("The tenant '%s' has more than one repository domain configured to be the default. Please configure only one default repository.", + tbt.getName()); + throw new Exception(msg); + } + + // + // If the only active tenant is not explicitly configuring a repository domain as default, do so now. + // + if (defaultIsSet == false) { + RepositoryDomainType repositoryDomain = repositoryDomainList.get(0); + repositoryDomain.setDefaultRepository(Boolean.TRUE); + defaultIsSet = true; + defaultRepositoryName = repositoryDomain.getRepositoryName(); + } + + if (logger.isDebugEnabled()) { + String msg = String.format("The tenant '%s' has configured the default repository to be '%s'.", + tbt.getName(), defaultRepositoryName); + logger.debug(msg); + } + + return; + } + + // + // If we have multiple tenants, figure out which one is declaring the default repository. + // + for (TenantBindingType tbt : tenantBindingTypeMap.values()) { + List repositoryDomainList = tbt.getRepositoryDomain(); + for (RepositoryDomainType repositoryDomain : repositoryDomainList) { + if (repositoryDomain.isDefaultRepository() && defaultIsSet == false) { + repositoryDomain.setDefaultRepository(Boolean.TRUE); + defaultIsSet = true; + defaultRepositoryName = repositoryDomain.getRepositoryName(); + } else if (repositoryDomain.isDefaultRepository() && defaultIsSet == true) { + moreThanOne = true; + String msg = String.format("The tenant '%s' configuration is declaring itself as the default repository domain. However, another tenant has already declared '%s' to be the default repository. Please ensure only one tenant is configured to have the default repository.", + tbt.getName(), defaultRepositoryName); + logger.error(msg); + } + } + } + + // + // If more than one tenant has declared itself the default repository domain then + // throw an exception + // + if (moreThanOne == true) { + String msg = "More than one tenant is configured to be the repository domain. Please configure only one default repository."; + throw new Exception(msg); + } + + // + // If no tenant has declared itself the default repository, look for an "implied" default. Tenants configured with + // no repository name are inferred to be the default repository. If more than one tenant is configured without a repository name, + // we'll use the first one found. + // + if (defaultIsSet == false) { + for (TenantBindingType tbt : tenantBindingTypeMap.values()) { + List repositoryDomainList = tbt.getRepositoryDomain(); + for (RepositoryDomainType repositoryDomain : repositoryDomainList) { + if (isImpliedDefaultRepository(repositoryDomain) && defaultIsSet == false) { + repositoryDomain.setDefaultRepository(Boolean.TRUE); + defaultIsSet = true; + defaultRepositoryName = repositoryDomain.getRepositoryName(); + } else if (isImpliedDefaultRepository(repositoryDomain) && defaultIsSet == true) { + moreThanOne = true; + String msg = String.format("The tenant '%s' configuration implies (no repository name was defined) it is the default repository domain. However, another tenant's domain has implied '%s' is the default repository. Please ensure only one tenant defines the default repository.", + tbt.getName(), defaultRepositoryName); + logger.error(msg); + } + } + } + } + + // + // As of v6.0, this is just a warning. However, future versions may require a default repository. + // + if (defaultIsSet == false) { + logger.warn("No tenant's configuration explicitly declared nor implied its repository to be the default. Future versions of CollectionSpace may require a default repository."); + } else if (logger.isDebugEnabled()) { + String msg = String.format("The default repository has been set to '%s'.", defaultRepositoryName); + logger.debug(msg); + } + } + private File getProtoElasticsearchConfigFile() throws Exception { File result = new File(getCspaceServicesConfigDir() + File.separator + getNuxeoProtoElasticsearchConfigFilename()); @@ -1505,8 +1643,10 @@ public class ServiceMain { /* * This method is filling out the proto-datasource-config.xml file with tenant specific repository information. */ - private Document updateRepositoryDatasourceDoc(Document repoConfigDoc, String repositoryName, + private Document updateRepositoryDatasourceDoc(Document repoConfigDoc, String repositoryName, List repoDomainList, String cspaceInstanceId) { + + boolean isDefaultRepository = ConfigUtils.containsDefaultRepository(repoDomainList); String databaseName = JDBCTools.getDatabaseName(repositoryName, cspaceInstanceId); repoConfigDoc = XmlTools.setAttributeValue(repoConfigDoc, "/component", "name", @@ -1550,12 +1690,20 @@ public class ServiceMain { repoConfigDoc = XmlTools.setAttributeValue(repoConfigDoc, ConfigUtils.DATASOURCE_EXTENSION_POINT_XPATH + "/datasource", "password", password); - // Set the element's name attribute - repoConfigDoc = XmlTools.setAttributeValue(repoConfigDoc, - ConfigUtils.DATASOURCE_EXTENSION_POINT_XPATH + "/link", "name", "jdbc/repository_" + repositoryName); - // Set the element's global attribute - repoConfigDoc = XmlTools.setAttributeValue(repoConfigDoc, - ConfigUtils.DATASOURCE_EXTENSION_POINT_XPATH + "/link", "global", datasoureName); + // + // Adjust various Nuxeo components' datasource links to use the tenant repository. + // In a multi-tenant deployment, it is unclear if there will be name clashes -i.e., two or more tenants declaring + // themselves to be the datasource for a Nuxeo component. + // + List linkNodes = XmlTools.getElementNodes(repoConfigDoc, ConfigUtils.DATASOURCE_EXTENSION_POINT_XPATH + "/link"); + for (DefaultElement node : linkNodes) { + Attribute nameAttribute = node.attribute("name"); + if (nameAttribute.getValue().equals(ConfigUtils.CS_TENANT_DATASOURCE_VALUE)) { + nameAttribute.setValue("jdbc/repository_" + repositoryName); + } + Attribute globalAttribute = node.attribute("global"); + globalAttribute.setValue(datasoureName); + } return repoConfigDoc; } diff --git a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java index b62dfd602..311bf37cb 100644 --- a/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java +++ b/services/common/src/main/java/org/collectionspace/services/common/storage/JDBCTools.java @@ -125,7 +125,7 @@ public class JDBCTools { try { envCtx.close(); } catch (Exception e) { - logger.error("Error getting DataSource for: " + dataSourceName, e); + logger.trace("Error getting DataSource for: " + dataSourceName, e); } } } diff --git a/services/config/src/main/java/org/collectionspace/services/common/config/ConfigUtils.java b/services/config/src/main/java/org/collectionspace/services/common/config/ConfigUtils.java index e218d69f3..3792c2df5 100644 --- a/services/config/src/main/java/org/collectionspace/services/common/config/ConfigUtils.java +++ b/services/config/src/main/java/org/collectionspace/services/common/config/ConfigUtils.java @@ -15,6 +15,7 @@ public class ConfigUtils { public static final String COMPONENT_EXTENSION_XPATH = "/component" + EXTENSION_XPATH; public static final String DATASOURCE_EXTENSION_POINT_XPATH = String.format(COMPONENT_EXTENSION_XPATH, "datasources"); public static final String REPOSITORY_EXTENSION_POINT_XPATH = String.format(COMPONENT_EXTENSION_XPATH, "repository"); + public static final String CS_TENANT_DATASOURCE_VALUE = "jdbc/TenantDS"; public static final String CONFIGURATION_EXTENSION_POINT_XPATH = String.format(COMPONENT_EXTENSION_XPATH, "configuration"); public static final String ELASTICSEARCH_INDEX_EXTENSION_XPATH = String.format(EXTENSION_XPATH, "elasticSearchIndex"); public static final String ELASTICSEARCH_EXTENSIONS_EXPANDER_STR = "%elasticSearchIndex_extensions%"; @@ -43,6 +44,24 @@ public class ConfigUtils { return result; } + + /* + * Returns 'true' if the tenant declares the default repository. + */ + public static boolean containsDefaultRepository(List repoDomainList) { + boolean result = false; + + if (repoDomainList != null && repoDomainList.isEmpty() == false) { + for (RepositoryDomainType repoDomain : repoDomainList) { + if (repoDomain.isDefaultRepository() == true) { + result = true; + break; + } + } + } + + return result; + } public static String getRepositoryName(TenantBindingType tenantBindingType, String domainName) { String result = null; @@ -60,7 +79,7 @@ public class ConfigUtils { } else { logger.error(String.format("There was no domain name specified on a call to getRepositoryName() method.")); } - + if (result == null && logger.isTraceEnabled()) { logger.trace(String.format("Could not find the repository name for tenent name='%s' and domain='%s'", tenantBindingType.getName(), domainName)); diff --git a/services/config/src/main/resources/tenant.xsd b/services/config/src/main/resources/tenant.xsd index 844ca79b3..0cd5bf5ff 100644 --- a/services/config/src/main/resources/tenant.xsd +++ b/services/config/src/main/resources/tenant.xsd @@ -82,6 +82,7 @@ + -- 2.47.3