diff --git a/.github/workflows/make.yml b/.github/workflows/make.yml index 5e4e53b0..cb80ba89 100644 --- a/.github/workflows/make.yml +++ b/.github/workflows/make.yml @@ -1,18 +1,73 @@ +#name: make +#on: push +#jobs: +# make: +# # https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job +# runs-on: ubuntu-22.04 +# steps: +# - uses: actions/checkout@v2 +# - uses: actions/setup-java@v1 +# with: +# java-version: 11 +# - uses: actions/setup-node@v2 +# with: +# node-version: 8 +# - run: | +# docker swarm init +# - run: docker compose -f docker-compose-dev.yml pull cadvisor node-exporter prometheus rabbit redis +# - run: make test name: make on: push + jobs: make: - # https://docs.github.com/en/actions/using-jobs/choosing-the-runner-for-a-job runs-on: ubuntu-22.04 + + services: + docker: + image: docker:24.0.7-dind + options: --privileged + env: + DOCKER_TLS_CERTDIR: "" + ports: + - 2375:2375 + + env: + DOCKER_HOST: tcp://localhost:2375 + DOCKER_DRIVER: overlay2 + steps: - - uses: actions/checkout@v2 - - uses: actions/setup-java@v1 - with: - java-version: 11 - - uses: actions/setup-node@v2 - with: - node-version: 8 - - run: | - docker swarm init - - run: docker compose -f docker-compose-dev.yml pull cadvisor node-exporter prometheus rabbit redis - - run: make test + - uses: actions/checkout@v2 + + - uses: actions/setup-java@v1 + with: + java-version: 11 + + - uses: actions/setup-node@v2 + with: + node-version: 8 + + - name: Wait for Docker to start + run: | + sleep 10 + docker version + + - name: Initialize Docker Swarm + run: docker swarm init + + - name: Pull required images + run: docker compose -f docker-compose-dev.yml pull cadvisor node-exporter prometheus rabbit redis + +# - name: Wait for RabbitMQ to be ready +# run: | +# echo "Waiting for RabbitMQ..." +# for i in {1..30}; do +# nc -z rabbit 5672 && echo "RabbitMQ is up!" && exit 0 +# echo "Still waiting for RabbitMQ..." +# sleep 3 +# done +# echo "RabbitMQ not responding on port 5672" >&2 +# docker ps +# exit 1 + - name: Run make test + run: make test diff --git a/Makefile b/Makefile index 53cfc5ff..fd4a76a5 100644 --- a/Makefile +++ b/Makefile @@ -17,8 +17,24 @@ start-dev: start-rabbitmq-cluster start-dev-platform start-dev-elk: start-rabbitmq-cluster start-dev-platform start-dev-elk start-rabbitmq-cluster: + @echo "πŸ“‘ Starting RabbitMQ cluster..." cd rabbitmq-cluster && make start - + @echo "βœ… RabbitMQ cluster started." + +# start-rabbitmq: +# @echo "πŸ“‘ Starting standalone RabbitMQ container on 'hobbit' network..." +# @docker network inspect hobbit >/dev/null || (echo "❌ Network 'hobbit' not found. Run 'make create-networks' first." && exit 1) +# @docker rm -f hobbit-rabbitmq >/dev/null 2>&1 || true +# @docker run -d \ +# --name hobbit-rabbitmq \ +# --network hobbit \ +# -p 5672:5672 \ +# -p 15672:15672 \ +# rabbitmq:3-management +# @docker network connect hobbit-core hobbit-rabbitmq +# @echo "βœ… RabbitMQ is running at:" +# @echo " AMQP: amqp://localhost:5672" +# @echo " Management: http://localhost:15672 (user: guest, pass: guest)" start-dev-platform: docker-compose -f docker-compose-dev.yml up -d @@ -40,9 +56,20 @@ build-dev-storage-image: docker build -t hobbitproject/hobbit-storage-service:dev --file ./platform-storage/storage-service/Dockerfile . create-networks: - @docker network inspect hobbit >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.100.0/24 hobbit && echo "Created network: hobbit") - @docker network inspect hobbit-core >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.101.0/24 hobbit-core && echo "Created network: hobbit-core") - @docker network inspect hobbit-services >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.102.0/24 hobbit-services && echo "Created network: hobbit-services") +# @docker network inspect hobbit >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.100.0/24 hobbit && echo "Created network: hobbit") +# @docker network inspect hobbit-core >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.101.0/24 hobbit-core && echo "Created network: hobbit-core") +# @docker network inspect hobbit-services >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.102.0/24 hobbit-services && echo "Created network: hobbit-services") + @echo "πŸ”Œ Checking or creating Docker network: hobbit" + @docker network inspect hobbit >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.100.0/24 hobbit && echo "βœ… Created network: hobbit") + + @echo "πŸ”Œ Checking or creating Docker network: hobbit-core" + @docker network inspect hobbit-core >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.101.0/24 hobbit-core && echo "βœ… Created network: hobbit-core") + + @echo "πŸ”Œ Checking or creating Docker network: hobbit-services" + @docker network inspect hobbit-services >/dev/null || (docker network create -d overlay --attachable --subnet 172.16.102.0/24 hobbit-services && echo "βœ… Created network: hobbit-services") + + docker network ls --no-trunc + @echo "🌐 Docker networks ready." set-keycloak-permissions: @chmod --changes 777 config/keycloak @@ -61,15 +88,39 @@ run-platform-elk: docker stack deploy --compose-file docker-compose-elk.yml elk docker stack deploy --compose-file docker-compose.yml platform +# test: create-networks install-parent-pom start-rabbitmq-cluster +# make --directory=platform-controller test +# cd platform-storage/storage-service && mvn --quiet --update-snapshots clean test +# cd analysis-component && mvn --quiet --update-snapshots clean test +# cd hobbit-gui/gui-client && sh -c 'test "$$TRAVIS" = "true" && npm --quiet ci; true' && sh -c 'test "$$TRAVIS" = "true" || npm --quiet install; true' && npm --quiet run lint && npm --quiet run build-prod +# cd hobbit-gui/gui-serverbackend && mvn --quiet --update-snapshots clean test + test: create-networks install-parent-pom + @echo "πŸ”§ Running tests in platform-controller..." make --directory=platform-controller test + + @echo "πŸ’Ύ Running tests in platform-storage/storage-service..." cd platform-storage/storage-service && mvn --quiet --update-snapshots clean test + + @echo "πŸ“Š Running tests in analysis-component..." cd analysis-component && mvn --quiet --update-snapshots clean test - cd hobbit-gui/gui-client && sh -c 'test "$$TRAVIS" = "true" && npm --quiet ci; true' && sh -c 'test "$$TRAVIS" = "true" || npm --quiet install; true' && npm --quiet run lint && npm --quiet run build-prod + + @echo "πŸ–₯️ Running lint and build in hobbit-gui/gui-client..." + cd hobbit-gui/gui-client && \ + sh -c 'test "$$TRAVIS" = "true" && npm --quiet ci; true' && \ + sh -c 'test "$$TRAVIS" = "true" || npm --quiet install; true' && \ + npm --quiet run lint && \ + npm --quiet run build-prod + + @echo "πŸ”™ Running tests in hobbit-gui/gui-serverbackend..." cd hobbit-gui/gui-serverbackend && mvn --quiet --update-snapshots clean test + @echo "βœ… All tests completed successfully!" + install-parent-pom: + @echo "πŸ“¦ Installing parent POM..." cd parent-pom && mvn --quiet install + @echo "βœ… Parent POM installed." local-controller: lc-build lc-run diff --git a/docker-compose-dev-temp.yml b/docker-compose-dev-temp.yml new file mode 100644 index 00000000..f59216a7 --- /dev/null +++ b/docker-compose-dev-temp.yml @@ -0,0 +1,61 @@ +version: '3.3' +services: + # message bus + rabbit: + image: rabbitmq:management + networks: + - hobbit + - hobbit-core + ports: + - "8081:15672" + # Forwarding the port for testing + - "5672:5672" + + # DB for controller + redis: + image: redis:4.0.7 + volumes: + - ./config/redis-db:/data + command: ["redis-server", "/data/redis.conf"] + networks: + - hobbit-core + ports: + # Forwarding the port for tests + - "6379:6379" + + node-exporter: + image: prom/node-exporter + networks: + - hobbit-core + + cadvisor: + image: gcr.io/cadvisor/cadvisor + networks: + - hobbit-core + volumes: + - /:/rootfs:ro + - /dev/disk:/dev/disk:ro + - /sys:/sys:ro + - /var/lib/docker:/var/lib/docker:ro + - /var/run:/var/run:rw + + prometheus: + image: prom/prometheus + networks: + - hobbit-core + ports: + - "9090:9090" + volumes: + - ./config/prometheus:/config:ro + command: --config.file=/config/prometheus.conf + depends_on: + - node-exporter + - cadvisor + +networks: + hobbit: + external: true + driver: overlay + hobbit-core: + external: true + driver: overlay diff --git a/docker-compose-dev.yml b/docker-compose-dev.yml index 888d1661..4ac128c1 100644 --- a/docker-compose-dev.yml +++ b/docker-compose-dev.yml @@ -9,6 +9,7 @@ services: HOBBIT_RABBIT_IMAGE: "rabbitmq:management" HOBBIT_RABBIT_HOST: "rabbit" HOBBIT_REDIS_HOST: "redis" + HOBBIT_RABBIT_EXPERIMENTS_HOST: "redis" DEPLOY_ENV: "develop" GITLAB_USER: "${GITLAB_USER}" GITLAB_EMAIL: "${GITLAB_EMAIL}" @@ -20,6 +21,7 @@ services: USE_GITLAB: "false" LOCAL_METADATA_DIRECTORY: "/metadata" DOCKER_AUTOPULL: "0" + RUN_ON: "docker" volumes: - /var/run/docker.sock:/var/run/docker.sock - ./metadata:/metadata diff --git a/kubernetes-yaml-files/00-1-rabbitmq-pv.yaml b/kubernetes-yaml-files/00-1-rabbitmq-pv.yaml new file mode 100644 index 00000000..4f975356 --- /dev/null +++ b/kubernetes-yaml-files/00-1-rabbitmq-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: rabbitmq-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain # or Delete, depending on your needs + storageClassName: "" # Important: Leave this empty if not using Storage Classes + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/rabbitmq diff --git a/kubernetes-yaml-files/00-rabbitmq-pvc.yaml b/kubernetes-yaml-files/00-rabbitmq-pvc.yaml new file mode 100644 index 00000000..44b71796 --- /dev/null +++ b/kubernetes-yaml-files/00-rabbitmq-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: rabbitmq-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/kubernetes-yaml-files/01-rabbitmq-deployment.yaml b/kubernetes-yaml-files/01-rabbitmq-deployment.yaml new file mode 100644 index 00000000..46eb362e --- /dev/null +++ b/kubernetes-yaml-files/01-rabbitmq-deployment.yaml @@ -0,0 +1,80 @@ +#apiVersion: apps/v1 +#kind: Deployment +#metadata: +# name: rabbitmq +# labels: +# app: rabbitmq +#spec: +# replicas: 1 +# selector: +# matchLabels: +# app: rabbitmq +# template: +# metadata: +# labels: +# app: rabbitmq +# spec: +# containers: +# - name: rabbitmq +# image: rabbitmq:3-management +# ports: +# - containerPort: 5672 # AMQP protocol (for applications) +# - containerPort: 15672 # Management UI (for admins) +# env: +# - name: RABBITMQ_DEFAULT_USER +# value: "guest" +# - name: RABBITMQ_DEFAULT_PASS +# value: "guest" +# volumeMounts: +# - mountPath: /var/lib/rabbitmq +# name: rabbitmq-storage +# volumes: +# - name: rabbitmq-storage +# persistentVolumeClaim: +# claimName: rabbitmq-pvc +apiVersion: apps/v1 +kind: Deployment +metadata: + name: rabbitmq + labels: + app: rabbitmq +spec: + replicas: 1 + selector: + matchLabels: + app: rabbitmq + template: + metadata: + labels: + app: rabbitmq + spec: + containers: + - name: rabbitmq + image: rabbitmq:3-management + securityContext: + runAsUser: 79184 # Replace with the actual numeric user ID + runAsGroup: 10000 + ports: + - containerPort: 5672 # AMQP protocol (for applications) + - containerPort: 15672 # Management UI (for admins) + env: + - name: RABBITMQ_DEFAULT_USER + value: "guest" + - name: RABBITMQ_DEFAULT_PASS + value: "guest" + volumeMounts: + - mountPath: /var/lib/rabbitmq + name: rabbitmq-storage + initContainers: + - name: configure-rabbitmq + image: busybox + command: ['sh', '-c', 'echo "loopback_users.guest = false" > /etc/rabbitmq/rabbitmq.conf'] + volumeMounts: + - mountPath: /etc/rabbitmq + name: rabbitmq-config + volumes: + - name: rabbitmq-storage + persistentVolumeClaim: + claimName: rabbitmq-pvc + - name: rabbitmq-config + emptyDir: {} diff --git a/kubernetes-yaml-files/02-rabbitmq-service.yaml b/kubernetes-yaml-files/02-rabbitmq-service.yaml new file mode 100644 index 00000000..0420a296 --- /dev/null +++ b/kubernetes-yaml-files/02-rabbitmq-service.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Service +metadata: + name: rabbitmq + labels: + app: rabbitmq +spec: + selector: + app: rabbitmq + ports: + - name: amqp + port: 5672 # Exposes AMQP port inside the cluster + targetPort: 5672 + - name: management + port: 15672 # Exposes the Management UI inside the cluster + targetPort: 15672 + type: ClusterIP # Internal access only diff --git a/kubernetes-yaml-files/03-01-virtuoso-pv.yaml b/kubernetes-yaml-files/03-01-virtuoso-pv.yaml new file mode 100644 index 00000000..93fab5a2 --- /dev/null +++ b/kubernetes-yaml-files/03-01-virtuoso-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: virtuoso-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain # or Delete, as needed + storageClassName: "" # Important: Leave empty if not using Storage Classes + nfs: + server: 131.234.29.64 # Your NFS server IP + path: /data/nfs/kubedata/virtuoso # The path to your exported NFS share diff --git a/kubernetes-yaml-files/03-virtuoso-pvc.yaml b/kubernetes-yaml-files/03-virtuoso-pvc.yaml new file mode 100644 index 00000000..7f6503bd --- /dev/null +++ b/kubernetes-yaml-files/03-virtuoso-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: virtuoso-pvc + labels: + app: virtuoso +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/kubernetes-yaml-files/04-virtuoso-deployment.yaml b/kubernetes-yaml-files/04-virtuoso-deployment.yaml new file mode 100644 index 00000000..8ea2f1c4 --- /dev/null +++ b/kubernetes-yaml-files/04-virtuoso-deployment.yaml @@ -0,0 +1,41 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: virtuoso + labels: + app: virtuoso +spec: + replicas: 1 + selector: + matchLabels: + app: virtuoso + template: + metadata: + labels: + app: virtuoso + spec: + securityContext: # This is the correct place for fsGroup + fsGroup: 1000 + containers: + - name: virtuoso + image: hobbitproject/virtuoso_opensource:v07.20.3217 + securityContext: + runAsUser: 79184 # Replace with the actual numeric user ID + runAsGroup: 10000 + ports: + - containerPort: 8890 # HTTP (SPARQL endpoint) + - containerPort: 1111 # ISQL (command-line interface) + env: + - name: VIRTUOSO_DBNAME + value: "virtuoso" + - name: VIRTUOSO_USER + value: "dba" + - name: VIRTUOSO_PASSWORD + value: "dba" + volumeMounts: + - mountPath: /opt/virtuoso-opensource/database + name: virtuoso-storage + volumes: + - name: virtuoso-storage + persistentVolumeClaim: + claimName: virtuoso-pvc diff --git a/kubernetes-yaml-files/05-virtuoso-service.yaml b/kubernetes-yaml-files/05-virtuoso-service.yaml new file mode 100644 index 00000000..e3571686 --- /dev/null +++ b/kubernetes-yaml-files/05-virtuoso-service.yaml @@ -0,0 +1,20 @@ +apiVersion: v1 +kind: Service +metadata: + name: virtuoso-service + labels: + app: virtuoso +spec: + selector: + app: virtuoso + ports: + - protocol: TCP + port: 8890 + targetPort: 8890 + name: sparql + nodePort: 31001 + - protocol: TCP + port: 1111 + targetPort: 1111 + name: isql + type: NodePort diff --git a/kubernetes-yaml-files/06-storage-deployment.yaml b/kubernetes-yaml-files/06-storage-deployment.yaml new file mode 100644 index 00000000..7270bcbc --- /dev/null +++ b/kubernetes-yaml-files/06-storage-deployment.yaml @@ -0,0 +1,59 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hobbit-storage-service-deployment + labels: + app: hobbit-storage-service +spec: + replicas: 1 + selector: + matchLabels: + app: hobbit-storage-service + template: + metadata: + labels: + app: hobbit-storage-service + spec: + securityContext: + fsGroup: 1000 + containers: + - name: storage-service + #image: hub.cs.upb.de/dice-research/images/hobbit-storage_service:dev0.0.5 + image: hobbitproject/hobbit-storage-service:latest + imagePullPolicy: IfNotPresent +# ports: +# - containerPort: 8080 + env: + - name: JAVA_OPTS + value: "-Xmx256m -Xms128m" + - name: HOBBIT_RABBIT_HOST + value: rabbitmq # The name of the RabbitMQ service (02-rabbitmq-service.yaml) + - name: SPARQL_ENDPOINT_URL + value: "http://virtuoso-service:8890/sparql" + - name: SPARQL_ENDPOINT_USERNAME + value: "HobbitPlatform" + - name: SPARQL_ENDPOINT_PASSWORD + value: "Password" + #- name: SPARQL_ENDPOINT_DATABASE + #value: "http://virtuoso-service:8890/sparql" + - name: hobbit.storage + value: rabbitmq + volumeMounts: + - mountPath: /path/to/mount + name: storage-volume + # livenessProbe: + # httpGet: + # path: /actuator/health + # port: 8080 + # initialDelaySeconds: 30 + # periodSeconds: 10 + # readinessProbe: + # httpGet: + # path: /actuator/health + # port: 8080 + # initialDelaySeconds: 30 + # periodSeconds: 10 + volumes: + - name: storage-volume + persistentVolumeClaim: + claimName: hobbit-storage-pvc diff --git a/kubernetes-yaml-files/07-storage-service.yaml b/kubernetes-yaml-files/07-storage-service.yaml new file mode 100644 index 00000000..a92a987f --- /dev/null +++ b/kubernetes-yaml-files/07-storage-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: hobbit-storage-service + labels: + app: hobbit-storage-service +spec: + selector: + app: hobbit-storage-service + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + type: NodePort # change for deployment ( for test keep it) diff --git a/kubernetes-yaml-files/08-01-storage-pv.yaml b/kubernetes-yaml-files/08-01-storage-pv.yaml new file mode 100644 index 00000000..472f1f36 --- /dev/null +++ b/kubernetes-yaml-files/08-01-storage-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: hobbit-storage-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain # Or Delete, as needed + storageClassName: "" # Important: Leave empty if not using Storage Classes + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/hobbit diff --git a/kubernetes-yaml-files/08-storage-pvc.yaml b/kubernetes-yaml-files/08-storage-pvc.yaml new file mode 100644 index 00000000..ec610886 --- /dev/null +++ b/kubernetes-yaml-files/08-storage-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: hobbit-storage-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/kubernetes-yaml-files/09-01-redis-pv.yaml b/kubernetes-yaml-files/09-01-redis-pv.yaml new file mode 100644 index 00000000..7c4b751b --- /dev/null +++ b/kubernetes-yaml-files/09-01-redis-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: redis-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Delete + storageClassName: "" # Important: Leave empty if not using Storage Classes + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/redis diff --git a/kubernetes-yaml-files/09-redis-pvc.yaml b/kubernetes-yaml-files/09-redis-pvc.yaml new file mode 100644 index 00000000..e258a3a1 --- /dev/null +++ b/kubernetes-yaml-files/09-redis-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: redis-pvc + labels: + app: redis +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/kubernetes-yaml-files/10-redis-deployment.yaml b/kubernetes-yaml-files/10-redis-deployment.yaml new file mode 100644 index 00000000..fef02142 --- /dev/null +++ b/kubernetes-yaml-files/10-redis-deployment.yaml @@ -0,0 +1,31 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: redis + labels: + app: redis +spec: + replicas: 1 + selector: + matchLabels: + app: redis + template: + metadata: + labels: + app: redis + spec: + securityContext: + fsGroup: 1000 + runAsUser: 79184 + containers: + - name: redis + image: redis:6.2.6 + ports: + - containerPort: 6379 + volumeMounts: + - mountPath: /data + name: redis-storage + volumes: + - name: redis-storage + persistentVolumeClaim: + claimName: redis-pvc diff --git a/kubernetes-yaml-files/100-debug.yaml b/kubernetes-yaml-files/100-debug.yaml new file mode 100644 index 00000000..02dc2b65 --- /dev/null +++ b/kubernetes-yaml-files/100-debug.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: Pod +metadata: + name: debug-pod +spec: + containers: + - name: debug-tools + image: nicolaka/netshoot + command: ["sh", "-c", "while true; do sleep 3600; done"] + diff --git a/kubernetes-yaml-files/11-redis-service.yaml b/kubernetes-yaml-files/11-redis-service.yaml new file mode 100644 index 00000000..a5df6ec2 --- /dev/null +++ b/kubernetes-yaml-files/11-redis-service.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: redis-service + labels: + app: redis +spec: + selector: + app: redis + ports: + - protocol: TCP + port: 6379 + targetPort: 6379 + type: ClusterIP # Change to LoadBalancer or NodePort if needed for external access diff --git a/kubernetes-yaml-files/12-01-controller-pv.yaml b/kubernetes-yaml-files/12-01-controller-pv.yaml new file mode 100644 index 00000000..68f7f2c5 --- /dev/null +++ b/kubernetes-yaml-files/12-01-controller-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: controller-pv +spec: + capacity: + storage: 2Gi # Increased to 2Gi to match the PVC request + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain # Or Delete, as needed + storageClassName: "" # Important: Leave empty if not using Storage Classes + nfs: + server: 131.234.29.64 # Your NFS server IP + path: /data/nfs/kubedata/controller diff --git a/kubernetes-yaml-files/12-controller-pvc.yaml b/kubernetes-yaml-files/12-controller-pvc.yaml new file mode 100644 index 00000000..304f57f7 --- /dev/null +++ b/kubernetes-yaml-files/12-controller-pvc.yaml @@ -0,0 +1,12 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: controller-pvc + labels: + app: controller +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi diff --git a/kubernetes-yaml-files/13-controller-deployment.yaml b/kubernetes-yaml-files/13-controller-deployment.yaml new file mode 100644 index 00000000..d8ec1362 --- /dev/null +++ b/kubernetes-yaml-files/13-controller-deployment.yaml @@ -0,0 +1,84 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: controller + labels: + app: controller +spec: + replicas: 1 + selector: + matchLabels: + app: controller + template: + metadata: + labels: + app: controller + spec: + serviceAccountName: controller-service-sa + securityContext: + fsGroup: 1000 + containers: + - name: controller + image: hub.cs.upb.de/dice-research/images/hobbit-platform_controller:dev0.0.83 + imagePullPolicy: IfNotPresent + securityContext: + allowPrivilegeEscalation: false + runAsUser: 0 + ports: + - containerPort: 8080 + env: + - name: HOBBIT_RABBIT_HOST + value: rabbitmq # The name of the RabbitMQ service (02-rabbitmq-service.yaml) + - name: SPARQL_ENDPOINT_URL + value: virtuoso + - name: HOBBIT_RABBIT_IMAGE + value: "rabbitmq:management" + - name: SPARQL_ENDPOINT_USERNAME + value: "dba" + - name: SPARQL_ENDPOINT_PASSWORD + value: "dba" + - name: SPARQL_ENDPOINT_DATABASE + value: "virtuoso:8890" + - name: HOBBIT_REDIS_HOST + value: "redis-service" + - name: GITLAB_TOKEN + valueFrom: + secretKeyRef: + name: gitlab-secret + key: GITLAB_TOKEN + - name: GITLAB_USER + value: "gitadmin" + - name: GITLAB_EMAIL + value: "gitadmin@project-hobbit.eu" + - name: DEPLOY_ENV + value: "develop" + - name: MAX_EXECUTION_TIME + value: "1200000" + - name: CONTAINER_PARENT_CHECK + value: "0" + volumeMounts: + - mountPath: /data + name: controller-storage # Only if you are using a PVC + - mountPath: /var/run/secrets/kubernetes.io/serviceaccount + name: kube-api-access + readOnly: true + volumes: + - name: controller-storage + persistentVolumeClaim: + claimName: controller-pvc # Only if you are using a PVC + - name: kube-api-access + projected: + sources: + - serviceAccountToken: + expirationSeconds: 3607 + path: token + - configMap: + name: kube-root-ca.crt + items: + - key: ca.crt + path: ca.crt + - downwardAPI: + items: + - path: namespace + fieldRef: + fieldPath: metadata.namespace diff --git a/kubernetes-yaml-files/14-controller-service.yaml b/kubernetes-yaml-files/14-controller-service.yaml new file mode 100644 index 00000000..a4790c8d --- /dev/null +++ b/kubernetes-yaml-files/14-controller-service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: controller-service + labels: + app: controller +spec: + selector: + app: controller + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + name: http + type: ClusterIP # Change to LoadBalancer or NodePort if needed diff --git a/kubernetes-yaml-files/15-controller-role.yaml b/kubernetes-yaml-files/15-controller-role.yaml new file mode 100644 index 00000000..32ddcca4 --- /dev/null +++ b/kubernetes-yaml-files/15-controller-role.yaml @@ -0,0 +1,24 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: Role +metadata: + name: node-pod-service-reader + namespace: default +rules: + - apiGroups: [""] + resources: ["nodes", "pods", "services", "namespaces"] + verbs: ["create", "get", "update", "delete", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: RoleBinding +metadata: + name: controller-service-account-binding + namespace: default +subjects: + - kind: ServiceAccount + name: controller-service-sa + namespace: default +roleRef: + kind: Role + name: node-pod-service-reader + apiGroup: rbac.authorization.k8s.io diff --git a/kubernetes-yaml-files/16-controller-service-account.yaml b/kubernetes-yaml-files/16-controller-service-account.yaml new file mode 100644 index 00000000..3d60e783 --- /dev/null +++ b/kubernetes-yaml-files/16-controller-service-account.yaml @@ -0,0 +1,5 @@ +apiVersion: v1 +kind: ServiceAccount +metadata: + name: controller-service-sa + namespace: default diff --git a/kubernetes-yaml-files/17-cluster-role.yaml b/kubernetes-yaml-files/17-cluster-role.yaml new file mode 100644 index 00000000..9ae872c7 --- /dev/null +++ b/kubernetes-yaml-files/17-cluster-role.yaml @@ -0,0 +1,23 @@ +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRole +metadata: + name: node-reader +rules: + - apiGroups: [""] + resources: ["nodes" , "pods", "services", "namespaces"] + verbs: ["create", "get", "update", "delete", "list", "watch"] + +--- +apiVersion: rbac.authorization.k8s.io/v1 +kind: ClusterRoleBinding +metadata: + name: controller-service-account-binding +subjects: + - kind: ServiceAccount + name: controller-service-sa + namespace: default +roleRef: + kind: ClusterRole + name: node-reader + apiGroup: rbac.authorization.k8s.io + diff --git a/kubernetes-yaml-files/18-keycloak-deployment.yaml b/kubernetes-yaml-files/18-keycloak-deployment.yaml new file mode 100644 index 00000000..99b8bed7 --- /dev/null +++ b/kubernetes-yaml-files/18-keycloak-deployment.yaml @@ -0,0 +1,64 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hobbit-keycloak + labels: + app: hobbit-keycloak +spec: + replicas: 1 + selector: + matchLabels: + app: hobbit-keycloak + template: + metadata: + labels: + app: hobbit-keycloak + spec: + volumes: + - name: nfs-volume + persistentVolumeClaim: + claimName: keycloack-pvc + - name: cfg-volume + emptyDir: { } + initContainers: + - name: init-copy-db + image: alpine:latest + command: + - /bin/sh + - -c + - | + mkdir -p /cfg-volume + cp /nfs/keycloak.h2.db /cfg-volume/keycloak.h2.db + chmod 777 /cfg-volume + chmod 666 /cfg-volume/keycloak.h2.db + volumeMounts: + - name: nfs-volume + mountPath: /nfs + - name: cfg-volume + mountPath: /cfg-volume + containers: + - name: hobbit-keycloak + image: hobbitproject/hobbit-keycloak:latest + ports: + - containerPort: 8080 + env: + - name: KEYCLOAK_USER + value: admin + - name: KEYCLOAK_PASSWORD + value: admin + - name: DB_VENDOR + value: h2 + volumeMounts: + - name: cfg-volume + mountPath: /cfg-volume + readinessProbe: + httpGet: + path: /auth/realms/master + port: 8080 + initialDelaySeconds: 30 + periodSeconds: 10 + livenessProbe: + httpGet: + path: /auth/realms/master + port: 8080 + initialDelaySeconds: 30 diff --git a/kubernetes-yaml-files/18-ui-deployment.yaml b/kubernetes-yaml-files/18-ui-deployment.yaml new file mode 100644 index 00000000..4e8093dc --- /dev/null +++ b/kubernetes-yaml-files/18-ui-deployment.yaml @@ -0,0 +1,36 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: hobbit-gui + labels: + app: hobbit-gui +spec: + replicas: 1 + selector: + matchLabels: + app: hobbit-gui + template: + metadata: + labels: + app: hobbit-gui + spec: + containers: + - name: hobbit-gui + image: hobbitproject/hobbit-gui:latest + ports: + - containerPort: 8080 + env: + - name: HOBBIT_RABBIT_HOST + value: "rabbitmq" + - name: KEYCLOAK_AUTH_URL + value: "http://localhost:8181/auth" + - name: CHECK_REALM_URL + value: "false" + - name: KEYCLOAK_DIRECT_URL + value: "http://keycloak:8080/auth" + - name: ELASTICSEARCH_HOST + value: "elasticsearch" + - name: ELASTICSEARCH_HTTP_PORT + value: "9200" + - name: USE_UI_AUTH + value: "false" diff --git a/kubernetes-yaml-files/19-keycloak.yaml b/kubernetes-yaml-files/19-keycloak.yaml new file mode 100644 index 00000000..5e6cce4d --- /dev/null +++ b/kubernetes-yaml-files/19-keycloak.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: Service +metadata: + name: hobbit-keycloak + labels: + app: hobbit-keycloak +spec: + ports: + - port: 8080 + targetPort: 8080 + nodePort: 31000 + selector: + app: hobbit-keycloak + type: NodePort diff --git a/kubernetes-yaml-files/19-ui-service.yaml b/kubernetes-yaml-files/19-ui-service.yaml new file mode 100644 index 00000000..e4e025c9 --- /dev/null +++ b/kubernetes-yaml-files/19-ui-service.yaml @@ -0,0 +1,13 @@ +apiVersion: v1 +kind: Service +metadata: + name: hobbit-gui +spec: + selector: + app: hobbit-gui + ports: + - protocol: TCP + port: 8080 + targetPort: 8080 + nodePort: 31000 + type: NodePort diff --git a/kubernetes-yaml-files/20-keycloak-pv.yaml b/kubernetes-yaml-files/20-keycloak-pv.yaml new file mode 100644 index 00000000..82093d21 --- /dev/null +++ b/kubernetes-yaml-files/20-keycloak-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: keycloak-pv +spec: + capacity: + storage: 1Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Retain # or Delete, depending on your needs + storageClassName: "" # Important: Leave this empty if not using Storage Classes + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/keycloak diff --git a/kubernetes-yaml-files/21-keycloak-pvc.yaml b/kubernetes-yaml-files/21-keycloak-pvc.yaml new file mode 100644 index 00000000..7ea0d9c6 --- /dev/null +++ b/kubernetes-yaml-files/21-keycloak-pvc.yaml @@ -0,0 +1,10 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: keycloack-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 1Gi diff --git a/kubernetes-yaml-files/93-configmap.yaml b/kubernetes-yaml-files/93-configmap.yaml new file mode 100644 index 00000000..80840f7e --- /dev/null +++ b/kubernetes-yaml-files/93-configmap.yaml @@ -0,0 +1,26 @@ +apiVersion: v1 +kind: ConfigMap +metadata: + name: riak-config +data: + 00-autoconfig.sh: | + #!/bin/bash + CONFIG=/etc/riak/riak.conf + if [ -z "$RIAK_NODE_NAME" ]; then + RIAK_NODE_NAME=riak + fi + if [ -z "$RIAK_DISTRIBUTED_COOKIE" ]; then + RIAK_DISTRIBUTED_COOKIE=riak + fi + LOCAL_HOSTNAME_FQDN=$(hostname -f) + LOCAL_HOSTNAME_IP=$(hostname -I | awk '{ print $1; }') + if echo "$LOCAL_HOSTNAME_FQDN" | grep -iq '\.'; then + RIAK_NODE_HOSTNAME="$LOCAL_HOSTNAME_FQDN" + else + RIAK_NODE_HOSTNAME="$LOCAL_HOSTNAME_IP" + fi + echo "# Autogenerated configuration from '/etc/riak/prestart.d/00-autoconfig.sh' via '$0'" >> $CONFIG + echo "nodename = $RIAK_NODE_NAME@$RIAK_NODE_HOSTNAME" >> $CONFIG + echo "distributed_cookie = $RIAK_DISTRIBUTED_COOKIE" >> $CONFIG + echo "listener.protobuf.internal = 0.0.0.0:8087" >> $CONFIG + echo "listener.http.internal = 0.0.0.0:8098" >> $CONFIG diff --git a/kubernetes-yaml-files/94-riak-pv.yaml b/kubernetes-yaml-files/94-riak-pv.yaml new file mode 100644 index 00000000..85f5b577 --- /dev/null +++ b/kubernetes-yaml-files/94-riak-pv.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolume +metadata: + name: riaktest-pv +spec: + capacity: + storage: 2Gi + accessModes: + - ReadWriteOnce + persistentVolumeReclaimPolicy: Delete #, depending on your needs + storageClassName: "" # Important: Leave this empty if not using Storage Classes + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/riaktest diff --git a/kubernetes-yaml-files/95-riak-pvc.yaml b/kubernetes-yaml-files/95-riak-pvc.yaml new file mode 100644 index 00000000..a36d21e2 --- /dev/null +++ b/kubernetes-yaml-files/95-riak-pvc.yaml @@ -0,0 +1,11 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: riak-pvc +spec: + accessModes: + - ReadWriteOnce + resources: + requests: + storage: 2Gi + diff --git a/kubernetes-yaml-files/96-riaklatest.yaml b/kubernetes-yaml-files/96-riaklatest.yaml new file mode 100644 index 00000000..2cb2bd9b --- /dev/null +++ b/kubernetes-yaml-files/96-riaklatest.yaml @@ -0,0 +1,48 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: riak-deployment +spec: + replicas: 1 + selector: + matchLabels: + app: riak + template: + metadata: + labels: + app: riak + spec: + initContainers: + - name: riak-init + image: bash:latest + command: ["/bin/bash", "-c", "/scripts/00-autoconfig.sh"] + volumeMounts: + - name: script-volume + mountPath: /scripts + - name: riak-config + mountPath: /etc/riak/riak.conf + subPath: riak.conf + containers: + - name: riak + image: stojan/riak-kv + ports: + - containerPort: 8087 + env: + - name: RICK_START_ARGS + value: "--enable-search --enable-crypto" + volumeMounts: + - name: riak-data + mountPath: /var/lib/riak + - name: riak-config + mountPath: /etc/riak/riak.conf + subPath: riak.conf + volumes: + - name: riak-data + persistentVolumeClaim: + claimName: riak-pvc + - name: script-volume + configMap: + name: riak-config + defaultMode: 0755 + - name: riak-config + emptyDir: {} diff --git a/kubernetes-yaml-files/97-riaktest.yaml b/kubernetes-yaml-files/97-riaktest.yaml new file mode 100644 index 00000000..6c8221eb --- /dev/null +++ b/kubernetes-yaml-files/97-riaktest.yaml @@ -0,0 +1,17 @@ +apiVersion: v1 +kind: Pod +metadata: + name: riak-debug-pod +spec: + securityContext: + runAsUser: 0 + fsGroup: 0 + containers: + - name: debug-container + image: git.project-hobbit.eu:4567/gitadmin/basho-riak-kv:1.0.1 + stdin: true + tty: true + securityContext: + allowPrivilegeEscalation: true + runAsUser: 0 + runAsGroup: 0 diff --git a/kubernetes-yaml-files/98-pod-sytem-benchmark-test.yaml b/kubernetes-yaml-files/98-pod-sytem-benchmark-test.yaml new file mode 100644 index 00000000..8df24743 --- /dev/null +++ b/kubernetes-yaml-files/98-pod-sytem-benchmark-test.yaml @@ -0,0 +1,66 @@ +apiVersion: v1 +kind: Pod +metadata: + name: hobbit-pod-system-benchmark-test +spec: + imagePullSecrets: + - name: gitlab-registry + containers: + - name: hobbit-container-test + image: git.project-hobbit.eu:4567/gitadmin/platform-benchmark + env: + - name: HOBBIT_RABBIT_HOST + value: "service-1740670758851-sep-benchmark-328399376" + - name: HOBBIT_SESSION_ID + value: "1740670758851" + - name: HOBBIT_EXPERIMENT_URI + value: "http://w3id.org/hobbit/experiments#1740670758851" + - name: BENCHMARK_PARAMETERS_MODEL + value: | + { + "@id" : "http://w3id.org/hobbit/experiments#New", + "@type" : "hobbit:Experiment", + "hasExperimentType" : "gerbil:A2KB", + "hasNumberOfDocuments" : "200", + "involvesBenchmark" : "gerbil2:GerbilBenchmark", + "involvesSystem" : "ex:DummySystemInstance1", + "@context" : { + "involvesSystem" : { + "@id" : "http://w3id.org/hobbit/vocab#involvesSystem", + "@type" : "@id" + }, + "involvesBenchmark" : { + "@id" : "http://w3id.org/hobbit/vocab#involvesBenchmark", + "@type" : "@id" + }, + "hasExperimentType" : { + "@id" : "http://w3id.org/gerbil/hobbit/vocab#hasExperimentType", + "@type" : "@id" + }, + "hasNumberOfDocuments" : { + "@id" : "http://w3id.org/gerbil/hobbit/vocab#hasNumberOfDocuments", + "@type" : "http://www.w3.org/2001/XMLSchema#unsignedInt" + }, + "hobbit" : "http://w3id.org/hobbit/vocab#", + "ex" : "http://example.org/", + "rdf" : "http://www.w3.org/1999/02/22-rdf-syntax-ns#", + "owl" : "http://www.w3.org/2002/07/owl#", + "gerbil" : "http://w3id.org/gerbil/vocab#", + "xsd" : "http://www.w3.org/2001/XMLSchema#", + "rdfs" : "http://www.w3.org/2000/01/rdf-schema#", + "gerbil2" : "http://w3id.org/gerbil/hobbit/vocab#" + } + } + - name: HOBBIT_SYSTEM_URI + value: "http://w3id.org/hobbit/platform-benchmark/vocab#PlatformBenchSystem_1" + - name: HOBBIT_CONTAINER_NAME + value: "controller-754749f957-lh4kq" + - name: GITLAB_USER + value: "gitadmin" + - name: GITLAB_EMAIL + value: "gitadmin@project-hobbit.eu" + - name: GITLAB_TOKEN + valueFrom: + secretKeyRef: + name: gitlab-secret + key: GITLAB_TOKEN diff --git a/kubernetes-yaml-files/99-test-job.yaml b/kubernetes-yaml-files/99-test-job.yaml new file mode 100644 index 00000000..6de5d29d --- /dev/null +++ b/kubernetes-yaml-files/99-test-job.yaml @@ -0,0 +1,33 @@ +apiVersion: v1 +kind: Pod +metadata: + name: test-hobbit-pod + labels: + app: hobbit-app +spec: + restartPolicy: Never + containers: + - name: hobbit-container + image: maven + command: ["sh", "-c"] + args: ["tail -f /dev/null"] # Keeps the container running indefinitely + env: + - name: HOBBIT_RABBIT_HOST + value: rabbitmq + - name: BENCHMARK + value: http://w3id.org/hobbit/platform-benchmark/vocab#PlatformBenchmark + - name: SYSTEM + value: http://w3id.org/hobbit/platform-benchmark/vocab#PlatformBenchSystem_1 + - name: BENCHMARK_PARAM_FILE + value: /usr/src/app/src/test/resources/org/hobbit/controller/exampleExperiment.ttl + - name: USERNAME + value: testuser + volumeMounts: + - mountPath: /usr/src/app + name: app-volume + volumes: + - name: app-volume + nfs: + server: 131.234.29.64 + path: /data/nfs/kubedata/test + readOnly: false diff --git a/kubernetes-yaml-files/README.md b/kubernetes-yaml-files/README.md new file mode 100644 index 00000000..1a7ec546 --- /dev/null +++ b/kubernetes-yaml-files/README.md @@ -0,0 +1,75 @@ +# Kubernetes YAML Files + +This directory contains the Kubernetes YAML configuration files used by the Hobbit project to deploy and manage application components on Kubernetes clusters. These configurations define the deployments, services, ingress rules, and other necessary Kubernetes objects. + +## Overview + +The Kubernetes YAML files in this directory are essential for automating the provisioning and scaling of services. The configurations allow seamless deployments as part of the project's continuous integration and delivery pipelines. They are meant to be used as a starting point and can be customized to better suit different environments or specific deployment requirements. + +## Directory Structure + +The directory is organized to facilitate clarity and ease of use: +- **Deployment Files:** Define how pods are deployed and managed. This usually includes specifications such as replicas, container images, resource limits, and update strategies. +- **Service Files:** Expose the pods to internal or external networks. These YAMLs typically cover ClusterIP, NodePort, or LoadBalancer services. +- **Ingress Files:** Manage external access to the services in the cluster. These files are configured to work with Ingress controllers. +- **ConfigMap/Secret Files:** Provide a way to decouple configuration settings and sensitive data from container images. Adjust these according to your environment’s needs. + +## Prerequisites + +To successfully apply these configurations: +- You must have a working Kubernetes cluster. +- Ensure that `kubectl` is installed and configured to communicate with your cluster. +- Familiarity with Kubernetes concepts (pods, deployments, services, ingress, etc.) is helpful. + +## Usage + +### Applying the Configurations + +1. **Navigate to the Directory:** + Open your terminal and change the current directory to the location where the YAML files are stored. + + ```bash + cd path/to/kubernetes-yaml-files + ``` + +2. **Deploy the Resources:** + Use `kubectl` to apply all the configurations at once: + + ```bash + kubectl apply -f . + ``` + + This command iterates through all YAML files and creates or updates the Kubernetes objects accordingly. + +3. **Verify the Deployment:** + To check if the deployments are running correctly, use: + + ```bash + kubectl get deployments + kubectl get pods + ``` + + Review logs if any of the pods are not running as expected: + + ```bash + kubectl logs [pod-name] + ``` +it is suggester keep the order of files while deploying.(00 until 21 the next ones are for testing and development purpose) + +### Customizing Configurations + +- **Environment Variables:** Some files might reference environment variables. Edit these sections as needed to match your environment. +- **Resource Limits:** Customize CPU and memory resources based on your cluster’s capacity. +- **External Dependencies:** Review and adjust configurations for services like Ingress or persistent storage if your project setup requires it. +- **Define secrets of Git based on your data:** like bellow +```bash +kubectl create secret generic gitlab-secret --from-literal=GITLAB_USER=XXX --from-literal=GITLAB_TOKEN=XXX +```` +and if need to reach docker for images like this + +```bash +kubectl create secret docker-registry gitlab-registry-secret \ --docker-server=[for example:git.project-hobbit.eu:4567] \ --docker-username=XXX \ --docker-password=XXX \ --docker-email=XXX \ -n default + +``` + +``` diff --git a/platform-controller/Makefile b/platform-controller/Makefile index 486875fb..47906c31 100644 --- a/platform-controller/Makefile +++ b/platform-controller/Makefile @@ -11,19 +11,23 @@ run: DOCKER_HOST=tcp://localhost:2375 \ HOBBIT_RABBIT_HOST=localhost \ java -cp \ - target/platform-controller-0.0.1-SNAPSHOT.jar \ + target/platform-controller.jar \ org.hobbit.core.run.ComponentStarter \ org.hobbit.controller.PlatformController test: - # Without HOBBIT_RABBIT_EXPERIMENTS_HOST we won't be able to connect to RabbitMQ - docker compose --file=../docker-compose-dev.yml up -d cadvisor node-exporter prometheus rabbit redis - HOBBIT_RABBIT_IMAGE=rabbitmq:management \ - HOBBIT_RABBIT_HOST=localhost \ - HOBBIT_RABBIT_EXPERIMENTS_HOST=localhost \ - HOBBIT_REDIS_HOST=localhost \ - PROMETHEUS_HOST=localhost \ + @echo "πŸ”§ Starting required services with Docker Compose..." + docker compose --file=../docker-compose-dev.yml up -d cadvisor node-exporter prometheus redis rabbit + @export HOBBIT_RABBIT_IMAGE=rabbitmq:management && \ + export HOBBIT_RABBIT_HOST=localhost && \ + export HOBBIT_RABBIT_EXPERIMENTS_HOST=localhost && \ + export HOBBIT_REDIS_HOST=localhost && \ + export MAX_EXECUTION_TIME=1200000 && \ + export PROMETHEUS_HOST=localhost && \ mvn --quiet --update-snapshots -Dtest=$(test) clean test + @echo "βœ… Tests completed successfully." +# docker compose --file=../docker-compose-dev.yml down + test-single: docker rmi busybox diff --git a/platform-controller/pom.xml b/platform-controller/pom.xml index f3ee6fbd..91c86ead 100644 --- a/platform-controller/pom.xml +++ b/platform-controller/pom.xml @@ -28,6 +28,9 @@ platform-controller jar + + 18.0.0 + @@ -105,6 +108,22 @@ 2.8.8 + + + + io.kubernetes + client-java-api + ${kubernetes-client.version} + + + + io.kubernetes + client-java + ${kubernetes-client.version} + + + + javax.activation activation diff --git a/platform-controller/src/main/java/org/hobbit/controller/ExperimentManager.java b/platform-controller/src/main/java/org/hobbit/controller/ExperimentManager.java index d6adfe52..669c236f 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/ExperimentManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/ExperimentManager.java @@ -44,9 +44,9 @@ import org.hobbit.controller.data.ExperimentStatus; import org.hobbit.controller.data.ExperimentStatus.States; import org.hobbit.controller.data.SetupHardwareInformation; -import org.hobbit.controller.docker.ClusterManager; -import org.hobbit.controller.docker.ContainerManager; -import org.hobbit.controller.docker.MetaDataFactory; +import org.hobbit.controller.containers.ClusterManager; +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.MetaDataFactory; import org.hobbit.controller.execute.ExperimentAbortTimerTask; import org.hobbit.controller.utils.RabbitMQConnector; import org.hobbit.core.Commands; @@ -74,7 +74,7 @@ * running experiment. * * @author Michael Röder (roeder@informatik.uni-leipzig.de) - * + * @author Farshad Afshari farshad.afshari@uni-paderborn.de */ public class ExperimentManager implements Closeable { private static final Logger LOGGER = LoggerFactory.getLogger(ExperimentManager.class); @@ -188,13 +188,24 @@ public void createNextExperiment() { LOGGER.debug("There is no experiment to start."); return; } - LOGGER.info("Creating next experiment " + config.id + " with benchmark " + config.benchmarkUri + LOGGER.debug("Creating next experiment " + config.id + " with benchmark " + config.benchmarkUri + " and system " + config.systemUri + " to the queue."); experimentStatus = new ExperimentStatus(config, HobbitExperiments.getExperimentURI(config.id)); createRabbitMQ(config); BenchmarkMetaData benchmark = controller.imageManager().getBenchmark(config.benchmarkUri); + + if(benchmark==null){ + LOGGER.debug("There is no benchmark found for {}",config.benchmarkUri); + }else{ + LOGGER.trace("Benchmark found for {}",config.benchmarkUri); + LOGGER.trace("Benchmark name is {}",benchmark.getName()); + LOGGER.trace("Benchmark description is {}",benchmark.getDescription()); + LOGGER.trace("Benchmark main image is {}",benchmark.getMainImage()); + LOGGER.trace("Benchmark uri is {}",benchmark.getUri()); + } + if ((benchmark == null) || (benchmark.mainImage == null)) { // Think about reusing the existing object created above experimentStatus = new ExperimentStatus(config, HobbitExperiments.getExperimentURI(config.id), @@ -204,6 +215,11 @@ public void createNextExperiment() { } SystemMetaData system = controller.imageManager().getSystem(config.systemUri); + if(system==null){ + LOGGER.debug("There is no system found for system " + config.systemUri); + }else{ + LOGGER.debug("System found for benchmark " + config.systemUri); + } if ((system == null) || (system.mainImage == null)) { // Think about reusing the existing object created above experimentStatus = new ExperimentStatus(config, HobbitExperiments.getExperimentURI(config.id), @@ -260,6 +276,7 @@ public void createNextExperiment() { Constants.BENCHMARK_PARAMETERS_MODEL_KEY + "=" + config.serializedBenchParams, Constants.SYSTEM_URI_KEY + "=" + config.systemUri }, null, null, config.id, Collections.emptyMap()); + LOGGER.trace(" containerID is {}" , containerId); if (containerId == null) { experimentStatus.addError(HobbitErrors.BenchmarkCreationError); throw new Exception("Couldn't create benchmark controller " + config.benchmarkUri); @@ -279,6 +296,7 @@ public void createNextExperiment() { Constants.HOBBIT_SESSION_ID_KEY + "=" + config.id, Constants.SYSTEM_PARAMETERS_MODEL_KEY + "=" + serializedSystemParams }, null, null, config.id, getHardwareConstraints(config.serializedBenchParams)); + LOGGER.debug(" containerID is {}" , containerId); if (containerId == null) { LOGGER.error("Couldn't start the system. Trying to cancel the benchmark."); forceBenchmarkTerminate_unsecured(HobbitErrors.SystemCreationError); @@ -323,21 +341,24 @@ protected static Map getHardwareConstraints(String serializedBen } protected void createRabbitMQ(ExperimentConfiguration config) throws Exception { + LOGGER.debug("create RabbitMQ"); String rabbitMQAddress = hobbitConfig.getString(RABBIT_MQ_EXPERIMENTS_HOST_NAME_KEY, (String) null); + LOGGER.trace("Using the newly started RabbitMQ for the experiment: {}", rabbitMQAddress); if (rabbitMQAddress == null) { - LOGGER.info("Starting new RabbitMQ for the experiment..."); + LOGGER.debug("Starting new RabbitMQ for the experiment..."); rabbitMQAddress = controller.containerManager.startContainer(hobbitConfig.getString(RABBIT_IMAGE_ENV_KEY), Constants.CONTAINER_TYPE_BENCHMARK, null, new String[] {}, null, null, config.id, Collections.emptyMap()); + LOGGER.debug("Service initialized for RabbitMQ with this name: {}", rabbitMQAddress); if (rabbitMQAddress == null) { experimentStatus.addError(HobbitErrors.UnexpectedError); // FIXME throw new Exception("Couldn't start new RabbitMQ for the experiment"); } experimentStatus.setRootContainer(rabbitMQAddress); - LOGGER.info("Using the newly started RabbitMQ for the experiment: {}", rabbitMQAddress); + LOGGER.debug("Using the newly started RabbitMQ for the experiment: {}", rabbitMQAddress); } else { - LOGGER.info("Using the configured RabbitMQ for the experiment: {}", rabbitMQAddress); + LOGGER.debug( "Using the configured RabbitMQ for the experiment: {}", rabbitMQAddress); } experimentStatus.setRabbitMQContainer(rabbitMQAddress); @@ -599,7 +620,7 @@ public void notifyTermination(String containerId, long exitCode) { // send a message using sendToCmdQueue(command, // data) comprising a command that indicates that a // container terminated and the container name - String containerName = controller.containerManager.getContainerName(containerId); + String containerName = controller.containerManager.getContainerPodName(containerId); if (containerName != null) { try { controller.sendToCmdQueue(Constants.HOBBIT_SESSION_ID_FOR_BROADCASTS, @@ -663,7 +684,7 @@ public void systemOrBenchmarkReady(boolean systemReportedReady, String sessionId * daemon */ private void startBenchmark_unsecured() throws IOException { - String containerName = controller.containerManager.getContainerName(experimentStatus.getSystemContainer()); + String containerName = controller.containerManager.getContainerPodName(experimentStatus.getSystemContainer()); if (containerName == null) { throw new IOException( "Couldn't derive container name of the system container for sending start message to the benchmark."); @@ -799,7 +820,7 @@ public void setController(PlatformController controller) { /** * Add reported error to the experiment result model if the experiment with the * given session is still running. - * + * * @param sessionId the session ID of the container that reported the * error * @param errorData the data of the reported error diff --git a/platform-controller/src/main/java/org/hobbit/controller/PlatformController.java b/platform-controller/src/main/java/org/hobbit/controller/PlatformController.java index 419dbb1b..ec538a60 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/PlatformController.java +++ b/platform-controller/src/main/java/org/hobbit/controller/PlatformController.java @@ -23,18 +23,13 @@ import java.nio.charset.StandardCharsets; import java.text.SimpleDateFormat; import java.time.Duration; -import java.util.ArrayList; -import java.util.Calendar; -import java.util.HashMap; -import java.util.HashSet; -import java.util.List; -import java.util.Map; -import java.util.Properties; -import java.util.Set; -import java.util.Timer; -import java.util.TimerTask; +import java.util.*; import java.util.concurrent.Semaphore; +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.util.Config; import org.apache.commons.configuration2.EnvironmentConfiguration; import org.apache.commons.io.IOUtils; import org.apache.jena.query.Dataset; @@ -48,20 +43,8 @@ import org.apache.jena.rdf.model.Resource; import org.apache.jena.vocabulary.RDF; import org.hobbit.controller.analyze.ExperimentAnalyzer; +import org.hobbit.controller.containers.*; import org.hobbit.controller.data.ExperimentConfiguration; -import org.hobbit.controller.docker.ClusterManager; -import org.hobbit.controller.docker.ClusterManagerImpl; -import org.hobbit.controller.docker.ContainerManager; -import org.hobbit.controller.docker.ContainerManagerImpl; -import org.hobbit.controller.docker.ContainerStateObserver; -import org.hobbit.controller.docker.ContainerStateObserverImpl; -import org.hobbit.controller.docker.ContainerTerminationCallback; -import org.hobbit.controller.docker.FileBasedImageManager; -import org.hobbit.controller.docker.GitlabBasedImageManager; -import org.hobbit.controller.docker.ImageManager; -import org.hobbit.controller.docker.ImageManagerFacade; -import org.hobbit.controller.docker.ResourceInformationCollector; -import org.hobbit.controller.docker.ResourceInformationCollectorImpl; import org.hobbit.controller.front.FrontEndApiHandler; import org.hobbit.controller.queue.ExperimentQueue; import org.hobbit.controller.queue.ExperimentQueueImpl; @@ -103,6 +86,7 @@ * This class implements the functionality of the central platform controller. * * @author Michael Röder (roeder@informatik.uni-leipzig.de) + * @author Farshad Afshari farshad.afshari@uni-paderborn.de * */ public class PlatformController extends AbstractComponent implements ContainerTerminationCallback, ExperimentAnalyzer { @@ -168,7 +152,8 @@ public class PlatformController extends AbstractComponent implements ContainerTe */ protected DataSender sender2Analysis; /** - * A manager for Docker containers. + * A manager for Kubernetes/Docker containers. + * TODO : is it good way ? */ protected ContainerManager containerManager; /** @@ -220,11 +205,20 @@ public class PlatformController extends AbstractComponent implements ContainerTe protected Timer challengeCheckTimer; protected HobbitConfiguration hobbitConfig; + /** + * keep the variable which framework used Docker swarm or Kubernetes + */ + protected ContainerEngine engine; + + //@Value("${container.manager.timeoutMilliSeconds:60000}") + private int TIMEOUT_MILLISECONDS = 60000; + /** * Default constructor. */ public PlatformController() { super(); + this.engine = whereAmIRunning(); } /** @@ -234,55 +228,121 @@ public PlatformController(ExperimentManager expManager) { this(); this.expManager = expManager; expManager.setController(this); + this.engine = whereAmIRunning(); } - @Override - public void init() throws Exception { - // First initialize the super class - super.init(); - LOGGER.debug("Platform controller initialization started."); + private ContainerEngine whereAmIRunning() { + String runOn = Optional.ofNullable(System.getenv("RUN_ON")) + .orElse("kubernetes") + .toLowerCase(); + + switch (runOn) { + case "docker": + LOGGER.info("Platform running on Docker Swarm "); + return ContainerEngine.DOCKER_SWARM; + case "kubernetes": + LOGGER.info("Platform running on Kubernetes"); + return ContainerEngine.KUBERNETES; + default: + LOGGER.warn("Unknown RUN_ON value '{}', defaulting to KUBERNETES.", runOn); + return ContainerEngine.KUBERNETES; + } + } + + private void kubernetsInit() throws IOException { + LOGGER.info("Initializing Kubernetes version"); + hobbitConfig = new HobbitConfiguration(); + hobbitConfig.addConfiguration(new EnvironmentConfiguration()); + LOGGER.debug("configuration initialized."); + + ApiClient client = Config.defaultClient(); + client.setConnectTimeout(TIMEOUT_MILLISECONDS); + client.setReadTimeout(TIMEOUT_MILLISECONDS); + client.setWriteTimeout(TIMEOUT_MILLISECONDS); + Configuration.setDefaultApiClient(client); + + LOGGER.debug("kub client initialized."); + + clusterManager = new org.hobbit.controller.containers.kubernetes.ClusterManagerImpl(client); + containerManager = new org.hobbit.controller.containers.kubernetes.ContainerManagerImpl(client); + LOGGER.debug("Container manager initialized."); + // Create container observer (polls status every 5s) + containerObserver = new org.hobbit.controller.containers.kubernetes.ContainerStateObserverImpl((KubExtendedContainerManager)containerManager, 5 * 1000); + containerObserver.addTerminationCallback(this); + // Tell the manager to add container to the observer + containerManager.addContainerObserver(containerObserver); + resInfoCollector = new org.hobbit.controller.containers.kubernetes.ResourceInformationCollectorImpl(containerManager,client,new CoreV1Api(client)); + containerObserver.startObserving(); + LOGGER.debug("Container observer initialized."); + } + + private void dockerSwarmInit() throws Exception { + LOGGER.info("Initializing Docker swarm version"); hobbitConfig = new HobbitConfiguration(); hobbitConfig.addConfiguration(new EnvironmentConfiguration()); // Set task history limit for swarm cluster to 0 (will remove all terminated // containers) // Only for prod mode - clusterManager = new ClusterManagerImpl(); + + LOGGER.debug("configuration initialized."); + + clusterManager = new org.hobbit.controller.containers.docker.ClusterManagerImpl(); if (DEPLOY_ENV.equals(DEPLOY_ENV_TESTING) || DEPLOY_ENV.equals(DEPLOY_ENV_DEVELOP)) { LOGGER.debug("Ignoring task history limit parameter. Will remain default (run 'docker info' for details)."); } else { LOGGER.debug( - "Production mode. Setting task history limit to 0. All terminated containers will be removed."); + "Production mode. Setting task history limit to 0. All terminated containers will be removed."); clusterManager.setTaskHistoryLimit(0); } - // create container manager - containerManager = new ContainerManagerImpl(); + containerManager = new org.hobbit.controller.containers.docker.ContainerManagerImpl(); LOGGER.debug("Container manager initialized."); // Create container observer (polls status every 5s) - containerObserver = new ContainerStateObserverImpl(containerManager, 5 * 1000); + containerObserver = new org.hobbit.controller.containers.docker.ContainerStateObserverImpl(containerManager, 5 * 1000); containerObserver.addTerminationCallback(this); // Tell the manager to add container to the observer containerManager.addContainerObserver(containerObserver); - resInfoCollector = new ResourceInformationCollectorImpl(containerManager); + resInfoCollector = new org.hobbit.controller.containers.docker.ResourceInformationCollectorImpl(containerManager); containerObserver.startObserving(); LOGGER.debug("Container observer initialized."); + } + @Override + public void init() throws Exception { + // First initialize the super class + super.init(); + LOGGER.debug("Platform controller initialization started."); + + switch (engine){ + case DOCKER_SWARM: + LOGGER.info("Platform running on Docker Swarm"); + dockerSwarmInit(); + break; + case KUBERNETES: + LOGGER.info("Platform running on Kubernetes"); + kubernetsInit(); + break; + default: + kubernetsInit(); + break; + } List managers = new ArrayList(); if (System.getenv().containsKey(LOCAL_METADATA_DIR_KEY)) { String metadataDirectory = System.getenv().get(LOCAL_METADATA_DIR_KEY); - LOGGER.info("Local metadata directory: {}", metadataDirectory); + LOGGER.debug("Local metadata directory: {}", metadataDirectory); managers.add(new FileBasedImageManager(metadataDirectory)); } else { - LOGGER.info("Using default directory for local metadata."); + LOGGER.debug("Using default directory for local metadata."); managers.add(new FileBasedImageManager()); } boolean useGitlab = true; if (System.getenv().containsKey(USE_GITLAB_KEY)) { try { useGitlab = Boolean.parseBoolean(System.getenv().get(USE_GITLAB_KEY)); + LOGGER.debug("Using git lab enabled"); } catch (Exception e) { LOGGER.error("Couldn't parse value of " + USE_GITLAB_KEY + ". It will be ignored."); } @@ -321,14 +381,14 @@ public void run() { } }, PUBLISH_CHALLENGES, PUBLISH_CHALLENGES); - LOGGER.info("Platform controller initialized."); + LOGGER.debug("Platform controller initialized."); } /** * This method sets the RabbitMQ connector for the command queue. */ public void setExpRabbitMQConnector(RabbitMQConnector rabbitMQConnector) { - LOGGER.info("Setting experiment's RabbitMQ connector for the command queue: {}", rabbitMQConnector); + LOGGER.debug("Setting experiment's RabbitMQ connector for the command queue: {}", rabbitMQConnector); assert this.rabbitMQConnector == null : "RabbitMQ connector should be null"; this.rabbitMQConnector = rabbitMQConnector; } @@ -339,7 +399,7 @@ public void setExpRabbitMQConnector(RabbitMQConnector rabbitMQConnector) { * @throws Exception */ public void closeExpRabbitMQConnector() { - LOGGER.info("Closing experiment's RabbitMQ connector for the command queue: {}", rabbitMQConnector); + LOGGER.debug("Closing experiment's RabbitMQ connector for the command queue: {}", rabbitMQConnector); if(rabbitMQConnector != null) { IOUtils.closeQuietly(rabbitMQConnector); rabbitMQConnector = null; @@ -377,11 +437,12 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas LOGGER.info("received command: session={}, command={}, data={}", sessionId, Commands.toString(command), data != null ? RabbitMQUtils.readString(data) : "null"); } else { - LOGGER.info("received command: session={}, command={}", sessionId, Commands.toString(command)); + LOGGER.debug("received command: session={}, command={}", sessionId, Commands.toString(command)); } // Determine the command switch (command) { case Commands.DOCKER_CONTAINER_START: { + LOGGER.debug("Starting container with name: {}", sessionId); StartCommandData startParams = null; String containerName = ""; if (expManager.isExpRunning(sessionId)) { @@ -389,6 +450,7 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas startParams = GsonUtils.deserializeObjectWithGson(gson, data, StartCommandData.class, false); // trigger creation containerName = createContainer(startParams); + LOGGER.debug("Starting container with name: {}", containerName); } else { LOGGER.error( "Got a request to start a container for experiment \"{}\" which is either not running or was already stopped. Returning null.", @@ -417,6 +479,7 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas break; } case Commands.DOCKER_CONTAINER_STOP: { + LOGGER.debug("cmd-Stopping container, sessionId : {}", sessionId); // get containerId from params StopCommandData stopParams = GsonUtils.deserializeObjectWithGson(gson, data, StopCommandData.class, false); // trigger stop @@ -424,14 +487,17 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas break; } case Commands.BENCHMARK_READY_SIGNAL: { + LOGGER.debug("Benchmark Ready signal: {}", sessionId); expManager.systemOrBenchmarkReady(false, sessionId); break; } case Commands.SYSTEM_READY_SIGNAL: { + LOGGER.debug("System Ready signal: {}", sessionId); expManager.systemOrBenchmarkReady(true, sessionId); break; } case Commands.TASK_GENERATION_FINISHED: { + LOGGER.debug("Task generator finished: {}", sessionId); expManager.taskGenFinished(sessionId); break; } @@ -447,7 +513,7 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas // FIXME use the session id to make sure that only containers of this session // are observed ResourceUsageInformation resUsage = resInfoCollector.getSystemUsageInformation(); - LOGGER.info("Returning usage information: {}", resUsage != null ? resUsage.toString() : "null"); + LOGGER.debug("Returning usage information: {}", resUsage != null ? resUsage.toString() : "null"); if (replyTo != null) { byte[] response; if (resUsage != null) { @@ -498,8 +564,9 @@ public void receiveCommand(byte command, byte[] data, String sessionId, AMQP.Bas * @return the name of the created container */ private String createContainer(StartCommandData data) { - String parentId = containerManager.getContainerId(data.parent); - if ((parentId == null) && (CONTAINER_PARENT_CHECK)) { + LOGGER.debug("create Container in platform controller {}",data.toString()); + //String parentId = containerManager.getContainerPodId(data.parent); + if ((data.parent == null) && (CONTAINER_PARENT_CHECK)) { LOGGER.error("Couldn't create container because the parent \"{}\" is not known.", data.parent); return null; } @@ -510,12 +577,14 @@ private String createContainer(StartCommandData data) { pullImage = true; } - String containerId = containerManager.startContainer(data.image, data.type, parentId, data.environmentVariables, + String containerDNSFriendlyIP_OR_containerIDForDocker = containerManager.startContainer(data.image, data.type, data.parent, data.environmentVariables, data.networkAliases, null, pullImage, null); - if (containerId == null) { + LOGGER.debug("container Identifier is {}", containerDNSFriendlyIP_OR_containerIDForDocker); + if (containerDNSFriendlyIP_OR_containerIDForDocker == null) { return null; } else { - return containerManager.getContainerName(containerId); + LOGGER.debug("convert ID to PODNAME"); + return containerDNSFriendlyIP_OR_containerIDForDocker; } } @@ -525,10 +594,21 @@ private String createContainer(StartCommandData data) { * @param containerName name of the container that should be stopped */ public void stopContainer(String containerName) { - String containerId = containerManager.getContainerId(containerName); - if (containerId != null) { - containerManager.removeContainer(containerId); + LOGGER.debug("Stopping container with name: {}", containerName); + switch (engine){ + case KUBERNETES: + containerManager.removeContainer(containerName); + break; + case DOCKER_SWARM: + String containerId = containerManager.getContainerPodId(containerName); + containerManager.removeContainer(containerId); + break; + default: + containerManager.removeContainer(containerName); + break; } + + } @Override @@ -539,7 +619,7 @@ public void run() throws Exception { @Override public void notifyTermination(String containerId, long exitCode) { - LOGGER.info("Container " + containerId + " stopped with exitCode=" + exitCode); + LOGGER.debug("Container " + containerId + " stopped with exitCode=" + exitCode); // Check whether this container was part of an experiment expManager.notifyTermination(containerId, exitCode); // Remove the container from the observer @@ -739,7 +819,7 @@ public void handleFrontEndCmd(byte bytes[], String replyTo, BasicProperties repl case FrontEndApiCommands.GET_SYSTEMS_OF_USER: { // get the user name String email = RabbitMQUtils.readString(buffer); - LOGGER.info("Loading systems of user \"{}\"", email); + LOGGER.debug("Loading systems of user \"{}\"", email); response = RabbitMQUtils.writeString(gson.toJson(imageManager.getSystemsOfUser(email))); break; } @@ -806,6 +886,7 @@ private void handleErrorReport(String sessionId, ErrorData errorData) { LOGGER.error("Got an error report without container ID. It will be ignored."); return; } + LOGGER.debug("handle error report for session \"{}\" and container Id {}", sessionId,errorData.getContainerId()); String containerType = containerManager.getContainerType(errorData.getContainerId()); boolean isBenchmarkContainer = Constants.CONTAINER_TYPE_BENCHMARK.equals(containerType); if (!isBenchmarkContainer && (!Constants.CONTAINER_TYPE_SYSTEM.equals(containerType))) { @@ -890,7 +971,7 @@ private void executeChallengeExperiments(String challengeUri) { } // add to queue for (ExperimentConfiguration ex : experiments) { - LOGGER.info("Adding experiment " + ex.id + " with benchmark " + ex.benchmarkUri + " and system " + LOGGER.debug("Adding experiment " + ex.id + " with benchmark " + ex.benchmarkUri + " and system " + ex.systemUri + " to the queue."); queue.add(ex); } @@ -906,7 +987,7 @@ private void executeChallengeExperiments(String challengeUri) { */ protected static synchronized void scheduleDateOfNextExecution(StorageServiceClient storage, String challengeUri, Calendar now) { - LOGGER.info("Scheduling dateOfNextExecution for challenge {}...", challengeUri); + LOGGER.debug("Scheduling dateOfNextExecution for challenge {}...", challengeUri); String query = SparqlQueries.getRepeatableChallengeInfoQuery(challengeUri, Constants.CHALLENGE_DEFINITION_GRAPH_URI); Model challengeModel = storage.sendConstructQuery(query); @@ -1346,6 +1427,7 @@ private static String readVersion() { IOUtils.closeQuietly(is); } LOGGER.info("Platform has version {}", version); + LOGGER.info("RABITMQ HOST: {}", System.getenv("HOBBIT_RABBIT_HOST")); return version; } } diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/AbstactImageManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/AbstactImageManager.java similarity index 97% rename from platform-controller/src/main/java/org/hobbit/controller/docker/AbstactImageManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/AbstactImageManager.java index f83907a8..795307cf 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/AbstactImageManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/AbstactImageManager.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.Collections; import java.util.Comparator; @@ -7,6 +7,7 @@ import java.util.stream.Collectors; import java.util.stream.Stream; +import org.hobbit.controller.containers.ImageManager; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.ImageMetaData; import org.hobbit.core.data.SystemMetaData; @@ -14,7 +15,7 @@ /** * An abstract implementation of the {@link ImageManager} interface which takes * care of systems or benchmarks which have the same URI. - * + * * @author Michael Röder (michael.roeder@uni-paderborn.de) * */ @@ -30,7 +31,7 @@ public List getBenchmarks() { /** * A method that returns a list of unchecked benchmarks (they may have * overlapping URIs). - * + * * @return a list of known benchmarks */ protected abstract List getUncheckedBenchmarks(); @@ -43,7 +44,7 @@ public List getSystems() { /** * A method that returns a list of unchecked systems (they may have overlapping * URIs). - * + * * @return a list of known systems */ protected abstract List getUncheckedSystems(); @@ -85,7 +86,7 @@ public SystemMetaData getSystem(String systemUri) { /** * Identifies duplicates and marks them using the * {@link #addErrorToDuplicates(List)} method. - * + * * @param images * a list of images that may contain duplicates * @return the same list @@ -98,7 +99,7 @@ protected List markDuplicates(List images) { /** * Sets the {@link ImageMetaData#defError} attribute of all meta data elements * that are identified as duplicates. - * + * * @param images * the list of image meta data objects that have the same URI */ @@ -127,7 +128,7 @@ protected void addErrorToDuplicates(List images) { /** * Creates a stream of lists each containing all image meta data instances that * are sharing the same URI. - * + * * @param images * a list of image meta data instances * @return a stream of lists grouped by the image meta data URI @@ -143,7 +144,7 @@ protected static Stream> createListPerUri(List * makes sure that the correct {@link #getSystems()} method is used (i.e., the * method that adds additional information to the systems which can not be added * later on, after the filtering). - * + * * @param filter * the filter that is used. Should be thread safe. * @return the filtered list diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ClusterManager.java similarity index 91% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ClusterManager.java index 9f8b93dc..5b21db8a 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ClusterManager.java @@ -1,7 +1,6 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import com.spotify.docker.client.exceptions.DockerException; -import com.spotify.docker.client.messages.Info; /** * This interface is implemented by classes that can be used to manage and @@ -16,7 +15,8 @@ public interface ClusterManager { * * @return com.spotify.docker.client.messages.Info */ - public Info getClusterInfo() throws DockerException, InterruptedException; + // it just used in one test then commented + //public Info getClusterInfo() throws DockerException, InterruptedException; /** * Get number of nodes in the cluster diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerEngine.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerEngine.java new file mode 100644 index 00000000..cb5278f7 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerEngine.java @@ -0,0 +1,6 @@ +package org.hobbit.controller.containers; + +public enum ContainerEngine { + DOCKER_SWARM, + KUBERNETES; +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerManager.java similarity index 84% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ContainerManager.java index 3daf465e..64f0d429 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerManager.java @@ -14,19 +14,18 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.List; import java.util.Map; +import io.kubernetes.client.openapi.models.V1Pod; +import org.hobbit.controller.data.ContainerCriteria; import org.hobbit.core.Constants; -import com.spotify.docker.client.exceptions.DockerException; -import com.spotify.docker.client.messages.ContainerStats; -import com.spotify.docker.client.messages.swarm.Service; /** - * This interface is implemented by classes that can be used to manage Docker + * This interface is implemented by classes that can be used to manage Docker or Kubernetes * containers. * * @author Michael Röder (roeder@informatik.uni-leipzig.de) @@ -85,12 +84,12 @@ public interface ContainerManager { * * @param imageName name of the image to start * @param type container type - * @param parent parent id + * @param parentId parent id * * * @return container id */ - public String startContainer(String imageName, String type, String parent); + public String startContainer(String imageName, String type, String parentId); /** * Starts the container with the given image name. @@ -181,6 +180,16 @@ public String startContainer(String imageName, String containerType, String pare public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] netAliases, String[] command, String experimentId, Map constraints); + +// /** +// * +// * @param containerIdentifier podname or container id +// * @param toOutsideOfTheCluster if yes means outside the cluster should reachable +// * @param ports map of all ports +// * @return service name +// */ +// public String startService(String containerIdentifier, boolean toOutsideOfTheCluster, Map ports); + /** * Stops the container with the given container Id. * @@ -216,22 +225,24 @@ public String startContainer(String imageName, String containerType, String pare /** * Returns container's exit code or null if container is still running. * - * @param container + * @param serviceName */ - public Long getContainerExitCode(String serviceName) throws DockerException, InterruptedException; + public Long getContainerPodExitCode(String serviceName) throws ContainerPodException ,InterruptedException; - /** - * Returns container info - * - * @param containerId - */ - public Service getContainerInfo(String serviceName) throws InterruptedException, DockerException; + + //TODO maybe remove this method from interface just keep the usage in implementation for dokcer +// /** +// * Returns container info +// * +// * @param serviceName +// */ +// public Service getContainerInfo(String serviceName) throws InterruptedException, DockerException; /** - * Get a list of services + * Get a list of services names */ - public default List getContainers() { - return getContainers(Service.Criteria.builder().build()); + public default List getContainers() { + return getContainers(ContainerCriteria.builder().build()); } /** @@ -240,7 +251,7 @@ public default List getContainers() { * @Service.Criteria criteria service criteria for filtering the list of * services */ - public List getContainers(Service.Criteria criteria); + public List getContainers(ContainerCriteria criteria); /** * @deprecated Platform uses names as IDs. Retrieves the container Id for the @@ -248,24 +259,24 @@ public default List getContainers() { * be found. */ @Deprecated - public String getContainerId(String name); + public String getContainerPodId(String name); /** * @deprecated Platform uses names as IDs. Returns the name of the container * with the given Id or {@code null} if such a container can not be * found - * + * * @param containerId the Id of the container for which the name should be * retrieved * @return the name of the container with the given Id or {@code null} if such a * container can not be found */ @Deprecated - public String getContainerName(String containerId); + public String getContainerPodName(String containerId); /** * Adds the given observer to the list of internal observers. - * + * * @param containerObserver the observer that should be added to the internal * list */ @@ -278,23 +289,23 @@ public default List getContainers() { */ public void pullImage(String imageName); - /** - * Returns statistics of the container with the given Id or {@code null} if the - * container can not be found or an error occurs. - * - * @param containerId the Id of the container for which statistics should be - * requested - * @return statistics of the container with the given Id or {@code null} if the - * container can not be found or an error occurs. - */ - public ContainerStats getStats(String containerId); +// /** +// * Returns statistics of the container with the given Id or {@code null} if the +// * container can not be found or an error occurs. +// * +// * @param containerId the Id of the container for which statistics should be +// * requested +// * @return statistics of the container with the given Id or {@code null} if the +// * container can not be found or an error occurs. +// */ +// public ContainerStats getStats(String containerId); /** * Returns the type of the container as string. The type is typically one of * {@link Constants#CONTAINER_TYPE_BENCHMARK}, * {@link Constants#CONTAINER_TYPE_DATABASE} or * {@link Constants#CONTAINER_TYPE_SYSTEM}. - * + * * @param containerId * @return */ diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerPodException.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerPodException.java new file mode 100644 index 00000000..6f9a1d4a --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerPodException.java @@ -0,0 +1,7 @@ +package org.hobbit.controller.containers; + +public class ContainerPodException extends Exception { + public ContainerPodException(String message, Throwable cause) { + super(message, cause); + } + } diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserver.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerStateObserver.java similarity index 98% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserver.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ContainerStateObserver.java index 727a7d06..2c9da86b 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserver.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerStateObserver.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.List; @@ -73,7 +73,7 @@ public interface ContainerStateObserver { /** * Returns the list of observed containers. - * + * * @return the list of containers that should be observer */ public List getObservedContainers(); diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallback.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerTerminationCallback.java similarity index 92% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallback.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ContainerTerminationCallback.java index e28937e7..7322a9fd 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallback.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ContainerTerminationCallback.java @@ -14,7 +14,9 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; + +import org.hobbit.controller.containers.ContainerStateObserver; /** * These methods have to be implemented by a class that should be called if a diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/FileBasedImageManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/FileBasedImageManager.java similarity index 93% rename from platform-controller/src/main/java/org/hobbit/controller/docker/FileBasedImageManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/FileBasedImageManager.java index e96a6faf..c3880b62 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/FileBasedImageManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/FileBasedImageManager.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.io.File; import java.io.IOException; @@ -28,6 +28,7 @@ import org.apache.commons.io.FileUtils; import org.apache.jena.rdf.model.Model; +import org.hobbit.controller.containers.AbstactImageManager; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.SystemMetaData; import org.slf4j.Logger; @@ -37,7 +38,7 @@ public class FileBasedImageManager extends AbstactImageManager { private static final Logger LOGGER = LoggerFactory.getLogger(FileBasedImageManager.class); - private static final String DEFAULT_DEF_FOLDER = "metadata"; + private static final String DEFAULT_DEF_FOLDER = "/data/metadata"; private static final Date DEFAULT_DATE = new Date(0); private final String inputFolder; @@ -130,11 +131,13 @@ protected void readFile(File f, List newBenchmarks, List getUncheckedBenchmarks() { + //LOGGER.info("getUncheckedBenchmarks from File size {}", benchmarks.size()); return new ArrayList<>(benchmarks); } @Override protected List getUncheckedSystems() { + //LOGGER.info("getUncheckedSystems from File size {}", systems.size()); return new ArrayList<>(systems); } diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/GitlabBasedImageManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/GitlabBasedImageManager.java similarity index 84% rename from platform-controller/src/main/java/org/hobbit/controller/docker/GitlabBasedImageManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/GitlabBasedImageManager.java index 76af0876..21f446ca 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/GitlabBasedImageManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/GitlabBasedImageManager.java @@ -14,27 +14,32 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.List; import java.util.Set; import java.util.stream.Collectors; +import org.hobbit.controller.containers.AbstactImageManager; +import org.hobbit.controller.containers.ImageManager; import org.hobbit.controller.gitlab.GitlabController; import org.hobbit.controller.gitlab.GitlabControllerImpl; import org.hobbit.controller.gitlab.Project; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.SystemMetaData; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * An {@link ImageManager} implementation relying on the usage of a * {@link GitlabController}. - * + * * Created by Timofey Ermilov on 22/09/16. - * + * */ public class GitlabBasedImageManager extends AbstactImageManager implements ImageManager { + private static final Logger LOGGER = LoggerFactory.getLogger(GitlabBasedImageManager.class); // gitlab access controller private GitlabControllerImpl gitlab; @@ -50,8 +55,8 @@ public void runWhenGitlabIsReady(Runnable r) { @Override protected List getUncheckedBenchmarks() { return gitlab.getAllProjects().parallelStream().filter(p -> p.benchmarkModel != null) - .flatMap(p -> MetaDataFactory.modelToBenchmarkMetaData(p.benchmarkModel, p.name, p.createdAt).stream()) - .collect(Collectors.toList()); + .flatMap(p -> MetaDataFactory.modelToBenchmarkMetaData(p.benchmarkModel, p.name, p.createdAt).stream()) + .collect(Collectors.toList()); } @Override diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ImageManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ImageManager.java similarity index 99% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ImageManager.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ImageManager.java index e9edef50..7e67834f 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ImageManager.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ImageManager.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.Collections; import java.util.List; diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ImageManagerFacade.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ImageManagerFacade.java similarity index 97% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ImageManagerFacade.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ImageManagerFacade.java index 8fe487d9..2f0df8d2 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ImageManagerFacade.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ImageManagerFacade.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.util.ArrayList; import java.util.Arrays; @@ -12,7 +12,7 @@ * This Facade is able to manage one or more {@link ImageManager} instances and * handles the problem of benchmarks or systems sharing the same URI (see * https://github.com/hobbit-project/platform/issues/136). - * + * * @author Michael Röder (michael.roeder@uni-paderborn.de) * */ diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/KubExtendedContainerManager.java b/platform-controller/src/main/java/org/hobbit/controller/containers/KubExtendedContainerManager.java new file mode 100644 index 00000000..ae7e9cd0 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/KubExtendedContainerManager.java @@ -0,0 +1,7 @@ +package org.hobbit.controller.containers; + +import io.kubernetes.client.openapi.models.V1Pod; + +public interface KubExtendedContainerManager extends ContainerManager { + public V1Pod getPod(String podIP); +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/MetaDataFactory.java b/platform-controller/src/main/java/org/hobbit/controller/containers/MetaDataFactory.java similarity index 99% rename from platform-controller/src/main/java/org/hobbit/controller/docker/MetaDataFactory.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/MetaDataFactory.java index 5731c4b1..50516ae6 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/MetaDataFactory.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/MetaDataFactory.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; import java.io.ByteArrayInputStream; import java.io.StringReader; @@ -93,7 +93,7 @@ public static Model byteArrayToModel(byte data[], String lang) { * Creates a new model that contains all triples of the old model except the * definitions of other hobbit:Benchmark elements than the benchmark with the * given URI. - * + * * @param model the model from which all triples will be copied * @param benchmarkUri the URI of the only benchmark which is not removed from * the copied model @@ -110,7 +110,7 @@ public static Model getModelWithUniqueBenchmark(Model model, String benchmarkUri * have a system URI as subject. A system URI is a URI of a resource {@code s} * for which a triple {@code s rdf:type hobbit:SystemInstance} can be found in * the given model. - * + * * @param model the model from which all triples will be copied * @param systemUri the URI of the only system which is not removed from the * copied model diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollector.java b/platform-controller/src/main/java/org/hobbit/controller/containers/ResourceInformationCollector.java similarity index 73% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollector.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/ResourceInformationCollector.java index 6969542c..66f938f9 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollector.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/ResourceInformationCollector.java @@ -1,5 +1,6 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers; +import org.hobbit.controller.data.ContainerCriteria; import org.hobbit.controller.data.SetupHardwareInformation; import org.hobbit.core.data.usage.ResourceUsageInformation; import com.spotify.docker.client.messages.swarm.Service.Criteria; @@ -14,7 +15,7 @@ public interface ResourceInformationCollector { public ResourceUsageInformation getSystemUsageInformation(); - public ResourceUsageInformation getUsageInformation(Criteria criteria); + public ResourceUsageInformation getUsageInformation(ContainerCriteria criteria); public SetupHardwareInformation getHardwareInformation(); diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManagerImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ClusterManagerImpl.java similarity index 97% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManagerImpl.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/ClusterManagerImpl.java index d628800a..84bb78d2 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ClusterManagerImpl.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ClusterManagerImpl.java @@ -1,7 +1,8 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.util.stream.Stream; +import org.hobbit.controller.containers.ClusterManager; import org.slf4j.Logger; import org.slf4j.LoggerFactory; diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManagerImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerManagerImpl.java similarity index 91% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManagerImpl.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerManagerImpl.java index 0d109e6c..cb503860 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerManagerImpl.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerManagerImpl.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.util.ArrayList; import java.util.Arrays; @@ -29,7 +29,13 @@ import java.util.regex.Pattern; import java.util.stream.Collectors; +import org.hobbit.controller.containers.ContainerPodException; +import org.hobbit.controller.containers.ContainerStateObserver; +import org.hobbit.controller.data.ContainerCriteria; +import org.hobbit.controller.data.ContainerCriteriaToServiceCriteriaMapper; import org.hobbit.controller.gitlab.GitlabControllerImpl; +import org.hobbit.controller.containers.ClusterManager; +import org.hobbit.controller.containers.ContainerManager; import org.hobbit.controller.utils.Waiting; import org.hobbit.core.Constants; import org.slf4j.Logger; @@ -561,8 +567,8 @@ public String startContainer(String imageName, String[] command) { return startContainer(imageName, null, "", command); } - public String startContainer(String imageName, String type, String parent) { - return startContainer(imageName, type, parent, null); + public String startContainer(String imageName, String type, String parentId) { + return startContainer(imageName, type, parentId, null); } @Override @@ -615,10 +621,15 @@ public String startContainer(String imageName, String containerType, String pare return startContainer(imageName, containerType, parentId, env, netAliases, command, true, constraints); } +// @Override +// public String startService(String containerIdentifier, boolean toOutsideOfTheCluster, Map ports) { +// return containerIdentifier; +// } + @Override public void removeContainer(String serviceName) { try { - Long exitCode = getContainerExitCode(serviceName); + Long exitCode = getContainerPodExitCode(serviceName); if (DEPLOY_ENV.equals(DEPLOY_ENV_DEVELOP)) { LOGGER.info("Will not remove container {}. " + "Development mode is enabled.", serviceName); } else if (DEPLOY_ENV.equals(DEPLOY_ENV_TESTING) && (exitCode != null && exitCode != 0)) { @@ -681,8 +692,8 @@ public void removeParentAndChildren(String parent) { } } - @Override - public Service getContainerInfo(String serviceName) throws InterruptedException, DockerException { + //@Override + public Service getContainerInfo(String serviceName) throws InterruptedException, ContainerPodException { if (serviceName == null) { return null; } @@ -691,62 +702,74 @@ public Service getContainerInfo(String serviceName) throws InterruptedException, info = dockerClient.inspectService(serviceName); } catch (ServiceNotFoundException e) { // return null + } catch (DockerException e) { + throw new ContainerPodException("Error getting container pod exit code: " , e); } return info; } @Override - public List getContainers(Service.Criteria criteria) { + public List getContainers(ContainerCriteria criteria) { try { - return dockerClient.listServices(criteria); + Service.Criteria serviceCriteria = ContainerCriteriaToServiceCriteriaMapper.map(criteria); + List services = dockerClient.listServices(serviceCriteria); + List serviceNames = new ArrayList<>(); + for (Service c : services) { + serviceNames.add(c.spec().name()); + } + return serviceNames; } catch (Exception e) { return new ArrayList<>(); } } @Override - public Long getContainerExitCode(String serviceName) throws DockerException, InterruptedException { - if (getContainerInfo(serviceName) == null) { - LOGGER.warn( + public Long getContainerPodExitCode(String serviceName) throws ContainerPodException, InterruptedException { + try { + if (getContainerInfo(serviceName) == null) { + LOGGER.warn( "Couldn't get the exit code for container {}. Service doesn't exist. Assuming it was stopped by the platform.", serviceName); - return DOCKER_EXITCODE_SIGKILL; - } + return DOCKER_EXITCODE_SIGKILL; + } - // Service exists, but no tasks are observed. - List tasks = dockerClient.listTasks(Task.Criteria.builder().serviceName(serviceName).build()); - if (tasks.size() == 0) { - LOGGER.warn("Couldn't get the exit code for container {}. Service has no tasks. Returning null.", + // Service exists, but no tasks are observed. + List tasks = dockerClient.listTasks(Task.Criteria.builder().serviceName(serviceName).build()); + if (tasks.size() == 0) { + LOGGER.warn("Couldn't get the exit code for container {}. Service has no tasks. Returning null.", serviceName); - return null; - } + return null; + } - for (Task task : tasks) { - if (!UNFINISHED_TASK_STATES.contains(task.status().state())) { - // Task is finished. - Long exitCode = task.status().containerStatus().exitCode(); - if (exitCode == null) { - LOGGER.warn("Couldn't get the exit code for container {}. Task is finished. Returning 0.", + for (Task task : tasks) { + if (!UNFINISHED_TASK_STATES.contains(task.status().state())) { + // Task is finished. + Long exitCode = task.status().containerStatus().exitCode(); + if (exitCode == null) { + LOGGER.warn("Couldn't get the exit code for container {}. Task is finished. Returning 0.", serviceName); - return 0l; + return 0l; + } + return exitCode; } - return exitCode; } - } - // Task is not finished. - return null; + // Task is not finished. + return null; + }catch (DockerException e) { + throw new ContainerPodException("Error getting container pod exit code: " , e); + } } @Deprecated @Override - public String getContainerId(String name) { + public String getContainerPodId(String name) { return name; } @Deprecated @Override - public String getContainerName(String containerId) { + public String getContainerPodName(String containerId) { return containerId; } @@ -766,19 +789,19 @@ public static boolean containsVersionTag(String imageName) { return imageName.indexOf(':', pos) >= 0; } - @Override - public ContainerStats getStats(String containerId) { - ContainerStats stats = null; - try { - stats = dockerClient.stats(containerId); - } catch (Exception e) { - LOGGER.warn("Error while requesting usage stats for {}. Returning null. Error: {}", containerId, - e.getLocalizedMessage()); - } - return stats; - } - - @Override +// @Override +// public ContainerStats getStats(String containerId) { +// ContainerStats stats = null; +// try { +// stats = dockerClient.stats(containerId); +// } catch (Exception e) { +// LOGGER.warn("Error while requesting usage stats for {}. Returning null. Error: {}", containerId, +// e.getLocalizedMessage()); +// } +// return stats; +// } + + //@Override public String getContainerType(String containerId) { Service container = null; try { diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserverImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerStateObserverImpl.java similarity index 91% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserverImpl.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerStateObserverImpl.java index e181609f..133c35f5 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerStateObserverImpl.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerStateObserverImpl.java @@ -14,13 +14,18 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.util.ArrayList; import java.util.List; import java.util.Timer; import java.util.TimerTask; +import io.kubernetes.client.openapi.ApiException; +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.ContainerPodException; +import org.hobbit.controller.containers.ContainerStateObserver; +import org.hobbit.controller.containers.ContainerTerminationCallback; import org.slf4j.Logger; import org.slf4j.LoggerFactory; @@ -94,7 +99,7 @@ public void run() { } for (String id : containerIds) { try { - Long exitStatus = manager.getContainerExitCode(id); + Long exitStatus = manager.getContainerPodExitCode(id); if (exitStatus != null) { // notify all callbacks @@ -106,7 +111,7 @@ public void run() { } } } - } catch (DockerException | InterruptedException e) { + } catch (ContainerPodException | InterruptedException e ) { LOGGER.error("Couldn't get the status of container " + id + ". It will be ignored during this run but will be checked again during the next run."); } diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallbackImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImpl.java similarity index 90% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallbackImpl.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImpl.java index 175ec4f5..148f1c6b 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ContainerTerminationCallbackImpl.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImpl.java @@ -14,7 +14,9 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; + +import org.hobbit.controller.containers.ContainerTerminationCallback; /** * Created by Timofey Ermilov on 01/09/16. diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/DockerUtility.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/DockerUtility.java similarity index 94% rename from platform-controller/src/main/java/org/hobbit/controller/docker/DockerUtility.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/DockerUtility.java index 2b3ba85c..6ccc0c52 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/DockerUtility.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/DockerUtility.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import com.spotify.docker.client.DefaultDockerClient; import com.spotify.docker.client.DockerClient; diff --git a/platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollectorImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorImpl.java similarity index 89% rename from platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollectorImpl.java rename to platform-controller/src/main/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorImpl.java index a4384067..2ae3b4da 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/docker/ResourceInformationCollectorImpl.java +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorImpl.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.io.IOException; import java.net.MalformedURLException; @@ -13,6 +13,9 @@ import javax.ws.rs.core.UriBuilder; import org.apache.commons.io.IOUtils; +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.ResourceInformationCollector; +import org.hobbit.controller.data.ContainerCriteria; import org.hobbit.core.Constants; import org.hobbit.core.data.usage.CpuStats; import org.hobbit.core.data.usage.DiskStats; @@ -96,8 +99,8 @@ public ResourceInformationCollectorImpl(ContainerManager manager, String prometh @Override public ResourceUsageInformation getSystemUsageInformation() { - return getUsageInformation(Service.Criteria.builder() - .labels(ImmutableMap.of(ContainerManager.LABEL_TYPE, Constants.CONTAINER_TYPE_SYSTEM)) + return getUsageInformation(ContainerCriteria.builder() + .withLabels(ImmutableMap.of(ContainerManager.LABEL_TYPE, Constants.CONTAINER_TYPE_SYSTEM)) .build()); } @@ -114,21 +117,27 @@ private long countRunningTasks(String serviceName) { } } + private boolean isServiceRunning(String serviceName) { + try { + return dockerClient.listTasks( + Task.Criteria.builder().serviceName(serviceName).build() + ).stream() + .anyMatch(t -> TaskStatus.TASK_STATE_RUNNING.equals(t.status().state())); + } catch (DockerException | InterruptedException e) { + LOGGER.error("Error checking if service {} is running: {}", serviceName, e.getMessage(), e); + return false; // Or throw an exception if you prefer + } + } + @Override - public ResourceUsageInformation getUsageInformation(Service.Criteria criteria) { - List services = manager.getContainers(criteria); + public ResourceUsageInformation getUsageInformation(ContainerCriteria criteria) { + List servicesName = manager.getContainers(criteria); + + ResourceUsageInformation resourceInfo = servicesName.parallelStream() + .filter(this::isServiceRunning) // Use a method to check if the service is running + .map(this::requestCpuAndMemoryStats) + .collect(Collectors.reducing(ResourceUsageInformation::staticMerge)).orElse(null); - Map containerMapping = new HashMap<>(); - for (Service c : services) { - containerMapping.put(c.spec().name(), c); - } - ResourceUsageInformation resourceInfo = containerMapping.keySet().parallelStream() - // filter all containers that are not running - .filter(s -> countRunningTasks(s) != 0) - // get the stats for the single - .map(id -> requestCpuAndMemoryStats(id)) - // sum up the stats - .collect(Collectors.reducing(ResourceUsageInformation::staticMerge)).orElse(null); return resourceInfo; } diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ClusterManagerImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ClusterManagerImpl.java new file mode 100644 index 00000000..ef4a7883 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ClusterManagerImpl.java @@ -0,0 +1,131 @@ +package org.hobbit.controller.containers.kubernetes; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.V1Node; +import io.kubernetes.client.openapi.models.V1NodeList; +import org.hobbit.controller.containers.ClusterManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.List; + +/** + * Implementation of the {@link ClusterManager} interface using Kubernetes API. + * This class provides methods to manage and monitor a Kubernetes cluster, including listing nodes, + * checking cluster health, and managing task history limits. + * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ +public class ClusterManagerImpl implements ClusterManager { + private static final Logger LOGGER = LoggerFactory.getLogger(ClusterManagerImpl.class); + private final CoreV1Api coreV1Api; + private Integer taskHistoryLimit = 0; // Default task history limit + + /** + * Constructs a new {@code ClusterManagerImpl} with the given Kubernetes API client. + * + * @param apiClient The Kubernetes API client used to interact with the cluster. + */ + public ClusterManagerImpl(ApiClient apiClient) { + LOGGER.info("Creating a new cluster manager "); + this.coreV1Api = new CoreV1Api(apiClient); + LOGGER.info("coreV1Api created, lets test it"); + try{ + + V1NodeList nodeList = coreV1Api.listNode(null, null, null, null, null, null, null, null, null, false); + List nodes = nodeList.getItems(); + LOGGER.info(nodes.size() + " nodes found"); + }catch(Exception e){ + LOGGER.error("Failed to create cluster manager coreV1Api is the root of the problem", e); + } + } + + /** + * Returns the total number of nodes in the Kubernetes cluster. + * + * @return The total number of nodes. + * @throws InterruptedException If the operation is interrupted. + */ + @Override + public long getNumberOfNodes() throws InterruptedException { + try { + V1NodeList nodeList = coreV1Api.listNode(null, null, null, null, null, null, null, null, null, false); + return nodeList.getItems().size(); + } catch (ApiException e) { + throw new RuntimeException("Failed to get the number of nodes", e); + } + } + + /** + * Returns the number of nodes in the Kubernetes cluster that match a specific label. + * + * @param label The label selector used to filter nodes. + * @return The number of nodes matching the label. + * @throws InterruptedException If the operation is interrupted. + */ + @Override + public long getNumberOfNodes(String label) throws InterruptedException { + try { + String labelSelector = label; + V1NodeList nodeList = coreV1Api.listNode(null, null, null, labelSelector, null, null, null, null, null, false); + return nodeList.getItems().size(); + } catch (ApiException e) { + throw new RuntimeException("Failed to get the number of nodes with label: " + label, e); + } + } + + /** + * Checks if the Kubernetes cluster is healthy by verifying that all nodes are in a 'Ready' state. + * + * @return {@code true} if the cluster is healthy, otherwise {@code false}. + * @throws InterruptedException If the operation is interrupted. + */ + @Override + public boolean isClusterHealthy() throws InterruptedException { + //LOGGER.info("Checking cluster health"); + try { + V1NodeList nodeList = coreV1Api.listNode(null, null, null, null, null, null, null, null, null, false); + List nodes = nodeList.getItems(); + //LOGGER.info(nodes.size() + " nodes found"); + for (V1Node node : nodes) { + String status = node.getStatus().getConditions() + .stream() + .filter(condition -> "Ready".equals(condition.getType())) + .findFirst() + .map(condition -> condition.getStatus()) + .orElse("Unknown"); + + if (!"True".equalsIgnoreCase(status)) { + return false; + } + } + return true; + } catch (ApiException e) { + LOGGER.error(e.getMessage()); + e.printStackTrace(); + throw new RuntimeException("Failed to check cluster health", e); + } + } + + // TODO do we need this and should the environment variable be one or define another for kubernetes like KUB_NODE_NUMBER + @Override + public long getExpectedNumberOfNodes() { + String expectedNodesEnv = System.getenv("SWARM_NODE_NUMBER"); + return expectedNodesEnv != null ? Long.parseLong(expectedNodesEnv) : 0; + } + + @Override + public void setTaskHistoryLimit(Integer taskHistoryLimit) throws InterruptedException { + if (taskHistoryLimit == null || taskHistoryLimit < 0) { + throw new IllegalArgumentException("Task history limit must be non-negative"); + } + this.taskHistoryLimit = taskHistoryLimit; + // TODO define this for kubernetes because as default I could not find usage ! + } + + @Override + public int getTaskHistoryLimit() throws InterruptedException { + return this.taskHistoryLimit; + } +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerManagerImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerManagerImpl.java new file mode 100644 index 00000000..0118c570 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerManagerImpl.java @@ -0,0 +1,1019 @@ +package org.hobbit.controller.containers.kubernetes; + +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.ContainerPodException; +import org.hobbit.controller.containers.ContainerStateObserver; +import org.hobbit.controller.containers.KubExtendedContainerManager; +import org.hobbit.controller.data.ContainerCriteria; +import org.hobbit.controller.utils.Waiting; +import org.hobbit.core.Constants; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.ApiException; +import io.kubernetes.client.util.generic.GenericKubernetesApi; +import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.openapi.apis.CoreV1Api; +import io.kubernetes.client.openapi.models.*; + +import java.net.*; +import java.util.*; +import java.util.concurrent.TimeUnit; +import java.util.regex.Matcher; +import java.util.regex.Pattern; + +/** + * KubernetesContainerManager is a class that implements the ContainerManager interface to manage + * containers in a Kubernetes environment. It provides methods for creating, removing, starting, + * stopping, and retrieving information about pods. + * + * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ +public class ContainerManagerImpl implements ContainerManager, KubExtendedContainerManager { + + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerManagerImpl.class); + private static final String DEFAULT_GIT_USERNAME = "gitadmin"; + private static final String DEFAULT_GIT_EMAIL = "gitadmin@project-hobbit.eu"; + private static final String RUN_ON_KUBERNETES_FLAG = "kubernetes"; + private ApiClient client; + //TODO do we need to make config able ? + private String nameSpace = "default"; + private static final long KUBERNETES_POLL_INTERVAL = 5000; // Poll interval in ms + + /** + * Logging separator for type/experiment id. + */ + private static final String LOGGING_SEPARATOR = "_sep_"; + //public static final String LOGGING_TAG = "{{.ImageName}}/{{.Name}}/{{.ID}}"; + public static final String DEPLOY_ENV_KEY = "DEPLOY_ENV"; + private static final String DEPLOY_ENV_DEVELOP = "develop"; + private static final String DEPLOY_ENV_TESTING = "testing"; + private static final String DEPLOY_ENV = System.getenv().containsKey(DEPLOY_ENV_KEY) + ? System.getenv().get(DEPLOY_ENV_KEY) + : "production"; + private static final long TIMEOUT_SECONDS = 60; // Adjust the timeout as needed + private static final int MAX_POD_NAME_LENGTH = 63; + private static final long POD_PHASE_CHECK_INTERVAL = 2000; // in ms + private static final Pattern VALID_POD_NAME_REGEX = Pattern.compile("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$"); + + + /** + * Observers that should be notified if a container terminates. + */ + private final List containerObservers = new ArrayList<>(); + + + public ContainerManagerImpl(ApiClient client) { + LOGGER.info("Initializing Kubernetes client"); + this.client = client; + } + + + + @Deprecated + public String startContainer(String imageName) { + return null; + } + + @Deprecated + public String startContainer(String imageName, String[] command){ + return null; + } + + @Override + public String startContainer(String imageName, String type, String parentId) { + return startContainer(imageName, type, parentId, null); + } + + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] command) { + return startContainer(imageName, containerType, parentId, null, command); + } + + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] command) { + return startContainer(imageName, containerType, parentId, env, null, command); + } + + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] netAliases, String[] command) { + return startContainer(imageName, containerType, parentId, env, null, command, true, + Collections.emptyMap()); + } + + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] command, boolean pullImage) { + return startContainer(imageName, containerType, parentId, env, null, command, true, Collections.emptyMap()); + } + + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] netAliases, String[] command, boolean pullImage, Map constraints) { + //in Interface there is no experimentID , keep it for backward compatibility + return startContainer(imageName, containerType, parentId, env, null, command, "", constraints); + } + + /** + * Generates a unique pod name based on the given module IRI and container type. + * + * If the {@code containerType} is {@code null}, the method defaults to using "notype" + * as the prefix. The generated name combines the hash code of the {@code moduleIri}, + * multiplied by 31, with the current system time in milliseconds, cast to an integer. + * This ensures a high probability of uniqueness. + * + * @param moduleIri the IRI (Internationalized Resource Identifier) of the module + * @param containerType the type of container (e.g., "docker", "kubernetes"); may be {@code null} + * @return a generated pod name string that includes the container type (or "notype") and a unique numeric suffix + */ + public String generatePodName(String moduleIri,String containerType) { + if(containerType==null){ + return "notype" + (moduleIri.hashCode() * 31 + (int) (System.currentTimeMillis())); + } + return containerType + (moduleIri.hashCode() * 31 + (int) (System.currentTimeMillis())); + } + + /** + * Shortens and validates a pod name to ensure it meets Kubernetes naming conventions. + * + * @param podName The original pod name. + * @return The shortened and validated pod name. + * @throws IllegalArgumentException If the pod name is null, empty, or cannot be sanitized. + */ + public static String shortenAndValidatePodName(String podName) { + if (podName == null || podName.isEmpty()) { + throw new IllegalArgumentException("Pod name cannot be null or empty."); + } + + String shortenedName = podName; + + // Shorten the name if it exceeds the maximum allowed length + if (podName.length() > MAX_POD_NAME_LENGTH) { + // Shorten the name, prioritizing the end (more unique). + int excessLength = podName.length() - MAX_POD_NAME_LENGTH; + int charsToKeep = podName.length() - excessLength; + shortenedName = podName.substring(0, charsToKeep); + + //Ensure it ends with alphanumeric + while (shortenedName.length() > 0 && !Character.isLetterOrDigit(shortenedName.charAt(shortenedName.length()-1))) { + shortenedName = shortenedName.substring(0, shortenedName.length()-1); + } + } + + //Validate the shortened name + Matcher matcher = VALID_POD_NAME_REGEX.matcher(shortenedName); + if (!matcher.matches()) { + //Handle invalid characters (replace with hyphens or remove) + shortenedName = shortenedName.replaceAll("[^a-z0-9-]", "-"); + // Ensure starts and ends with alphanumeric after sanitization + while (shortenedName.length() > 0 && !Character.isLetterOrDigit(shortenedName.charAt(0))) { + shortenedName = shortenedName.substring(1); + } + while (shortenedName.length() > 0 && !Character.isLetterOrDigit(shortenedName.charAt(shortenedName.length()-1))) { + shortenedName = shortenedName.substring(0, shortenedName.length()-1); + } + //Re-check length after sanitization + if (shortenedName.length() > MAX_POD_NAME_LENGTH) { + shortenedName = shortenedName.substring(0, MAX_POD_NAME_LENGTH); + } + matcher = VALID_POD_NAME_REGEX.matcher(shortenedName); + if (!matcher.matches()) { + throw new IllegalArgumentException("Pod name could not be sanitized to comply with naming conventions."); + } + } + + return shortenedName; + } + + // we do not use netAliases + @Override + public String startContainer(String imageName, String containerType, String parentId, String[] env, String[] netAliases, String[] command, String experimentId, Map constraints) { + String podName = generatePodName(imageName,containerType); + if (experimentId != null) { + podName = experimentId + LOGGING_SEPARATOR + generatePodName(imageName,containerType); + } + + podName = shortenAndValidatePodName(podName); + + LOGGER.debug("start Container podname is {}", podName); + + if (imageName != null) { + LOGGER.debug("image name is {}", imageName); + } else { + LOGGER.debug("image name is null"); + } + + if (containerType != null) { + LOGGER.debug("container type is {}", containerType); + } else { + LOGGER.debug("container type is null"); + } + if (parentId != null) { + if (!parentId.contains(".pod.cluster.")) { + LOGGER.debug("wrong parent ID {}",parentId); + //parentId = getContainerPodName(parentId); + LOGGER.debug("new parent ID is {}",parentId); + } + } + + if (parentId != null) { + LOGGER.debug("Parent ID is {}", parentId); + //todo just as a patch, the parent id somewhere insert wrong and as container id + } else { + LOGGER.debug("Parent ID is null"); + } + + if (env != null && env.length > 0) { + LOGGER.debug("env is {}", String.join(",", env)); + } else { + LOGGER.debug("env is null or empty"); + } + + if (command != null && command.length > 0) { + LOGGER.debug("command is {}", String.join(",", command)); + } else { + LOGGER.debug("command is null or empty"); + } + + if (experimentId != null) { + LOGGER.debug("experimentID is {}", experimentId); + } else { + LOGGER.debug("experimentID is null"); + } + + return createContainerKub(imageName, podName, containerType, parentId, env, command, constraints); + } + + /** + * Retrieves the IPv4 address of the pod. + * + * This method iterates through the network interfaces to find an active, non-loopback interface + * and returns the first available IPv4 address it finds. + * + * @return The IPv4 address of the pod, or "Unknown-IP" if no valid IP is found. + * @throws RuntimeException if no valid IPv4 address is found. + */ + public static String getPodIP() { + try { + // Get the local host's network interfaces + Enumeration networkInterfaces = NetworkInterface.getNetworkInterfaces(); + + while (networkInterfaces.hasMoreElements()) { + NetworkInterface networkInterface = networkInterfaces.nextElement(); + + // Ignore loopback and inactive interfaces + if (!networkInterface.isLoopback() && networkInterface.isUp()) { + Enumeration addresses = networkInterface.getInetAddresses(); + + while (addresses.hasMoreElements()) { + InetAddress address = addresses.nextElement(); + + // Ignore IPv6 addresses and return the first available IPv4 address found + if (address instanceof Inet4Address) { + return address.getHostAddress(); + } + } + } + } + } catch (SocketException e) { + LOGGER.error("getPodIP failed", e); + throw new RuntimeException("Error retrieving pod IP address", e); + } + + // Log and throw an exception if no valid IP address is found + LOGGER.error("No valid IPv4 address found for the pod"); + throw new RuntimeException("No valid IPv4 address found for the pod"); + } + + // this is kind of the patch can remove after ckan image fetch without docker.io + private String completeImageName(String imageName) { + if ("dicegroup/ckan-hobbit-db".equals(imageName)) { + return "docker.io/dicegroup/ckan-hobbit-db:latest"; + } + return imageName; + } + + /** + * Prepares and updates the environment variables array by adding a new variable representing the current pod's DNS ID. + * + * @param env The original array of environment variables. If {@code null} or empty, a new array will be created. + * @return A new array of environment variables with the additional variable for the current pod's DNS ID. + */ + private String[] prepareEnvironmentVariables(String[] env) { + String thisPodName = Constants.CONTAINER_NAME_KEY + "=" + convertIP2dnsId(getPodIP()); + if (env == null || env.length == 0) { + return new String[]{thisPodName}; + } + String[] updatedEnv = Arrays.copyOf(env, env.length + 1); + updatedEnv[env.length] = thisPodName; + return updatedEnv; + } + + /** + * Converts all container names in the environment variables array to their corresponding DNS IDs. + * + * @param env The array of environment variables to be converted. + * @return The updated array of environment variables with converted container names. + */ + private String[] convertAllNamesInENV(String[] env) { + for (int i = 0; i < env.length; i++) { + env[i] = changeIfItIsContainerName(env[i]); + } + return env; + } + + /** + * Parses an array of environment variables in the format "key=value" into a list of {@code V1EnvVar} objects. + * + * @param env The array of environment variables to be parsed. + * @return A list of {@code V1EnvVar} objects representing the parsed environment variables. + */ + private List parseEnvironmentVariables(String[] env) { + List environmentVariables = new ArrayList<>(); + for (String envVar : env) { + String[] parts = envVar.split("=", 2); + if (parts.length == 2) { + environmentVariables.add(new V1EnvVar().name(parts[0]).value(parts[1])); + } else { + LOGGER.warn("Skipping invalid environment variable: {}", envVar); + } + } + return environmentVariables; + } + + /** + * Adds default environment variables to the given list of {@code V1EnvVar} objects. + * + * @param environmentVariables The list of environment variables to which the default variables will be added. + */ + private void addDefaultEnvironmentVariables(List environmentVariables) { + environmentVariables.add(new V1EnvVar().name("GITLAB_USER").value(DEFAULT_GIT_USERNAME)); + environmentVariables.add(new V1EnvVar().name("GITLAB_EMAIL").value(DEFAULT_GIT_EMAIL)); + environmentVariables.add(new V1EnvVar().name("RUN_ON").value(RUN_ON_KUBERNETES_FLAG)); + } + + /** + * Determines the type of the container based on the provided container type or the type of its parent pod. + * + * @param containerType The type of the container. If {@code null} or empty, it will be resolved from the parent pod. + * @param parentPodName The name of the parent pod. If {@code null}, no parent resolution will be attempted. + * @return The determined type of the container, or {@code null} if the type cannot be resolved. + */ + private String determineContainerType(String containerType, String parentPodName) { + String parentType = parentPodName != null ? getContainerType(parentPodName) : null; + if (containerType == null || containerType.isEmpty()) { + if (parentType == null) { + LOGGER.error("Unable to resolve parent container type. Returning null."); + return null; + } + containerType = parentType; + } + return containerType; + } + + /** + * Creates a security context for a container based on the provided image name. + * + * @param imageName The name of the Docker image for the container. + * @return A {@code V1SecurityContext} object configured with appropriate security settings. If the image name matches + * "earthquakesan/ckan-solr:2.8.0", a less privileged security context is returned. + */ + private V1SecurityContext createSecurityContext(String imageName) { + V1SecurityContext securityContext = new V1SecurityContext(); + if (!imageName.contains("earthquakesan/ckan-solr:2.8.0")) { + securityContext.setCapabilities(new V1Capabilities() + .addAddItem("NET_RAW").addAddItem("SYS_ADMIN").addAddItem("AUDIT_WRITE")); + securityContext.setRunAsUser(0L); + securityContext.setRunAsGroup(0L); + securityContext.allowPrivilegeEscalation(true); + } else { + LOGGER.debug("do not use as admin"); + } + return securityContext; + } + + /** + * Creates a container specification for a Kubernetes pod based on the provided parameters. + * + * @param podName The name of the pod. + * @param imageName The Docker image name for the container. + * @param environmentVariables A list of {@code V1EnvVar} objects representing the environment variables for the container. + * @param command An array of strings representing the command to run in the container. If {@code null}, no command is set. + * @param constraints A map of resource constraints that will be used to create resource requirements for the container. + * @return A {@code V1Container} object configured with the provided specifications. + */ + private V1Container createContainerSpec(String podName, String imageName, List environmentVariables, + String[] command, Map constraints) { + V1ResourceRequirements resourceRequirements = createResourceRequirementsFromConstraints(constraints); + return new V1Container() + .name(podName) + .image(imageName) + .env(environmentVariables) + .command(command != null ? Arrays.asList(command) : null) + .resources(resourceRequirements) + .securityContext(createSecurityContext(imageName)); + } + + /** + * Applies a node selector to the given pod specification based on the container type and its parent type. + * + * @param podSpec The {@code V1PodSpec} object to which the node selector will be applied. + * @param containerType The type of the container. This determines the appropriate node group for the pod. + * @param parentType The type of the parent container or pod. If {@code null}, it is assumed there is no parent. + */ + private void applyNodeSelector(V1PodSpec podSpec, String containerType, String parentType) { + if ((((parentType == null) || Constants.CONTAINER_TYPE_BENCHMARK.equals(parentType)) + && Constants.CONTAINER_TYPE_SYSTEM.equals(containerType)) + || Constants.CONTAINER_TYPE_SYSTEM.equals(parentType)) { + + LOGGER.debug("Setting node selector to 'system-nodes' for container type: {}", containerType); + podSpec.nodeSelector(Collections.singletonMap("node-group", "system-nodes")); + + } else if (Constants.CONTAINER_TYPE_DATABASE.equals(containerType) + && ((parentType == null) || Constants.CONTAINER_TYPE_BENCHMARK.equals(parentType) + || Constants.CONTAINER_TYPE_DATABASE.equals(parentType))) { + + LOGGER.debug("Setting node selector to 'benchmark-nodes' for DATABASE container type."); + podSpec.nodeSelector(Collections.singletonMap("node-group", "benchmark-nodes")); + + } else if (Constants.CONTAINER_TYPE_BENCHMARK.equals(containerType) + && ((parentType == null) || Constants.CONTAINER_TYPE_BENCHMARK.equals(parentType))) { + + LOGGER.debug("Setting node selector to 'benchmark-nodes' for BENCHMARK container type."); + podSpec.nodeSelector(Collections.singletonMap("node-group", "benchmark-nodes")); + + } else { + LOGGER.error("Got a request to create a container with type={} and parentType={}. " + + "No rule found to determine its type.", containerType, parentType); + // then apply it where you (KUBERNETES) can + podSpec.nodeSelector(null); + } + } + + /** + * Creates a pod specification with the given container and additional settings based on the image name, container type, and parent type. + * + * @param container The {@code V1Container} object that will be added to the pod. + * @param imageName The name of the Docker image used for the container. + * @param containerType The type of the container. This determines the appropriate node group for the pod. + * @param parentType The type of the parent container or pod. If {@code null}, it is assumed there is no parent. + * @return A {@code V1PodSpec} object configured with the provided specifications and settings. + */ + private V1PodSpec createPodSpec(V1Container container, String imageName, String containerType, String parentType) { + V1PodSpec podSpec = new V1PodSpec() + .restartPolicy("Never") + .addContainersItem(container) + .addImagePullSecretsItem(new V1LocalObjectReference().name("gitlab-registry-secret")) + .addImagePullSecretsItem(new V1LocalObjectReference().name("my-dockerhub-secret")); + + if (!imageName.contains("earthquakesan/ckan-solr:2.8.0")) { + V1PodSecurityContext podSecurityContext = new V1PodSecurityContext(); + podSecurityContext.setFsGroup(0L); + podSecurityContext.setRunAsUser(0L); + podSpec.setSecurityContext(podSecurityContext); + } + + applyNodeSelector(podSpec, containerType, parentType); + return podSpec; + } + + + /** + * Creates metadata for a Kubernetes pod, including labels that identify the container type and parent pod. + * + * @param podName The name of the pod. + * @param containerType The type of the container. This will be included as a label in the metadata. + * @param parentPodName The name of the parent pod, if any. This will also be included as a label in the metadata. + * @return A {@code V1ObjectMeta} object containing the metadata for the pod. + */ + private V1ObjectMeta createPodMetadata(String podName, String containerType, String parentPodName) { + Map labels = new HashMap<>(); + labels.put(LABEL_TYPE, containerType); + labels.put(LABEL_PARENT, parentPodName); + labels.put("app", podName); + return new V1ObjectMeta().name(podName).namespace(nameSpace).labels(labels); + } + + + /** + * Deploys a Kubernetes pod and monitors its startup. + * + * @param pod The {@code V1Pod} object representing the pod to be deployed. + * @param podName The name of the pod. + * @return A string which is the pod IP as a DNS frinely version, or {@code null} if the deployment fails. + */ + private String deployPod(V1Pod pod, String podName) { + try { + GenericKubernetesApi podClient = new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "", "v1", "pods", client); + V1Pod createdPod = podClient.create(pod).throwsApiException().getObject(); + return monitorPodStartup(podClient, createdPod); + } catch (ApiException e) { + LOGGER.error("Failed to create pod for image. Returning null.", e); + return null; + } + } + + + /** + * Monitors the startup of a Kubernetes pod and waits until it reaches the "Running" phase with an assigned IP. + * If the pod successfully starts, it converts the pod's IP to a DNS ID and notifies registered observers. + * + * @param podClient The Kubernetes API client for managing pods. + * @param createdPod The newly created pod object. + * @return The converted DNS ID of the pod if it reaches the "Running" phase with an assigned IP within the timeout period, or {@code null} otherwise. + */ + + private String monitorPodStartup(GenericKubernetesApi podClient, V1Pod createdPod) { + String createdPodName = createdPod.getMetadata().getName(); + + if (createdPodName != null) { + LOGGER.debug("Successfully created pod with name: {}", createdPodName); + long startTime = System.currentTimeMillis(); + V1PodList podList = podClient.list(nameSpace).getObject(); + while (System.currentTimeMillis() - startTime < TimeUnit.SECONDS.toMillis(TIMEOUT_SECONDS)) { + try { + V1Pod targetPod = podList.getItems().stream() + .filter(podTocheck -> createdPodName.equals(podTocheck.getMetadata().getName())) + .findFirst() + .orElse(null); + + LOGGER.debug("Target pod found: {}", targetPod.getMetadata().getName()); + + String podPhase = targetPod.getStatus().getPhase(); + if ("Running".equals(podPhase)) { + String podIP = targetPod.getStatus().getPodIP(); + if (podIP != null && !podIP.isEmpty()) { + String convertedIP = convertIP2dnsId(podIP); + for (ContainerStateObserver observer : containerObservers) { + observer.addObservedContainer(convertedIP); + } + LOGGER.debug("return this converted IP: {}", convertedIP); + return convertedIP; + } + } + LOGGER.trace("pod phase is {}", podPhase); + podList = podClient.list(nameSpace).getObject(); + }catch (Exception ex){ + LOGGER.error(ex.getMessage()); + } + try { + Thread.sleep(POD_PHASE_CHECK_INTERVAL); // Wait for 2 second before checking again + } catch (InterruptedException e) { + LOGGER.error("Thread interrupted while waiting for pod IP: " + e.getMessage()); + } + } + + LOGGER.warn("Timeout reached. Pod {} IP is still not available.", createdPodName); + } else { + LOGGER.error("Failed to create the pod."); + } + LOGGER.warn("return null"); + return null; // Return null if the IP address is not available within the timeout + } + + /** + * Creates and deploys a Kubernetes container within a pod. + * + * @param imageName The name of the Docker image to be used for the container. + * @param podName The name of the pod in which the container will run. + * @param containerType The type of the container, which can influence how it is managed or monitored. + * @param parentPodName The name of the parent pod, if any. This helps in organizing and tracking related containers. + * @param env An array of environment variables to be set for the container. + * @param command An array of commands to be executed within the container. + * @param constraints A map of constraints that may affect the deployment or behavior of the container. + * @return POD name which is a DNS friendly pod IP , or {@code null} if it fails. + */ + private String createContainerKub(String imageName, String podName, String containerType, String parentPodName, + String[] env, String[] command, Map constraints) { + imageName = completeImageName(imageName); + LOGGER.debug("Creating container: Image = {}, Pod Name = {}, Container Type = {}, Parent ID = {}", + imageName, podName, containerType, parentPodName); + + env = prepareEnvironmentVariables(env); + env = convertAllNamesInENV(env); + + List environmentVariables = parseEnvironmentVariables(env); + addDefaultEnvironmentVariables(environmentVariables); + + containerType = determineContainerType(containerType, parentPodName); + if (containerType == null) return null; + + V1Container container = createContainerSpec(podName, imageName, environmentVariables, command, constraints); + + String parentType = getParentType(parentPodName); + + V1PodSpec podSpec = createPodSpec(container, imageName, containerType, parentType); + // this is a patch for some situation which container type is null + if ((((parentType == null) || Constants.CONTAINER_TYPE_BENCHMARK.equals(parentType)) + && Constants.CONTAINER_TYPE_SYSTEM.equals(containerType)) + || Constants.CONTAINER_TYPE_SYSTEM.equals(parentType)) { + containerType = Constants.CONTAINER_TYPE_SYSTEM; + LOGGER.info("TTHHIISS IISS HHAAPPEENNEEDD"); + } + V1ObjectMeta metadata = createPodMetadata(podName, containerType, parentPodName); + V1Pod pod = new V1Pod().metadata(metadata).spec(podSpec); + + return deployPod(pod, podName); + } + + /** + * Retrieves the type of the parent pod. + * + * @param parentPodName The name of the parent pod. + * @return The type of the parent pod, or {@code null} if the parent pod does not exist or its type cannot be determined. + */ + private String getParentType(String parentPodName) { + String parentType = null; + if(parentPodName != null) { + parentType = getContainerType(parentPodName); + } + return parentType; + } + + /** + * Checks if a given string is a container name and, if so, replaces it with its corresponding IP address. + * + * @param s The input string to be checked and potentially modified. + * @return The original string if it is not a valid container name, or the modified string with the container name replaced by its IP address. + */ + private String changeIfItIsContainerName(String s) { + String[] parts = s.split("="); + if(parts.length !=2 ){ + LOGGER.error("Invalid container name. {}",s); + return s; + }else { + if (isItContainerName(parts[1])) { + String newName = mapName2IP(parts[1]); + return parts[0] + "=" + newName; + } + return s; + } + } + + /** + * Converts a pod IP address to its corresponding DNS identifier. + * + * @param podIP The IP address of the pod. + * @return A string representing the DNS identifier for the pod, formatted as `ip-address.default.pod.cluster.local`. + */ + private String convertIP2dnsId(String podIP) { + return podIP.replace(".", "-") + ".default.pod.cluster.local"; + } + + /** + * Maps a DNS identifier back to the pod name by querying the Kubernetes API. + * + * @param ip The DNS identifier of the pod, typically in the format `ip-address.default.pod.cluster.local`. + * @return The name of the pod corresponding to the given IP address, or {@code null} if no matching pod is found. + */ + private String mapIp2Name(String ip) { + + String podIp = ip.replace("-",".").replace(".default.pod.cluster.local", ""); + //LOGGER.info("map this IP: {} which extracted from this {}", podIp, ip); + + GenericKubernetesApi podClient = + new GenericKubernetesApi<>(V1Pod.class, V1PodList.class, "", "v1", "pods", client); + + V1PodList podList = podClient.list(nameSpace).getObject(); + V1Pod targetPod = podList.getItems().stream() + .filter(podTocheck -> podIp.equals(podTocheck.getStatus().getPodIP())) + .findFirst() + .orElse(null); + return targetPod.getMetadata().getName(); + } + + /** + * Retrieves a Kubernetes pod by its IP address. + * + * @param podIP The IP address of the pod. + * @return The {@link V1Pod} object corresponding to the given IP address, or {@code null} if no matching pod is found. + */ + @Override + public V1Pod getPod(String podIP) { + //LOGGER.info("getPod with {}",podIP); + String podName = mapIp2Name(podIP); + //LOGGER.info("Attempting to retrieve pod: {} in namespace: {}", podName, this.nameSpace); + if(podName == null){ + LOGGER.error("No pod found with podIP: {}", podIP); + } + CoreV1Api coreV1Api = new CoreV1Api(client); + try { + // Inspect the pod by name + V1Pod pod = coreV1Api.readNamespacedPod(podName, this.nameSpace, null); + //LOGGER.info("Successfully retrieved pod: {} in namespace: {}", podName, this.nameSpace); + return pod; + } catch (ApiException exc) { + LOGGER.error("Failed to get pod: {} in namespace: {}", podName, this.nameSpace); + return null; + } + } + + /** + * Creates Kubernetes resource requirements based on the provided constraints. + * + * @param constraints A map of constraints that may include memory and CPU limits. + * @return A {@link V1ResourceRequirements} object representing the resource limits for a pod or container. + */ + private V1ResourceRequirements createResourceRequirementsFromConstraints(Map constraints) { + V1ResourceRequirements resourceRequirements = new V1ResourceRequirements(); + Map limits = new HashMap<>(); + + if (constraints != null && (constraints.containsKey(MEMORY_LIMIT_CONSTRAINT) + || constraints.containsKey(NANO_CPU_LIMIT_CONSTRAINT))) { + + // if there is a memory limitation + if (constraints.containsKey(MEMORY_LIMIT_CONSTRAINT)) { + long memory = (Long) constraints.get(MEMORY_LIMIT_CONSTRAINT); + limits.put("memory", new Quantity(String.valueOf(memory))); + } + // if there is a CPU limitation + if (constraints.containsKey(NANO_CPU_LIMIT_CONSTRAINT)) { + // CPU quota in units of 10^-9 CPUs. + long nanoCPUs = (Long) constraints.get(NANO_CPU_LIMIT_CONSTRAINT); + limits.put("cpu", new Quantity(String.valueOf(nanoCPUs))); + } + } + + resourceRequirements.setLimits(limits); + return resourceRequirements; + } + + + @Deprecated + @Override + public void stopContainer(String containerId) { + LOGGER.error("ContainerManager.stopContainer() is deprecated! Will remove container instead"); + removeContainer(containerId); + } + + /** + * Removes a Kubernetes pod by its IP address, with conditions based on the deployment environment and exit code. + * + * @param podIp The IP address of the pod to be removed. + */ + @Override + public void removeContainer(String podIp) { + + LOGGER.info("removing pod {}.", podIp); + //String podName = mapIp2Name(podIp); + try { + Long exitCode = getContainerPodExitCode(podIp); + + if (DEPLOY_ENV.equals(DEPLOY_ENV_DEVELOP)) { + LOGGER.info("Will not remove pod {}. Development mode is enabled.", podIp); + } else if (DEPLOY_ENV.equals(DEPLOY_ENV_TESTING) && (exitCode != null && exitCode != 0)) { + LOGGER.info("Will not remove pod {}. ExitCode: {} != 0 and testing mode is enabled.", podIp, exitCode); + } else { + LOGGER.info("Removing pod {}.", podIp); + + // Initialize the API client + CoreV1Api api = new CoreV1Api(client); + + String podName = mapIp2Name(podIp); + + // Delete the pod + V1DeleteOptions deleteOptions = new V1DeleteOptions(); + api.deleteNamespacedPod(podName, nameSpace, null, null, null, null, null, deleteOptions); + + // Wait for the pod to be deleted + Waiting.waitFor(() -> { + try { + api.readNamespacedPod(podName, nameSpace, null); + return false; + } catch (ApiException e) { + if (e.getCode() == 404) { + return true; // Pod not found + } + throw e; // Other errors should not be ignored + } + }, KUBERNETES_POLL_INTERVAL); + } + } catch (ApiException e) { + if (e.getCode() == 404) { + LOGGER.error("Couldn't remove pod {} because it doesn't exist", podIp); + } else { + LOGGER.error("Couldn't remove pod {}.", podIp, e); + } + } catch (Exception e) { + LOGGER.error("Unexpected error while removing pod {}.", podIp, e); + } + } + + @Deprecated + @Override + public void stopParentAndChildren(String parentId) { + LOGGER.error("ContainerManager.stopParentAndChildren() is deprecated! Will remove them instead"); + removeParentAndChildren(parentId); + } + + + /** + * Removes a parent pod and all its child pods recursively. + * + * @param parentPodIp The IP address of the parent pod to be removed. + */ + @Override + public void removeParentAndChildren(String parentPodIp) { + LOGGER.info("removing parent and child pod {}.", parentPodIp); + // Remove the parent pod + + removeContainer(parentPodIp); + // Find child pods + try { + CoreV1Api api = new CoreV1Api(client); + + // Search for pods with the label "parent=" + String labelSelector = String.format(LABEL_PARENT+"=%s", parentPodIp); + LOGGER.info("Removing parent and child pod {}.", labelSelector); + V1PodList childPods = api.listNamespacedPod( + nameSpace, null, null, null, null, labelSelector, null, null, null, null, false); + + for (V1Pod childPod : childPods.getItems()) { + if (childPod != null && childPod.getMetadata() != null) { + String childPodIp = childPod.getStatus().getPodIP(); + LOGGER.info("Removing child pod with ip{}.", childPodIp); + String convertedChildIP =convertIP2dnsId(childPodIp); + LOGGER.info("Removing child pod with convertedip{}.", convertedChildIP); + // Recursively remove the child pod and its children + removeParentAndChildren(convertedChildIP); + } + } + } catch (ApiException e) { + LOGGER.error("Error while finding child pods: " + e.getResponseBody(), e); + } catch (Exception e) { + LOGGER.error("Unexpected error while removing pods: " + e.toString(), e); + } + } + + /** + * Retrieves the exit code of a container within a Kubernetes pod. + * + * @param podIp The IP address of the pod. + * @return The exit code of the terminated container, or null if the pod is still running or has no container statuses. + * @throws ContainerPodException If there is an error retrieving the pod's exit code. + */ + @Override + public Long getContainerPodExitCode(String podIp) throws ContainerPodException { + LOGGER.info("get exit code for pod {}.", podIp); + String podName = mapIp2Name(podIp); + LOGGER.info("get exit code for pod {}.", podName); + try { + // Initialize the API client + CoreV1Api api = new CoreV1Api(client); + + // Fetch the pod details + V1Pod pod = api.readNamespacedPod(podName, nameSpace, null); + + // Check if the pod has a terminated container + if (pod.getStatus() == null || pod.getStatus().getContainerStatuses() == null) { + LOGGER.warn("Couldn't get the exit code for pod {}. Pod has no container statuses.", podName); + return null; + } + + for (V1ContainerStatus containerStatus : pod.getStatus().getContainerStatuses()) { + V1ContainerStateTerminated terminatedState = containerStatus.getState().getTerminated(); + if (terminatedState != null) { + return terminatedState.getExitCode().longValue(); + } + } + + // Pod is still running + LOGGER.warn("Couldn't get the exit code for pod {}. Pod is not terminated.", podName); + return null; + } catch (ApiException e) { + throw new ContainerPodException("Error getting container pod exit code: " + podName, e); + } + } + + // we do not use this here + // todo change in interface and define another class as return value which is Service and V1Pod + // maybe for kubernetes we dont need this method ! we cover usage of this method in docker version in in the method in diffrent way here + +// @Override +// public Service getContainerInfo(String serviceName) throws InterruptedException, DockerException { +// return null; +// } + + + @Override + public List getContainers(ContainerCriteria criteria) { + return null; + } + + + @Override + public String getContainerPodId(String podIp) { + return podIp; + } + + // this is the container name which is the dns friendly IP of a pod + @Override + public String getContainerPodName(String podId) { + return podId; + } + /** + * Checks if a given name corresponds to an existing pod in the specified namespace. + * + * @param name The name of the pod to check. + * @return {@code true} if the pod exists, otherwise {@code false}. + */ + + private Boolean isItContainerName(String name) { + try { + CoreV1Api api = new CoreV1Api(client); + V1PodList podList = api.listNamespacedPod(nameSpace, null, null, null, null, null, null, null, null, null, false); + V1Pod targetPod = podList.getItems().stream() + .filter(podTocheck -> name.equals(podTocheck.getMetadata().getName())) + .findFirst() + .orElse(null); + + if (targetPod == null) { + return false; + } + return true; + }catch (ApiException e) { + LOGGER.error("Error while finding pods: " + e.getResponseBody(), e); + return false; + } + } + + /** + * Maps a pod name to its corresponding IP address and converts it to a DNS-compatible format. + * + * @param name The name of the pod. + * @return The converted DNS-compatible IP address of the pod, or null if the pod is not found or in an unexpected state. + */ + private String mapName2IP(String name) { + try { + CoreV1Api api = new CoreV1Api(client); + V1PodList podList = api.listNamespacedPod(nameSpace, null, null, null, null, null, null, null, null, null, false); + V1Pod targetPod = podList.getItems().stream() + .filter(podTocheck -> name.equals(podTocheck.getMetadata().getName())) + .findFirst() + .orElse(null); + + LOGGER.debug("Target pod found: {}", targetPod.getMetadata().getName()); + + String podPhase = targetPod.getStatus().getPhase(); + if ("Running".equals(podPhase)) { + String podIP = targetPod.getStatus().getPodIP(); + LOGGER.debug("Pod IP: {}", podIP); + if (podIP != null && !podIP.isEmpty()) { + LOGGER.debug("Obtained Pod IP: {}", podIP); + String convertedIP = convertIP2dnsId(podIP); + LOGGER.debug("Converted IP: {}", convertedIP); + return convertedIP; + } + } + return null; + }catch (Exception ex){ + LOGGER.error(ex.getMessage()); + return null; + } + } + + + @Override + public void addContainerObserver(ContainerStateObserver containerObserver) { + containerObservers.add(containerObserver); + } + + @Override + public void pullImage(String imageName) { + // we dont need this in kubernetes + } + +// //TODO: do we need this for kubernetes? no usage in docker version +// @Override +// public ContainerStats getStats(String containerId) { +// return null; +// } + + + /** + * Retrieves the type of a container within a Kubernetes pod based on its labels. + * + * @param podIP The IP address of the pod. + * @return The type of the container, or null if the pod or its type label is not found. + */ + @Override + public String getContainerType(String podIP) { + LOGGER.debug("getContainerType({})", podIP); + // Logic to retrieve the parent pod based on the podName + V1Pod parent = getPod(podIP); + + if (parent == null) { + LOGGER.warn("Parent pod not found for pod : {}", podIP); + return null; + } + + String containerType = parent.getMetadata().getLabels().get(LABEL_TYPE); + + if (containerType == null) { + LOGGER.warn("Container type not found in labels for pod: {}", podIP); + } else { + LOGGER.debug("Resolved container type for pod {}: {}", podIP, containerType); + } + + return containerType; + } +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerStateObserverImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerStateObserverImpl.java new file mode 100644 index 00000000..d6f487dc --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ContainerStateObserverImpl.java @@ -0,0 +1,124 @@ +package org.hobbit.controller.containers.kubernetes; + +import io.kubernetes.client.openapi.models.V1Pod; +import org.hobbit.controller.containers.ContainerStateObserver; +import org.hobbit.controller.containers.ContainerTerminationCallback; +import org.hobbit.controller.containers.KubExtendedContainerManager; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.ArrayList; +import java.util.List; +import java.util.Timer; +import java.util.TimerTask; + +/** + * A concrete implementation of {@link ContainerStateObserver} that monitors Kubernetes pods and + * notifies registered callbacks when a pod terminates. + * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ + +public class ContainerStateObserverImpl implements ContainerStateObserver { + + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerStateObserverImpl.class); + + private KubExtendedContainerManager manager; + private final List monitoredContainers; + private final List terminationCallbacks; + private final Timer timer; + private final int repeatInterval; + + public ContainerStateObserverImpl(KubExtendedContainerManager manager, int repeatInterval) { + this.manager = manager; + this.monitoredContainers = new ArrayList<>(); + this.terminationCallbacks = new ArrayList<>(); + this.timer = new Timer(); + this.repeatInterval = repeatInterval; + } + + @Override + public void startObserving() { + timer.scheduleAtFixedRate(new TimerTask() { + @Override + public void run() { + List containerConvertedIPs; + synchronized (monitoredContainers) { + containerConvertedIPs = new ArrayList<>(monitoredContainers); + } + + for (String containerConvertedIP : containerConvertedIPs) { + try { + V1Pod pod = manager.getPod(containerConvertedIP); + if (pod != null && isPodTerminated(pod)) { + int exitCode = getPodExitCode(pod); + + for (ContainerTerminationCallback callback : terminationCallbacks) { + try { + callback.notifyTermination(containerConvertedIP, exitCode); + } catch (Exception e) { + LOGGER.error("Error while calling container termination callback.", e); + } + } + } + } catch (Exception e) { + LOGGER.error("Couldn't get the status of container " + containerConvertedIP + ". It will be ignored during this run but will be checked again during the next run.", e); + } + } + } + }, repeatInterval, repeatInterval); + } + + @Override + public void stopObserving() { + timer.cancel(); + timer.purge(); + } + + @Override + public void addTerminationCallback(ContainerTerminationCallback callback) { + terminationCallbacks.add(callback); + } + + @Override + public void removeTerminationCallback(ContainerTerminationCallback callback) { + terminationCallbacks.remove(callback); + } + + @Override + public void addObservedContainer(String containerConvertedIP) { + synchronized (monitoredContainers) { + if (!monitoredContainers.contains(containerConvertedIP)) { + monitoredContainers.add(containerConvertedIP); + } + } + } + + @Override + public void removedObservedContainer(String containerConvertedIP) { + synchronized (monitoredContainers) { + monitoredContainers.remove(containerConvertedIP); + } + } + + @Override + public List getObservedContainers() { + synchronized (monitoredContainers) { + return new ArrayList<>(monitoredContainers); + } + } + + private boolean isPodTerminated(V1Pod pod) { + return pod.getStatus() != null && "Succeeded".equals(pod.getStatus().getPhase()) || "Failed".equals(pod.getStatus().getPhase()); + } + + private int getPodExitCode(V1Pod pod) { + if (pod.getStatus() != null && pod.getStatus().getContainerStatuses() != null) { + return pod.getStatus().getContainerStatuses().stream() + .filter(status -> status.getState() != null && status.getState().getTerminated() != null) + .findFirst() + .map(status -> status.getState().getTerminated().getExitCode().intValue()) + .orElse(0); + } + return 0; + } +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ResourceInformationCollectorImpl.java b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ResourceInformationCollectorImpl.java new file mode 100644 index 00000000..0e02fe9a --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/containers/kubernetes/ResourceInformationCollectorImpl.java @@ -0,0 +1,238 @@ +package org.hobbit.controller.containers.kubernetes; + + +import com.google.common.collect.ImmutableMap; +import com.fasterxml.jackson.databind.JsonNode; +import com.fasterxml.jackson.databind.ObjectMapper; +import io.kubernetes.client.custom.Quantity; +import io.kubernetes.client.openapi.Configuration; +import io.kubernetes.client.openapi.models.V1Node; +import io.kubernetes.client.openapi.models.V1NodeSystemInfo; +import io.kubernetes.client.openapi.models.V1Pod; +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.ResourceInformationCollector; +import org.hobbit.controller.data.ContainerCriteria; +import org.hobbit.controller.data.NodeHardwareInformation; +import org.hobbit.controller.data.SetupHardwareInformation; +import org.hobbit.core.Constants; +import org.hobbit.core.data.usage.CpuStats; +import org.hobbit.core.data.usage.DiskStats; +import org.hobbit.core.data.usage.MemoryStats; +import org.hobbit.core.data.usage.ResourceUsageInformation; + +import io.kubernetes.client.openapi.ApiClient; +import io.kubernetes.client.openapi.apis.CoreV1Api; + +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.LinkedList; +import java.util.List; +import java.util.Map; +import java.util.stream.Collectors; + +/** + * This class implements the {@link ResourceInformationCollector} interface to gather resource usage information and hardware details from a Kubernetes cluster. It uses the Kubernetes Java client to interact with the Kubernetes API. + * + * The implementation provides methods to: + * Get system-wide resource usage information. + * Get resource usage information for specific containers based on criteria. + * Fetch hardware information of the nodes in the Kubernetes cluster. + * + * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ + +public class ResourceInformationCollectorImpl implements ResourceInformationCollector { + + private static final Logger LOGGER = LoggerFactory.getLogger(ResourceInformationCollectorImpl.class); + + //TODO make configable + private int TIMEOUT_MILLISECONDS = 60000; + private final String namespace = "default"; + + private ApiClient apiClient; + private CoreV1Api coreV1Api; + private ContainerManager manager; + + public ResourceInformationCollectorImpl(ContainerManager manager,ApiClient apiClient, CoreV1Api coreV1Api) { + this.manager = manager; + this.apiClient = apiClient; + this.apiClient.setConnectTimeout(TIMEOUT_MILLISECONDS); + this.apiClient.setReadTimeout(TIMEOUT_MILLISECONDS); + this.apiClient.setWriteTimeout(TIMEOUT_MILLISECONDS); + Configuration.setDefaultApiClient(this.apiClient); + + // Create a CoreV1Api instance + this.coreV1Api = new CoreV1Api(this.apiClient); + } + + @Override + public ResourceUsageInformation getSystemUsageInformation() { + return getUsageInformation(ContainerCriteria.builder() + .withLabels(ImmutableMap.of(ContainerManager.LABEL_TYPE, Constants.CONTAINER_TYPE_SYSTEM)) + .build()); + } + + @Override + public ResourceUsageInformation getUsageInformation(ContainerCriteria criteria) { + List servicesName = manager.getContainers(criteria); + + LOGGER.info("number of services which has thecriteri {} is: {}", criteria.toString(),servicesName.size()); + ResourceUsageInformation resourceInfo = servicesName.parallelStream() + .filter(this::isPodRunning) // Use a method to check if the service is running + .map(this::requestCpuAndMemoryStats) + .collect(Collectors.reducing(ResourceUsageInformation::staticMerge)).orElse(null); + + return resourceInfo; + } + + protected ResourceUsageInformation requestCpuAndMemoryStats(String podName) { + ResourceUsageInformation resourceUsage = fetchPodMetrics(this.apiClient, this.namespace, podName); + + return resourceUsage; + } + + private boolean isPodRunning(String podName) { + try { + // Fetch the pod details by name + V1Pod pod = this.coreV1Api.readNamespacedPod(podName, this.namespace, null); + + // Check the pod's status + String status = pod.getStatus().getPhase(); + return "Running".equalsIgnoreCase(status); + } catch (Exception e) { + System.err.println("Error checking status for Pod " + podName + ": " + e.getMessage()); + e.printStackTrace(); + } + return false; + } + + @Override + public SetupHardwareInformation getHardwareInformation() { + SetupHardwareInformation information = new SetupHardwareInformation(); + try{ + List nodes = coreV1Api.listNode( + null, // pretty + true, // allowWatchBookmarks + null, // continue + null, // fieldSelector + null, // labelSelector (not setting any label filter) + null, // limit (no limit set, returns all results) + null, // resourceVersion + null, // resourceVersionMatch + null, // timeoutSeconds + false // watch + ).getItems(); + + // Iterate through each node + for (V1Node node : nodes) { + V1NodeSystemInfo systemInfo = node.getStatus().getNodeInfo(); + + LOGGER.debug("Kernel Version: " + systemInfo.getKernelVersion()); + LOGGER.debug("Machine ID: " + systemInfo.getMachineID()); + LOGGER.debug("Container Runtime Version: " + systemInfo.getContainerRuntimeVersion()); + LOGGER.debug("Kubelet Version: " + systemInfo.getKubeletVersion()); + LOGGER.debug("KubeProxy Version: " + systemInfo.getKubeProxyVersion()); + + Map capacity = node.getStatus().getCapacity(); +// Map allocatable = node.getStatus().getAllocatable(); + + String cpu = capacity.get("cpu").toSuffixedString(); // e.g., "8" + String memory = capacity.get("memory").toSuffixedString(); // e.g., "32Gi" + + + NodeHardwareInformation nodeInfo = new NodeHardwareInformation(); + nodeInfo.setOs(systemInfo.getOsImage()); + nodeInfo.setCpu(Long.valueOf(cpu),new LinkedList<>()); + nodeInfo.setMemory(parseMemoryUsage(memory),0L); + information.addNode(nodeInfo); + } + } catch (Exception e) { + LOGGER.error("Error getting hardware information", e); + } + return information; + } + + private static ResourceUsageInformation fetchPodMetrics(ApiClient client, String namespace, String podName) { + try { + // Metrics server endpoint for the pod + String metricsUrl = "/apis/metrics.k8s.io/v1beta1/namespaces/" + namespace + "/pods/" + podName; + + // Send a request to the metrics API + okhttp3.Request request = new okhttp3.Request.Builder() + .url(client.getBasePath() + metricsUrl) + .get() + .build(); + + okhttp3.Response response = client.getHttpClient().newCall(request).execute(); + + if (response.isSuccessful() && response.body() != null) { + String responseBody = response.body().string(); + + // Parse the JSON response + ObjectMapper mapper = new ObjectMapper(); + JsonNode root = mapper.readTree(responseBody); + + // Extract metrics for the pod + ResourceUsageInformation resourceUsage = new ResourceUsageInformation(); + + long cpuTotalUsage = 0; + long memoryTotalUsage = 0; + + // Iterate over containers in the pod + JsonNode containers = root.path("containers"); + for (JsonNode container : containers) { + String cpuUsageStr = container.path("usage").path("cpu").asText(); + String memoryUsageStr = container.path("usage").path("memory").asText(); + + cpuTotalUsage += parseCpuUsage(cpuUsageStr); + memoryTotalUsage += parseMemoryUsage(memoryUsageStr); + } + + // Set resource usage data + CpuStats cpuStats = new CpuStats(); + cpuStats.setTotalUsage(cpuTotalUsage); + + MemoryStats memoryStats = new MemoryStats(); + memoryStats.setUsageSum(memoryTotalUsage); + + DiskStats diskStats = new DiskStats(); // Metrics Server does not provide disk usage + diskStats.setFsSizeSum(0); // Set to 0 or implement a disk usage fetcher + + resourceUsage.setCpuStats(cpuStats); + resourceUsage.setMemoryStats(memoryStats); + resourceUsage.setDiskStats(diskStats); + + return resourceUsage; + } else { + System.err.println("Failed to fetch metrics for Pod " + podName + ": " + response.message()); + } + + } catch (Exception e) { + System.err.println("Error fetching metrics for Pod " + podName + ": " + e.getMessage()); + e.printStackTrace(); + } + return null; + } + + private static long parseCpuUsage(String cpuUsageStr) { + // Convert CPU usage string (e.g., "12345n" for nanoseconds) to long + if (cpuUsageStr.endsWith("n")) { + return Long.parseLong(cpuUsageStr.replace("n", "")); + } + return 0; + } + + private static long parseMemoryUsage(String memoryUsageStr) { + // Convert memory usage string (e.g., "128Mi", "512Ki") to bytes + if (memoryUsageStr.endsWith("Mi")) { + return Long.parseLong(memoryUsageStr.replace("Mi", "")) * 1024 * 1024; + } else if (memoryUsageStr.endsWith("Ki")) { + return Long.parseLong(memoryUsageStr.replace("Ki", "")) * 1024; + } else if (memoryUsageStr.endsWith("Gi")) { + return Long.parseLong(memoryUsageStr.replace("Gi", "")) * 1024 * 1024 * 1024; + } + return 0; + } + +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteria.java b/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteria.java new file mode 100644 index 00000000..90fb61b7 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteria.java @@ -0,0 +1,145 @@ +package org.hobbit.controller.data; + +import java.util.Map; +import java.util.HashMap; + +/** + * Represents criteria used to identify or filter containers, based on container ID, + * container name, and labels. This class is typically used in scenarios where specific + * container characteristics need to be matched, such as during orchestration or monitoring + * in containerized environments. + * + *

Instances of this class are immutable and should be created using the + * {@link ContainerCriteria.Builder}.

+ * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ +public class ContainerCriteria { + + /** + * The unique identifier of the container. + */ + protected String containerId; + + /** + * The name of the container. + */ + protected String containerName; + + /** + * A map of labels associated with the container. + */ + protected Map labels; + + /** + * Builder class for constructing {@link ContainerCriteria} instances in a flexible and + * readable manner. + */ + public static class Builder { + private String containerId; + private String containerName; + private Map labels = new HashMap<>(); + + /** + * Sets the container ID. + * + * @param containerId the unique container ID + * @return the builder instance + */ + public Builder withContainerId(String containerId) { + this.containerId = containerId; + return this; + } + + /** + * Sets the container name. + * + * @param containerName the name of the container + * @return the builder instance + */ + public Builder withContainerName(String containerName) { + this.containerName = containerName; + return this; + } + + /** + * Adds a map of labels to the container criteria. + * + * @param labels a map of label key-value pairs + * @return the builder instance + */ + public Builder withLabels(Map labels) { + this.labels.putAll(labels); + return this; + } + + /** + * Adds a single label key-value pair to the container criteria. + * + * @param key the label key + * @param value the label value + * @return the builder instance + */ + public Builder withLabel(String key, String value) { + this.labels.put(key, value); + return this; + } + + /** + * Builds and returns a {@link ContainerCriteria} instance with the configured properties. + * + * @return a new {@link ContainerCriteria} instance + */ + public ContainerCriteria build() { + ContainerCriteria criteria = new ContainerCriteria(); + criteria.containerId = this.containerId; + criteria.containerName = this.containerName; + criteria.labels = new HashMap<>(this.labels); // Create a copy to prevent external modification + return criteria; + } + } + + /** + * Returns the container ID. + * + * @return the container ID, or {@code null} if not set + */ + public String getContainerId() { + return containerId; + } + + /** + * Returns the container name. + * + * @return the container name, or {@code null} if not set + */ + public String getContainerName() { + return containerName; + } + + /** + * Returns the labels associated with the container. + * + * @return a map of label key-value pairs + */ + public Map getLabels() { + return labels; + } + + /** + * Creates a new {@link Builder} instance to construct {@link ContainerCriteria} objects. + * + * @return a new {@link Builder} + */ + public static Builder builder() { + return new Builder(); + } + + @Override + public String toString() { + return "ContainerCriteria{" + + "containerId='" + containerId + '\'' + + ", containerName='" + containerName + '\'' + + ", labels=" + labels + + '}'; + } +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteriaToServiceCriteriaMapper.java b/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteriaToServiceCriteriaMapper.java new file mode 100644 index 00000000..58297100 --- /dev/null +++ b/platform-controller/src/main/java/org/hobbit/controller/data/ContainerCriteriaToServiceCriteriaMapper.java @@ -0,0 +1,98 @@ +package org.hobbit.controller.data; + +import com.spotify.docker.client.messages.swarm.Service; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +import java.util.Collections; + +/** + * Maps Kubernetes-style {@link ContainerCriteria} to Docker Swarm {@link Service.Criteria} + * in order to enable service discovery or filtering across container orchestration platforms. + * + *

This is particularly useful in hybrid environments or migration setups where systems + * originally designed for Kubernetes need to operate within a Docker Swarm context. It allows + * interoperability by translating container selection logic into the format understood by Docker clients.

+ * + *

Mapping Logic:

+ *
    + *
  • {@code containerId} β†’ {@code serviceId}
  • + *
  • {@code containerName} β†’ {@code serviceName}
  • + *
  • {@code labels} β†’ {@code labels}
  • + *
+ * + *

Note: This class logs an error and returns {@code null} if the input + * {@code ContainerCriteria} is {@code null}.

+ * + *

Usage Example:

+ *
{@code
+ * ContainerCriteria criteria = ContainerCriteria.builder()
+ *     .withContainerId("12345")
+ *     .withContainerName("my-service")
+ *     .withLabel("app", "my-app")
+ *     .build();
+ *
+ * Service.Criteria serviceCriteria = ContainerCriteriaToServiceCriteriaMapper.map(criteria);
+ * List services = dockerClient.listServices(serviceCriteria);
+ * }
+ * @author Farshad Afshari farshad.afshari@uni-paderborn.de + */ + +public class ContainerCriteriaToServiceCriteriaMapper { + private static final Logger LOGGER = LoggerFactory.getLogger(ContainerCriteriaToServiceCriteriaMapper.class); + public static Service.Criteria map(ContainerCriteria containerCriteria) { + if (containerCriteria == null) { + LOGGER.error("containerCriteria is null can not map it to ServiceCriteria"); + return null; + } + + Service.Criteria.Builder builder = Service.Criteria.builder(); + + if (containerCriteria.getContainerId() != null) { + // map container ID to service + builder.serviceId(containerCriteria.getContainerId()); + // also is possible to use labels like bellow + //builder.labels(Collections.singletonMap("container_id", containerCriteria.getContainerId())); + } + + if (containerCriteria.getContainerName() != null) { + //map container ID to service ID + builder.serviceName(containerCriteria.getContainerName()); + //same as ID we can use label if need + //builder.labels(Collections.singletonMap("container_name", containerCriteria.getContainerName())); + } + + if (containerCriteria.getLabels() != null && !containerCriteria.getLabels().isEmpty()) { + builder.labels(containerCriteria.getLabels()); + } + + return builder.build(); + } + + + public static void main(String[] args) { + // Example Usage + ContainerCriteria criteria = ContainerCriteria.builder() + .withContainerId("12345") + .withContainerName("my-service") + .withLabel("app", "my-app") + .build(); + + Service.Criteria serviceCriteria = map(criteria); + + if (serviceCriteria != null) { + System.out.println(serviceCriteria.labels()); // Print the labels for verification + } + + ContainerCriteria criteria2 = ContainerCriteria.builder() + .withLabels(Collections.singletonMap("env", "prod")) + .build(); + + Service.Criteria serviceCriteria2 = map(criteria2); + + if (serviceCriteria2 != null) { + System.out.println(serviceCriteria2.labels()); // Print the labels for verification + } + + } +} diff --git a/platform-controller/src/main/java/org/hobbit/controller/data/ExperimentStatus.java b/platform-controller/src/main/java/org/hobbit/controller/data/ExperimentStatus.java index 6ee359fb..7a41efb3 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/data/ExperimentStatus.java +++ b/platform-controller/src/main/java/org/hobbit/controller/data/ExperimentStatus.java @@ -35,8 +35,8 @@ import org.apache.jena.vocabulary.RDF; import org.hobbit.controller.ExperimentManager; import org.hobbit.controller.PlatformController; -import org.hobbit.controller.docker.ImageManager; -import org.hobbit.controller.docker.MetaDataFactory; +import org.hobbit.controller.containers.ImageManager; +import org.hobbit.controller.containers.MetaDataFactory; import org.hobbit.controller.execute.ExperimentAbortTimerTask; import org.hobbit.core.rabbit.RabbitMQUtils; import org.hobbit.vocab.HOBBIT; @@ -598,7 +598,7 @@ public void addMetaDataToResult(ImageManager imageManager, long endTimeStamp, /** * Returns the next error report ID for this experiment. Note that each call of * this method increases the error ID counter internally. - * + * * @return the next error report ID for this experiment */ public int getNextErrorReportId() { diff --git a/platform-controller/src/main/java/org/hobbit/controller/data/NodeHardwareInformation.java b/platform-controller/src/main/java/org/hobbit/controller/data/NodeHardwareInformation.java index 83ac5834..654a68d9 100644 --- a/platform-controller/src/main/java/org/hobbit/controller/data/NodeHardwareInformation.java +++ b/platform-controller/src/main/java/org/hobbit/controller/data/NodeHardwareInformation.java @@ -32,6 +32,8 @@ import org.hobbit.utils.rdf.TripleHashCalculator; import org.hobbit.vocab.HobbitHardware; import org.hobbit.vocab.MEXCORE; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; /** * This class is used to store information about hardware the experiment runs @@ -42,6 +44,9 @@ */ public class NodeHardwareInformation { + + private static final Logger LOGGER = LoggerFactory.getLogger(NodeHardwareInformation.class); + /** * Formatted hardware information. */ @@ -137,11 +142,34 @@ public Resource addToModel(Model model) { } private StmtIterator distinguishingProperties(Model model, Resource self) { + + // Check for nulls and provide default values + String instanceValue = (instance == null) ? "default-instance" : instance; + if (instance == null) { + LOGGER.warn("instance is null, using default value: {}", instanceValue); + } + + String cpuValue = (cpu == null) ? "default-cpu" : cpu; + if (cpu == null) { + LOGGER.warn("cpu is null, using default value: {}", cpuValue); + } + + String memoryValue = (memory == null) ? "default-memory" : memory; + if (memory == null) { + LOGGER.warn("memory is null, using default value: {}", memoryValue); + } + + String osValue = (os == null) ? "default-os" : os; + if (os == null) { + LOGGER.warn("os is null, using default value: {}", osValue); + } + + return new StmtIteratorImpl( - Stream.of((Statement) new StatementImpl(self, RDFS.label, model.createLiteral(instance)), - (Statement) new StatementImpl(self, MEXCORE.cpu, model.createLiteral(cpu)), - (Statement) new StatementImpl(self, MEXCORE.memory, model.createLiteral(memory)), - (Statement) new StatementImpl(self, DOAP.os, model.createLiteral(os))).iterator()); + Stream.of((Statement) new StatementImpl(self, RDFS.label, model.createLiteral(instanceValue)), + (Statement) new StatementImpl(self, MEXCORE.cpu, model.createLiteral(cpuValue)), + (Statement) new StatementImpl(self, MEXCORE.memory, model.createLiteral(memoryValue)), + (Statement) new StatementImpl(self, DOAP.os, model.createLiteral(osValue))).iterator()); } @Override diff --git a/platform-controller/src/test/java/org/hobbit/controller/ExperimentHardwareInformationTest.java b/platform-controller/src/test/java/org/hobbit/controller/ExperimentHardwareInformationTest.java index 04c69df2..2ff27347 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/ExperimentHardwareInformationTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/ExperimentHardwareInformationTest.java @@ -10,7 +10,7 @@ import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; import org.hobbit.controller.data.ExperimentConfiguration; -import org.hobbit.controller.docker.ResourceInformationCollectorImpl; +import org.hobbit.controller.containers.docker.ResourceInformationCollectorImpl; import org.hobbit.controller.mocks.DummyImageManager; import org.hobbit.controller.mocks.DummyPlatformController; import org.hobbit.controller.mocks.DummyStorageServiceClient; diff --git a/platform-controller/src/test/java/org/hobbit/controller/PlatformControllerTest.java b/platform-controller/src/test/java/org/hobbit/controller/PlatformControllerTest.java index 5d8a287e..afca1c04 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/PlatformControllerTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/PlatformControllerTest.java @@ -32,8 +32,8 @@ import org.hobbit.controller.data.ExperimentConfiguration; import org.hobbit.controller.data.ExperimentStatus; import org.hobbit.controller.data.ExperimentStatus.States; -import org.hobbit.controller.docker.ContainerManagerBasedTest; -import org.hobbit.controller.docker.ContainerManagerImpl; +import org.hobbit.controller.containers.docker.ContainerManagerBasedTest; +import org.hobbit.controller.containers.docker.ContainerManagerImpl; import org.hobbit.core.Commands; import org.hobbit.core.Constants; import org.hobbit.utils.config.HobbitConfiguration; @@ -93,7 +93,7 @@ public void close() throws Exception { * container creation is checked by the test. The second command has an invalid * (because unknown) session id and the test checks whether the second command * creates an additional container. - * + * * @throws Exception */ @Test @@ -103,7 +103,7 @@ public void receiveCreateContainerCommand() throws Exception { // create and execute parent container final String parentId = manager.startContainer("busybox", Constants.CONTAINER_TYPE_SYSTEM, null, new String[] { "sh", "-c", "while :; do sleep 1; done" }); - final String parentName = manager.getContainerName(parentId); + final String parentName = manager.getContainerPodName(parentId); services.add(parentId); // create and execute test container diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ClusterManagerImplTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ClusterManagerImplTest.java similarity index 91% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ClusterManagerImplTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ClusterManagerImplTest.java index c3060c51..3d6e5751 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ClusterManagerImplTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ClusterManagerImplTest.java @@ -1,7 +1,9 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import com.spotify.docker.client.messages.Info; +import org.hobbit.controller.containers.docker.ClusterManagerImpl; import org.junit.Before; +import org.junit.Ignore; import org.junit.Test; import static org.junit.Assert.*; @@ -15,7 +17,7 @@ public void setUp() throws Exception { clusterManager = new ClusterManagerImpl(); } - @Test + @Ignore public void getClusterInfo() throws Exception { final Info info = clusterManager.getClusterInfo(); assertNotNull(info); @@ -55,4 +57,4 @@ public void setTaskHistoryLimit() throws Exception { } -} \ No newline at end of file +} diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerAccessByHostnameTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerAccessByHostnameTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ContainerAccessByHostnameTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerAccessByHostnameTest.java index f70bed93..314cbdd7 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerAccessByHostnameTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerAccessByHostnameTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotNull; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerBasedTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerBasedTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerBasedTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerBasedTest.java index dfa3512b..982022e0 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerBasedTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerBasedTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.util.ArrayList; import java.util.List; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerImplTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerImplTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerImplTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerImplTest.java index 2fc27180..e100110f 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerManagerImplTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerManagerImplTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import static org.junit.Assert.assertEquals; import static org.junit.Assert.assertNotEquals; @@ -235,8 +235,8 @@ public void getContainerIdAndName() throws Exception { services.add(containerId); // compare containerId and retrieved id - String containerName = manager.getContainerName(containerId); - assertEquals(containerId, manager.getContainerId(containerName)); + String containerName = manager.getContainerPodName(containerId); + assertEquals(containerId, manager.getContainerPodId(containerName)); } private void removeImage(String imageName) throws Exception { @@ -341,7 +341,7 @@ public void pullUpdatedImage() throws Exception { Long exitCode = null; while (exitCode == null) { Thread.sleep(500); - exitCode = manager.getContainerExitCode(testTask); + exitCode = manager.getContainerPodExitCode(testTask); } assertEquals("Service is using first image version", Long.valueOf(1), exitCode); @@ -361,7 +361,7 @@ public void pullUpdatedImage() throws Exception { exitCode = null; while (exitCode == null) { Thread.sleep(500); - exitCode = manager.getContainerExitCode(testTask); + exitCode = manager.getContainerPodExitCode(testTask); } assertEquals("Service is using second image version", Long.valueOf(2), exitCode); @@ -431,13 +431,13 @@ public void testNetworkAliases() throws Exception { assertNotNull(pingContainer); services.add(pingContainer); Thread.sleep(10000); - assertEquals("Result of pinging the container's network alias", Long.valueOf(0), manager.getContainerExitCode(pingContainer)); + assertEquals("Result of pinging the container's network alias", Long.valueOf(0), manager.getContainerPodExitCode(pingContainer)); pingContainer = manager.startContainer(busyboxImageName, Constants.CONTAINER_TYPE_BENCHMARK, null, null, null, new String[]{"ping", "-c", "1", "-W", "2", "nonexistant"}); assertNotNull(pingContainer); services.add(pingContainer); Thread.sleep(10000); - assertEquals("Result of pinging the nonexisting host", Long.valueOf(1), manager.getContainerExitCode(pingContainer)); + assertEquals("Result of pinging the nonexisting host", Long.valueOf(1), manager.getContainerPodExitCode(pingContainer)); } } diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerStateObserverImplTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerStateObserverImplTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ContainerStateObserverImplTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerStateObserverImplTest.java index d13ae82f..08d01ce6 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerStateObserverImplTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerStateObserverImplTest.java @@ -14,8 +14,9 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; +import org.hobbit.controller.containers.ContainerManager; import org.junit.Assert; import org.junit.Before; import org.junit.Test; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerTerminationCallbackImplTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImplTest.java similarity index 89% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ContainerTerminationCallbackImplTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImplTest.java index 6f08ead8..75fe7e71 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ContainerTerminationCallbackImplTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ContainerTerminationCallbackImplTest.java @@ -14,8 +14,9 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; +import org.hobbit.controller.containers.docker.ContainerTerminationCallbackImpl; import org.junit.Test; import static org.junit.Assert.*; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/FileBasedImageManagerTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/FileBasedImageManagerTest.java similarity index 90% rename from platform-controller/src/test/java/org/hobbit/controller/docker/FileBasedImageManagerTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/FileBasedImageManagerTest.java index 37f3674f..c00fcda7 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/FileBasedImageManagerTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/FileBasedImageManagerTest.java @@ -1,5 +1,6 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; +import org.hobbit.controller.containers.FileBasedImageManager; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.SystemMetaData; import org.junit.Assert; @@ -8,9 +9,10 @@ /** * A simple test which tests the ability of the {@link FileBasedImageManager} * class to load metadata from a file. - * + * * @author Michael Röder (michael.roeder@uni-paderborn.de) * + * TODO: maybe need to move */ public class FileBasedImageManagerTest { diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/GitlabBasedImageManagerTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/GitlabBasedImageManagerTest.java similarity index 98% rename from platform-controller/src/test/java/org/hobbit/controller/docker/GitlabBasedImageManagerTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/GitlabBasedImageManagerTest.java index ee935ea9..5ecdbc1c 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/GitlabBasedImageManagerTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/GitlabBasedImageManagerTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.util.ArrayList; import java.util.Collection; @@ -26,6 +26,7 @@ import org.apache.jena.rdf.model.Model; import org.apache.jena.vocabulary.RDF; import org.hobbit.controller.ConnectivityAssumptionUtils; +import org.hobbit.controller.containers.GitlabBasedImageManager; import org.hobbit.controller.gitlab.GitlabControllerImplTest; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.SystemMetaData; @@ -41,6 +42,8 @@ /** * Created by Timofey Ermilov on 22/09/16. + * + * TODO: maybe need to move */ public class GitlabBasedImageManagerTest { private GitlabBasedImageManager imageManager; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/LongImageNameTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/LongImageNameTest.java similarity index 93% rename from platform-controller/src/test/java/org/hobbit/controller/docker/LongImageNameTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/LongImageNameTest.java index 35550252..b43bfd53 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/LongImageNameTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/LongImageNameTest.java @@ -14,14 +14,17 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import static org.junit.Assert.assertNotNull; import org.hobbit.core.Constants; import org.junit.Test; - +/** + * + * TODO: maybe need to move + */ public class LongImageNameTest extends ContainerManagerBasedTest { private static final String imageName = "hobbitproject/nonexisting_image_name_thats_too_long"; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/MetaDataFactoryTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/MetaDataFactoryTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/MetaDataFactoryTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/MetaDataFactoryTest.java index a67778cf..f2ea0de5 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/MetaDataFactoryTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/MetaDataFactoryTest.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.io.IOException; import java.io.InputStream; @@ -10,12 +10,17 @@ import org.apache.commons.compress.utils.IOUtils; import org.apache.log4j.lf5.util.StreamUtils; import org.hobbit.controller.ParameterForwardingTest; +import org.hobbit.controller.containers.MetaDataFactory; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.ImageMetaData; import org.hobbit.core.data.SystemMetaData; import org.junit.Assert; import org.junit.Test; +/** + * + * TODO: maybe need to move + */ public class MetaDataFactoryTest { @Test diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/ResourceInformationCollectorTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorTest.java similarity index 98% rename from platform-controller/src/test/java/org/hobbit/controller/docker/ResourceInformationCollectorTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorTest.java index 0fa2b317..73b95611 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/ResourceInformationCollectorTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/ResourceInformationCollectorTest.java @@ -1,4 +1,4 @@ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import static org.junit.Assert.assertNotNull; @@ -7,6 +7,7 @@ import org.apache.jena.sparql.vocabulary.DOAP; import org.apache.jena.vocabulary.RDF; import org.apache.jena.vocabulary.RDFS; +import org.hobbit.controller.containers.ResourceInformationCollector; import org.hobbit.controller.data.SetupHardwareInformation; import org.hobbit.core.Constants; import org.hobbit.core.data.usage.ResourceUsageInformation; diff --git a/platform-controller/src/test/java/org/hobbit/controller/docker/VersionTagIdentificationTest.java b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/VersionTagIdentificationTest.java similarity index 97% rename from platform-controller/src/test/java/org/hobbit/controller/docker/VersionTagIdentificationTest.java rename to platform-controller/src/test/java/org/hobbit/controller/containers/docker/VersionTagIdentificationTest.java index 2a1a9a56..49cbf85b 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/docker/VersionTagIdentificationTest.java +++ b/platform-controller/src/test/java/org/hobbit/controller/containers/docker/VersionTagIdentificationTest.java @@ -14,7 +14,7 @@ * You should have received a copy of the GNU General Public License * along with platform-controller. If not, see . */ -package org.hobbit.controller.docker; +package org.hobbit.controller.containers.docker; import java.io.IOException; import java.util.ArrayList; @@ -27,6 +27,7 @@ import org.junit.runners.Parameterized; import org.junit.runners.Parameterized.Parameters; + @RunWith(Parameterized.class) public class VersionTagIdentificationTest { diff --git a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyContainerManager.java b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyContainerManager.java index 24e8ffe5..63353cc5 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyContainerManager.java +++ b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyContainerManager.java @@ -5,15 +5,18 @@ import java.util.Map; import java.util.concurrent.Semaphore; -import org.hobbit.controller.docker.ContainerManager; -import org.hobbit.controller.docker.ContainerStateObserver; -import org.hobbit.controller.docker.ContainerTerminationCallback; +import io.kubernetes.client.openapi.models.V1Pod; +import org.hobbit.controller.containers.ContainerManager; +import org.hobbit.controller.containers.ContainerStateObserver; +import org.hobbit.controller.containers.ContainerTerminationCallback; import com.spotify.docker.client.messages.ContainerStats; import com.spotify.docker.client.messages.swarm.Service; import com.spotify.docker.client.messages.swarm.Service.Criteria; +import org.hobbit.controller.containers.KubExtendedContainerManager; +import org.hobbit.controller.data.ContainerCriteria; -public class DummyContainerManager implements ContainerManager { +public class DummyContainerManager implements ContainerManager, KubExtendedContainerManager { private Semaphore benchmarkControllerTerminated; private ContainerTerminationCallback terminationCallback; @@ -107,28 +110,28 @@ public void removeParentAndChildren(String parent) { stopContainer(parent); } - @Override - public Service getContainerInfo(String serviceName) { - return null; - } +// @Override +// public Service getContainerInfo(String serviceName) { +// return null; +// } @Override - public List getContainers(Criteria criteria) { + public List getContainers(ContainerCriteria criteria) { return new ArrayList<>(0); } @Override - public Long getContainerExitCode(String serviceName) { + public Long getContainerPodExitCode(String serviceName) { return null; } @Override - public String getContainerId(String name) { + public String getContainerPodId(String name) { return name; } @Override - public String getContainerName(String containerId) { + public String getContainerPodName(String containerId) { return containerId; } @@ -143,7 +146,7 @@ public void pullImage(String imageName) { System.out.println("..."); } - @Override + //@Override public ContainerStats getStats(String containerId) { return null; } @@ -153,4 +156,8 @@ public String getContainerType(String containerId) { return null; } + @Override + public V1Pod getPod(String podIP) { + return null; + } } diff --git a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyImageManager.java b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyImageManager.java index ce815c2e..92cbc8ab 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyImageManager.java +++ b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyImageManager.java @@ -4,7 +4,7 @@ import java.util.HashSet; import java.util.List; import org.apache.jena.rdf.model.ModelFactory; -import org.hobbit.controller.docker.ImageManager; +import org.hobbit.controller.containers.ImageManager; import org.hobbit.core.data.BenchmarkMetaData; import org.hobbit.core.data.SystemMetaData; diff --git a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyPlatformController.java b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyPlatformController.java index 2ef8c689..62db965e 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyPlatformController.java +++ b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyPlatformController.java @@ -4,7 +4,7 @@ import com.spotify.docker.client.exceptions.DockerCertificateException; import java.io.IOException; import java.util.concurrent.Semaphore; -import org.hobbit.controller.docker.ClusterManagerImpl; +import org.hobbit.controller.containers.docker.ClusterManagerImpl; import org.hobbit.controller.PlatformController; import org.hobbit.controller.queue.InMemoryQueue; diff --git a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyResourceInformationCollector.java b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyResourceInformationCollector.java index 06bcb903..932420eb 100644 --- a/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyResourceInformationCollector.java +++ b/platform-controller/src/test/java/org/hobbit/controller/mocks/DummyResourceInformationCollector.java @@ -1,8 +1,9 @@ package org.hobbit.controller.mocks; import com.spotify.docker.client.messages.swarm.Service; +import org.hobbit.controller.data.ContainerCriteria; import org.hobbit.controller.data.SetupHardwareInformation; -import org.hobbit.controller.docker.ResourceInformationCollector; +import org.hobbit.controller.containers.ResourceInformationCollector; import org.hobbit.core.data.usage.ResourceUsageInformation; public class DummyResourceInformationCollector implements ResourceInformationCollector { @@ -13,7 +14,7 @@ public ResourceUsageInformation getSystemUsageInformation() { }; @Override - public ResourceUsageInformation getUsageInformation(Service.Criteria criteria) { + public ResourceUsageInformation getUsageInformation(ContainerCriteria criteria) { return null; }; diff --git a/platform-storage/storage-service/Dockerfile b/platform-storage/storage-service/Dockerfile index 072e57ca..d8ae9ae3 100644 --- a/platform-storage/storage-service/Dockerfile +++ b/platform-storage/storage-service/Dockerfile @@ -2,12 +2,12 @@ FROM maven:3-eclipse-temurin-11 AS build WORKDIR /usr/src/hobbit-platform COPY parent-pom/pom.xml parent-pom/ RUN mvn --file parent-pom -Dmaven.test.skip=true install -ARG project=platform-storage/storage-service -COPY ${project}/pom.xml ${project}/ -RUN mvn --file ${project} dependency:go-offline -COPY ${project}/src ${project}/src -RUN mvn --file ${project} -Dmaven.test.skip=true package - +WORKDIR /usr/src/hobbit-platform/platform-storage/storage-service +COPY platform-storage/storage-service/pom.xml . +RUN mvn dependency:go-offline +COPY platform-storage/storage-service/src src +RUN mvn -Dmaven.test.skip=true package FROM eclipse-temurin:11 +WORKDIR /app COPY --from=build /usr/src/hobbit-platform/platform-storage/storage-service/target/storage-service.jar . CMD ["java", "-cp", "storage-service.jar", "org.hobbit.core.run.ComponentStarter", "org.hobbit.storage.service.StorageService"] diff --git a/platform-storage/storage-service/src/main/java/org/hobbit/storage/service/StorageService.java b/platform-storage/storage-service/src/main/java/org/hobbit/storage/service/StorageService.java index 66f50db0..bd5f64e4 100644 --- a/platform-storage/storage-service/src/main/java/org/hobbit/storage/service/StorageService.java +++ b/platform-storage/storage-service/src/main/java/org/hobbit/storage/service/StorageService.java @@ -218,28 +218,36 @@ else if (query.isAskType()) public void init() throws Exception { super.init(); - sparqlEndpointUrl = getEnvValue(SPARQL_ENDPOINT_URL_KEY, true) + "-auth"; - String username = getEnvValue(SPARQL_ENDPOINT_USERNAME_KEY, true); - String password = getEnvValue(SPARQL_ENDPOINT_PASSWORD_KEY, true); - credentials = new UsernamePasswordCredentials(username, password); + try { + sparqlEndpointUrl = getEnvValue(SPARQL_ENDPOINT_URL_KEY, true) ;//+ "-auth"; + String username = getEnvValue(SPARQL_ENDPOINT_USERNAME_KEY, true); + String password = getEnvValue(SPARQL_ENDPOINT_PASSWORD_KEY, true); + credentials = new UsernamePasswordCredentials(username, password); + + HttpClientBuilder clientBuilder = HttpClientBuilder.create(); + clientBuilder.setDefaultCredentialsProvider(this); + client = clientBuilder.build(); + + queryExecFactory = new QueryExecutionFactoryHttp(sparqlEndpointUrl, new DatasetDescription(), client); + queryExecFactory = new QueryExecutionFactoryPaginated(queryExecFactory, MAX_RESULT_SIZE); + + queue = incomingDataQueueFactory.createDefaultRabbitQueue(QUEUE_NAME); - HttpClientBuilder clientBuilder = HttpClientBuilder.create(); - clientBuilder.setDefaultCredentialsProvider(this); - client = clientBuilder.build(); + queue.channel.basicQos(MAX_NUMBER_PARALLEL_REQUESTS); - queryExecFactory = new QueryExecutionFactoryHttp(sparqlEndpointUrl, new DatasetDescription(), client); - queryExecFactory = new QueryExecutionFactoryPaginated(queryExecFactory, MAX_RESULT_SIZE); + consumer = new QueueingConsumer(queue.channel); - queue = incomingDataQueueFactory.createDefaultRabbitQueue(QUEUE_NAME); - queue.channel.basicQos(MAX_NUMBER_PARALLEL_REQUESTS); + queue.channel.basicConsume(QUEUE_NAME, false, consumer); - consumer = new QueueingConsumer(queue.channel); - queue.channel.basicConsume(QUEUE_NAME, false, consumer); + } catch (Exception e) { + LOGGER.error("Error during initialization: ", e); + throw e; // Re-throw after logging + } } @Override public void run() throws Exception { - LOGGER.info("[Storage Service] Awaiting Storage Service requests"); + LOGGER.info("[Storage Service] Awaiting Storage Service requests "); ExecutorService executor = Executors.newFixedThreadPool(MAX_NUMBER_PARALLEL_REQUESTS); Delivery delivery; while (true) { @@ -341,7 +349,7 @@ public void setCredentials(AuthScope arg0, Credentials arg1) { /** * Main method for debugging purposes. - * + * * @param args */ public static void main(String[] args) { diff --git a/rabbitmq-cluster/Makefile b/rabbitmq-cluster/Makefile index 085018df..c9e9b0be 100644 --- a/rabbitmq-cluster/Makefile +++ b/rabbitmq-cluster/Makefile @@ -1,11 +1,11 @@ -haproxy=$(shell docker ps -a | grep haproxy | cut -d' ' -f 1) +haproxy=$(shell docker ps -a | grep haproxy | cut -d' ' -f 1) start: - docker-compose up -d + docker compose up -d restart-haproxy: docker stop ${haproxy} && docker rm ${haproxy} - docker-compose up -d + docker compose up -d stop: - docker-compose down + docker compose down