diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/AbstractComponent.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/AbstractComponent.java index c5cadca..ea47bf8 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/AbstractComponent.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/AbstractComponent.java @@ -374,12 +374,6 @@ public long getUserId() { @Override public Optional isReady() { - if (getCmcc().getStatus().getMilestone() == null) { - log.debug("foo"); - } - if (getComponentSpec().getMilestone() == null) { - log.debug("foo"); - } if (getCmcc().getStatus().getMilestone().compareTo(getComponentSpec().getMilestone()) < 0) return Optional.empty(); return Optional.of(getTargetState().isStatefulSetReady(getTargetState().getResourceNameFor(this))); diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasJdbcClient.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasJdbcClient.java index 1e27d3c..1242abf 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasJdbcClient.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasJdbcClient.java @@ -12,7 +12,6 @@ import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; import java.util.List; @@ -50,16 +49,15 @@ default ClientSecretRef getJdbcClientSecretRef(String schemaName) { if (!getTargetState().getCmcc().getSpec().getWith().getDatabases()) { throw new CustomResourceConfigError("No MySQL client secret reference found for " + schemaName + ", and with.databases is false"); } - return getTargetState().getClientSecretRef(JDBC_CLIENT_SECRET_REF_KIND, schemaName, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(secretName), - getTargetState().loadOrBuildSecret(secretName, Map.of( + return getTargetState().getClientSecretRef(JDBC_CLIENT_SECRET_REF_KIND, schemaName, + (clientSecret, password) -> getTargetState().loadOrBuildSecret(clientSecret, Map.of( ClientSecretRef.DEFAULT_DRIVER_KEY, "com.mysql.cj.jdbc.Driver", ClientSecretRef.DEFAULT_HOSTNAME_KEY, serviceName, ClientSecretRef.DEFAULT_PASSWORD_KEY, password, ClientSecretRef.DEFAULT_SCHEMA_KEY, schemaName, ClientSecretRef.DEFAULT_URL_KEY, "jdbc:mysql://" + serviceName + ":3306/" + schemaName, ClientSecretRef.DEFAULT_USERNAME_KEY, schemaName - )) + ) ) ); } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasMongoDBClient.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasMongoDBClient.java index a4cfd30..93df16d 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasMongoDBClient.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasMongoDBClient.java @@ -12,7 +12,6 @@ import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; import java.util.List; @@ -47,15 +46,13 @@ default ClientSecretRef getMongoDBClientSecretRef(String schemaName) { if (!getTargetState().getCmcc().getSpec().getWith().getDatabases()) { throw new CustomResourceConfigError("No MongoDB client secret reference found for " + schemaName + ", and with.databases is false"); } - return getTargetState().getClientSecretRef(MONGODB_CLIENT_SECRET_REF_KIND, schemaName, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(secretName), - getTargetState().loadOrBuildSecret(secretName, Map.of( - ClientSecretRef.DEFAULT_PASSWORD_KEY, password, - ClientSecretRef.DEFAULT_SCHEMA_KEY, schemaName, - ClientSecretRef.DEFAULT_URL_KEY, "mongodb://" + schemaName + ":" + password + "@" + serviceName + ":27017/" + schemaName, - ClientSecretRef.DEFAULT_USERNAME_KEY, schemaName - )) - ) + return getTargetState().getClientSecretRef(MONGODB_CLIENT_SECRET_REF_KIND, schemaName, + (clientSecret, password) -> getTargetState().loadOrBuildSecret(clientSecret, Map.of( + ClientSecretRef.DEFAULT_PASSWORD_KEY, password, + ClientSecretRef.DEFAULT_SCHEMA_KEY, schemaName, + ClientSecretRef.DEFAULT_URL_KEY, "mongodb://" + schemaName + ":" + password + "@" + serviceName + ":27017/" + schemaName, + ClientSecretRef.DEFAULT_USERNAME_KEY, schemaName + )) ); } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasUapiClient.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasUapiClient.java index 618c2ad..251eb0f 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasUapiClient.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/HasUapiClient.java @@ -12,10 +12,7 @@ import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.TargetState; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; -import io.fabric8.kubernetes.api.model.ObjectMeta; import java.util.List; import java.util.Map; @@ -68,14 +65,12 @@ default ClientSecretRef getUapiClientSecretRef(String schemaName) { if (!getTargetState().getCmcc().getSpec().getWith().getDatabases()) { throw new CustomResourceConfigError("No UAPI client secret reference found for " + schemaName + ", and with.databases is false"); } - return getTargetState().getClientSecretRef(UAPI_CLIENT_SECRET_REF_KIND, schemaName, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(secretName), - getTargetState().loadOrBuildSecret(secretName, Map.of( - ClientSecretRef.DEFAULT_PASSWORD_KEY, password, - ClientSecretRef.DEFAULT_SCHEMA_KEY, schemaName, - ClientSecretRef.DEFAULT_USERNAME_KEY, schemaName - )) - ) + return getTargetState().getClientSecretRef(UAPI_CLIENT_SECRET_REF_KIND, schemaName, + (clientSecret, password) -> getTargetState().loadOrBuildSecret(clientSecret, Map.of( + ClientSecretRef.DEFAULT_PASSWORD_KEY, password, + ClientSecretRef.DEFAULT_SCHEMA_KEY, schemaName, + ClientSecretRef.DEFAULT_USERNAME_KEY, schemaName + )) ); } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/corba/ContentServerComponent.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/corba/ContentServerComponent.java index bb96504..0233918 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/corba/ContentServerComponent.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/corba/ContentServerComponent.java @@ -12,11 +12,9 @@ import com.tsystemsmms.cmcc.cmccoperator.components.HasJdbcClient; import com.tsystemsmms.cmcc.cmccoperator.components.HasService; -import com.tsystemsmms.cmcc.cmccoperator.components.HasUapiClient; -import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.crds.ComponentSpec; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; +import com.tsystemsmms.cmcc.cmccoperator.targetstate.ClientSecret; import com.tsystemsmms.cmcc.cmccoperator.targetstate.TargetState; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; import io.fabric8.kubernetes.api.model.*; @@ -26,10 +24,7 @@ import java.util.*; -import static com.tsystemsmms.cmcc.cmccoperator.components.HasMongoDBClient.MONGODB_CLIENT_SECRET_REF_KIND; -import static com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef.DEFAULT_PASSWORD_KEY; -import static com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef.DEFAULT_USERNAME_KEY; -import static com.tsystemsmms.cmcc.cmccoperator.utils.Utils.format; +import static com.tsystemsmms.cmcc.cmccoperator.utils.Utils.EnvVarSecret; @Slf4j public class ContentServerComponent extends CorbaComponent implements HasJdbcClient, HasService { @@ -122,6 +117,18 @@ public EnvVarSet getEnvVars() { break; } + for (ClientSecret cs : getTargetState().getClientSecrets(UAPI_CLIENT_SECRET_REF_KIND).values()) { + Secret secret = cs.getSecret().orElseThrow(() -> new CustomResourceConfigError("Unable to find secret for clientSecretRef \"" + cs.getRef().getSecretName() + "\"")); + String username = secret.getStringData().get(cs.getRef().getUsernameKey()); + if (cs.getRef().getUsernameKey() == null || username == null) { + throw new CustomResourceConfigError("Secret \"" + secret.getMetadata().getName() + + "\" does not contain the field \"" + cs.getRef().getUsernameKey() + + "\" for the username, or it is null"); + } + env.add(EnvVarSecret("CAP_SERVER_INITIALPASSWORD_" + username.toUpperCase(Locale.ROOT), + cs.getRef().getSecretName(), cs.getRef().getPasswordKey())); + } + return env; } @@ -140,20 +147,6 @@ public Map getSpringBootProperties() { properties.put("replicator.publication-ior-url", getTargetState().getServiceUrlFor("content-server", "mls")); } - properties.putAll(getPasswordsAsProperties()); - - return properties; - } - - public Map getPasswordsAsProperties() { - Map secrets = getTargetState().getDefaultClientSecrets(UAPI_CLIENT_SECRET_REF_KIND); - Map properties = new HashMap<>(); - - for (DefaultClientSecret dcs : secrets.values()) { - Map data = dcs.getSecret().getStringData(); - properties.put("cap.server.initialPassword." + data.get(DEFAULT_USERNAME_KEY), - data.get(DEFAULT_PASSWORD_KEY)); - } return properties; } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MongoDBComponent.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MongoDBComponent.java index ebbf38d..08a21a0 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MongoDBComponent.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MongoDBComponent.java @@ -12,10 +12,9 @@ import com.tsystemsmms.cmcc.cmccoperator.components.AbstractComponent; import com.tsystemsmms.cmcc.cmccoperator.components.HasService; -import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.crds.ComponentSpec; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; +import com.tsystemsmms.cmcc.cmccoperator.targetstate.ClientSecret; import com.tsystemsmms.cmcc.cmccoperator.targetstate.TargetState; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; import io.fabric8.kubernetes.api.model.*; @@ -45,12 +44,11 @@ public MongoDBComponent(KubernetesClient kubernetesClient, TargetState targetSta @Override public void requestRequiredResources() { - String name = getTargetState().getSecretName(MONGODB_CLIENT_SECRET_REF_KIND, MONGODB_ROOT_USERNAME); - getTargetState().getClientSecretRef(MONGODB_CLIENT_SECRET_REF_KIND, MONGODB_ROOT_USERNAME, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(name), getTargetState().loadOrBuildSecret(name, Map.of( + getTargetState().getClientSecretRef(MONGODB_CLIENT_SECRET_REF_KIND, MONGODB_ROOT_USERNAME, + (clientSecret, password) -> getTargetState().loadOrBuildSecret(clientSecret, Map.of( DEFAULT_PASSWORD_KEY, password, DEFAULT_USERNAME_KEY, MONGODB_ROOT_USERNAME - ))) + )) ); } @@ -180,7 +178,7 @@ List buildExtraConfigMaps() { } public static Map createUsersFromClientSecrets(TargetState targetState) { - Map secrets = targetState.getDefaultClientSecrets(MONGODB_CLIENT_SECRET_REF_KIND); + Map secrets = targetState.getClientSecrets(MONGODB_CLIENT_SECRET_REF_KIND); if (secrets == null) { log.warn("No MongoDB users to be created"); @@ -188,18 +186,18 @@ public static Map createUsersFromClientSecrets(TargetState targe } StringBuilder createUsersJs = new StringBuilder(); - DefaultClientSecret root = secrets.get(MONGODB_ROOT_USERNAME); - if (root == null) + ClientSecret rootClientSecret = secrets.get(MONGODB_ROOT_USERNAME); + if (rootClientSecret == null) throw new CustomResourceConfigError("No secret available for MongoDB root user"); // log in as root - Map rootDetails = root.getSecret().getStringData(); + Map rootData = rootClientSecret.getStringData(); createUsersJs.append("db = db.getSiblingDB('admin');\n"); createUsersJs.append(format("db.auth('{}', '{}');\n", - rootDetails.get(DEFAULT_USERNAME_KEY), rootDetails.get(DEFAULT_PASSWORD_KEY))); + rootData.get(DEFAULT_USERNAME_KEY), rootData.get(DEFAULT_PASSWORD_KEY))); - for (DefaultClientSecret dcs : secrets.values()) { - Map data = dcs.getSecret().getStringData(); + for (ClientSecret cs : secrets.values()) { + Map data = cs.getStringData(); if (data.get(DEFAULT_USERNAME_KEY).equals(MONGODB_ROOT_USERNAME)) continue; // we would like to give client only rights to a specific database, but CM requires the right to create multiple databases (or somehow know which DBs will be created; the list is undocumented, however. diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MySQLComponent.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MySQLComponent.java index f88291e..11b276d 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MySQLComponent.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/components/generic/MySQLComponent.java @@ -15,7 +15,7 @@ import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; import com.tsystemsmms.cmcc.cmccoperator.crds.ComponentSpec; import com.tsystemsmms.cmcc.cmccoperator.targetstate.CustomResourceConfigError; -import com.tsystemsmms.cmcc.cmccoperator.targetstate.DefaultClientSecret; +import com.tsystemsmms.cmcc.cmccoperator.targetstate.ClientSecret; import com.tsystemsmms.cmcc.cmccoperator.targetstate.TargetState; import com.tsystemsmms.cmcc.cmccoperator.utils.EnvVarSet; import io.fabric8.kubernetes.api.model.*; @@ -44,12 +44,11 @@ public MySQLComponent(KubernetesClient kubernetesClient, TargetState targetState @Override public void requestRequiredResources() { - String name = getTargetState().getSecretName(getComponentSpec().getType(), MYSQL_ROOT_USERNAME); - getTargetState().getClientSecretRef(getComponentSpec().getType(), MYSQL_ROOT_USERNAME, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(name), getTargetState().loadOrBuildSecret(name, Map.of( + getTargetState().getClientSecretRef(getComponentSpec().getType(), MYSQL_ROOT_USERNAME, + (clientSecret, password) -> getTargetState().loadOrBuildSecret(clientSecret, Map.of( ClientSecretRef.DEFAULT_PASSWORD_KEY, password, ClientSecretRef.DEFAULT_USERNAME_KEY, MYSQL_ROOT_USERNAME - ))) + )) ); } @@ -181,15 +180,15 @@ List buildExtraConfigMaps() { } public static Map createUsersFromClientSecrets(TargetState targetState) { - Map secrets = targetState.getDefaultClientSecrets(JDBC_CLIENT_SECRET_REF_KIND); + Map secrets = targetState.getClientSecrets(JDBC_CLIENT_SECRET_REF_KIND); if (secrets == null) { throw new CustomResourceConfigError("No MySQL users to be created"); } StringBuilder sql = new StringBuilder(); - for (DefaultClientSecret dcs : secrets.values()) { - Map data = dcs.getSecret().getStringData(); + for (ClientSecret cs : secrets.values()) { + Map data = cs.getStringData(); if (data.get(ClientSecretRef.DEFAULT_USERNAME_KEY).equals(MYSQL_ROOT_USERNAME)) continue; sql.append(format("CREATE SCHEMA IF NOT EXISTS {} CHARACTER SET utf8mb4 COLLATE utf8mb4_bin;\n", data.get(ClientSecretRef.DEFAULT_SCHEMA_KEY))); diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/resource/JobReconciler.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/resource/JobReconciler.java index 0d9761e..9dbfa86 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/resource/JobReconciler.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/resource/JobReconciler.java @@ -25,10 +25,8 @@ public class JobReconciler implements Reconciler { @Override public void reconcile(KubernetesClient kubernetesClient, String namespace, HasMetadata resource) { Resource existing = kubernetesClient.batch().v1().jobs().inNamespace(namespace).withName(resource.getMetadata().getName()); - if (existing.get() != null) { -// log.debug("skipping {}/{}", resource.getKind(), resource.getMetadata().getName()); - } else { -// log.debug("reconciling {}/{}", resource.getKind(), resource.getMetadata().getName()); + if (existing.get() == null) { + log.debug("starting {}/{}", resource.getKind(), resource.getMetadata().getName()); kubernetesClient.resource(resource).inNamespace(namespace).createOrReplace(); } } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/AbstractTargetState.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/AbstractTargetState.java index 1790340..d1a80ee 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/AbstractTargetState.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/AbstractTargetState.java @@ -11,11 +11,10 @@ package com.tsystemsmms.cmcc.cmccoperator.targetstate; import com.tsystemsmms.cmcc.cmccoperator.CoreMediaContentCloudReconciler; -import com.tsystemsmms.cmcc.cmccoperator.components.*; +import com.tsystemsmms.cmcc.cmccoperator.components.Component; +import com.tsystemsmms.cmcc.cmccoperator.components.ComponentCollection; import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; -import com.tsystemsmms.cmcc.cmccoperator.crds.ComponentSpec; import com.tsystemsmms.cmcc.cmccoperator.crds.CoreMediaContentCloud; -import com.tsystemsmms.cmcc.cmccoperator.crds.Milestone; import com.tsystemsmms.cmcc.cmccoperator.ingress.CmccIngressGeneratorFactory; import com.tsystemsmms.cmcc.cmccoperator.utils.RandomString; import io.fabric8.kubernetes.api.model.*; @@ -31,7 +30,7 @@ import java.nio.charset.StandardCharsets; import java.util.*; -import java.util.function.Function; +import java.util.function.BiConsumer; import java.util.stream.Collectors; import static com.tsystemsmms.cmcc.cmccoperator.components.HasUapiClient.UAPI_ADMIN_USERNAME; @@ -44,7 +43,6 @@ public abstract class AbstractTargetState implements TargetState { private static final RandomString randomDatabasePassword = new RandomString(16); - @Getter final KubernetesClient kubernetesClient; @Getter @@ -56,8 +54,7 @@ public abstract class AbstractTargetState implements TargetState { @Getter final ResourceNamingProvider resourceNamingProvider; - final Map> clientSecretRefs = new HashMap<>(); - final Map> clientSecrets = new HashMap<>(); + final Map> clientSecrets = new HashMap<>(); public AbstractTargetState(BeanFactory beanFactory, KubernetesClient kubernetesClient, CmccIngressGeneratorFactory cmccIngressGeneratorFactory, ResourceNamingProviderFactory resourceNamingProviderFactory, CoreMediaContentCloud cmcc) { this.kubernetesClient = kubernetesClient; @@ -87,12 +84,12 @@ public void advanceToNextMilestoneOnComponentsReady() { } if (ready == active) { cmcc.getStatus().setMilestone(cmcc.getStatus().getMilestone().getNext()); - log.info("[{}] Advancing to milestone {}", getContextForLogging(), getCmcc().getStatus().getMilestone()); + log.info("[{}] Waiting for components, all {} ready: Advancing to milestone {}", getContextForLogging(), active, getCmcc().getStatus().getMilestone()); } else { log.info("[{}] Waiting for components, {} of {} ready: still waiting for {}", getContextForLogging(), ready, active, - stillWaiting.stream().collect(Collectors.joining(", "))); + String.join(", ", stillWaiting)); } } @@ -100,16 +97,12 @@ public void advanceToNextMilestoneOnComponentsReady() { public List buildResources() { LinkedList resources = new LinkedList<>(); int convergenceLoops = MAX_CONVERGENCE_LOOP; - Milestone previous = getCmcc().getStatus().getMilestone(); buildClientSecretRefs(); while (!converge() && convergenceLoops-- > 0) { log.debug("Not yet converged, {} more tries", convergenceLoops); } - if (getCmcc().getStatus().getMilestone() != previous) { - onMilestoneReached(); - } resources.addAll(buildComponentResources()); resources.addAll(buildExtraResources()); @@ -127,20 +120,21 @@ public void requestRequiredResources() { public void buildClientSecretRefs() { // add the declared clientSecretRefs to the list of all clientSecretRefs, both declared and managed for (Map.Entry> perKind : getCmcc().getSpec().getClientSecretRefs().entrySet()) { - Map managed = clientSecretRefs.computeIfAbsent(perKind.getKey(), k -> new HashMap<>()); - managed.putAll(perKind.getValue()); + Map secrets = clientSecrets.computeIfAbsent(perKind.getKey(), k -> new HashMap<>()); + for (Map.Entry e : perKind.getValue().entrySet()) { + secrets.put(e.getKey(), new ClientSecret(e.getValue())); + } } /* We always need an uapi entry for admin. The management-tools will also request this clientSecretRef, but that component is not always there, so we might remove the autogenerated secret again if we wouldn't request it here every time. */ - String name = getSecretName(UAPI_CLIENT_SECRET_REF_KIND, UAPI_ADMIN_USERNAME); - getClientSecretRef(UAPI_CLIENT_SECRET_REF_KIND, UAPI_ADMIN_USERNAME, password -> - new DefaultClientSecret(ClientSecretRef.defaultClientSecretRef(name), loadOrBuildSecret(name, Map.of( + getClientSecretRef(UAPI_CLIENT_SECRET_REF_KIND, UAPI_ADMIN_USERNAME, + (clientSecret, password) -> loadOrBuildSecret(clientSecret, Map.of( ClientSecretRef.DEFAULT_PASSWORD_KEY, password, ClientSecretRef.DEFAULT_USERNAME_KEY, UAPI_ADMIN_USERNAME - ))) + )) ); } @@ -173,8 +167,13 @@ public LinkedList buildComponentResources() { public LinkedList buildExtraResources() { final LinkedList resources = new LinkedList<>(); - for (Map.Entry> e : clientSecrets.entrySet()) { - resources.addAll(e.getValue().values().stream().map(DefaultClientSecret::getSecret).collect(Collectors.toList())); + for (Map.Entry> e : clientSecrets.entrySet()) { + for (ClientSecret clientSecret : e.getValue().values()) { + Secret secret = clientSecret.getSecret() + .orElseThrow(() -> new CustomResourceConfigError("Unable to find secret for clientSecretRef \"" + clientSecret.getRef().getSecretName() + "\"")); + if (isWeOwnThis(secret)) + resources.add(secret); + } } return resources; } @@ -203,37 +202,54 @@ public String getContextForLogging() { + "@" + getCmcc().getStatus().getMilestone()); } - @Override - public Map getDefaultClientSecrets(String kind) { - Map result = new HashMap<>(); + public ClientSecret getClientSecret(String kind, String schema) { + Map perKind = clientSecrets.get(kind); + if (perKind == null) { + throw new CustomResourceConfigError("No secrets requested for type \"" + kind + "\""); + } + ClientSecret clientSecret = perKind.get(schema); + if (clientSecret == null) { + throw new CustomResourceConfigError("No secret for schema \"" + schema + "\" requested for type \"" + kind + "\""); + } + return clientSecret; + } + @Override + public Map getClientSecrets(String kind) { if (clientSecrets.get(kind) == null) { throw new IllegalArgumentException("Unknown clientSecretRef type \"" + kind + "\""); } - for (Map.Entry e : clientSecrets.get(kind).entrySet()) { - Secret secret = loadSecret(e.getValue().getRef().getSecretName()); - result.put(e.getKey(), new DefaultClientSecret(e.getValue().getRef(), secret == null ? e.getValue().getSecret() : secret)); - } - return result; + // make sure secrets are loaded/created + clientSecrets.get(kind).values().stream() + .filter(cs -> cs.getSecret().isEmpty()) + .forEach(cs -> cs.setSecret(loadSecret(cs.getRef().getSecretName()))); + + return clientSecrets.get(kind); } @Override - public ClientSecretRef getClientSecretRef(String kind, String - schema, Function buildDefaultClientSecret) { - Map kindRefs = clientSecretRefs.get(kind); - if (kindRefs == null || kindRefs.get(schema) == null) { -// throw new IllegalArgumentException("Component needs a client secret for " + kind + "/" + schema + ", but none exists"); - if (kindRefs == null) { - kindRefs = new HashMap<>(); - clientSecretRefs.put(kind, kindRefs); - } - Map perKind = clientSecrets.computeIfAbsent(kind, k -> new HashMap<>()); - DefaultClientSecret defaultClientSecret = perKind.computeIfAbsent(schema, k -> buildDefaultClientSecret.apply(getClientPassword())); - kindRefs.put(schema, defaultClientSecret.getRef()); + public ClientSecretRef getClientSecretRef(String kind, String schema, BiConsumer buildOrLoadSecret) { + Map perKind = clientSecrets.computeIfAbsent(kind, k -> new HashMap<>()); + ClientSecret clientSecret = perKind.get(schema); + + // the individual HasFooClient interfaces are responsible for checking if generating a secret is desirable + if (clientSecret == null) { + clientSecret = new ClientSecret(ClientSecretRef.defaultClientSecretRef(getSecretName(kind, schema))); + perKind.put(schema, clientSecret); + } + buildOrLoadSecret.accept(clientSecret, getClientPassword()); + return clientSecret.getRef(); + } + + @Override + public Collection getClientSecretRefs(String kind) { + Map kindRefs = clientSecrets.get(kind); + if (kindRefs == null) { + throw new IllegalArgumentException("No client secret refs have been requested for kind \"" + kind + "\""); } - return kindRefs.get(schema); + return kindRefs.values().stream().map(ClientSecret::getRef).collect(Collectors.toList()); } @@ -300,51 +316,6 @@ public ObjectMeta getResourceMetadataFor(Component component, String... addition } - public void scaleComponent(String name) { - RollableScalableResource sts = kubernetesClient.apps().statefulSets(). - inNamespace(cmcc.getMetadata().getNamespace()) - .withName(name); - if (sts == null) { - throw new IllegalArgumentException("No such StatefulSet " + name); - } - sts.edit(r -> new StatefulSetBuilder(r).editOrNewSpec().withReplicas(0).endSpec().build()); - } - - - public List getStatefulSetFor(HasService component) { - return kubernetesClient.apps().statefulSets(). - inNamespace(cmcc.getMetadata().getNamespace()) - .withLabels(component.getSelectorLabels()) - .list().getItems(); - } - - /** - * Checks if a StatefulSet is ready. - * - * @param cs component to check - * @return true if the stateful set has as many ready pods as specified - */ - public boolean isComponentStatefulSetReady(ComponentSpec cs) { - HasService component = getComponentCollection().getHasServiceComponent(cs); - return isStatefulSetReady(getStatefulSetFor(component), getServiceNameFor(component)); - } - - public boolean isStatefulSetReady(List statefulSets, String label) { - if (statefulSets.size() == 0) { - log.debug("Can't find StatefulSet " + label); - return false; - } - if (statefulSets.size() > 1) { - log.debug("Found more than one StatefulSet " + label); - return false; - } - StatefulSetStatus status = statefulSets.get(0).getStatus(); - log.debug("sts {}: replicas {} / available {}", statefulSets.get(0).getMetadata().getName(), status.getReplicas(), status.getReadyReplicas()); - if (status.getReplicas() == null || status.getReadyReplicas() == null) - return false; - return status.getReplicas() > 0 && status.getReadyReplicas().equals(status.getReplicas()); - } - @Override public boolean isJobReady(String name) { Job job = kubernetesClient.batch().v1().jobs().inNamespace(cmcc.getMetadata().getNamespace()).withName(name).get(); @@ -368,6 +339,21 @@ public boolean isStatefulSetReady(String name) { return status.getReplicas() > 0 && status.getReadyReplicas().equals(status.getReplicas()); } + /** + * Returns true if this resource is owned by the operator. + * + * @param resource to check + * @return true if we own this + */ + public boolean isWeOwnThis(HasMetadata resource) { + OwnerReference us = getOurOwnerReference(); + for (OwnerReference them : resource.getMetadata().getOwnerReferences()) { + if (us.equals(them)) + return true; + } + return false; + } + /** * Load a secret from the cluster. The stringData map will be populated. * @@ -392,5 +378,20 @@ public Secret loadSecret(String name) { public void onMilestoneReached() { } - ; + /** + * Restart a component by scaling its StatefulSet to 0. The next control loop will re-set the replicas to 1 again. + * + * @param name name of the StatefulSet + */ + public void restartStatefulSet(String name) { + RollableScalableResource sts = kubernetesClient.apps().statefulSets(). + inNamespace(cmcc.getMetadata().getNamespace()) + .withName(name); + if (sts == null) { + throw new IllegalArgumentException("No such StatefulSet " + name); + } + sts.edit(r -> new StatefulSetBuilder(r).editOrNewSpec().withReplicas(0).endSpec().build()); + } + + } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/ClientSecret.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/ClientSecret.java new file mode 100644 index 0000000..aaa5e85 --- /dev/null +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/ClientSecret.java @@ -0,0 +1,61 @@ +/* + * Copyright (c) 2022. T-Systems Multimedia Solutions GmbH + * + * 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.tsystemsmms.cmcc.cmccoperator.targetstate; + +import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; +import io.fabric8.kubernetes.api.model.Secret; +import lombok.Getter; +import lombok.Setter; + +import java.util.Map; +import java.util.Optional; + +/** + * Holds a secret reference and a secret. The optional secret is filled when either the operator is generating the + * secret (and thus the Secret resource has to be built), or the operator needs to access one of the keys in the + * secret directly. + */ +public class ClientSecret { + @Getter + @Setter + private ClientSecretRef ref; + private Secret secret; + + public ClientSecret(ClientSecretRef ref) { + this.ref = ref; + this.secret = null; + } + + public ClientSecret(ClientSecretRef ref, Secret secret) { + this.ref = ref; + this.secret = secret; + } + + public Optional getSecret() { + return Optional.ofNullable(secret); + } + + public void setSecret(Secret secret) { + this.secret = secret; + } + + /** + * Returns the entries of the secret. + * + * @return the base64-decoded entries of the secret + */ + public Map getStringData() { + return getSecret() + .orElseThrow(() -> new CustomResourceConfigError("Unable to find secret for clientSecretRef \"" + getRef().getSecretName() + "\"")) + .getStringData(); + } + +} diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultClientSecret.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultClientSecret.java deleted file mode 100644 index 96eda3c..0000000 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultClientSecret.java +++ /dev/null @@ -1,24 +0,0 @@ -/* - * Copyright (c) 2022. T-Systems Multimedia Solutions GmbH - * - * 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.tsystemsmms.cmcc.cmccoperator.targetstate; - -import com.tsystemsmms.cmcc.cmccoperator.crds.ClientSecretRef; -import io.fabric8.kubernetes.api.model.Secret; -import lombok.Data; - -/** - * Hold a secret reference and a secret when the operator is creating a default secret for a network client. - */ -@Data -public class DefaultClientSecret { - final ClientSecretRef ref; - final Secret secret; -} diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultTargetState.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultTargetState.java index 8ed0b29..b041c03 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultTargetState.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/DefaultTargetState.java @@ -13,17 +13,13 @@ import com.tsystemsmms.cmcc.cmccoperator.components.ComponentSpecBuilder; import com.tsystemsmms.cmcc.cmccoperator.components.generic.MongoDBComponent; import com.tsystemsmms.cmcc.cmccoperator.components.generic.MySQLComponent; -import com.tsystemsmms.cmcc.cmccoperator.crds.ComponentSpec; import com.tsystemsmms.cmcc.cmccoperator.crds.CoreMediaContentCloud; import com.tsystemsmms.cmcc.cmccoperator.crds.Milestone; import com.tsystemsmms.cmcc.cmccoperator.ingress.CmccIngressGeneratorFactory; -import io.fabric8.kubernetes.api.model.batch.v1.Job; import io.fabric8.kubernetes.client.KubernetesClient; import lombok.extern.slf4j.Slf4j; import org.springframework.beans.factory.BeanFactory; -import java.util.Collection; -import java.util.LinkedList; import java.util.List; import static com.tsystemsmms.cmcc.cmccoperator.utils.Utils.concatOptional; @@ -110,28 +106,20 @@ public boolean converge() { // move to the next milestone when everything is ready advanceToNextMilestoneOnComponentsReady(); - return previousMilestone == getCmcc().getStatus().getMilestone(); + if (!getCmcc().getStatus().getMilestone().equals(previousMilestone)) { + onMilestoneReached(); + } + + return previousMilestone.equals(getCmcc().getStatus().getMilestone()); } @Override public void onMilestoneReached() { super.onMilestoneReached(); - if (cmcc.getStatus().getMilestone() == Milestone.ContentServerReady) { + if (cmcc.getStatus().getMilestone().equals(Milestone.ContentServerReady)) { log.info("[{}] Restarting CMS and MLS", getContextForLogging()); - scaleComponent(componentCollection.getServiceNameFor("content-server", "cms")); - scaleComponent(componentCollection.getServiceNameFor("content-server", "mls")); - } - } - - private boolean isReady(String jobName) { - jobName = concatOptional(cmcc.getSpec().getDefaults().getNamePrefix(), jobName); - Job job = kubernetesClient.batch().v1().jobs().inNamespace(cmcc.getMetadata().getNamespace()).withName(jobName).get(); - boolean ready = job != null && job.getStatus() != null && job.getStatus().getSucceeded() != null && job.getStatus().getSucceeded() > 0; - if (ready) { - log.debug("job {}: has succeeded", jobName); - } else { - log.debug("job {}: waiting for successful completion", jobName); + restartStatefulSet(componentCollection.getServiceNameFor("content-server", "cms")); + restartStatefulSet(componentCollection.getServiceNameFor("content-server", "mls")); } - return ready; } } diff --git a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/TargetState.java b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/TargetState.java index fdbd220..b6d53c5 100644 --- a/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/TargetState.java +++ b/src/main/java/com/tsystemsmms/cmcc/cmccoperator/targetstate/TargetState.java @@ -18,8 +18,11 @@ import com.tsystemsmms.cmcc.cmccoperator.ingress.CmccIngressGeneratorFactory; import io.fabric8.kubernetes.api.model.*; +import java.util.Collection; import java.util.List; import java.util.Map; +import java.util.Optional; +import java.util.function.BiConsumer; import java.util.function.Function; import static com.tsystemsmms.cmcc.cmccoperator.utils.Utils.concatOptional; @@ -39,27 +42,26 @@ public interface TargetState { List buildResources(); /** - * Build a secret. + * Fill in the secret in the ClientSecret. Either load the secret from the cluster, or generate a secret from the + * given maps. * - * @param name of the secret + * @param cs the secret reference * @param entries for the secret, will be base64 encoded - * @return the secret */ - default Secret loadOrBuildSecret(String name, Map entries) { - Secret secret = loadSecret(name); - if (secret != null) { - return secret; - } - return new SecretBuilder() - .withMetadata(getResourceMetadataFor(name)) + default void loadOrBuildSecret(ClientSecret cs, Map entries) { + Secret secret = loadSecret(cs.getRef().getSecretName()); + cs.setSecret(secret != null ? secret : new SecretBuilder() + .withMetadata(getResourceMetadataFor(cs.getRef().getSecretName())) .withType("Opaque") .withStringData(entries) - .build(); + .build()); } /** - * @param name - * @return + * Load a secret from the cluster. + * + * @param name resource + * @return secret or null */ Secret loadSecret(String name); @@ -79,12 +81,29 @@ default Secret loadOrBuildSecret(String name, Map entries) { * object. For example, when configuring with.databases, the necessary secrets are created, and in turn, * the database accounts created. * - * @param kind kind of network client, for example "jdbc", "mongodb", or "uapi". - * @param schema schema or account name. Must be unique among all entries of a kind. - * @param buildDefaultSecret method that creates a suitable ClientSecret if there is no ClientSecretRef + * @param kind kind of network client, for example "jdbc", "mongodb", or "uapi". + * @param schema schema or account name. Must be unique among all entries of a kind. + * @param buildOrLoadSecret method that creates a suitable ClientSecret if there is no ClientSecretRef * @return the secret reference */ - ClientSecretRef getClientSecretRef(String kind, String schema, Function buildDefaultSecret); + ClientSecretRef getClientSecretRef(String kind, String schema, BiConsumer buildOrLoadSecret); + + /** + * Returns all requested client secret refs of a kind. + * + * @param kind tpye of client + * @return collection of client secret refs + */ + Collection getClientSecretRefs(String kind); + + /** + * Returns a client secrets for the given kind amd schema. + * + * @param kind kind of service + * @param schema the schema/user + * @return default client secrets + */ + ClientSecret getClientSecret(String kind, String schema); /** * Returns all default client secrets for the given kind. Can be used to create users in a server. @@ -92,7 +111,7 @@ default Secret loadOrBuildSecret(String name, Map entries) { * @param kind kind of service * @return default client secrets */ - Map getDefaultClientSecrets(String kind); + Map getClientSecrets(String kind); /** * Return the CoreMediaContentCloud custom resource this target state is working on. diff --git a/src/main/resources/log4j2-spring.xml b/src/main/resources/log4j2-spring.xml index fae3587..325fc78 100644 --- a/src/main/resources/log4j2-spring.xml +++ b/src/main/resources/log4j2-spring.xml @@ -22,6 +22,6 @@ - + \ No newline at end of file