diff --git a/.gitignore b/.gitignore index 96bbe75..0b4fa21 100644 --- a/.gitignore +++ b/.gitignore @@ -39,6 +39,7 @@ local.properties .settings/ .loadpath .factorypath +Servers/ # Locally stored "Eclipse launch configurations" *.launch @@ -47,3 +48,4 @@ local.properties hs_err_pid* headers traces +*.bind.json diff --git a/README.md b/README.md index 7f96fe8..de3f928 100644 --- a/README.md +++ b/README.md @@ -39,7 +39,7 @@ latest `latest` specify that the latest stable release of Matomo has been selected, which corresponds to release file `matomo.zip`. Then the default release can also be selected as follows: -* The file `default-release.txt` contains the release name that has been chosen: +* The file `default-release.txt` contains the release name that has been chosen. Example: ``` 3.6.1 ``` @@ -119,6 +119,7 @@ applications: MATOMO-SERVICE_SMTP_CREDS: $SMTP_SERVICE_CREDS$ MATOMO-SERVICE_DOMAIN: $DOMAIN$ MATOMO-SERVICE_PHPBUILDPACK: $PHP_BUILPACK$ + MATOMO-SERVICE_SHARED_CREDS: $SHARED_MYSQL_CREDS$ MATOMO-SERVICE_SHARED-DB_CREDS: $GLOBAL_SHARED_MYSQL_CREDS$ MATOMO-SERVICE_MATOMO-SHARED-DB_CREDS: $MATOMO_SHARED_MYSQL_CREDS$ MATOMO-SERVICE_DEDICATED-DB_CREDS: $DEDICATED_MYSQL_CREDS$ @@ -145,6 +146,7 @@ Change the file to fit your context by replacing all $...$ strings accordingly: | $SMTP_SERVICE_CREDS$ | This is a column-separated string which mainly consists of the names of the credential fields in VCAP_SERVICES for the SMTP service to be bound to in order to send e-mails from Matomo instances | o-smtp:smtp-prod:host :port:username:password | | $DOMAIN$ | The domain within which service instances are exposed | matomo.mycompany.com | | $PHP_BUILDPACK$ | The PHP buildpack to be used to push Matomo instance to CF | php_buildpack | + | $SHARED_MYSQL_CREDS$ | This is a column-separated string which mainly consists of the names of the service and plan as well as the names of the credential fields in VCAP_SERVICES for the MySQL/MariaDB service to be bound to in order to store data for the shared Matomo instance for mutualized service instances | p-mysql:100MB:name:hostname :port:username:password | | $GLOBAL_SHARED_MYSQL_CREDS$ | This is a column-separated string which mainly consists of the names of the service and plan as well as the names of the credential fields in VCAP_SERVICES for the MySQL/MariaDB service to be bound to in order to store data for Matomo instances within a globally shared DB | p-mysql:100MB:name:hostname :port:username:password | | $MATOMO_SHARED_MYSQL_CREDS$ | This is a column-separated string which mainly consists of the names of the service and plan as well as the names of the credential fields in VCAP_SERVICES for the MySQL/MariaDB service to be bound to in order to store data for Matomo instances within a shared DB exclusively associated with Matomo service | p-mysql:1GB:name:hostname :port:username:password | | $DEDICARED_MYSQL_CREDS$ | This is a column-separated string which mainly consists of the names of the credential fields in VCAP_SERVICES for the MySQL/MariaDB service to be bound to in order to store data for Matomo instances within a dedicated DB | ded-mysql:10GB:name:hostname :port:username:password | diff --git a/manifest.yml b/manifest.yml index 21e5710..6d145cc 100644 --- a/manifest.yml +++ b/manifest.yml @@ -25,6 +25,7 @@ applications: MATOMO-SERVICE_SMTP_CREDS: $SMTP_SERVICE_CREDS$ MATOMO-SERVICE_DOMAIN: $DOMAIN$ MATOMO-SERVICE_PHPBUILDPACK: $PHP_BUILPACK$ + MATOMO-SERVICE_SHARED_CREDS: $SHARED_MYSQL_CREDS$ MATOMO-SERVICE_SHARED-DB_CREDS: $GLOBAL_SHARED_MYSQL_CREDS$ MATOMO-SERVICE_MATOMO-SHARED-DB_CREDS: $MATOMO_SHARED_MYSQL_CREDS$ MATOMO-SERVICE_DEDICATED-DB_CREDS: $DEDICATED_MYSQL_CREDS$ diff --git a/src/main/doc/markdown/index.md b/src/main/doc/markdown/index.md index c305fa1..93fde82 100755 --- a/src/main/doc/markdown/index.md +++ b/src/main/doc/markdown/index.md @@ -13,13 +13,16 @@ Note that the actual releases proposed by the service can be found [here](releas Go to your CF marketplace and create a service instance by choosing among the proposed plans. There are three (only the first one is currently implemented, the two others are expected soon). Their objective is to provide different isolation levels in term of load and security of an instance, especially on the management of data (information from tracked Web sites): -1. global-shared-db +1. shared + All Matomo service instances are managed with many others (useful for dev purpose) within a unique Matomo server run in cluster mode within CloudFoundry. + +2. global-shared-db Data of all instances are stored in a database platform mutualized with many others (useful for dev purpose) within a unique schema. Matomo is run by one container (application instance) within CloudFoundry. -2. matomo-shared-db +3. matomo-shared-db Data of this Matomo service instance is stored in a mutualized database platform with all other Matomo service instances of this kind (useful for tracked Web sites with small / medium traffic) but within dedicated schema. Matomo is run by a cluster of two containers and is configured to run in cluster mode. -3. dedicated-db +4. dedicated-db Data of this Matomo service instance is stored in a dedicated database platform (useful for tracked Web sites with high traffic). Matomo is run by a cluster of at least two containers and is configured to run in cluster mode. It may scale up two a ten containers cluster as requested by the owner of the service instance. Indeed, the choice among them depends on the traffic of the tracked Web site and has an impact on the cost of the service instance. @@ -57,6 +60,8 @@ Version upgrade policy (see corresponding parameter name above) allows the creat * `Explicit`: This policy gives complete control on release management of Matomo instances to its owner. This means that no upgrade of release may happen until explicitly requested by the instance owner. This can only be done by requesting an update of this instance (see section "Update instance"). +Notice that no parameter can be specified for instances of the `shared` plan. They are always managed by the latest release available, which is automatically upgraded when a new one is published by the service. + While you have created a service instance, you can access it through the dashboard link associated to your instance. Indeed, to log into that Matomo instance, you need credentials. For that, you have to bind to that instance (see section "Bind to instances"). In case the creation of your instance has failed, you can delete it and retry. Even if an operation is frozen "in progress", it will fail after 30 minutes elapse time in this status. Then it will be finally possible to delete it in the end. @@ -88,6 +93,10 @@ cf update-service ms -c '{"memorySize": 768}' ``` This number is forced to stay in the interval [256..2048]. This means that if a lower value than 256 is specified, then 256 is forced. In the same way, if a higher value than 2048 is specified, then 2048 is forced. +## The special case of the instance used for the "shared" plan + +MCFS-SharedMatomoInstance + ## Bind to instances --- diff --git a/src/main/java/com/orange/oss/matomocfservice/ApplicationConfiguration.java b/src/main/java/com/orange/oss/matomocfservice/ApplicationConfiguration.java index a77c83b..e86bac1 100755 --- a/src/main/java/com/orange/oss/matomocfservice/ApplicationConfiguration.java +++ b/src/main/java/com/orange/oss/matomocfservice/ApplicationConfiguration.java @@ -35,7 +35,6 @@ import com.orange.oss.matomocfservice.web.service.ApplicationInformation; import com.orange.oss.matomocfservice.web.service.InstanceIdMgr; import com.orange.oss.matomocfservice.web.service.MatomoInstanceService; -import com.orange.oss.matomocfservice.web.service.MatomoReleases; import com.orange.oss.matomocfservice.web.service.PlatformService; /** diff --git a/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrImpl.java b/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrImpl.java index 14d0484..759bd36 100755 --- a/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrImpl.java +++ b/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrImpl.java @@ -124,7 +124,7 @@ public void initialize() { LOGGER.debug("CONFIG::CloudFoundryMgr-initialize: SMTP service instance already exist"); smtpReady = true; }) - .subscribe(); + .block(); // Check if global shared DB service has already been created and create otherwise cfops.services().getInstance(GetServiceInstanceRequest.builder() .name(properties.getDbCreds(ServiceCatalogConfiguration.PLANGLOBSHARDB_UUID).getInstanceServiceName(null)) diff --git a/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrProperties.java b/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrProperties.java index dfaad53..e3b0458 100755 --- a/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrProperties.java +++ b/src/main/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgrProperties.java @@ -33,6 +33,7 @@ import org.springframework.beans.factory.annotation.Autowired; import org.springframework.beans.factory.annotation.Value; import org.springframework.context.annotation.Configuration; +import org.springframework.util.Assert; import com.orange.oss.matomocfservice.servicebroker.ServiceCatalogConfiguration; @@ -45,6 +46,7 @@ public class CloudFoundryMgrProperties { private final static Logger LOGGER = LoggerFactory.getLogger(CloudFoundryMgrProperties.class); private final static String SMTPINSTNAME = "mcfs-smtp"; final static String GLOBSHAREDDBINSTNAME = "mcfs-globshared-db"; + final static String SHAREDDBUNKNOW = "unknown-shared-db"; private final static String SERVICESUFFIX = "-DB"; @Value("${matomo-service.matomo-debug:false}") private boolean matomoDebug; @@ -57,6 +59,8 @@ public class CloudFoundryMgrProperties { private String servicePhpBuildpack; @Value("${matomo-service.max-service-instances}") private int maxServiceInstances; + @Value("${matomo-service.shared.creds}") + private String sharedCredsStr; private DbCreds sharedCreds = null; @Value("${matomo-service.shared-db.creds}") private String sharedDbCredsStr; @@ -116,7 +120,7 @@ public DbCreds getDbCreds(String planid) { return this.dedicatedDbCreds; } else if (planid.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { if (this.sharedCreds == null) { - this.sharedCreds = new DbCreds(null, null); + this.sharedCreds = new DbCreds(this.sharedCredsStr, SHAREDDBUNKNOW); } return this.sharedCreds; } @@ -180,36 +184,25 @@ public class DbCreds { private String password; DbCreds(String creds, String sharedServiceName) { + Assert.notNull(creds, "Should always provide a creds definition string"); this.sharedServiceName = sharedServiceName; - if (creds != null) { - String[] credsarr = creds.split(":"); - this.service = credsarr[0]; - this.plan = credsarr[1]; - this.name = credsarr[2]; - this.host = credsarr[3]; - this.port = credsarr[4]; - this.user = credsarr[5]; - this.password = credsarr[6]; - } else { - this.service = null; - this.plan = null; - this.name = null; - this.host = null; - this.port = null; - this.user = null; - this.password = null; - } + String[] credsarr = creds.split(":"); + this.service = credsarr[0]; + this.plan = credsarr[1]; + this.name = credsarr[2]; + this.host = credsarr[3]; + this.port = credsarr[4]; + this.user = credsarr[5]; + this.password = credsarr[6]; } public DbCreds addVars(Builder b) { - if (this.service != null) { - b.environmentVariable("MCFS_DBSRV", this.service); - b.environmentVariable("MCFS_DBNAME", this.name); - b.environmentVariable("MCFS_DBHOST", this.host); - b.environmentVariable("MCFS_DBPORT", this.port); - b.environmentVariable("MCFS_DBUSER", this.user); - b.environmentVariable("MCFS_DBPASSWD", this.password); - } + b.environmentVariable("MCFS_DBSRV", this.service); + b.environmentVariable("MCFS_DBNAME", this.name); + b.environmentVariable("MCFS_DBHOST", this.host); + b.environmentVariable("MCFS_DBPORT", this.port); + b.environmentVariable("MCFS_DBUSER", this.user); + b.environmentVariable("MCFS_DBPASSWD", this.password); return this; } @@ -227,7 +220,7 @@ public boolean isDedicatedDb() { } public String getInstanceServiceName(String appName) { - if (sharedServiceName == null) { + if ((sharedServiceName == null) || (sharedServiceName.equals(SHAREDDBUNKNOW))) { return appName + SERVICESUFFIX; } return this.sharedServiceName; diff --git a/src/main/java/com/orange/oss/matomocfservice/web/domain/PBinding.java b/src/main/java/com/orange/oss/matomocfservice/web/domain/PBinding.java index 7a6ed06..2c69728 100755 --- a/src/main/java/com/orange/oss/matomocfservice/web/domain/PBinding.java +++ b/src/main/java/com/orange/oss/matomocfservice/web/domain/PBinding.java @@ -43,7 +43,7 @@ public class PBinding extends POperationStatus { @ManyToOne @JoinColumn(name = "matomo_instance_id") private final PMatomoInstance pmatomoInstance; - + @Column(length = LENGTH_ID) private final String appId; diff --git a/src/main/java/com/orange/oss/matomocfservice/web/domain/PMatomoInstance.java b/src/main/java/com/orange/oss/matomocfservice/web/domain/PMatomoInstance.java index 9e8d454..f90be25 100755 --- a/src/main/java/com/orange/oss/matomocfservice/web/domain/PMatomoInstance.java +++ b/src/main/java/com/orange/oss/matomocfservice/web/domain/PMatomoInstance.java @@ -18,6 +18,8 @@ import javax.persistence.Column; import javax.persistence.Entity; +import javax.persistence.JoinColumn; +import javax.persistence.ManyToOne; import javax.persistence.Table; import org.springframework.cloud.servicebroker.model.instance.OperationState; @@ -85,7 +87,11 @@ public class PMatomoInstance extends POperationStatus { @Column(length = LENGTH_TIMEZONE) private String timeZone; - + + @ManyToOne + @JoinColumn(name = "shared_matomo_instance_id") + private PMatomoInstance sharedInstance; + protected PMatomoInstance() { super(); this.idUrl = -1; @@ -104,6 +110,7 @@ protected PMatomoInstance() { this.instances = 0; this.memorySize = 0; this.timeZone = null; + this.sharedInstance = null; } public PMatomoInstance(String uuid, int idUrl, String name, PlatformKind pfkind, String pfapi, String planid, PPlatform pf, Parameters mip) { @@ -124,6 +131,20 @@ public PMatomoInstance(String uuid, int idUrl, String name, PlatformKind pfkind, this.timeZone = mip.getTimeZone(); } + public PMatomoInstance(String uuid, int idUrl, String name, PlatformKind pfkind, String pfapi, String planid, PPlatform pf, Parameters mip, PMatomoInstance shared) { + this(uuid, idUrl, name, pfkind, pfapi, planid, pf, mip); + this.configFileContent = "fake_content".getBytes(); + this.sharedInstance = shared; + } + + public boolean isSharedPlan() { + return this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID); + } + + public PMatomoInstance getSharedInstance() { + return this.sharedInstance; + } + public int getIdUrl() { return this.idUrl; } @@ -152,6 +173,9 @@ public PlatformKind getPlatformKind() { public String getPlatformApiLocation() { return platformApiLocation; } + public void setPlatformApiLocation(String api) { + this.platformApiLocation = api; + } public String getPlanId() { return this.planId; @@ -173,77 +197,122 @@ public void setConfigFileContent(byte cfc[]) { } public boolean getAutomaticVersionUpgrade() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getAutomaticVersionUpgrade(); + } return this.automaticVersionUpgrade; } public void setAutomaticVersionUpgrade(boolean avu) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.automaticVersionUpgrade = avu; super.touch(); } public int getInstances() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getInstances(); + } return this.instances; } public void setIntances(int instances) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.instances = instances; super.touch(); } public int getMemorySize() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getMemorySize(); + } return this.memorySize; } public void setMemorySize(int memorysize) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.memorySize = memorysize; super.touch(); } public String getTimeZone() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getTimeZone(); + } return this.timeZone; } public void setTimeZone(String timezone) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.timeZone = timezone; super.touch(); } public void setInstalledVersion(String instVers) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.installedVersion = instVers; super.touch(); } public String getInstalledVersion() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getInstalledVersion(); + } return this.installedVersion; } public String getPassword() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return null; + } return this.password; } public void setDbCred(String dbc) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.dbCred = dbc; } public String getDbCred() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return null; + } return this.dbCred; } public void setTokenAuth(String ta) { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return; + } this.tokenAuth = ta; } public String getTokenAuth() { + if (this.planId.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + return this.sharedInstance.getTokenAuth(); + } return this.tokenAuth; } public Parameters getParameters() { return new Parameters() - .version(this.installedVersion) - .autoVersionUpgrade(this.automaticVersionUpgrade) - .cfInstances(this.instances) - .memorySize(this.memorySize) - .timeZone(this.timeZone); + .version(getInstalledVersion()) + .autoVersionUpgrade(getAutomaticVersionUpgrade()) + .cfInstances(getInstances()) + .memorySize(getMemorySize()) + .timeZone(getTimeZone()); } public enum PlatformKind { diff --git a/src/main/java/com/orange/oss/matomocfservice/web/domain/POperationStatus.java b/src/main/java/com/orange/oss/matomocfservice/web/domain/POperationStatus.java index d456f9c..b1390ac 100755 --- a/src/main/java/com/orange/oss/matomocfservice/web/domain/POperationStatus.java +++ b/src/main/java/com/orange/oss/matomocfservice/web/domain/POperationStatus.java @@ -43,7 +43,7 @@ public abstract class POperationStatus { @Id @Column(length = LENGTH_ID, updatable = false, nullable = false) - private final String uuid; + private String uuid; private final ZonedDateTime createTime; @@ -80,6 +80,9 @@ public POperationStatus(String uuid, OpCode opcode, OperationState opstate, PPla public String getUuid() { return this.uuid; } + public void setUuid(String uuid) { + this.uuid = uuid; + } public ZonedDateTime getCreateTime() { return this.createTime; diff --git a/src/main/java/com/orange/oss/matomocfservice/web/service/BindingService.java b/src/main/java/com/orange/oss/matomocfservice/web/service/BindingService.java index 77b3ad5..6a3f9ae 100755 --- a/src/main/java/com/orange/oss/matomocfservice/web/service/BindingService.java +++ b/src/main/java/com/orange/oss/matomocfservice/web/service/BindingService.java @@ -127,7 +127,7 @@ public String createBinding(String bindid, String instid, String appid, Map opmi = miRepo.findByName(SHAREDINSTANCENAME); + if (opmi.isPresent()) { + LOGGER.debug("Matomo Instance for shared plan already exist"); + sharedReady = true; + return; + } + // Create the Matomo service instance for the shared plan + // TODO : formalize constants in following create... + createMatomoInstance(SHAREDINSTANCEINITUUID, SHAREDINSTANCENAME, PlatformKind.CLOUDFOUNDRY, "", + ServiceCatalogConfiguration.PLANDEDICATEDDB_UUID, pfs.getUnknownPlatformId(), + new Parameters().autoVersionUpgrade(true).cfInstances(SHAREDDEFAULTNBAPPINSTANCE).memorySize(SHAREDDEFAULTMEMSIZE)); } public PMatomoInstance getMatomoInstance(String instanceId, String platformId) { @@ -139,7 +157,7 @@ public PMatomoInstance getMatomoInstance(String instanceId, String platformId) { Assert.notNull(platformId, "platform id mustn't be null"); LOGGER.debug("SERV::getMatomoInstance: instanceId={}, platformId={}", instanceId, platformId); Optional opmi = miRepo.findById(instanceId); - if (! opmi.isPresent()) { + if (!opmi.isPresent()) { LOGGER.debug("Matomo Instance with ID=" + instanceId + " not known in Platform with ID=" + platformId); return null; } @@ -175,13 +193,43 @@ public String createMatomoInstance(String uuid, String instname, PlatformKind pf if (apiinfolocation == null) { apiinfolocation = ""; } + PMatomoInstance pmi; + // Shared service bootstrap: test if we want to create the shared Matomo instance + // which already exist and associate it with a relevant UUID + if (instname.equals(SHAREDINSTANCENAME) && !uuid.equals(SHAREDINSTANCEINITUUID)) { + EntityManager em = beginTx(); + try { + pmi = getMatomoInstance(SHAREDINSTANCEINITUUID, pfs.getUnknownPlatformId()); + if (pmi == null) { + LOGGER.warn("Shared Matomo instance has already been associated with the CF platform that host the service: can just do it once, ignored"); + return "Shared Matomo instance has already been associated with the CF platform that host the service: can just do it once, ignored"; + } + pmi.setUuid(uuid); + pmi.setPlatformApiLocation(apiinfolocation); + savePMatomoInstance(pmi, pmi.getLastOperationState()); + LOGGER.info("Shared Matomo instance has been associated with the CF platform that host the service"); + return "Shared Matomo instance has been associated with the CF platform that host the service"; + } catch (Exception e) { + LOGGER.error("Error while associating shared Matomo instance: " + e.getMessage()); + return "Error while associating shared Matomo instance: " + e.getMessage(); + } finally { + commitTx(em); + } + } + LOGGER.debug("SERV::createMatomoInstance: instanceId={}, platformId={}, instName={}, apiInfoLoc={}", uuid, pfid, instname, apiinfolocation); - if (!cfMgr.isSmtpReady() - || (planid.equals(ServiceCatalogConfiguration.PLANGLOBSHARDB_UUID) && !cfMgr.isGlobalSharedReady())) { - LOGGER.error("Cannot create any kind of instance: service unavailable (retry later on)"); - return "Cannot create any kind of instance: service unavailable (retry later on)"; + if (!cfMgr.isSmtpReady()) { + LOGGER.error("Cannot create any kind of instance: service unavailable / no SMTP (retry later on)"); + return "Cannot create any kind of instance: service unavailable / no SMTP (retry later on)"; + } + if (planid.equals(ServiceCatalogConfiguration.PLANGLOBSHARDB_UUID) && !cfMgr.isGlobalSharedReady()) { + LOGGER.error("Cannot create instance (PLANGLOBSHARDB_UUID): service unavailable / no global shared DB (retry later on)"); + return "Cannot create any kind of instance: service unavailable / no global shared DB (retry later on)"; + } + if (planid.equals(ServiceCatalogConfiguration.PLANSHARED_UUID) && !sharedReady) { + LOGGER.error("Cannot create instance (PLANSHARED_UUID): service unavailable / no Matomo shared instance (retry later on)"); + return "Cannot create any kind of instance: service unavailable / no Matomo shared instance (retry later on)"; } - PMatomoInstance pmi; synchronized (this) { EntityManager em = beginTx(); try { @@ -190,76 +238,87 @@ public String createMatomoInstance(String uuid, String instname, PlatformKind pf return "Matomo Instance with ID=" + uuid + " already exists in Platform with ID=" + pfid; } PPlatform ppf = getPPlatform(pfid); - pmi = new PMatomoInstance(uuid, instanceIdMgr.allocateInstanceId(), instname, pfkind, apiinfolocation, - planid, ppf, parameters); - savePMatomoInstance(pmi, OperationState.IN_PROGRESS); + if (planid.equals(ServiceCatalogConfiguration.PLANSHARED_UUID)) { + pmi = new PMatomoInstance(uuid, instanceIdMgr.allocateInstanceId(), instname, pfkind, apiinfolocation, + planid, ppf, parameters, miRepo.findByName(SHAREDINSTANCENAME).get()); + savePMatomoInstance(pmi, OperationState.SUCCEEDED); + return null; + } else { + pmi = new PMatomoInstance(uuid, instanceIdMgr.allocateInstanceId(), instname, pfkind, apiinfolocation, + planid, ppf, parameters); + savePMatomoInstance(pmi, OperationState.IN_PROGRESS); + } } catch (Exception e) { return "Error while initializing creation: " + e.getMessage(); } finally { commitTx(em); } } - MatomoReleases.createLinkedTree(parameters.getVersion(), pmi.getIdUrlStr()); - Mono createdb = (properties.getDbCreds(planid).isDedicatedDb()) - ? cfMgr.createDedicatedDb(pmi.getIdUrlStr(), planid) - : Mono.empty(); + final String idurlstr = pmi.getIdUrlStr(); + final boolean clustmode = pmi.getClusterMode(); + MatomoReleases.createLinkedTree(parameters.getVersion(), idurlstr); + Mono createdb = uuid.equals(SHAREDINSTANCEINITUUID) + ? cfMgr.createDedicatedDb(idurlstr, ServiceCatalogConfiguration.PLANSHARED_UUID) + : (properties.getDbCreds(planid).isDedicatedDb()) + ? cfMgr.createDedicatedDb(idurlstr, planid) + : Mono.empty(); createdb.timeout(Duration.ofMinutes(CloudFoundryMgr.CREATEDBSERV_TIMEOUT)) .doOnError(t -> { - EntityManager nem = beginTx(); + EntityManager em = beginTx(); PMatomoInstance npmi = miRepo.getOne(uuid); - nem.unwrap(Session.class).update(npmi); + em.unwrap(Session.class).update(npmi); LOGGER.error("Create dedicated DB for instance \"" + npmi.getUuid() + "\" failed.", t); savePMatomoInstance(npmi, OperationState.FAILED); - commitTx(nem); + commitTx(em); }).doOnSuccess(v -> { LOGGER.debug("Create dedicated DB phase for \"" + uuid + "\" succeeded."); - cfMgr.deployMatomoCfApp(pmi.getIdUrlStr(), uuid, planid, parameters, Parameters.MINMEMORYSIZE, 1) - .doOnError(tt -> { - EntityManager nem = beginTx(); - PMatomoInstance npmi = miRepo.getOne(uuid); - nem.unwrap(Session.class).update(npmi); - LOGGER.error("Async create app instance (phase 1) \"" + uuid + "\" failed.", tt); - MatomoReleases.deleteLinkedTree(pmi.getIdUrlStr()); - savePMatomoInstance(npmi, OperationState.FAILED); - commitTx(nem); - }).doOnSuccess(vv -> { - EntityManager nem = beginTx(); - PMatomoInstance npmi = miRepo.getOne(uuid); - nem.unwrap(Session.class).update(npmi); - if (npmi.getLastOperationState() == OperationState.FAILED) { - LOGGER.debug("Async create app too late (phase 1) \"" + uuid + "\": already failed"); - } else { - LOGGER.debug("Async create app instance (phase 1) \"" + uuid + "\" succeeded"); - cfMgr.deleteAssociatedDbSchema(npmi); // make sure the DB situation is clean - if (!cfMgr.initializeMatomoInstance(npmi.getIdUrlStr(), uuid, npmi.getPassword(), - npmi.getPlanId())) { - MatomoReleases.deleteLinkedTree(npmi.getIdUrlStr()); - savePMatomoInstance(npmi, OperationState.FAILED); - commitTx(nem); + cfMgr.deployMatomoCfApp(idurlstr, uuid, planid, parameters, Parameters.MINMEMORYSIZE, 1) + .doOnError(tt -> { + EntityManager nem = beginTx(); + PMatomoInstance npmi = miRepo.getOne(uuid); + nem.unwrap(Session.class).update(npmi); + LOGGER.error("Async create app instance (phase 1) \"" + uuid + "\" failed.", tt); + MatomoReleases.deleteLinkedTree(idurlstr); + savePMatomoInstance(npmi, OperationState.FAILED); + commitTx(nem); + }).doOnSuccess(vv -> { + EntityManager nem = beginTx(); + PMatomoInstance npmi = miRepo.getOne(uuid); + nem.unwrap(Session.class).update(npmi); + if (npmi.getLastOperationState() == OperationState.FAILED) { + LOGGER.debug("Async create app too late (phase 1) \"" + uuid + "\": already failed"); } else { - commitTx(nem); - cfMgr.getInstanceConfigFile(pmi.getIdUrlStr(), parameters.getVersion(), pmi.getClusterMode()) - .doOnError(ttt -> { - EntityManager nnem = beginTx(); - PMatomoInstance nnpmi = miRepo.getOne(uuid); - nnem.unwrap(Session.class).update(nnpmi); - LOGGER.debug("Cannot retrieve config file from Matomo instance.", ttt); - MatomoReleases.deleteLinkedTree(nnpmi.getIdUrlStr()); - savePMatomoInstance(nnpmi, OperationState.FAILED); - commitTx(nnem); - }).doOnSuccess(ach -> { - EntityManager nnem = beginTx(); - PMatomoInstance nnpmi = miRepo.getOne(uuid); - nnem.unwrap(Session.class).update(nnpmi); - nnpmi.setConfigFileContent(ach.fileContent); - savePMatomoInstance(nnpmi, null); - commitTx(nnem); - settleMatomoInstance(nnpmi, NOPEINSTIDS, parameters, true, - properties.getDbCreds(nnpmi.getPlanId())).subscribe(); - }).subscribe(); + LOGGER.debug("Async create app instance (phase 1) \"" + uuid + "\" succeeded"); + cfMgr.deleteAssociatedDbSchema(npmi); // make sure the DB situation is clean + if (!cfMgr.initializeMatomoInstance(idurlstr, uuid, npmi.getPassword(), + npmi.getPlanId())) { + MatomoReleases.deleteLinkedTree(idurlstr); + savePMatomoInstance(npmi, OperationState.FAILED); + commitTx(nem); + } else { + commitTx(nem); + cfMgr.getInstanceConfigFile(idurlstr, parameters.getVersion(), clustmode) + .doOnError(ttt -> { + EntityManager nnem = beginTx(); + PMatomoInstance nnpmi = miRepo.getOne(uuid); + nnem.unwrap(Session.class).update(nnpmi); + LOGGER.debug("Cannot retrieve config file from Matomo instance.", ttt); + MatomoReleases.deleteLinkedTree(idurlstr); + savePMatomoInstance(nnpmi, OperationState.FAILED); + commitTx(nnem); + }).doOnSuccess(ach -> { + EntityManager nnem = beginTx(); + PMatomoInstance nnpmi = miRepo.getOne(uuid); + nnem.unwrap(Session.class).update(nnpmi); + nnpmi.setConfigFileContent(ach.fileContent); + savePMatomoInstance(nnpmi, null); + commitTx(nnem); + settleMatomoInstance(nnpmi, NOPEINSTIDS, parameters, true, + properties.getDbCreds(nnpmi.getPlanId())).subscribe(); + }).subscribe(); + } } - } - }).subscribe(); + }).subscribe(); }).subscribe(); return null; } @@ -292,6 +351,11 @@ public String deleteMatomoInstance(String uuid, String platformId) { return "Error: cannot delete Matomo service instance with ID=" + uuid + ": operation already in progress."; } pmi.setLastOperation(POperationStatus.OpCode.DELETE_SERVICE_INSTANCE); + if (pmi.isSharedPlan()) { + pmi.setConfigFileContent(null); + savePMatomoInstance(pmi, OperationState.SUCCEEDED); + return "Delete completed for instance with ID=" + uuid; + } savePMatomoInstance(pmi, OperationState.IN_PROGRESS); // delete data associated with the instance under deletion cfMgr.deleteAssociatedDbSchema(pmi); @@ -369,6 +433,9 @@ public String updateMatomoInstance(String uuid, String pfid, Parameters paramete LOGGER.debug("SERV::updateMatomoInstance: KO -> operation in progress."); return null; } + if (pmi.isSharedPlan()) { + return null; + } pmi.setLastOperation(POperationStatus.OpCode.UPDATE_SERVICE_INSTANCE); savePMatomoInstance(pmi, OperationState.IN_PROGRESS); if (pmi.getAutomaticVersionUpgrade() != parameters.isAutoVersionUpgrade()) { @@ -487,7 +554,7 @@ private Mono updateMatomoInstanceActual(PMatomoInstance pmi, InstIds ins @SuppressWarnings("unchecked") private Mono settleMatomoInstance(PMatomoInstance pmi, InstIds instids, Parameters mip, boolean retrievetoken, CloudFoundryMgrProperties.DbCreds dbCreds) { - String uuid = pmi.getUuid(); + String uuid = pmi.getUuid(), instname = pmi.getName(); return cfMgr.deployMatomoCfApp(pmi.getIdUrlStr(), pmi.getUuid(), pmi.getPlanId(), mip, pmi.getMemorySize(), pmi.getInstances()) .doOnError(t -> { EntityManager nem = beginTx(); @@ -531,6 +598,9 @@ private Mono settleMatomoInstance(PMatomoInstance pmi, InstIds instids, Pa } else { nnpmi.setTokenAuth(token); savePMatomoInstance(nnpmi, OperationState.SUCCEEDED); + if (instname.equals(SHAREDINSTANCENAME) && uuid.equals(SHAREDINSTANCEINITUUID)) { + sharedReady = true; + } LOGGER.debug("Async settle app instance (phase 2) \"" + nnpmi.getUuid() + "\" succeeded"); } commitTx(nnem); diff --git a/src/main/resources/db/changelog/changelog-master.xml b/src/main/resources/db/changelog/changelog-master.xml index e46897b..652d652 100755 --- a/src/main/resources/db/changelog/changelog-master.xml +++ b/src/main/resources/db/changelog/changelog-master.xml @@ -26,4 +26,5 @@ + diff --git a/src/main/resources/db/changelog/changes/db-changelog-1.5.xml b/src/main/resources/db/changelog/changes/db-changelog-1.5.xml new file mode 100755 index 0000000..1fd8cd2 --- /dev/null +++ b/src/main/resources/db/changelog/changes/db-changelog-1.5.xml @@ -0,0 +1,28 @@ + + + + + + + + + diff --git a/src/main/resources/db/changelog/changes/update-matomoinstances-table-changelog-1.5.xml b/src/main/resources/db/changelog/changes/update-matomoinstances-table-changelog-1.5.xml new file mode 100755 index 0000000..9ef5daf --- /dev/null +++ b/src/main/resources/db/changelog/changes/update-matomoinstances-table-changelog-1.5.xml @@ -0,0 +1,34 @@ + + + + + + + + + + + + + + + diff --git a/src/test/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgr4Test.java b/src/test/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgr4Test.java index 8f95e96..3c4830f 100755 --- a/src/test/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgr4Test.java +++ b/src/test/java/com/orange/oss/matomocfservice/cfmgr/CloudFoundryMgr4Test.java @@ -40,6 +40,10 @@ public class CloudFoundryMgr4Test extends CloudFoundryMgrAbs { private final static Logger LOGGER = LoggerFactory.getLogger(CloudFoundryMgr4Test.class); private CfMgr4TResponseMask respMask; + public CloudFoundryMgr4Test() { + respMask = new CfMgr4TResponseMask(); + } + public void setResponseMask(CfMgr4TResponseMask m) { respMask = m; } diff --git a/src/test/resources/application.yml b/src/test/resources/application.yml index 1e459a3..a507284 100755 --- a/src/test/resources/application.yml +++ b/src/test/resources/application.yml @@ -1,7 +1,7 @@ debug: false test: - currentRelease: 3.13.5 + currentRelease: 3.14.1 lowerReleaseNotAvailable: 3.6.1 logging: @@ -52,6 +52,8 @@ matomo-service: max-service-instances: 10 smtp: creds: o-smtp:smtpplan:host:port:username:password + shared: + creds: p-mysql:10MB:name:hostname:port:username:password shared-db: creds: p-mysql:10MB:name:hostname:port:username:password matomo-shared-db: