package com.indeed.operators.rabbitmq.reconciliation.rabbitmq; import com.indeed.operators.rabbitmq.controller.SecretsController; import com.indeed.operators.rabbitmq.model.Labels; import com.indeed.operators.rabbitmq.model.crd.rabbitmq.RabbitMQCustomResource; import com.indeed.operators.rabbitmq.model.crd.rabbitmq.RabbitMQCustomResourceSpec; import com.indeed.operators.rabbitmq.model.crd.rabbitmq.RabbitMQStorageResources; import com.indeed.operators.rabbitmq.model.rabbitmq.RabbitMQCluster; import com.indeed.operators.rabbitmq.model.rabbitmq.RabbitMQUser; import com.indeed.operators.rabbitmq.reconciliation.RabbitClusterConfigurationException; import com.indeed.operators.rabbitmq.reconciliation.validators.RabbitClusterValidator; import com.indeed.operators.rabbitmq.resources.RabbitMQContainers; import com.indeed.operators.rabbitmq.resources.RabbitMQPods; import com.indeed.operators.rabbitmq.resources.RabbitMQSecrets; import com.indeed.operators.rabbitmq.resources.RabbitMQServices; import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.IntOrString; import io.fabric8.kubernetes.api.model.OwnerReference; import io.fabric8.kubernetes.api.model.Secret; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.apps.StatefulSet; import io.fabric8.kubernetes.api.model.apps.StatefulSetBuilder; import io.fabric8.kubernetes.api.model.policy.PodDisruptionBudget; import io.fabric8.kubernetes.api.model.policy.PodDisruptionBudgetBuilder; import java.util.Collections; import java.util.List; import java.util.Optional; import java.util.stream.Collectors; import static com.indeed.operators.rabbitmq.Constants.RABBITMQ_STORAGE_NAME; public class RabbitMQClusterFactory { private final List<RabbitClusterValidator> clusterValidators; private final RabbitMQContainers rabbitMQContainers; private final RabbitMQPods rabbitMQPods; private final RabbitMQSecrets rabbitMQSecrets; private final RabbitMQServices rabbitMQServices; private final SecretsController secretsController; public RabbitMQClusterFactory( final List<RabbitClusterValidator> clusterValidators, final RabbitMQContainers rabbitMQContainers, final RabbitMQPods rabbitMQPods, final RabbitMQSecrets rabbitMQSecrets, final RabbitMQServices rabbitMQServices, final SecretsController secretsController ) { this.clusterValidators = clusterValidators; this.rabbitMQContainers = rabbitMQContainers; this.rabbitMQPods = rabbitMQPods; this.rabbitMQSecrets = rabbitMQSecrets; this.rabbitMQServices = rabbitMQServices; this.secretsController = secretsController; } public RabbitMQCluster fromCustomResource(final RabbitMQCustomResource resource) throws RabbitClusterConfigurationException { final String clusterName = resource.getName(); final String namespace = resource.getMetadata().getNamespace(); final RabbitMQCustomResourceSpec spec = resource.getSpec(); final List<String> errors = clusterValidators.stream() .map(validator -> validator.validate(spec.getClusterSpec())) .flatMap(List::stream) .collect(Collectors.toList()); if (!errors.isEmpty()) { throw new RabbitClusterConfigurationException(errors); } final Secret adminSecret = getOrGenerateAdminSecret(resource); final Secret erlangCookieSecret = getOrGenerateErlangSecret(resource); final Service mainService = rabbitMQServices.buildService(namespace, resource); final Service discoveryService = rabbitMQServices.buildDiscoveryService(namespace, resource); final Optional<Service> loadBalancerService; if (spec.isCreateLoadBalancer()) { loadBalancerService = Optional.of(rabbitMQServices.buildLoadBalancerService(namespace, resource)); } else { loadBalancerService = Optional.empty(); } final Optional<Service> nodePortService; if (spec.isCreateNodePort()) { nodePortService = Optional.of(rabbitMQServices.buildNodePortService(namespace, resource)); } else { nodePortService = Optional.empty(); } final Container container = rabbitMQContainers.buildContainer( namespace, clusterName, spec.getRabbitMQImage(), spec.getComputeResources(), spec.getClusterSpec().getHighWatermarkFraction()); final StatefulSet statefulSet = buildStatefulSet(resource, container); final PodDisruptionBudget podDisruptionBudget = buildPodDisruptionBudget(resource); final List<RabbitMQUser> users = buildUsers(resource); return RabbitMQCluster.newBuilder() .withName(clusterName) .withNamespace(namespace) .withAdminSecret(adminSecret) .withErlangCookieSecret(erlangCookieSecret) .withMainService(mainService) .withDiscoveryService(discoveryService) .withLoadBalancerService(loadBalancerService) .withNodePortService(nodePortService) .withStatefulSet(statefulSet) .withPodDisruptionBudget(podDisruptionBudget) .withShovels(resource.getSpec().getClusterSpec().getShovels()) .withUsers(users) .withPolicies(spec.getClusterSpec().getPolicies()) .withOperatorPolicies(spec.getClusterSpec().getOperatorPolicies()) .build(); } private Secret getOrGenerateErlangSecret(final RabbitMQCustomResource resource) { final Secret existingErlangSecret = secretsController.get(RabbitMQSecrets.getErlangCookieSecretName(resource.getName()), resource.getMetadata().getNamespace()); return existingErlangSecret != null ? existingErlangSecret : rabbitMQSecrets.createErlangCookieSecret(resource); } private Secret getOrGenerateAdminSecret(final RabbitMQCustomResource resource) { final Secret existingAdminSecret = secretsController.get(RabbitMQSecrets.getClusterSecretName(resource.getName()), resource.getMetadata().getNamespace()); return existingAdminSecret != null ? existingAdminSecret : rabbitMQSecrets.createClusterSecret(resource); } private StatefulSet buildStatefulSet( final RabbitMQCustomResource resource, final Container container ) { final String clusterName = resource.getName(); final String namespace = resource.getMetadata().getNamespace(); final RabbitMQStorageResources storage = resource.getSpec().getStorageResources(); return new StatefulSetBuilder() .withNewMetadata() .withName(clusterName) .withNamespace(namespace) .withOwnerReferences( new OwnerReference( resource.getApiVersion(), true, true, resource.getKind(), clusterName, resource.getMetadata().getUid() ) ) .addToLabels(Labels.Kubernetes.INSTANCE, clusterName) .addToLabels(Labels.Kubernetes.MANAGED_BY, Labels.Values.RABBITMQ_OPERATOR) .addToLabels(Labels.Kubernetes.PART_OF, Labels.Values.RABBITMQ) .endMetadata() .withNewSpec() .withReplicas(resource.getSpec().getReplicas()) .withServiceName(RabbitMQServices.getDiscoveryServiceName(clusterName)) .withNewSelector().addToMatchLabels(Labels.Kubernetes.INSTANCE, clusterName).endSelector() .withNewTemplate() .withNewMetadata() .addToLabels(Labels.Kubernetes.INSTANCE, clusterName) .addToLabels(Labels.Kubernetes.MANAGED_BY, Labels.Values.RABBITMQ_OPERATOR) .addToLabels(Labels.Kubernetes.PART_OF, Labels.Values.RABBITMQ) .addToLabels(Labels.Indeed.getIndeedLabels(resource)) .addToAnnotations("ad.datadoghq.com/" + clusterName + ".check_names", "[\"rabbitmq\"]") .addToAnnotations("ad.datadoghq.com/" + clusterName + ".init_configs", "[{}]") .addToAnnotations("ad.datadoghq.com/" + clusterName + ".instances", "[{\"rabbitmq_api_url\":\"http://%%host%%:15672/api\",\"rabbitmq_user\":\"monitoring\",\"rabbitmq_pass\":\"monitoring\"}]") .endMetadata() .withSpec(rabbitMQPods.buildPodSpec(clusterName, resource.getSpec().getInitContainerImage(), container)) .endTemplate() .addNewVolumeClaimTemplate() .withNewMetadata() .withName(RABBITMQ_STORAGE_NAME) .withOwnerReferences( new OwnerReference( resource.getApiVersion(), true, true, resource.getKind(), clusterName, resource.getMetadata().getUid() ) ) .addToLabels(Labels.Kubernetes.INSTANCE, clusterName) .addToLabels(Labels.Kubernetes.MANAGED_BY, Labels.Values.RABBITMQ_OPERATOR) .addToLabels(Labels.Kubernetes.PART_OF, Labels.Values.RABBITMQ) .endMetadata() .withNewSpec() .withStorageClassName(storage.getStorageClassName()) .withAccessModes("ReadWriteOnce") .withNewResources() .withRequests(Collections.singletonMap("storage", storage.getStorage())) .endResources() .endSpec() .endVolumeClaimTemplate() .endSpec() .build(); } private PodDisruptionBudget buildPodDisruptionBudget(final RabbitMQCustomResource resource) { final String namespace = resource.getMetadata().getNamespace(); return new PodDisruptionBudgetBuilder() .withNewMetadata() .withName(String.format("%s-poddisruptionbudget", resource.getName())) .withNamespace(namespace) .withOwnerReferences( new OwnerReference( resource.getApiVersion(), true, true, resource.getKind(), resource.getName(), resource.getMetadata().getUid() ) ) .endMetadata() .withNewSpec() .withMaxUnavailable(new IntOrString(1)) .withNewSelector() .withMatchLabels(Collections.singletonMap(Labels.Kubernetes.INSTANCE, resource.getName())) .endSelector() .endSpec() .build(); } private List<RabbitMQUser> buildUsers(final RabbitMQCustomResource resource) { return resource.getSpec().getClusterSpec().getUsers().stream() .map(user -> { final Secret maybeExistingUserSecret = secretsController.get(RabbitMQSecrets.getUserSecretName(user.getUsername(), resource.getName()), resource.getMetadata().getNamespace()); return new RabbitMQUser( user.getUsername(), maybeExistingUserSecret != null ? maybeExistingUserSecret : rabbitMQSecrets.createUserSecret(user.getUsername(), resource), resource.getMetadata(), new OwnerReference( resource.getApiVersion(), true, true, resource.getKind(), resource.getName(), resource.getMetadata().getUid() ), user.getVhosts(), user.getTags() ); }) .collect(Collectors.toList()); } }