package org.arquillian.cube.kubernetes.impl; import io.fabric8.kubernetes.api.model.v2_2.Endpoints; import io.fabric8.kubernetes.api.model.v2_2.HasMetadata; import io.fabric8.kubernetes.api.model.v2_2.Pod; import io.fabric8.kubernetes.api.model.v2_2.PodList; import io.fabric8.kubernetes.api.model.v2_2.ReplicationController; import io.fabric8.kubernetes.api.model.v2_2.ReplicationControllerList; import io.fabric8.kubernetes.api.model.v2_2.Service; import io.fabric8.kubernetes.api.model.v2_2.ServiceList; import io.fabric8.kubernetes.api.model.v2_2.ServicePort; import io.fabric8.kubernetes.api.model.v2_2.extensions.ReplicaSet; import io.fabric8.kubernetes.api.model.v2_2.extensions.ReplicaSetList; import io.fabric8.kubernetes.clnt.v2_2.KubernetesClient; import io.fabric8.kubernetes.clnt.v2_2.KubernetesClientTimeoutException; import java.io.IOException; import java.io.InputStream; import java.net.URL; import java.util.ArrayList; import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.concurrent.TimeUnit; import java.util.concurrent.atomic.AtomicReference; import org.arquillian.cube.kubernetes.api.AnnotationProvider; import org.arquillian.cube.kubernetes.api.Configuration; import org.arquillian.cube.kubernetes.api.DependencyResolver; import org.arquillian.cube.kubernetes.api.FeedbackProvider; import org.arquillian.cube.kubernetes.api.KubernetesResourceLocator; import org.arquillian.cube.kubernetes.api.Logger; import org.arquillian.cube.kubernetes.api.NamespaceService; import org.arquillian.cube.kubernetes.api.ResourceInstaller; import org.arquillian.cube.kubernetes.api.Session; import org.arquillian.cube.kubernetes.api.SessionCreatedListener; import org.jboss.arquillian.core.spi.Validate; import static org.arquillian.cube.impl.util.SystemEnvironmentVariables.propertyToEnvironmentVariableName; import static org.arquillian.cube.kubernetes.impl.utils.ProcessUtil.runCommand; public class SessionManager implements SessionCreatedListener { private final Session session; private final KubernetesClient client; private final Configuration configuration; private final AnnotationProvider annotationProvider; private final NamespaceService namespaceService; private final KubernetesResourceLocator kubernetesResourceLocator; private final DependencyResolver dependencyResolver; private final ResourceInstaller resourceInstaller; private final FeedbackProvider feedbackProvider; private final List<HasMetadata> resources = new ArrayList<>(); private final AtomicReference<ShutdownHook> shutdownHookRef = new AtomicReference<>(); public SessionManager(Session session, KubernetesClient client, Configuration configuration, AnnotationProvider annotationProvider, NamespaceService namespaceService, KubernetesResourceLocator kubernetesResourceLocator, DependencyResolver dependencyResolver, ResourceInstaller resourceInstaller, FeedbackProvider feedbackProvider) { Validate.notNull(session, "A Session instance is required."); Validate.notNull(client, "A KubernetesClient instance is required."); Validate.notNull(configuration, "Configuration is required."); Validate.notNull(annotationProvider, "An AnnotationProvider instance is required."); Validate.notNull(namespaceService, "A NamespaceService instance is required."); Validate.notNull(dependencyResolver, "A DependencyResolver instance is required."); Validate.notNull(kubernetesResourceLocator, "A KubernetesResourceLocator instance is required."); Validate.notNull(resourceInstaller, "A ResourceInstaller instance is required."); Validate.notNull(feedbackProvider, "A FeedbackProvider instance is required."); this.session = session; this.client = client; this.configuration = configuration; this.annotationProvider = annotationProvider; this.namespaceService = namespaceService; this.kubernetesResourceLocator = kubernetesResourceLocator; this.dependencyResolver = dependencyResolver; this.resourceInstaller = resourceInstaller; this.feedbackProvider = feedbackProvider; } private static String getSessionStatus(Session session) { if (session.getFailed().get() > 0) { return "FAILED"; } else { return "PASSED"; } } /** * Creates a namespace if needed. * @param session The {@link Session}. */ public void createNamespace(Session session) { Logger log = session.getLogger(); String namespace = session.getNamespace(); log.status("Creating kubernetes resources inside namespace: " + namespace); log.info("if you use OpenShift then type this switch namespaces: oc project " + namespace); log.info( "if you use kubernetes then type this to switch namespaces: kubectl config set-context `kubectl config current-context` --namespace=" + namespace); Map<String, String> namespaceAnnotations = annotationProvider.create(session.getId(), Constants.RUNNING_STATUS); if (namespaceService.exists(session.getNamespace())) { //namespace exists } else if (configuration.isNamespaceLazyCreateEnabled()) { namespaceService.create(session.getNamespace(), namespaceAnnotations); } else { throw new IllegalStateException("Namespace [" + session.getNamespace() + "] doesn't exists"); } } public void createEnvironment(Session session) { Logger log = session.getLogger(); try { URL configUrl = configuration.getEnvironmentConfigUrl(); List<URL> dependencyUrls = !configuration.getEnvironmentDependencies().isEmpty() ? configuration.getEnvironmentDependencies() : dependencyResolver.resolve(session); if (configuration.isEnvironmentInitEnabled()) { if (configuration.getEnvironmentSetupScriptUrl() != null) { setupEnvironment(); } for (URL dependencyUrl : dependencyUrls) { log.info("Found dependency: " + dependencyUrl); resources.addAll(resourceInstaller.install(dependencyUrl)); } if (configUrl == null) { configUrl = kubernetesResourceLocator.locate(); } if (configUrl != null) { log.status("Applying kubernetes configuration from: " + configUrl); try (InputStream is = configUrl.openStream()) { resources.addAll(resourceInstaller.install(configUrl)); } } else { log.warn("Did not find any kubernetes configuration."); } List<HasMetadata> resourcesToWait = new ArrayList<>(resources); //Also handle services externally specified for (String service : configuration.getWaitForServiceList()) { Endpoints endpoints = client.endpoints().inNamespace(session.getNamespace()).withName(service).get(); if (endpoints != null) { resourcesToWait.add(endpoints); } } if (!resourcesToWait.isEmpty()) { try { client.resourceList(resourcesToWait) .waitUntilReady(configuration.getWaitTimeout(), TimeUnit.MILLISECONDS); } catch (KubernetesClientTimeoutException t) { log.warn("There are resources in not ready state:"); for (HasMetadata r : t.getResourcesNotReady()) { log.error( r.getKind() + " name: " + r.getMetadata().getName() + " namespace:" + r.getMetadata() .getNamespace()); feedbackProvider.onResourceNotReady(r); } throw new IllegalStateException("Environment not initialized in time.", t); } } } display(); } catch (Exception e) { try { clean(Constants.ERROR_STATUS); } catch (Exception me) { } throw new RuntimeException(e); } } @Override public void start() { Logger log = session.getLogger(); log.status("Using Kubernetes at: " + client.getMasterUrl()); createNamespace(session); addShutdownHook(); try { createEnvironment(session); } catch (Throwable t){ removeShutdownHook(); throw t; } } @Override public void stop() { try { clean(getSessionStatus(session)); } finally { removeShutdownHook(); } } @Override public void clean(String status) { String namespace = session.getNamespace(); try { if (configuration.isNamespaceCleanupEnabled()) { resourceInstaller.uninstall(resources); } /* * While it does make perfect sense to either clean or destroy, * in some cases clean is implicit-ly defined. That can implict-ly disable namespace destruction. * So, its more clean if we check of both conditions (double if vs if/else). * */ if (configuration.isNamespaceDestroyEnabled()) { namespaceService.destroy(namespace); } else { try { namespaceService.annotate(session.getNamespace(), annotationProvider.create(session.getId(), status)); } catch (Throwable t) { session.getLogger() .warn("Could not annotate namespace: [" + namespace + "] with status: [" + status + "]."); } } } finally { tearDownEnvironment(); } } @Override public void display() { ReplicaSetList replicaSetList = client.extensions().replicaSets().inNamespace(session.getNamespace()).list(); if (replicaSetList.getItems() != null) { for (ReplicaSet replicaSet : replicaSetList.getItems()) { session.getLogger().info("ReplicaSet: [" + replicaSet.getMetadata().getName() + "]"); } } ReplicationControllerList replicationControllerList = client.replicationControllers().inNamespace(session.getNamespace()).list(); if (replicationControllerList.getItems() != null) { for (ReplicationController replicationController : replicationControllerList.getItems()) { session.getLogger() .info("Replication controller: [" + replicationController.getMetadata().getName() + "]"); } } PodList podList = client.pods().inNamespace(session.getNamespace()).list(); if (podList != null) { for (Pod pod : podList.getItems()) { session.getLogger() .info("Pod: [" + pod.getMetadata().getName() + "] Status: [" + pod.getStatus().getPhase() + "]"); } } ServiceList serviceList = client.services().inNamespace(session.getNamespace()).list(); if (serviceList != null) { for (Service service : serviceList.getItems()) { StringBuilder sb = new StringBuilder(); sb.append("Service: [").append(service.getMetadata().getName()).append("]") .append(" IP: [").append(service.getSpec().getClusterIP()).append("]") .append(" Ports: [ "); for (ServicePort servicePort : service.getSpec().getPorts()) { sb.append(servicePort.getPort()).append(" "); } sb.append("]"); session.getLogger().info(sb.toString()); } } } private void setupEnvironment() { Logger log = session.getLogger(); log.info("Executing environment setup script from:" + configuration.getEnvironmentSetupScriptUrl()); try { runCommand(log, configuration.getEnvironmentSetupScriptUrl(), createScriptEnvironment()); } catch (IOException ex) { throw new RuntimeException(ex); } } private void tearDownEnvironment() { if (configuration.getEnvironmentTeardownScriptUrl() != null) { try { session.getLogger() .info( "Executing environment teardown script from:" + configuration.getEnvironmentTeardownScriptUrl()); runCommand(session.getLogger(), configuration.getEnvironmentTeardownScriptUrl(), createScriptEnvironment()); } catch (IOException ex) { session.getLogger().warn("Failed to execute teardown script, due to: " + ex.getMessage()); } } } /** * Creates the environment variables, that will be passed to the shell script (startup, teardown). */ private Map<String, String> createScriptEnvironment() { Map<String, String> env = new HashMap<>(); env.putAll(System.getenv()); env.put(propertyToEnvironmentVariableName(Configuration.KUBERNETES_NAMESPACE), configuration.getNamespace()); env.put(propertyToEnvironmentVariableName(Configuration.KUBERNETES_DOMAIN), configuration.getKubernetesDomain()); env.put(propertyToEnvironmentVariableName(Configuration.KUBERNETES_MASTER), configuration.getMasterUrl().toString()); env.put(propertyToEnvironmentVariableName(Configuration.DOCKER_REGISTY), configuration.getDockerRegistry()); return env; } private void addShutdownHook() { ShutdownHook hook = new ShutdownHook(new Runnable() { @Override public void run() { SessionManager.this.clean(Constants.ABORTED_STATUS); } }); Runtime.getRuntime().addShutdownHook(hook); shutdownHookRef.set(hook); } /** * Removes the {@link ShutdownHook}. */ private void removeShutdownHook() { ShutdownHook hook = shutdownHookRef.get(); if (hook != null) { Runtime.getRuntime().removeShutdownHook(hook); } } }