def _create_or_update_case_config_map(self, config_map_name, cases_dict,
                                       api: k8s.client.CoreV1Api):
     cfg_map_meta = k8s.client.V1ObjectMeta(
         namespace=PROJECT_NAMESPACE,
         name=config_map_name,
         labels={CLEANUP_LABEL: CLEANUP_ALWAYS},
     )
     cfg_map = k8s.client.V1ConfigMap(metadata=cfg_map_meta)
     cfg_map.data = {"cases.yaml": yaml.dump(cases_dict)}
     try:
         api.read_namespaced_config_map(name=config_map_name,
                                        namespace=PROJECT_NAMESPACE)
         resp = api.patch_namespaced_config_map(config_map_name,
                                                PROJECT_NAMESPACE, cfg_map)
         if isinstance(resp, k8s.client.V1ConfigMap):
             self.logger.debug("Patched config map with test cases")
             self.logger.debug(resp)
         else:
             raise Exception("Failed to patch cases ConfigMap")
     except k8s.client.rest.ApiException as api_exception:
         if api_exception.reason == "Not Found":
             resp = api.create_namespaced_config_map(
                 cfg_map.metadata.namespace, cfg_map)
             if isinstance(resp, k8s.client.V1ConfigMap):
                 self.logger.debug("Created config map with test cases")
             else:
                 raise Exception("Failed to create cases ConfigMap")
         else:
             raise api_exception
    def _adopt_completed_pods(self, kube_client: kubernetes.client.CoreV1Api):
        """

        Patch completed pod so that the KubernetesJobWatcher can delete it.

        :param kube_client: kubernetes client for speaking to kube API
        """
        kwargs = {
            'field_selector': "status.phase=Succeeded",
            'label_selector': 'kubernetes_executor=True',
        }
        pod_list = kube_client.list_namespaced_pod(
            namespace=self.kube_config.kube_namespace, **kwargs)
        for pod in pod_list.items:
            self.log.info("Attempting to adopt pod %s", pod.metadata.name)
            pod.metadata.labels['airflow-worker'] = str(self.scheduler_job_id)
            try:
                kube_client.patch_namespaced_pod(
                    name=pod.metadata.name,
                    namespace=pod.metadata.namespace,
                    body=PodGenerator.serialize_pod(pod),
                )
            except ApiException as e:
                self.log.info("Failed to adopt pod %s. Reason: %s",
                              pod.metadata.name, e)
 def _create_project_namespace_if_missing(self, api: k8s.client.CoreV1Api):
     namespace_labels = {
         ROLE_LABEL: "daemon-runner-namespace",
         CLEANUP_LABEL: CLEANUP_ON_REQUEST
     }
     namespace_list = api.list_namespace(
         label_selector=labels_to_string(namespace_labels))
     if not namespace_list.items:
         namespace = k8s.client.V1Namespace(
             metadata=k8s.client.V1ObjectMeta(name=PROJECT_NAMESPACE,
                                              labels=namespace_labels))
         api.create_namespace(namespace)
    def collect_results(self, pod_selector, api: k8s.client.CoreV1Api):
        """
        Queries pods of runner daemon set and waits for a corresponding configmap for each to be filled.
        Returns the merged data of all configMaps.
        """
        daemon_pods = []
        try:
            daemon_pods = api.list_namespaced_pod(
                PROJECT_NAMESPACE,
                label_selector=labels_to_string(pod_selector)).items
            self.logger.debug("Found %s daemon runner pods", len(daemon_pods))
        except k8s.client.rest.ApiException as api_exception:
            self.logger.error(api_exception)

        # Todo should we just use labels ?
        expected_result_map_names = [
            f"{d.metadata.name}-results" for d in daemon_pods
        ]
        result_config_maps = []
        # retry polling results until they are all returned
        while len(result_config_maps) < len(daemon_pods):
            try:
                result_config_maps = [
                    api.read_namespaced_config_map(name=result,
                                                   namespace=PROJECT_NAMESPACE)
                    for result in expected_result_map_names
                ]
            except k8s.client.rest.ApiException as api_exception:
                if api_exception.reason == "Not Found":
                    pass
                else:
                    raise api_exception
            self.logger.debug("Map names: %s",
                              [m.metadata.name for m in result_config_maps])
            self.logger.debug("Expected names: %s", expected_result_map_names)
            time.sleep(2)
        yamls = [yaml.safe_load(c.data["results"]) for c in result_config_maps]
        self.logger.debug("Found following yamls in result config maps:%s",
                          yamls)
        times = {
            c.metadata.name: yaml.safe_load(c.data["runtimes"])
            for c in result_config_maps if "runtimes" in c.data
        }
        return {k: v
                for yam in [y.items() for y in yamls] for k, v in yam}, times
 def refresh_cluster_resources(self, api: k8s.client.CoreV1Api):
     format_string = "Found {} {}: {}"
     logger.debug("Refreshing cluster resources")
     non_kube_namespace_selector = "metadata.namespace!=kube-system,metadata.namespace!=kube-public"
     pods = api.list_pod_for_all_namespaces(
         field_selector=non_kube_namespace_selector).items
     logger.debug(format_string.format(len(pods), "pods", pods))
     svcs = api.list_service_for_all_namespaces(
         field_selector=non_kube_namespace_selector).items
     logger.debug(format_string.format(len(svcs), "services", svcs))
     namespaces = api.list_namespace(
         field_selector=
         "metadata.name!=kube-system,metadata.name!=kube-public").items
     logger.debug(
         format_string.format(len(namespaces), "namespaces", namespaces))
     self._current_pods = pods
     self._current_services = svcs
     self._current_namespaces = namespaces
    def _try_get_ingress_url(self, api: kubernetes.client.CoreV1Api) -> str:
        """Return Ingress url when service is ready."""
        items = api.list_service_for_all_namespaces().items
        for item in items:
            ingress = item.status.load_balancer.ingress
            if ingress:
                return 'http://{}/'.format(ingress[0].hostname or ingress[0].ip)

        # @backoff.on_predicate(backoff.constant) will keep running this method
        # until it gets a non-falsey result. Return value of '' means that the
        # service is not ready yet.
        return ''
 def refresh_cluster_resources(self, api: k8s.client.CoreV1Api):
     """
     Fetches all pods, services and namespaces from the cluster and updates the corresponding class variables
     """
     format_string = "Found {} {}: {}"
     self.logger.debug("Refreshing cluster resources")
     non_kube_namespace_selector = (
         "metadata.namespace!=kube-system,metadata.namespace!=kube-public")
     pods = api.list_pod_for_all_namespaces(
         field_selector=non_kube_namespace_selector).items
     self.logger.debug(format_string.format(len(pods), "pods", pods))
     svcs = api.list_service_for_all_namespaces(
         field_selector=non_kube_namespace_selector).items
     self.logger.debug(format_string.format(len(svcs), "services", svcs))
     namespaces = api.list_namespace(
         field_selector=
         "metadata.name!=kube-system,metadata.name!=kube-public").items
     self.logger.debug(
         format_string.format(len(namespaces), "namespaces", namespaces))
     self._current_pods = pods
     self._current_services = svcs
     self.current_namespaces = namespaces
def create_update_configmap(
        k8s_client: kubernetes.client.CoreV1Api, namespace: str,
        configmap: kubernetes.client.V1ConfigMap
) -> kubernetes.client.V1ConfigMap:
    """
    Try to create a new namespaced configmap, and fall back to replacing
    a namespaced configmap if the create fails with a 409 (conflict)

    :rtype: client.V1ConfigMap
    :param k8s_client: The kubernetes.ApiClient object to use
    :param namespace: The namespace to update a configmap within
    :param configmap: The kubernetes.ConfigMap to apply
    :return: The kubernetes.ConfigMap API response
    """
    try:
        res = k8s_client.create_namespaced_config_map(namespace, configmap)
    except kubernetes.client.exceptions.ApiException as e:
        if e.status == 409:
            # 409 conflict = it exists... try to replace instead
            res = k8s_client.replace_namespaced_config_map(
                configmap.metadata['name'], namespace, configmap)
        else:
            raise e
    return res
    def create_namespace(self, name, api: k8s.client.CoreV1Api, labels=None):
        """
        Creates a namespace with the according labels
        """
        namespace = k8s.client.V1Namespace(metadata=k8s.client.V1ObjectMeta(
            name=name, labels=add_illuminatio_labels(labels)))

        try:
            resp = api.create_namespace(body=namespace)
            self.logger.debug(f"Created namespace {resp.metadata.name}")
            self.current_namespaces.append(resp)

            return resp
        except k8s.client.rest.ApiException as api_exception:
            self.logger.error(api_exception)
            exit(1)
Beispiel #10
0
def patch_pod_metadata(
    namespace: str,
    pod_name: str,
    patch: dict,
    k8s_api: kubernetes.client.CoreV1Api = None,
):
    k8s_api = k8s_api or kubernetes.client.CoreV1Api()
    patch = {'metadata': patch}
    for retry in range(patch_retries):
        try:
            pod = k8s_api.patch_namespaced_pod(
                name=pod_name,
                namespace=namespace,
                body=patch,
            )
            return pod
        except Exception as e:
            print(e)
            sleep(sleep_time)
    def namespace_exists(self, name, api: k8s.client.CoreV1Api):
        """
        Check if a namespace exists
        """
        for namespace in self.current_namespaces:
            if namespace.metadata.name != name:
                continue

            self.logger.debug(f"Found namespace {name} in cache")
            return namespace

        resp = None

        try:
            resp = api.read_namespace(name=name)
        except k8s.client.rest.ApiException as api_exception:
            if api_exception.reason == "Not Found":
                return None

            raise api_exception

        return resp
 def _find_or_create_cluster_resources_for_cases(self, cases_dict,
                                                 api: k8s.client.CoreV1Api):
     resolved_cases = {}
     from_host_mappings = {}
     to_host_mappings = {}
     port_mappings = {}
     for from_host_string, target_dict in cases_dict.items():
         from_host = Host.from_identifier(from_host_string)
         self.logger.debug("Searching pod for host %s", from_host)
         if not isinstance(from_host, (ClusterHost, GenericClusterHost)):
             raise ValueError(
                 "Only ClusterHost and GenericClusterHost fromHosts are supported by this Orchestrator"
             )
         namespaces_for_host = self._find_or_create_namespace_for_host(
             from_host, api)
         from_host = ClusterHost(namespaces_for_host[0].metadata.name,
                                 from_host.pod_labels)
         self.logger.debug("Updated fromHost with found namespace: %s",
                           from_host)
         pods_for_host = [
             pod for pod in self._current_pods if from_host.matches(pod)
         ]
         # create pod if none for fromHost is in cluster (and add it to podsForHost)
         if not pods_for_host:
             self.logger.debug("Creating dummy pod for host %s", from_host)
             additional_labels = {
                 ROLE_LABEL: "from_host_dummy",
                 CLEANUP_LABEL: CLEANUP_ALWAYS,
             }
             # TODO replace 'dummy' with a more suitable name to prevent potential conflicts
             container = k8s.client.V1Container(
                 image=self.oci_images["target"], name="dummy")
             dummy = create_pod_manifest(from_host, additional_labels,
                                         f"{PROJECT_PREFIX}-dummy-",
                                         container)
             resp = api.create_namespaced_pod(dummy.metadata.namespace,
                                              dummy)
             if isinstance(resp, k8s.client.V1Pod):
                 self.logger.debug("Dummy pod %s created succesfully",
                                   resp.metadata.name)
                 pods_for_host = [resp]
                 self._current_pods.append(resp)
             else:
                 self.logger.error("Failed to create dummy pod! Resp: %s",
                                   resp)
         else:
             self.logger.debug("Pods matching %s already exist: ",
                               from_host, pods_for_host)
         # resolve target names for fromHost and add them to resolved cases dict
         pod_identifier = "%s:%s" % (
             pods_for_host[0].metadata.namespace,
             pods_for_host[0].metadata.name,
         )
         self.logger.debug("Mapped pod_identifier: %s", pod_identifier)
         from_host_mappings[from_host_string] = pod_identifier
         (
             names_per_host,
             port_names_per_host,
         ) = self._get_target_names_creating_them_if_missing(
             target_dict, api)
         to_host_mappings[from_host_string] = names_per_host
         port_mappings[from_host_string] = port_names_per_host
         resolved_cases[pod_identifier] = {
             names_per_host[t]:
             [port_names_per_host[t][p] for p in target_dict[t]]
             for t in target_dict
         }
     return resolved_cases, from_host_mappings, to_host_mappings, port_mappings
 def _get_target_names_creating_them_if_missing(self, target_dict,
                                                api: k8s.client.CoreV1Api):
     service_names_per_host = {}
     port_dict_per_host = {}
     for host_string in target_dict.keys():
         host = Host.from_identifier(host_string)
         if isinstance(host, GenericClusterHost):
             self.logger.debug(
                 "Found GenericClusterHost %s,"
                 "Rewriting it to a ClusterHost in default namespace now.",
                 host,
             )
             host = ClusterHost("default", host.pod_labels)
         if not isinstance(host, ClusterHost):
             raise ValueError(
                 "Only ClusterHost targets are supported by this Orchestrator."
                 " Host: %s, hostString: %s" % (host, host_string))
         self.logger.debug("Searching service for host %s", host)
         services_for_host = [
             svc for svc in self._current_services if host.matches(svc)
         ]
         self.logger.debug(
             "Found services %s for host %s ",
             [svc.metadata for svc in services_for_host],
             host,
         )
         rewritten_ports = self._rewrite_ports_for_host(
             target_dict[host_string], services_for_host)
         self.logger.debug("Rewritten ports: %s", rewritten_ports)
         port_dict_per_host[host_string] = rewritten_ports
         if not services_for_host:
             gen_name = "%s-test-target-pod-" % PROJECT_PREFIX
             target_container = k8s.client.V1Container(
                 image=self.oci_images["target"], name="runner")
             pod_labels_tuple = (ROLE_LABEL, "test_target_pod")
             target_pod = create_pod_manifest(
                 host=host,
                 additional_labels={
                     pod_labels_tuple[0]: pod_labels_tuple[1],
                     CLEANUP_LABEL: CLEANUP_ALWAYS,
                 },
                 generate_name=gen_name,
                 container=target_container,
             )
             target_ports = [
                 int(port.replace("-", ""))
                 for port in port_dict_per_host[host_string].values()
             ]
             svc = create_service_manifest(
                 host,
                 {pod_labels_tuple[0]: pod_labels_tuple[1]},
                 {
                     ROLE_LABEL: "test_target_svc",
                     CLEANUP_LABEL: CLEANUP_ALWAYS
                 },
                 target_ports,
             )
             target_pod_namespace = host.namespace
             resp = api.create_namespaced_pod(
                 namespace=target_pod_namespace, body=target_pod)
             if isinstance(resp, k8s.client.V1Pod):
                 self.logger.debug("Target pod %s created succesfully",
                                   resp.metadata.name)
                 self._current_pods.append(resp)
             else:
                 self.logger.error("Failed to create pod! Resp: %s", resp)
             resp = api.create_namespaced_service(namespace=host.namespace,
                                                  body=svc)
             if isinstance(resp, k8s.client.V1Service):
                 service_names_per_host[host_string] = resp.spec.cluster_ip
                 self.logger.debug("Target svc %s created succesfully",
                                   resp.metadata.name)
                 self._current_services.append(resp)
             else:
                 self.logger.error("Failed to create target svc! Resp: %s",
                                   resp)
         else:
             service_names_per_host[host_string] = services_for_host[
                 0].spec.cluster_ip
     return service_names_per_host, port_dict_per_host
    def collect_results(self, api: k8s.client.CoreV1Api):
        """ Queries pods of runner daemon set and waits for a corresponding configmap for each to be filled.
            Returns the merged data of all configMaps. """
        # Todo fix me!
        # api.list_node(label_selector="!node-role.kubernetes.io/master").items
        non_master_nodes = api.list_node().items
        logger.debug("Found " + str(len(non_master_nodes)) +
                     " non master nodes")
        daemon_pods = []
        # we re-request daemon pods until the number exactly match because pods are sometimes overprovisioned
        # and then immediately deleted, causing the target number of ConfigMaps to never be reached
        apps_api = k8s.client.AppsV1Api()
        while self.runner_daemon_set is None:
            logger.info("Waiting for runner_daemon_set to become initialized")
            try:
                self.runner_daemon_set = apps_api.read_namespaced_daemon_set(
                    namespace=PROJECT_NAMESPACE, name=DAEMONSET_NAME)
                if isinstance(self.runner_daemon_set, k8s.client.V1DaemonSet):
                    break
            except k8s.client.rest.ApiException as api_exception:
                logger.info("exception occured!")
                if api_exception.reason != "Not Found":
                    raise (api_exception)
            time.sleep(1)

        while len(daemon_pods) != len(non_master_nodes):
            daemon_pods = api.list_namespaced_pod(
                PROJECT_NAMESPACE,
                label_selector=labels_to_string(
                    self.runner_daemon_set.spec.selector.match_labels)).items
            logger.debug("Found " + str(len(daemon_pods)) +
                         " daemon runner pods")
            time.sleep(2)
        expected_result_map_names = [
            d.metadata.name + "-results" for d in daemon_pods
        ]
        result_config_maps = []
        # retry polling results until they are all returned
        while len(result_config_maps) < len(daemon_pods):
            try:
                result_config_maps = [
                    api.read_namespaced_config_map(name=result,
                                                   namespace=PROJECT_NAMESPACE)
                    for result in expected_result_map_names
                ]
            except k8s.client.rest.ApiException as api_exception:
                if api_exception.reason == "Not Found":
                    pass
                else:
                    raise (api_exception)
            logger.debug("Map names: " +
                         str([m.metadata.name for m in result_config_maps]))
            logger.debug("Expected names: " + str(expected_result_map_names))
            time.sleep(2)
        yamls = [yaml.safe_load(c.data["results"]) for c in result_config_maps]
        logger.debug("Found following yamls in result config maps:" +
                     str(yamls))
        times = {
            c.metadata.name: yaml.safe_load(c.data["runtimes"])
            for c in result_config_maps if "runtimes" in c.data
        }
        return {k: v
                for yam in [y.items() for y in yamls] for k, v in yam}, times
 def _find_or_create_cluster_resources_for_cases(self, cases_dict,
                                                 api: k8s.client.CoreV1Api):
     resolved_cases = {}
     from_host_mappings = {}
     to_host_mappings = {}
     port_mappings = {}
     for from_host_string, target_dict in cases_dict.items():
         from_host = Host.from_identifier(from_host_string)
         logger.debug("Searching pod for host " + str(from_host))
         if not (isinstance(from_host, ClusterHost)
                 or isinstance(from_host, GenericClusterHost)):
             raise ValueError(
                 "Only ClusterHost and GenericClusterHost fromHosts are supported by this Orchestrator"
             )
         namespaces_for_host = self._find_or_create_namespace_for_host(
             from_host, api)
         from_host = ClusterHost(namespaces_for_host[0].metadata.name,
                                 from_host.pod_labels)
         logger.debug("Updated fromHost with found namespace: " +
                      str(from_host))
         pods_for_host = [
             pod for pod in self._current_pods if from_host.matches(pod)
         ]
         # create pod if none for fromHost is in cluster (and add it to podsForHost)
         if not pods_for_host:
             logger.debug("Creating dummy pod for host " + str(from_host))
             additional_labels = {
                 ROLE_LABEL: "from_host_dummy",
                 CLEANUP_LABEL: CLEANUP_ALWAYS
             }
             container = k8s.client.V1Container(image="nginx:stable",
                                                name="dummy")
             dummy = init_pod(from_host, additional_labels,
                              PROJECT_PREFIX + "-dummy-", container)
             resp = api.create_namespaced_pod(dummy.metadata.namespace,
                                              dummy)
             if isinstance(resp, k8s.client.V1Pod):
                 logger.debug("Dummy pod " + resp.metadata.name +
                              " created succesfully")
                 pods_for_host = [resp]
                 self._current_pods.append(resp)
             else:
                 logger.error("Failed to create dummy pod! Resp: " +
                              str(resp))
         else:
             logger.debug("Pods matching " + str(from_host) +
                          " already exist: " + str(pods_for_host))
         # resolve target names for fromHost and add them to resolved cases dict
         pod_identifier = pods_for_host[
             0].metadata.namespace + ":" + pods_for_host[0].metadata.name
         logger.debug("Mapped pod_identifier: " + str(pod_identifier))
         from_host_mappings[from_host_string] = pod_identifier
         names_per_host, port_names_per_host = self._get_target_names_creating_them_if_missing(
             target_dict, api)
         to_host_mappings[from_host_string] = names_per_host
         port_mappings[from_host_string] = port_names_per_host
         resolved_cases[pod_identifier] = {
             names_per_host[t]:
             [port_names_per_host[t][p] for p in target_dict[t]]
             for t in target_dict
         }
     return resolved_cases, from_host_mappings, to_host_mappings, port_mappings
 def _get_target_names_creating_them_if_missing(self, target_dict,
                                                api: k8s.client.CoreV1Api):
     svc_names_per_host = {}
     port_dict_per_host = {}
     for host_string in target_dict.keys():
         host = Host.from_identifier(host_string)
         if isinstance(host, GenericClusterHost):
             logger.debug(
                 "Found GenericClusterHost " + str(host) +
                 ". Rewriting it to a ClusterHost in default namespace now."
             )
             host = ClusterHost("default", host.pod_labels)
         if not isinstance(host, ClusterHost):
             raise ValueError(
                 "Only ClusterHost targets are supported by this Orchestrator. Host: "
                 + str(host) + ", hostString: " + host_string)
         logger.debug("Searching service for host " + str(host))
         services_for_host = [
             svc for svc in self._current_services if host.matches(svc)
         ]
         logger.debug("Found services {} for host {} ".format(
             [svc.metadata for svc in services_for_host], host))
         rewritten_ports = self._rewrite_ports_for_host(
             target_dict[host_string], services_for_host)
         logger.debug("Rewritten ports: " + str(rewritten_ports))
         port_dict_per_host[host_string] = rewritten_ports
         if not services_for_host:
             gen_name = PROJECT_PREFIX + "-test-target-pod-"
             target_container = k8s.client.V1Container(
                 image=self.target_image, name="runner")
             pod_labels_tuple = (ROLE_LABEL, "test_target_pod")
             target_pod = init_pod(host=host,
                                   additional_labels={
                                       pod_labels_tuple[0]:
                                       pod_labels_tuple[1],
                                       CLEANUP_LABEL: CLEANUP_ALWAYS
                                   },
                                   generate_name=gen_name,
                                   container=target_container)
             target_ports = [
                 int(port.replace("-", ""))
                 for port in port_dict_per_host[host_string].values()
             ]
             # ToDo we should use the cluser ip instead of the DNS names
             # so we don't need the lookups
             svc_name = "svc-" + convert_to_resource_name(
                 host.to_identifier())
             svc = init_svc(host,
                            {pod_labels_tuple[0]: pod_labels_tuple[1]}, {
                                 ROLE_LABEL: "test_target_svc",
                                 CLEANUP_LABEL: CLEANUP_ALWAYS
                             }, svc_name, target_ports)
             target_pod_namespace = host.namespace
             svc_names_per_host[
                 host_string] = target_pod_namespace + ":" + svc_name
             resp = api.create_namespaced_pod(
                 namespace=target_pod_namespace, body=target_pod)
             if isinstance(resp, k8s.client.V1Pod):
                 logger.debug("Target pod " + resp.metadata.name +
                              " created succesfully")
                 self._current_pods.append(resp)
             else:
                 logger.error("Failed to create pod! Resp: " + str(resp))
             resp = api.create_namespaced_service(namespace=host.namespace,
                                                  body=svc)
             if isinstance(resp, k8s.client.V1Service):
                 logger.debug("Target svc " + resp.metadata.name +
                              " created succesfully")
                 self._current_services.append(resp)
             else:
                 logger.error("Failed to create target svc! Resp: " +
                              str(resp))
         else:
             svc_names_per_host[host_string] = services_for_host[
                 0].metadata.namespace + ":" + services_for_host[
                     0].metadata.name
     return svc_names_per_host, port_dict_per_host