| /* |
| * Licensed to the Apache Software Foundation (ASF) under one |
| * or more contributor license agreements. See the NOTICE file |
| * distributed with this work for additional information |
| * regarding copyright ownership. The ASF licenses this file |
| * to you 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 org.apache.brooklyn.container.location.openshift; |
| |
| import java.net.InetAddress; |
| import java.util.Collections; |
| import java.util.List; |
| import java.util.Map; |
| |
| import io.fabric8.kubernetes.api.model.StatusDetails; |
| import org.apache.brooklyn.api.entity.Entity; |
| import org.apache.brooklyn.api.location.LocationSpec; |
| import org.apache.brooklyn.container.entity.openshift.OpenShiftPod; |
| import org.apache.brooklyn.container.entity.openshift.OpenShiftResource; |
| import org.apache.brooklyn.container.location.kubernetes.KubernetesClientRegistry; |
| import org.apache.brooklyn.container.location.kubernetes.KubernetesLocation; |
| import org.apache.brooklyn.container.location.kubernetes.machine.KubernetesMachineLocation; |
| import org.apache.brooklyn.location.ssh.SshMachineLocation; |
| import org.apache.brooklyn.util.core.config.ConfigBag; |
| import org.apache.brooklyn.util.core.config.ResolvingConfigBag; |
| import org.apache.brooklyn.util.net.Networking; |
| import org.slf4j.Logger; |
| import org.slf4j.LoggerFactory; |
| |
| import com.google.common.collect.ImmutableSet; |
| |
| import io.fabric8.kubernetes.api.model.Container; |
| import io.fabric8.kubernetes.api.model.HasMetadata; |
| import io.fabric8.kubernetes.api.model.Namespace; |
| import io.fabric8.kubernetes.api.model.Pod; |
| import io.fabric8.kubernetes.api.model.PodTemplateSpec; |
| import io.fabric8.kubernetes.api.model.PodTemplateSpecBuilder; |
| import io.fabric8.kubernetes.client.KubernetesClient; |
| import io.fabric8.kubernetes.client.KubernetesClientException; |
| import io.fabric8.openshift.api.model.DeploymentConfig; |
| import io.fabric8.openshift.api.model.DeploymentConfigBuilder; |
| import io.fabric8.openshift.api.model.DeploymentConfigStatus; |
| import io.fabric8.openshift.api.model.Project; |
| import io.fabric8.openshift.api.model.ProjectBuilder; |
| import io.fabric8.openshift.client.OpenShiftClient; |
| |
| public class OpenShiftLocation extends KubernetesLocation implements OpenShiftLocationConfig { |
| |
| public static final String OPENSHIFT_GENERATED_BY = "openshift.io/generated-by"; |
| private static final Logger LOG = LoggerFactory.getLogger(OpenShiftLocation.class); |
| private OpenShiftClient client; |
| |
| public OpenShiftLocation() { |
| super(); |
| } |
| |
| public OpenShiftLocation(Map<?, ?> properties) { |
| super(properties); |
| } |
| |
| @Override |
| public KubernetesClient getClient(ConfigBag config) { |
| if (client == null) { |
| KubernetesClientRegistry registry = getConfig(OPENSHIFT_CLIENT_REGISTRY); |
| client = (OpenShiftClient) registry.getKubernetesClient(ResolvingConfigBag.newInstanceExtending(getManagementContext(), config)); |
| } |
| return client; |
| } |
| |
| @Override |
| protected List<StatusDetails> handleResourceDelete(String resourceType, String resourceName, String namespace) { |
| List<StatusDetails> result = super.handleResourceDelete(resourceType, resourceName, namespace); |
| if (!result.isEmpty()) return result; |
| |
| try { |
| switch (resourceType) { |
| case OpenShiftResource.DEPLOYMENT_CONFIG: |
| return client.deploymentConfigs().inNamespace(namespace).withName(resourceName).delete(); |
| case OpenShiftResource.PROJECT: |
| return client.projects().withName(resourceName).delete(); |
| case OpenShiftResource.TEMPLATE: |
| return client.templates().inNamespace(namespace).withName(resourceName).delete(); |
| case OpenShiftResource.BUILD_CONFIG: |
| return client.buildConfigs().inNamespace(namespace).withName(resourceName).delete(); |
| } |
| } catch (KubernetesClientException kce) { |
| LOG.warn("Error deleting resource {}: {}", resourceName, kce); |
| } |
| return Collections.emptyList(); |
| } |
| |
| @Override |
| protected boolean findResourceAddress(LocationSpec<? extends KubernetesMachineLocation> locationSpec, Entity entity, HasMetadata metadata, String resourceType, String resourceName, String namespace) { |
| if (super.findResourceAddress(locationSpec, entity, metadata, resourceType, resourceName, namespace)) { |
| return true; |
| } |
| |
| if (resourceType.equals(OpenShiftResource.DEPLOYMENT_CONFIG)) { |
| DeploymentConfig deploymentConfig = (DeploymentConfig) metadata; |
| Map<String, String> labels = deploymentConfig.getSpec().getTemplate().getMetadata().getLabels(); |
| Pod pod = getPod(namespace, labels); |
| entity.sensors().set(OpenShiftPod.KUBERNETES_POD, pod.getMetadata().getName()); |
| |
| InetAddress node = Networking.getInetAddressWithFixedName(pod.getSpec().getNodeName()); |
| String podAddress = pod.getStatus().getPodIP(); |
| |
| locationSpec.configure("address", node); |
| locationSpec.configure(SshMachineLocation.PRIVATE_ADDRESSES, ImmutableSet.of(podAddress)); |
| |
| return true; |
| } else { |
| return false; |
| } |
| } |
| |
| @Override |
| protected synchronized Namespace createOrGetNamespace(final String name, Boolean create) { |
| Project project = client.projects().withName(name).get(); |
| ExitCondition projectReady = new ExitCondition() { |
| @Override |
| public Boolean call() { |
| Project actualProject = client.projects().withName(name).get(); |
| return actualProject != null && actualProject.getStatus().getPhase().equals(PHASE_ACTIVE); |
| } |
| |
| @Override |
| public String getFailureMessage() { |
| Project actualProject = client.projects().withName(name).get(); |
| return "Project for " + name + " " + (actualProject == null ? "absent" : " status " + actualProject.getStatus()); |
| } |
| }; |
| if (project != null) { |
| LOG.debug("Found project {}, returning it.", project); |
| } else if (create) { |
| project = client.projects().create(new ProjectBuilder().withNewMetadata().withName(name).endMetadata().build()); |
| LOG.debug("Created project {}.", project); |
| } else { |
| throw new IllegalStateException("Project " + name + " does not exist and namespace.create is not set"); |
| } |
| waitForExitCondition(projectReady); |
| return client.namespaces().withName(name).get(); |
| } |
| |
| @Override |
| protected synchronized void deleteEmptyNamespace(final String name) { |
| if (!name.equals("default") && isNamespaceEmpty(name)) { |
| if (client.projects().withName(name).get() != null && |
| !client.projects().withName(name).get().getStatus().getPhase().equals(PHASE_TERMINATING)) { |
| client.projects().withName(name).delete(); |
| ExitCondition exitCondition = new ExitCondition() { |
| @Override |
| public Boolean call() { |
| return client.projects().withName(name).get() == null; |
| } |
| |
| @Override |
| public String getFailureMessage() { |
| return "Project " + name + " still present"; |
| } |
| }; |
| waitForExitCondition(exitCondition); |
| } |
| } |
| } |
| |
| @Override |
| protected boolean isNamespaceEmpty(String namespace) { |
| return client.deploymentConfigs().inNamespace(namespace).list().getItems().isEmpty() && |
| client.services().inNamespace(namespace).list().getItems().isEmpty() && |
| client.secrets().inNamespace(namespace).list().getItems().isEmpty(); |
| } |
| |
| @Override |
| protected void deploy(final String namespace, Entity entity, Map<String, String> metadata, final String deploymentName, Container container, final Integer replicas, Map<String, String> secrets) { |
| PodTemplateSpecBuilder podTemplateSpecBuilder = new PodTemplateSpecBuilder() |
| .withNewMetadata() |
| .addToLabels("name", deploymentName) |
| .addToLabels(metadata) |
| .endMetadata() |
| .withNewSpec() |
| .addToContainers(container) |
| .endSpec(); |
| if (secrets != null) { |
| for (String secretName : secrets.keySet()) { |
| podTemplateSpecBuilder.withNewSpec() |
| .addToContainers(container) |
| .addNewImagePullSecret(secretName) |
| .endSpec(); |
| } |
| } |
| PodTemplateSpec template = podTemplateSpecBuilder.build(); |
| DeploymentConfig deployment = new DeploymentConfigBuilder() |
| .withNewMetadata() |
| .withName(deploymentName) |
| .addToAnnotations(OPENSHIFT_GENERATED_BY, "Apache Brooklyn") |
| .addToAnnotations(BROOKLYN_ENTITY_ID, entity.getId()) |
| .addToAnnotations(BROOKLYN_APPLICATION_ID, entity.getApplicationId()) |
| .endMetadata() |
| .withNewSpec() |
| .withNewStrategy() |
| .withType("Recreate") |
| .endStrategy() |
| .addNewTrigger() |
| .withType("ConfigChange") |
| .endTrigger() |
| .withReplicas(replicas) |
| .addToSelector("name", deploymentName) |
| .withTemplate(template) |
| .endSpec() |
| .build(); |
| client.deploymentConfigs().inNamespace(namespace).create(deployment); |
| ExitCondition exitCondition = new ExitCondition() { |
| @Override |
| public Boolean call() { |
| DeploymentConfig dc = client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get(); |
| DeploymentConfigStatus status = (dc == null) ? null : dc.getStatus(); |
| Integer replicas = (status == null) ? null : status.getAvailableReplicas(); |
| return replicas != null && replicas.intValue() == replicas; |
| } |
| |
| @Override |
| public String getFailureMessage() { |
| DeploymentConfig dc = client.deploymentConfigs().inNamespace(namespace).withName(deploymentName).get(); |
| DeploymentConfigStatus status = (dc == null) ? null : dc.getStatus(); |
| return "Namespace=" + namespace + "; deploymentName= " + deploymentName + "; Deployment=" + dc + "; status=" + status; |
| } |
| }; |
| waitForExitCondition(exitCondition); |
| LOG.debug("Deployed {} to namespace {}.", deployment, namespace); |
| } |
| |
| @Override |
| protected String getContainerResourceType() { |
| return OpenShiftResource.DEPLOYMENT_CONFIG; |
| } |
| |
| @Override |
| protected void undeploy(final String namespace, final String deployment) { |
| client.deploymentConfigs().inNamespace(namespace).withName(deployment).delete(); |
| ExitCondition exitCondition = new ExitCondition() { |
| @Override |
| public Boolean call() { |
| return client.deploymentConfigs().inNamespace(namespace).withName(deployment).get() == null; |
| } |
| |
| @Override |
| public String getFailureMessage() { |
| return "No deployment with namespace=" + namespace + ", deployment=" + deployment; |
| } |
| }; |
| waitForExitCondition(exitCondition); |
| } |
| |
| } |