diff --git a/.github/workflows/integration-test.yaml b/.github/workflows/integration-test.yaml index 457d6d5c..ff5de7a2 100644 --- a/.github/workflows/integration-test.yaml +++ b/.github/workflows/integration-test.yaml @@ -26,7 +26,7 @@ jobs: - name: Start KinD run: | source ./docker/accumulo/.env - kind create cluster --config ./cd/kind.yaml --image kindest/node:v1.21.10 + kind create cluster --config ./cd/kind.yaml kind load docker-image gchq/hdfs:$HADOOP_VERSION kind load docker-image gchq/gaffer:$GAFFER_VERSION kind load docker-image gchq/gaffer-rest:$GAFFER_VERSION diff --git a/cd/deploy_to_kind.sh b/cd/deploy_to_kind.sh index 44f6abef..19c046df 100755 --- a/cd/deploy_to_kind.sh +++ b/cd/deploy_to_kind.sh @@ -16,7 +16,7 @@ set -e -kind create cluster --quiet --config ./cd/kind.yaml --image kindest/node:v1.21.10 +kind create cluster --quiet --config ./cd/kind.yaml # This sets the values for: # HADOOP_VERSION diff --git a/docker/gaffer-jhub-options-server/Dockerfile b/docker/gaffer-jhub-options-server/Dockerfile index a0114b42..3fcb5d51 100644 --- a/docker/gaffer-jhub-options-server/Dockerfile +++ b/docker/gaffer-jhub-options-server/Dockerfile @@ -1,4 +1,4 @@ -FROM node:15.3.0-alpine3.12 +FROM node:16.16.0-alpine3.15 WORKDIR /srv/app COPY package.json ./ diff --git a/docker/gaffer-jhub-options-server/package.json b/docker/gaffer-jhub-options-server/package.json index e41f183a..cea013c2 100644 --- a/docker/gaffer-jhub-options-server/package.json +++ b/docker/gaffer-jhub-options-server/package.json @@ -5,7 +5,7 @@ "main": "src/server.js", "scripts": { "start": "node src/server.js", - "test": "mocha" + "test": "jest" }, "author": "", "license": "ISC", @@ -18,7 +18,6 @@ "pug": "^3.0.0" }, "devDependencies": { - "chai": "^4.2.0", - "mocha": "^8.2.1" + "jest": "^28.1.3" } } diff --git a/docker/gaffer-jhub-options-server/src/k8s-utils.js b/docker/gaffer-jhub-options-server/src/k8s-utils.js index 80e38f01..5dd65640 100644 --- a/docker/gaffer-jhub-options-server/src/k8s-utils.js +++ b/docker/gaffer-jhub-options-server/src/k8s-utils.js @@ -5,7 +5,9 @@ const templating = require('./templating.js') class K8sUtils { constructor(kubeConfig) { + this.kubeConfig = kubeConfig this.client = k8s.KubernetesObjectApi.makeApiClient(kubeConfig) + this.ingressApiVersion = null } async applySpec(spec) { @@ -48,6 +50,107 @@ class K8sUtils { return spec } + async getSupportedIngressApiVersion() { + if (this.ingressApiVersion !== null) + return this.ingressApiVersion + + const apiClient = this.kubeConfig.makeApiClient(k8s.ApisApi) + const apiResponse = await apiClient.getAPIVersions() + const apiVersions = apiResponse.body.groups.flatMap(group => group.versions.map(version => version.groupVersion)) + + if (apiVersions.indexOf('networking.k8s.io/v1') != -1) { + const response = await this.kubeConfig.makeApiClient(k8s.NetworkingV1Api).getAPIResources() + if (response.body.resources.filter(resource => resource.kind === 'Ingress').length > 0) { + this.ingressApiVersion = 'networking.k8s.io/v1' + return this.ingressApiVersion + } + } + + if (apiVersions.indexOf("networking.k8s.io/v1beta1") != -1) { + const response = await this.kubeConfig.makeApiClient(k8s.NetworkingV1beta1Api).getAPIResources() + if (response.body.resources.filter(resource => resource.kind === 'Ingress').length > 0) { + this.ingressApiVersion = 'networking.k8s.io/v1beta1' + return this.ingressApiVersion + } + } + + throw 'Failed to detect the API version supported by the Kubernetes cluster' + } + + async createIngress(name, namespace, labels, host, path, serviceName, servicePort) { + const ingressApiVersion = await this.getSupportedIngressApiVersion() + if (ingressApiVersion === 'networking.k8s.io/v1') { + return this.createIngressV1(name, namespace, labels, host, path, serviceName, servicePort) + } else if (ingressApiVersion === 'networking.k8s.io/v1beta1') { + return this.createIngressV1Beta1(name, namespace, labels, host, path, serviceName, servicePort) + } else { + throw `Unable to create ingress ${namespace}:${name} as support is missing for the Ingress version used by the Kubernetes cluster: ${this.ingressApiVersion}` + } + } + + async createIngressV1(name, namespace, labels, host, path, serviceName, servicePort) { + const ingressPath = { + pathType: 'ImplementationSpecific', + backend: { + service: { + name: serviceName, + port: { + number: servicePort + } + } + } + } + if (path) ingressPath.path = path + + const rule = { + http: { + paths: [ingressPath] + } + } + if (host) rule.host = host + + const ingress = { + apiVersion: 'networking.k8s.io/v1', + kind: 'Ingress', + metadata: { name, namespace, labels }, + spec: { + rules: [rule] + } + } + + const response = await this.applySpec(ingress) + return response + } + + async createIngressV1Beta1(name, namespace, labels, host, path, serviceName, servicePort) { + const ingressPath = { + backend: { + serviceName, + servicePort + } + } + if (path) ingressPath.path = path + + const rule = { + http: { + paths: [ingressPath] + } + } + if (host) rule.host = host + + const ingress = { + apiVersion: 'networking.k8s.io/v1beta1', + kind: 'Ingress', + metadata: { name, namespace, labels }, + spec: { + rules: [rule] + } + } + + const response = await this.applySpec(ingress) + return response + } + } module.exports = K8sUtils diff --git a/docker/gaffer-jhub-options-server/src/spark-config-provisioner.js b/docker/gaffer-jhub-options-server/src/spark-config-provisioner.js index 1c07cc58..e949b3a0 100644 --- a/docker/gaffer-jhub-options-server/src/spark-config-provisioner.js +++ b/docker/gaffer-jhub-options-server/src/spark-config-provisioner.js @@ -127,43 +127,16 @@ class SparkConfigProvisioner { async provisionDriverIngress(name, namespace, host, path, serviceName, servicePort, username, servername) { if (!host && !path) return null - const ingressPath = { - backend: { - serviceName: serviceName, - servicePort: servicePort - } + const labels = { + 'app.kubernetes.io/name': 'jhub-notebook-config', + 'app.kubernetes.io/component': 'spark-ui-ingress', + 'app.kubernetes.io/managed-by': appInfo.name, + 'app.kubernetes.io/version': appInfo.version, + 'hub.jupyter.org/username': username, + 'hub.jupyter.org/servername': servername } - if (path) ingressPath.path = path - const rule = { - http: { - paths: [ingressPath] - } - } - if (host) rule.host = host - - const ingress = { - apiVersion: 'extensions/v1beta1', - kind: 'Ingress', - metadata: { - name: name, - namespace: namespace, - labels: { - 'app.kubernetes.io/name': 'jhub-notebook-config', - 'app.kubernetes.io/component': 'spark-ui-ingress', - 'app.kubernetes.io/managed-by': appInfo.name, - 'app.kubernetes.io/version': appInfo.version, - 'hub.jupyter.org/username': username, - 'hub.jupyter.org/servername': servername - } - }, - spec: { - rules: [rule] - } - } - - const response = await this.utils.applySpec(ingress) - return response + return this.utils.createIngress(name, namespace, labels, host, path, serviceName, servicePort) } async getPodSpecConfig(username, servername, driverServiceName, namespace, sparkContainerImage, serviceAccountName, executorCores, executorMemory, hdfsEnabled, ingressHost, ingressPath) { diff --git a/docker/gaffer-jhub-options-server/src/spark-config-provisioner.test.js b/docker/gaffer-jhub-options-server/src/spark-config-provisioner.test.js new file mode 100644 index 00000000..2381b765 --- /dev/null +++ b/docker/gaffer-jhub-options-server/src/spark-config-provisioner.test.js @@ -0,0 +1,16 @@ + +const k8s = require('@kubernetes/client-node') +const SparkConfigProvisioner = require('./spark-config-provisioner.js') + +const kubeConfig = new k8s.KubeConfig() +kubeConfig.loadFromDefault() + +const spark = new SparkConfigProvisioner(kubeConfig) + +test('can provision Spark service', async () => { + await spark.provisionDriverService("spark-test-svc", "default", "user1", "server1", 80) +}) + +test('can provision Spark ingress', async () => { + await spark.provisionDriverIngress("spark-test-ingress", "default", "localhost", "/", "spark-test-svc", 80, "user1", "server1") +}) diff --git a/kubernetes/docs/kind-deployment.md b/kubernetes/docs/kind-deployment.md index 59c1d9b7..3a0789f9 100644 --- a/kubernetes/docs/kind-deployment.md +++ b/kubernetes/docs/kind-deployment.md @@ -15,7 +15,7 @@ The following instructions will guide you through provisioning and configuring a Simply run the following command to spin up a local Kubernetes cluster, running inside a Docker container: ``` -kind create cluster --image kindest/node:v1.21.10 +kind create cluster ``` diff --git a/kubernetes/gaffer-jhub/templates/options-server/rbac.yaml b/kubernetes/gaffer-jhub/templates/options-server/rbac.yaml index e49e20d6..5d66bfb8 100644 --- a/kubernetes/gaffer-jhub/templates/options-server/rbac.yaml +++ b/kubernetes/gaffer-jhub/templates/options-server/rbac.yaml @@ -40,6 +40,17 @@ rules: - "create" - "patch" - "update" +- apiGroups: + - "networking.k8s.io" + resources: + - "ingresses" + verbs: + - "get" + - "list" + - "watch" + - "create" + - "patch" + - "update" --- apiVersion: rbac.authorization.k8s.io/v1 kind: ClusterRoleBinding