All Downloads are FREE. Search and download functionalities are using the official Maven repository.

io.fabric8.jube.apimaster.ApiMasterService Maven / Gradle / Ivy

There is a newer version: 2.2.0
Show newest version
/**
 *  Copyright 2005-2014 Red Hat, Inc.
 *
 *  Red Hat 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 io.fabric8.jube.apimaster;

import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.Callable;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import javax.inject.Inject;
import javax.inject.Singleton;
import javax.validation.constraints.NotNull;
import javax.ws.rs.Consumes;
import javax.ws.rs.DELETE;
import javax.ws.rs.GET;
import javax.ws.rs.POST;
import javax.ws.rs.Path;
import javax.ws.rs.PathParam;
import javax.ws.rs.Produces;
import javax.ws.rs.QueryParam;

import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import io.fabric8.jube.ServiceIDs;
import io.fabric8.jube.Statuses;
import io.fabric8.jube.local.NodeHelper;
import io.fabric8.jube.local.ProcessMonitor;
import io.fabric8.jube.model.HostNode;
import io.fabric8.jube.model.HostNodeModel;
import io.fabric8.jube.process.Installation;
import io.fabric8.jube.process.ProcessManager;
import io.fabric8.jube.proxy.KubeProxy;
import io.fabric8.jube.replicator.Replicator;
import io.fabric8.kubernetes.api.KubernetesHelper;
import io.fabric8.kubernetes.api.model.Endpoints;
import io.fabric8.kubernetes.api.model.EndpointsList;
import io.fabric8.kubernetes.api.model.Minion;
import io.fabric8.kubernetes.api.model.MinionList;
import io.fabric8.kubernetes.api.model.PodState;
import io.fabric8.kubernetes.api.model.Container;
import io.fabric8.kubernetes.api.model.PodList;
import io.fabric8.kubernetes.api.model.Pod;
import io.fabric8.kubernetes.api.model.ReplicationControllerList;
import io.fabric8.kubernetes.api.model.ReplicationController;
import io.fabric8.kubernetes.api.model.ServiceList;
import io.fabric8.kubernetes.api.model.Service;
import io.fabric8.kubernetes.api.model.util.IntOrString;
import io.fabric8.utils.Objects;
import io.hawt.util.Strings;
import org.apache.deltaspike.core.api.config.ConfigProperty;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import static io.fabric8.jube.local.NodeHelper.getOrCreateCurrentState;
import static io.fabric8.utils.Lists.notNullList;

/**
 * Implements the local node controller
 */
@Singleton
@Path("v1beta2")
@Produces("application/json")
@Consumes("application/json")
public class ApiMasterService implements KubernetesExtensions {

    public static final String DEFAULT_HOSTNAME = "localhost";
    public static final String DEFAULT_HTTP_PORT = "8585";
    public static final String DEFAULT_NAMESPACE = "default";

    public static String hostName = DEFAULT_HOSTNAME;
    public static String port = DEFAULT_HTTP_PORT;

    private static final transient Logger LOG = LoggerFactory.getLogger(ApiMasterService.class);

    private final ProcessManager processManager;
    private final ApiMasterKubernetesModel model;
    private final Replicator replicator;
    private final ProcessMonitor processMonitor;
    private final KubeProxy kubeProxy;
    private final HostNodeModel hostNodeModel;
    private final ExecutorService localCreateThreadPool = Executors.newFixedThreadPool(10);
    private String namespace = "default";

    @Inject
    public ApiMasterService(ProcessManager processManager, ApiMasterKubernetesModel model, Replicator replicator, ProcessMonitor processMonitor, KubeProxy kubeProxy, HostNodeModel hostNodeModel,
                            @ConfigProperty(name = "JUBE_HOSTNAME", defaultValue = DEFAULT_HOSTNAME)
                            String hostName,
                            @ConfigProperty(name = "HTTP_PORT", defaultValue = DEFAULT_HTTP_PORT)
                            String port) {
        this.processManager = processManager;
        this.model = model;
        this.replicator = replicator;
        this.processMonitor = processMonitor;
        this.kubeProxy = kubeProxy;
        this.hostNodeModel = hostNodeModel;

        ApiMasterService.hostName = hostName;
        ApiMasterService.port = port;

        HostNode node = new HostNode();
        node.setHostName(hostName);
        node.setWebUrl("http://" + hostName + ":" + port + "/");
        node.setId(UUID.randomUUID().toString());
        hostNodeModel.write(node);

        ensureModelHasKubernetesServices(hostName, port);
    }

    public static String getHostName() {
        return hostName;
    }

    public static String getPort() {
        return port;
    }

    public String getNamespace() {
        return namespace;
    }

    public void setNamespace(String namespace) {
        this.namespace = namespace;
    }

    // Pods
    //-------------------------------------------------------------------------


    public PodList getPods() {
        return model.getPods();
    }

    public Pod getPod(@NotNull String podId) {
        Map map = model.getPodMap();
        return map.get(podId);
    }

    @Override
    public Pod getPod(@NotNull String podId, String namespace) {
        return model.getPod(podId, namespace);
    }

    @Override
    public PodList getPods(String namespace) {
        return model.getPods(namespace);
    }

    public String createPod(Pod entity) throws Exception {
        return model.remoteCreatePod(entity);
    }

    @Override
    public String createPod(Pod pod, String namespace) throws Exception {
        pod.setNamespace(namespace);
        return createPod(pod);
    }

    public String updatePod(final @NotNull String podId, final Pod pod) throws Exception {
        // TODO needs implementing remotely!

        return NodeHelper.excludeFromProcessMonitor(processMonitor, pod, new Callable() {
            @Override
            public String call() throws Exception {
                System.out.println("Updating pod " + pod);
                PodState desiredState = pod.getDesiredState();
                Objects.notNull(desiredState, "desiredState");

                PodState currentState = getOrCreateCurrentState(pod);
                List containers = KubernetesHelper.getContainers(pod);
                model.updatePod(podId, pod);

                return NodeHelper.createMissingContainers(processManager, model, pod, currentState, containers);
            }
        });
    }

    public String deletePod(@NotNull String podId) throws Exception {
        model.deletePod(podId, namespace);
        return null;
    }

    @Override
    public String deletePod(@NotNull String podId, String namespace) throws Exception {
        model.deletePod(podId, namespace);
        return null;
    }

    @Override
    public String updatePod(@NotNull String podId, Pod pod, String namespace) throws Exception {
        pod.setNamespace(namespace);
        return updatePod(podId, pod);
    }

    @Override
    public ServiceList getServices(String namespace) {
        return model.getServices(namespace);
    }

    @Override
    public Service getService(@NotNull String serviceId, String namespace) {
        return model.getService(serviceId, namespace);
    }

    @Override
    public String updateService(@NotNull String serviceId, Service service, String namespace) throws Exception {
        service.setNamespace(namespace);
        return updateService(serviceId, service);
    }

    // Replication Controllers
    //-------------------------------------------------------------------------


    public ReplicationControllerList getReplicationControllers() {
        return model.getReplicationControllers();
    }

    public ReplicationController getReplicationController(@NotNull String controllerId) {
        Map map = KubernetesHelper.getReplicationControllerMap(this);
        return map.get(controllerId);
    }

    @Override
    public ReplicationControllerList getReplicationControllers(String namespace) {
        return model.getReplicationControllers(namespace);
    }

    @Override
    public ReplicationController getReplicationController(@NotNull String replicationControllerId, String namespace) {
        return model.getReplicationController(replicationControllerId, namespace);
    }

    public String createReplicationController(ReplicationController entity) throws Exception {
        String id = model.getOrCreateId(entity.getId(), NodeHelper.KIND_REPLICATION_CONTROLLER);
        entity.setId(id);
        return updateReplicationController(id, entity);
    }

    @Override
    public String createReplicationController(ReplicationController replicationController, String namespace) throws Exception {
        replicationController.setNamespace(namespace);
        return createReplicationController(replicationController);
    }

    @Override
    public String updateReplicationController(@PathParam("controllerId") @NotNull String s, ReplicationController replicationController, @QueryParam("namespace") String s1) throws Exception {
        replicationController.setNamespace(namespace);
        return updateReplicationController(s, replicationController);
    }


    public String updateReplicationController(@NotNull String controllerId, ReplicationController replicationController) throws Exception {
        // lets ensure there's a default namespace set
        String namespace = replicationController.getNamespace();
        if (Strings.isBlank(namespace)) {
            replicationController.setNamespace(DEFAULT_NAMESPACE);
        }
        model.updateReplicationController(controllerId, replicationController);
        return null;
    }

    public String deleteReplicationController(@NotNull String controllerId) throws Exception {
        model.deleteReplicationController(controllerId, namespace);
        return null;
    }

    @Override
    public String deleteReplicationController(@NotNull String controllerId, String namespace) throws Exception {
        model.deleteReplicationController(controllerId, namespace);
        return null;
    }

    @Override
    public EndpointsList getEndpoints(@QueryParam("namespace") String namespace) {
        EndpointsList answer = new EndpointsList();
        List list = new ArrayList<>();
        answer.setItems(list);

        ServiceList services = getServices(namespace);
        if (services != null) {
            List pods = getPods(namespace).getItems();
            List items = notNullList(services.getItems());
            for (Service service : items) {
                Endpoints endpoints = createEndpointsForService(service, pods);
                if (endpoints != null) {
                    list.add(endpoints);
                }
            }
        }
        return answer;
    }


    // Services
    //-------------------------------------------------------------------------

    public ServiceList getServices() {
        return model.getServices();
    }

    public Service getService(@NotNull String serviceId) {
        Map map = model.getServiceMap();
        return map.get(serviceId);
    }

    public String createService(Service entity) throws Exception {
        String id = model.getOrCreateId(entity.getId(), NodeHelper.KIND_SERVICE);
        entity.setId(id);
        return updateService(id, entity);
    }

    @Override
    public String createService(Service service, String namespace) throws Exception {
        service.setNamespace(namespace);
        return createService(service);
    }


    public String updateService(@NotNull String id, Service entity) throws Exception {
        // lets set the IP
        entity.setPortalIP(getHostName());

        // lets ensure there's a default namespace set
        String namespace = entity.getNamespace();
        if (Strings.isBlank(namespace)) {
            entity.setNamespace(DEFAULT_NAMESPACE);
        }
        model.updateService(id, entity);
        return null;
    }

    public String deleteService(@NotNull String serviceId) throws Exception {
        model.deleteService(serviceId, namespace);
        return null;
    }

    @Override
    public String deleteService(@NotNull String serviceId, String namespace) throws Exception {
        model.deleteService(serviceId, namespace);
        return null;
    }

    @Override
    public Endpoints endpointsForService(@NotNull String serviceId, String namespace) {
        List pods = podList();
        Service service = getService(serviceId);
        return createEndpointsForService(service, pods);
    }

    protected List podList() {
        PodList podList = getPods();
        return notNullList(podList.getItems());
    }

    protected static Endpoints createEndpointsForService(Service service, List pods) {
        if (service == null) {
            return null;
        }
        Endpoints answer = new Endpoints();
        answer.setId(service.getId());
        List urls = new ArrayList<>();
        answer.setEndpoints(urls);
        IntOrString containerPort = service.getContainerPort();
        if (containerPort != null) {
            String strVal = containerPort.getStrVal();
            if (strVal == null) {
                Integer intVal = containerPort.getIntVal();
                if (intVal != null) {
                    strVal = intVal.toString();
                }
            }
            if (strVal != null) {
                String containerPortText = ":" + strVal;
                List podsForService = KubernetesHelper.getPodsForService(service, pods);
                for (Pod pod : podsForService) {
                    PodState currentState = pod.getCurrentState();
                    if (currentState != null) {
                        String podIP = currentState.getPodIP();
                        if (podIP != null) {
                            String url = podIP + containerPortText;
                        }
                    }
                }
            }
        }
        return answer;
    }

    public EndpointsList getEndpoints() {
        EndpointsList answer = new EndpointsList();
        List list = new ArrayList<>();
        answer.setItems(list);

        ServiceList services = getServices();
        if (services != null) {
            List pods = podList();
            List items = notNullList(services.getItems());
            for (Service service : items) {
                Endpoints endpoints = createEndpointsForService(service, pods);
                if (endpoints != null) {
                    list.add(endpoints);
                }
            }
        }
        return answer;
    }

    @Override
    public MinionList getMinions() {
        // TODO we should replace HostNode with Minion...
        MinionList answer = new MinionList();
        List items = new ArrayList<>();
        answer.setItems(items);
        Collection values = getHostNodes().values();
        for (HostNode value : values) {
            Minion minion = new Minion();
            minion.setId(value.getId());
            minion.setHostIP(value.getHostName());
            items.add(minion);
        }
        return answer;
    }

    @Override
    public Minion minion(@NotNull String name) {
        MinionList minionList = getMinions();
        List minions = notNullList(minionList.getItems());
        for (Minion minion : minions) {
            if (Objects.equal(minion.getId(), name)) {
                return minion;
            }
        }
        return null;
    }

    // Host nodes
    //-------------------------------------------------------------------------

    @GET
    @Path("hostNodes")
    @Produces("application/json")
    public Map getHostNodes() {
        return hostNodeModel.getMap();
    }

    @GET
    @Path("hostNodes/{id}")
    @Produces("application/json")
    public HostNode getHostNode(@PathParam("id") @NotNull String id) {
        return hostNodeModel.getEntity(id);
    }


    // Local operations
    //-------------------------------------------------------------------------

    @POST
    @Path("local/pods")
    @Consumes("application/json")
    @Override
    public String createLocalPod(Pod entity) throws Exception {
        String id = model.getOrCreateId(entity.getId(), NodeHelper.KIND_REPLICATION_CONTROLLER);
        entity.setId(id);
        return updateLocalPod(id, entity);
    }

    public String updateLocalPod(@NotNull final String podId, final Pod pod) throws Exception {
        System.out.println("Updating pod " + pod);
        PodState desiredState = pod.getDesiredState();
        Objects.notNull(desiredState, "desiredState");

        // lets ensure there's a default namespace set
        String namespace = pod.getNamespace();
        if (Strings.isBlank(namespace)) {
            pod.setNamespace(DEFAULT_NAMESPACE);
        }

        final PodState currentState = getOrCreateCurrentState(pod);
        final List containers = KubernetesHelper.getContainers(pod);

        NodeHelper.setPodStatus(pod, Statuses.WAITING);
        NodeHelper.setContainerRunningState(pod, podId, false);

        model.updatePod(podId, pod);
        localCreateThreadPool.submit(new Runnable() {
            @Override
            public void run() {
                Runnable task = new Runnable() {
                    @Override
                    public void run() {
                        try {
                            NodeHelper.createMissingContainers(processManager, model, pod, currentState, containers);
                        } catch (Exception e) {
                            LOG.error("Failed to create container " + podId + ". " + e, e);
                        }

                    }
                };
                NodeHelper.excludeFromProcessMonitor(processMonitor, pod, task);
            }
        });
        return pod.getId();
    }


    @DELETE
    @Path("local/pods/{id}")
    @Consumes("text/plain")
    @Override
    public String deleteLocalPod(@PathParam("id") @NotNull String id, @QueryParam("namespace") String namespace) throws Exception {
        NodeHelper.deletePod(processManager, model, id, namespace);
        return null;
    }

    @GET
    @Path("local/pods")
    @Consumes("application/json")
    @Override
    public PodList getLocalPods() {
        ImmutableMap installMap = processManager.listInstallationMap();
        ImmutableSet keys = installMap.keySet();
        List pods = new ArrayList<>();
        for (String key : keys) {
            Pod pod = model.getPod(key);
            if (pod != null) {
                pods.add(pod);
            }
        }
        PodList answer = new PodList();
        answer.setItems(pods);
        return answer;

    }


    /**
     * Lets ensure that the "kubernetes" and "kubernetes-ro" services are defined so that folks can access the core
     * REST API via kubernetes services
     */
    protected void ensureModelHasKubernetesServices(String hostName, String port) {
        ImmutableMap serviceMap = model.getServiceMap();
        Service service = null;
        try {
            service = serviceMap.get(ServiceIDs.KUBERNETES_SERVICE_ID);
            if (service == null) {
                service = createService(hostName, port);
                service.setId(ServiceIDs.KUBERNETES_SERVICE_ID);
                service.setSelector(createKubernetesServiceLabels());
                createService(service);
            }
            service = serviceMap.get(ServiceIDs.KUBERNETES_RO_SERVICE_ID);
            if (service == null) {
                service = createService(hostName, port);
                service.setId(ServiceIDs.KUBERNETES_RO_SERVICE_ID);
                service.setSelector(createKubernetesServiceLabels());
                createService(service);
            }
            service = serviceMap.get(ServiceIDs.FABRIC8_CONSOLE_SERVICE_ID);
            if (service == null) {
                service = createService(hostName, port);
                service.setId(ServiceIDs.FABRIC8_CONSOLE_SERVICE_ID);
                service.setSelector(createFabric8ConsoleServiceLabels());
                createService(service);
            }
        } catch (Exception e) {
            LOG.error("Failed to create service " + service + ". " + e, e);
        }
    }

    protected Map createFabric8ConsoleServiceLabels() {
        Map answer = new HashMap<>();
        answer.put("component", "fabric8Console");
        return answer;
    }

    protected Map createKubernetesServiceLabels() {
        Map answer = new HashMap<>();
        answer.put("component", "apiserver");
        answer.put("provider", "kubernetes");
        return answer;
    }

    protected Service createService(String hostName, String port) {
        Service service = new Service();
        service.setKind("Service");
        try {
            Integer portNumber = Integer.parseInt(port);
            if (portNumber != null) {
                service.setPort(portNumber);
                IntOrString containerPort = new IntOrString();
                containerPort.setIntVal(portNumber);
                service.setContainerPort(containerPort);
            }
        } catch (NumberFormatException e) {
            LOG.warn("Failed to parse port text: " + port + ". " + e, e);
        }
        service.setPortalIP(hostName);
        return service;
    }

}




© 2015 - 2024 Weber Informatics LLC | Privacy Policy