diff --git a/example/.DS_Store b/example/.DS_Store new file mode 100644 index 00000000..eb4bb013 Binary files /dev/null and b/example/.DS_Store differ diff --git a/example/.gitignore b/example/.gitignore new file mode 100644 index 00000000..1d17dae1 --- /dev/null +++ b/example/.gitignore @@ -0,0 +1 @@ +.venv diff --git a/example/Dockerfile.mock b/example/Dockerfile.mock new file mode 100644 index 00000000..e69de29b diff --git a/example/README.md b/example/README.md new file mode 100644 index 00000000..e69de29b diff --git a/example/create_openapi.py b/example/create_openapi.py new file mode 100644 index 00000000..47a81b33 --- /dev/null +++ b/example/create_openapi.py @@ -0,0 +1,48 @@ +import json +import os.path +from typing import List + +from fastapi import FastAPI +from fastapi.openapi.utils import get_openapi +from fastapi.responses import PlainTextResponse + +import interlink + +app = FastAPI() + + +@app.post("/create") +async def create_pod(pod: List[interlink.Pod]) -> interlink.CreateStruct: + raise NotImplementedError + + +@app.post("/delete") +async def delete_pod(pod: interlink.PodRequest) -> str: + raise NotImplementedError + + +@app.get("/status") +async def status_pod(pods: List[interlink.PodRequest]) -> List[interlink.PodStatus]: + raise NotImplementedError + + +@app.get("/getLogs", response_class=PlainTextResponse) +async def get_logs(req: interlink.LogRequest) -> bytes: + raise NotImplementedError + + +openapi_schema = os.path.join( + os.path.dirname(__file__), *["..", "docs", "openapi", "openapi.json"] +) + +with open(openapi_schema, "w") as f: + json.dump( + get_openapi( + title="interLink sidecar", + version=os.environ.get("VERSION", "v0.0.0"), + openapi_version=app.openapi()["openapi"], + description="openapi spec for interLink apis <-> provider sidecar communication", + routes=app.routes, + ), + f, + ) \ No newline at end of file diff --git a/example/interlink-docker/interlink/InterLinkConfig.yaml b/example/interlink-docker/interlink/InterLinkConfig.yaml new file mode 100644 index 00000000..6738ee3c --- /dev/null +++ b/example/interlink-docker/interlink/InterLinkConfig.yaml @@ -0,0 +1,9 @@ +VKTokenFile: "$HOME/interLink/token" +InterlinkAddress: "http://XXX.XXX.XXX.XXX" +SidecarURL: "http://docker-sidecar" +InterlinkPort: "3000" +SidecarPort: "4000" +ExportPodData: true +DataRootFolder: ".local/interlink/jobs/" +VerboseLogging: true +ErrorsOnlyLogging: false diff --git a/example/interlink-docker/interlink/docker-compose.yaml b/example/interlink-docker/interlink/docker-compose.yaml new file mode 100644 index 00000000..243c9d5f --- /dev/null +++ b/example/interlink-docker/interlink/docker-compose.yaml @@ -0,0 +1,46 @@ +version: '3.7' +services: + interlink: + build: + context: ../../../ + dockerfile: docker/Dockerfile.interlink + restart: always + #network_mode: "host" + ports: + - 3000:3000 + volumes: + - type: bind + source: ./ + target: /etc/interlink + environment: + - INTERLINKCONFIGPATH=/etc/interlink/InterLinkConfig.yaml + # healthcheck: + # test: ["CMD", "/check.sh"] + # interval: 10s + # timeout: 10s + # retries: 3 + # start_period: 5s + docker-sidecar: + image: ghcr.io/intertwin-eu/interlink-sidecar-docker:0.0.2-pre1 + restart: always + privileged: true + cap_add: + - SYS_ADMIN + #network_mode: "host" + ports: + - 4000:4000 + environment: + - INTERLINKCONFIGPATH=/etc/interlink/sidecarConfig.yaml + volumes: + - type: bind + source: ./ + target: /etc/interlink + - type: bind + source: /var/run/docker.sock + target: /var/run/docker.sock + # healthcheck: + # test: ["CMD", "/check.sh"] + # interval: 10s + # timeout: 10s + # retries: 3 + # start_period: 5s diff --git a/example/interlink-docker/interlink/sidecarConfig.yaml b/example/interlink-docker/interlink/sidecarConfig.yaml new file mode 100644 index 00000000..75b216df --- /dev/null +++ b/example/interlink-docker/interlink/sidecarConfig.yaml @@ -0,0 +1,15 @@ +SidecarURL: "http://docker-sidecar" +SidecarPort: "4000" +SbatchPath: "/usr/bin/sbatch" +ScancelPath: "/usr/bin/scancel" +SqueuePath: "/usr/bin/squeue" +CommandPrefix: "" +ExportPodData: true +DataRootFolder: ".local/interlink/jobs/" +Tsocks: false +TsocksPath: "$WORK/tsocks-1.8beta5+ds1/libtsocks.so" +TsocksLoginNode: "login01" +BashPath: /bin/bash +VerboseLogging: true +ErrorsOnlyLogging: false + diff --git a/example/interlink-docker/test_pod.yaml b/example/interlink-docker/test_pod.yaml new file mode 100644 index 00000000..7dcd1ecf --- /dev/null +++ b/example/interlink-docker/test_pod.yaml @@ -0,0 +1,21 @@ +apiVersion: v1 +kind: Pod +metadata: + name: test-pod-cfg-cowsay-dciangot + namespace: vk + annotations: + slurm-job.vk.io/flags: "--job-name=test-pod-cfg -t 2800 --ntasks=8 --nodes=1 --mem-per-cpu=2000" +spec: + restartPolicy: Never + containers: + - image: ghcr.io/grycap/cowsay + command: ["/bin/sh"] + args: ["-c", "\"touch /tmp/test.txt && sleep 60 && echo \\\"hello muu\\\" | /usr/games/cowsay \" " ] + imagePullPolicy: Always + name: cowsayo + dnsPolicy: ClusterFirst + nodeSelector: + kubernetes.io/hostname: test-vk + tolerations: + - key: virtual-node.interlink/no-schedule + operator: Exists diff --git a/example/interlink-docker/vk/InterLinkConfig.yaml b/example/interlink-docker/vk/InterLinkConfig.yaml new file mode 100644 index 00000000..02f7f092 --- /dev/null +++ b/example/interlink-docker/vk/InterLinkConfig.yaml @@ -0,0 +1,11 @@ +VKTokenFile: "$HOME/interLink/token" +InterlinkAddress: "http://XXX.XXX.XXX.XXX" +CommandPrefix: "" +ExportPodData: true +ServiceAccount: "interlink" +Namespace: "vk" +VerboseLogging: true +ErrorsOnlyLogging: false +CPU: 100 +Memory: 128Gi +Pod: 100 diff --git a/example/interlink-docker/vk/deployment.yaml b/example/interlink-docker/vk/deployment.yaml new file mode 100644 index 00000000..109d1a4a --- /dev/null +++ b/example/interlink-docker/vk/deployment.yaml @@ -0,0 +1,101 @@ +--- + +apiVersion: apps/v1 +kind: Deployment +metadata: + name: test-vk + labels: + nodeName: test-vk +spec: + replicas: 1 + selector: + matchLabels: + nodeName: test-vk + template: + metadata: + labels: + nodeName: test-vk + spec: + initContainers: + - name: settoken + image: "docker.io/alpine:3" + command: ["sh", "-c"] + args: ["touch /opt/interlink/token"] + volumeMounts: + - name: token + mountPath: /opt/interlink + containers: + - name: jaeger + image: jaegertracing/all-in-one:1.51 + - name: inttw-vk + image: ghcr.io/intertwin-eu/interlink/virtual-kubelet-inttw:latest + #image: dciangot/vk:latest + imagePullPolicy: Always + #command: ["sleep", "infinity"] + env: + - name: NODENAME + value: test-vk + - name: POD_IP + valueFrom: + fieldRef: + fieldPath: status.podIP + #- name: KUBECONFIG + # value: /etc/interlink/kubeconfig + - name: KUBELET_PORT + value: "10250" + - name: CONFIGPATH + value: "/etc/interlink/InterLinkConfig.yaml" + - name: VKTOKENFILE + value: "/opt/interlink/token" + volumeMounts: + - name: config + mountPath: /etc/interlink/InterLinkConfig.yaml + subPath: InterLinkConfig.yaml + - name: token + mountPath: /opt/interlink + resources: + limits: + cpu: 500m + memory: 600Mi + requests: + cpu: 50m + memory: 100Mi + - name: refresh-token + image: ghcr.io/intertwin-eu/virtual-kubelet-inttw-refresh:latest + imagePullPolicy: Always + env: + - name: IAM_SERVER + value: "https://dodas-iam.cloud.cnaf.infn.it/" + # TODO load env IAM client from secret + - name: IAM_CLIENT_ID + value: "DUMMY" + - name: IAM_CLIENT_SECRET + value: "DUMMY" + - name: IAM_REFRESH_TOKEN + value: "DUMMY" + - name: IAM_VK_AUD + value: intertw-vk + - name: TOKEN_PATH + value: /opt/interlink/token + resources: + limits: + cpu: 500m + memory: 600Mi + requests: + cpu: 50m + memory: 100Mi + volumeMounts: + - name: token + mountPath: /opt/interlink + serviceAccountName: interlink + volumes: + - name: config + configMap: + # Provide the name of the ConfigMap you want to mount. + name: vk-config + - name: kubeconfig + configMap: + # Provide the name of the ConfigMap you want to mount. + name: vk-kubeconfig + - name: token + emptyDir: {} diff --git a/example/interlink-docker/vk/kustomization.yaml b/example/interlink-docker/vk/kustomization.yaml new file mode 100644 index 00000000..3890ba28 --- /dev/null +++ b/example/interlink-docker/vk/kustomization.yaml @@ -0,0 +1,13 @@ +apiVersion: kustomize.config.k8s.io/v1beta1 +kind: Kustomization +resources: + - ./deployment.yaml + - ./service-account.yaml + +configMapGenerator: + - name: vk-config-json + files: + - vk-cfg.json=vk-cfg.json + - name: vk-config + files: + - InterLinkConfig.yaml=InterLinkConfig.yaml \ No newline at end of file diff --git a/example/interlink-docker/vk/otecol_config.yaml b/example/interlink-docker/vk/otecol_config.yaml new file mode 100644 index 00000000..2f6bb992 --- /dev/null +++ b/example/interlink-docker/vk/otecol_config.yaml @@ -0,0 +1,41 @@ +extensions: + health_check: + pprof: + endpoint: 0.0.0.0:1777 + zpages: + endpoint: 0.0.0.0:55679 + +receivers: + otlp: + protocols: + grpc: + endpoint: 0.0.0.0:4319 + http: + endpoint: 0.0.0.0:4318 + +processors: + batch: + +exporters: + otlp: + endpoint: "localhost:4317" + tls: + insecure: true + logging: + verbosity: detailed + +service: + + pipelines: + + traces: + receivers: [otlp, opencensus, jaeger, zipkin] + processors: [batch] + exporters: [logging] + + metrics: + receivers: [otlp, opencensus, prometheus] + processors: [batch] + exporters: [logging] + + extensions: [health_check, pprof, zpages] \ No newline at end of file diff --git a/example/interlink-docker/vk/service-account.yaml b/example/interlink-docker/vk/service-account.yaml new file mode 100644 index 00000000..3dbaa7f2 --- /dev/null +++ b/example/interlink-docker/vk/service-account.yaml @@ -0,0 +1,97 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: interlink + namespace: vk +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: interlink-role + namespace: vk +rules: +- apiGroups: + - "coordination.k8s.io" + resources: + - leases + verbs: + - update + - create + - get + - list + - watch + - patch +- apiGroups: + - "" + resources: + - configmaps + - secrets + - services + - serviceaccounts + - namespaces + verbs: + - get + - list + - watch +- apiGroups: + - "" + resources: + - pods + verbs: + - delete + - get + - list + - watch + - patch +- apiGroups: + - "" + resources: + - nodes + verbs: + - create + - get +- apiGroups: + - "" + resources: + - nodes/status + verbs: + - update + - patch +- apiGroups: + - "" + resources: + - pods/status + verbs: + - update + - patch +- apiGroups: + - "" + resources: + - events + verbs: + - create + - patch +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: interlink-rolebinding +subjects: +- kind: ServiceAccount + name: interlink + namespace: vk +roleRef: + apiGroup: rbac.authorization.k8s.io + kind: ClusterRole + name: interlink-role +--- +apiVersion: v1 +kind: Secret +metadata: + name: interlink-secret + namespace: vk + annotations: + kubernetes.io/service-account.name: interlink + labels: + kubernetes.io/service-account.name: interlink +type: kubernetes.io/service-account-token diff --git a/example/interlink/__init__.py b/example/interlink/__init__.py new file mode 100644 index 00000000..e5559f1a --- /dev/null +++ b/example/interlink/__init__.py @@ -0,0 +1,2 @@ +from .provider import * +from .spec import * diff --git a/example/interlink/interlink.egg-info/PKG-INFO b/example/interlink/interlink.egg-info/PKG-INFO new file mode 100644 index 00000000..c028c6da --- /dev/null +++ b/example/interlink/interlink.egg-info/PKG-INFO @@ -0,0 +1,11 @@ +Metadata-Version: 2.1 +Name: interlink +Version: 0.0.1 +Summary: interlink provider library +Home-page: package URL +Author: Diego Ciangottini +Author-email: diego.ciangottini@gmail.com +Classifier: Programming Language :: Python :: 3 +Classifier: Operating System :: OS Independent +Requires-Python: >=3.6 +Description-Content-Type: text/markdown diff --git a/example/interlink/interlink.egg-info/SOURCES.txt b/example/interlink/interlink.egg-info/SOURCES.txt new file mode 100644 index 00000000..64dcb304 --- /dev/null +++ b/example/interlink/interlink.egg-info/SOURCES.txt @@ -0,0 +1,7 @@ +README.md +setup.py +src/interlink/interlink.egg-info/PKG-INFO +src/interlink/interlink.egg-info/SOURCES.txt +src/interlink/interlink.egg-info/dependency_links.txt +src/interlink/interlink.egg-info/requires.txt +src/interlink/interlink.egg-info/top_level.txt \ No newline at end of file diff --git a/example/interlink/interlink.egg-info/dependency_links.txt b/example/interlink/interlink.egg-info/dependency_links.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/example/interlink/interlink.egg-info/dependency_links.txt @@ -0,0 +1 @@ + diff --git a/example/interlink/interlink.egg-info/requires.txt b/example/interlink/interlink.egg-info/requires.txt new file mode 100644 index 00000000..909e9224 --- /dev/null +++ b/example/interlink/interlink.egg-info/requires.txt @@ -0,0 +1,2 @@ +fastapi +pydantic diff --git a/example/interlink/interlink.egg-info/top_level.txt b/example/interlink/interlink.egg-info/top_level.txt new file mode 100644 index 00000000..8b137891 --- /dev/null +++ b/example/interlink/interlink.egg-info/top_level.txt @@ -0,0 +1 @@ + diff --git a/example/interlink/provider.py b/example/interlink/provider.py new file mode 100644 index 00000000..40378a6e --- /dev/null +++ b/example/interlink/provider.py @@ -0,0 +1,53 @@ +from fastapi import FastAPI, HTTPException +from .spec import * +from typing import List + + +class Provider(FastAPI): + def __init__( + self, + docker_client, + ): + self.docker = docker_client + self.container_pod_map = {} + + def create(self, pod: Pod) -> CreateStruct: + raise HTTPException(status_code=500, detail="NOT IMPLEMENTED YET") + + def delete(self, pod: PodRequest) -> None: + raise HTTPException(status_code=500, detail="NOT IMPLEMENTED YET") + + def status(self, pod: PodRequest) -> PodStatus: + raise HTTPException(status_code=500, detail="NOT IMPLEMENTED YET") + + def logs(self, req: LogRequest) -> bytes: + raise HTTPException(status_code=500, detail="NOT IMPLEMENTED YET") + + def create_pod(self, pod: Pod) -> CreateStruct: + try: + self.create(pod) + except Exception as ex: + raise ex + + return "Containers created" + + def delete_pod(self, pod: PodRequest) -> str: + try: + self.delete(pod) + except Exception as ex: + raise ex + + return "Containers deleted" + + def get_status(self, pods: List[PodRequest]) -> List[PodStatus]: + pod = pods[0] + + return [self.Status(pod)] + + def get_logs(self, req: LogRequest) -> bytes: + try: + logContent = self.Logs(req) + except Exception as ex: + raise ex + + return logContent \ No newline at end of file diff --git a/example/interlink/spec.py b/example/interlink/spec.py new file mode 100644 index 00000000..34f9a983 --- /dev/null +++ b/example/interlink/spec.py @@ -0,0 +1,196 @@ +import datetime +from typing import Dict, List, Optional + +from pydantic import BaseModel, Field + +class Metadata(BaseModel): + name: Optional[str] = None + namespace: Optional[str] = None + uid: Optional[str] = None + annotations: Optional[Dict[str, str]] = Field({}) + labels: Optional[Dict[str, str]] = Field({}) + generate_name: Optional[str] = None + + +class VolumeMount(BaseModel): + name: str + mount_path: str + sub_path: Optional[str] = None + read_only: Optional[bool] = False + mount_propagation: Optional[str] = None + + +class ConfigMapKeySelector(BaseModel): + key: str + name: Optional[str] = None + optional: Optional[bool] = None + + +class SecretKeySelector(BaseModel): + key: str + name: Optional[str] = None + optional: Optional[bool] = None + + +class EnvVarSource(BaseModel): + config_map_key_ref: Optional[ConfigMapKeySelector] = None + secret_key_ref: Optional[SecretKeySelector] = None + + +class EnvVar(BaseModel): + name: str + value: Optional[str] = None + value_from: Optional[EnvVarSource] = None + + +class SecurityContext(BaseModel): + allow_privilege_escalation: Optional[bool] = None + privileged: Optional[bool] = None + proc_mount: Optional[str] = None + read_only_file_system: Optional[bool] = None + run_as_group: Optional[int] = None + run_as_non_root: Optional[bool] = None + run_as_user: Optional[int] = None + + +class Container(BaseModel): + name: str + image: str + tag: str = "latest" + command: List[str] + args: Optional[List[str]] = Field([]) + resources: Optional[dict] = Field({}) + volume_mounts: Optional[List[VolumeMount]] = Field([]) + env: Optional[List[EnvVar]] = None + security_context: Optional[SecurityContext] = None + + +class KeyToPath(BaseModel): + key: Optional[str] + path: str + mode: Optional[int] = None + + +class SecretVolumeSource(BaseModel): + secret_name: str + items: Optional[List[KeyToPath]] = Field([]) + optional: Optional[bool] = None + default_mode: Optional[int] = None + + +class ConfigMapVolumeSource(BaseModel): + name: str + items: Optional[List[KeyToPath]] = Field([]) + optional: Optional[bool] = None + default_mode: Optional[int] = None + + +# class VolumeSource(BaseModel): +# emptyDir: Optional[dict] = None +# secret: Optional[SecretSource] = None +# configMap: Optional[ConfigMapVolumeSource] = None + + +class PodVolume(BaseModel): + name: str + # volumeSource: Optional[VolumeSource] = None + empty_dir: Optional[dict] = None + secret: Optional[SecretVolumeSource] = None + config_map: Optional[ConfigMapVolumeSource] = None + + +class PodSpec(BaseModel): + containers: List[Container] + init_containers: Optional[List[Container]] = None + volumes: Optional[List[PodVolume]] = None + preemption_policy: Optional[str] = None + priority_class_name: Optional[str] = None + priority: Optional[int] = None + restart_policy: Optional[str] = None + termination_grace_period_seconds: Optional[int] = None + + +class PodRequest(BaseModel): + metadata: Metadata + spec: PodSpec + + +class ConfigMap(BaseModel): + metadata: Metadata + data: Optional[dict] + binary_data: Optional[dict] = None + type: Optional[str] = None + immutable: Optional[bool] = None + + +class Secret(BaseModel): + metadata: Metadata + data: Optional[dict] = None + string_data: Optional[dict] = None + type: Optional[str] = None + immutable: Optional[bool] = None + + +class Volume(BaseModel): + name: str + config_maps: Optional[List[ConfigMap]] = None + secrets: Optional[List[Secret]] = None + empty_dirs: Optional[List[str]] = None + + +class Pod(BaseModel): + pod: PodRequest + container: List[Volume] + + +class StateTerminated(BaseModel): + exit_code: int + reason: Optional[str] = None + + +class StateRunning(BaseModel): + started_at: Optional[str] = None + + +class StateWaiting(BaseModel): + message: Optional[str] = None + reason: Optional[str] = None + + +class ContainerStates(BaseModel): + terminated: Optional[StateTerminated] = None + running: Optional[StateRunning] = None + waiting: Optional[StateWaiting] = None + + +class ContainerStatus(BaseModel): + name: str + state: ContainerStates + + +class PodStatus(BaseModel): + name: str + uid: str + namespace: str + containers: List[ContainerStatus] + + +class LogOpts(BaseModel): + tail: Optional[int] = None + limit_bytes: Optional[int] = None + timestamps: Optional[bool] = None + previous: Optional[bool] = None + since_seconds: Optional[int] = None + since_time: Optional[datetime.datetime] = None + + +class LogRequest(BaseModel): + namespace: str + pod_uid: str + pod_name: str + container_name: str + opts: LogOpts + +class CreateStruct(BaseModel): + pod_uid: str + pod_jid: str \ No newline at end of file diff --git a/example/provider_demo.py b/example/provider_demo.py new file mode 100644 index 00000000..83e6774a --- /dev/null +++ b/example/provider_demo.py @@ -0,0 +1,248 @@ +import interlink + +from fastapi.responses import PlainTextResponse +from fastapi import FastAPI, HTTPException +from typing import List +import docker +import re +import os + + +docker_client = docker.DockerClient() +# dockerCLI = docker.DockerClient(base_url="unix:///Users/dciangot/.docker/run/docker.sock") + +app = FastAPI() + + +class MyProvider(interlink.provider.Provider): + def __init__(self, docker): + super().__init__(docker) + + # Recover already running containers refs + self.container_pod_map = {} + statuses = self.docker.api.containers(all=True) + for status in statuses: + name = status["Names"][0] + if len(name.split("-")) > 1: + uid = "-".join(name.split("-")[-5:]) + self.container_pod_map.update({uid: [status["Id"]]}) + print(self.container_pod_map) + + def dump_volumes( + self, pods: List[interlink.PodVolume], volumes: List[interlink.Volume] + ) -> List[str]: + + dataList = [] + + # Match data source information (actual bytes) to the mount ref in pod description + for v in volumes: + if v.config_maps: + for data_source in v.config_maps: + for ref in pods: + pod_mount = ref.volume_source.config_map + if pod_mount: + if ref.name == data_source.metadata.name: + for filename, content in data_source.data.items(): + # write content to file + path = f"{data_source.metadata.namespace}-{data_source.metadata.name}/{filename}" + try: + os.makedirs( + os.path.dirname(path), exist_ok=True + ) + with open(path, "w") as f: + f.write(content) + except Exception as ex: + raise HTTPException(status_code=500, detail=ex) + + # dump list of written files + dataList.append(path) + + if v.secrets: + pass + + if v.empty_dirs: + pass + return dataList + + def create(self, pod: interlink.Pod) -> None: + container = pod.pod.spec.containers[0] + + if pod.pod.spec.volumes: + _ = self.dump_volumes(pod.pod.spec.volumes, pod.container) + + volumes = [] + if container.volume_mounts: + for mount in container.volume_mounts: + if mount.sub_path: + volumes.append( + f"{pod.pod.metadata.namespace}-{mount.name}/{mount.sub_path}:{mount.mount_path}" + ) + else: + volumes.append( + f"{pod.pod.metadata.namespace}-{mount.name}:{mount.mount_path}" + ) + + try: + cmds = " ".join(container.command) + args = " ".join(container.args) + docker_container = self.docker.containers.run( + f"{container.image}:{container.tag}", + f"{cmds} {args}", + name=f"{container.name}-{pod.pod.metadata.uid}", + detach=True, + volumes=volumes, + # runtime="nvidia", + # device_requests=[ + # docker.types.DeviceRequest(device_ids=["0"], capabilities=[['gpu']])] + ) + print(docker_container) + docker_run_id = docker_container.id + except Exception as ex: + raise HTTPException(status_code=500, detail=ex) + + self.container_pod_map.update({pod.pod.metadata.uid: [docker_run_id]}) + print(self.container_pod_map) + + print(pod) + + def delete(self, pod: interlink.PodRequest) -> None: + try: + print(f"docker rm -f {self.container_pod_map[pod.metadata.uid][0]}") + container = self.docker.containers.get( + self.container_pod_map[pod.metadata.uid][0] + ) + container.remove(force=True) + self.container_pod_map.pop(pod.metadata.uid) + except: + raise HTTPException(status_code=404, detail="No containers found for UUID") + print(pod) + return + + def status(self, pod: interlink.PodRequest) -> interlink.PodStatus: + print(self.container_pod_map) + print(pod.metadata.uid) + try: + container = self.docker.containers.get( + self.container_pod_map[pod.metadata.uid][0] + ) + status = container.status + except: + raise HTTPException(status_code=404, detail="No containers found for UUID") + + print(status) + + if status == "running": + try: + statuses = self.docker.api.containers( + filters={"status": "running", "id": container.id} + ) + print(statuses) + started_at = statuses[0]["Created"] + except Exception as ex: + raise HTTPException(status_code=500, detail=ex) + + return interlink.PodStatus( + name=pod.metadata.name, + UID=pod.metadata.uid, + namespace=pod.metadata.namespace, + containers=[ + interlink.ContainerStatus( + name=pod.spec.containers[0].name, + state=interlink.ContainerStates( + running=interlink.StateRunning(started_at=started_at), + waiting=None, + terminated=None, + ), + ) + ], + ) + elif status == "exited": + + try: + statuses = self.docker.api.containers( + filters={"status": "exited", "id": container.id} + ) + print(statuses) + reason = statuses[0]["Status"] + pattern = re.compile(r"Exited \((.*?)\)") + + exitCode = -1 + for match in re.findall(pattern, reason): + exitCode = int(match) + except Exception as ex: + raise HTTPException(status_code=500, detail=ex) + + return interlink.PodStatus( + name=pod.metadata.name, + UID=pod.metadata.uid, + namespace=pod.metadata.namespace, + containers=[ + interlink.ContainerStatus( + name=pod.spec.containers[0].name, + state=interlink.ContainerStates( + running=None, + waiting=None, + terminated=interlink.StateTerminated( + reason=reason, exitCode=exitCode + ), + ), + ) + ], + ) + + return interlink.PodStatus( + name=pod.metadata.name, + UID=pod.metadata.uid, + namespace=pod.metadata.namespace, + containers=[ + interlink.ContainerStatus( + name=pod.spec.containers[0].name, + state=interlink.ContainerStates( + running=None, + waiting=None, + terminated=interlink.StateTerminated( + reason="Completed", exitCode=0 + ), + ), + ) + ], + ) + + def Logs(self, req: interlink.LogRequest) -> bytes: + # TODO: manage more complicated multi container pod + # THIS IS ONLY FOR DEMONSTRATION + print(req.pod_uid) + print(self.container_pod_map[req.pod_uid]) + try: + container = self.docker.containers.get( + self.container_pod_map[req.pod_uid][0] + ) + # log = container.logs(timestamps=req.Opts.Timestamps, tail=req.Opts.Tail) + log = container.logs() + print(log) + except: + raise HTTPException(status_code=404, detail="No containers found for UUID") + return log + + +provider_new = MyProvider(docker_client) + + +@app.post("/create") +async def create_pod(pods: List[interlink.Pod]) -> str: + return provider_new.create_pod(pods) + + +@app.post("/delete") +async def delete_pod(pod: interlink.PodRequest) -> str: + return provider_new.delete_pod(pod) + + +@app.get("/status") +async def status_pod(pods: List[interlink.PodRequest]) -> List[interlink.PodStatus]: + return provider_new.get_status(pods) + + +@app.get("/getLogs", response_class=PlainTextResponse) +async def get_logs(req: interlink.LogRequest) -> bytes: + return provider_new.get_logs(req) \ No newline at end of file diff --git a/example/requirements.txt b/example/requirements.txt new file mode 100644 index 00000000..909e9224 --- /dev/null +++ b/example/requirements.txt @@ -0,0 +1,2 @@ +fastapi +pydantic diff --git a/example/setup.py b/example/setup.py new file mode 100644 index 00000000..45bfc93e --- /dev/null +++ b/example/setup.py @@ -0,0 +1,34 @@ +import setuptools + +with open("README.md", "r", encoding = "utf-8") as fh: + long_description = fh.read() + +install_requires = [] +with open("requirements.txt") as f: + for line in f.readlines(): + req = line.strip() + if not req or req.startswith(("-e", "#")): + continue + install_requires.append(req) + +setuptools.setup( + name = "interlink", + version = "0.0.1", + author = "Diego Ciangottini", + author_email = "diego.ciangottini@gmail.com", + description = "interlink provider library", + long_description = long_description, + long_description_content_type = "text/markdown", + url = "package URL", + project_urls = { + }, + classifiers = [ + "Programming Language :: Python :: 3", + "Operating System :: OS Independent", + ], + packages = ["interlink"], + python_requires = ">=3.6", + install_requires = install_requires +) + + diff --git a/example/test_pod.yaml b/example/test_pod.yaml new file mode 100644 index 00000000..7cf0809b --- /dev/null +++ b/example/test_pod.yaml @@ -0,0 +1,36 @@ +apiVersion: v1 +kind: Pod +metadata: + name: interlink-quickstart + namespace: default +spec: + nodeSelector: + kubernetes.io/hostname: my-civo-node + automountServiceAccountToken: false + containers: + - args: + - "\"600\"" + command: + - sleep + image: "docker://ubuntu" + imagePullPolicy: Always + name: my-container + resources: + limits: + cpu: "1" + memory: 1Gi + requests: + cpu: "1" + memory: 1Gi + tolerations: + - key: virtual-node.interlink/no-schedule + operator: Exists + - effect: NoExecute + key: node.kubernetes.io/not-ready + operator: Exists + tolerationSeconds: 300 + - effect: NoExecute + key: node.kubernetes.io/unreachable + operator: Exists + tolerationSeconds: 300 +