diff --git a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties index 9c7b6c62b2c..5adaf3860a2 100644 --- a/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties +++ b/assembly/assembly-wsmaster-war/src/main/webapp/WEB-INF/classes/che/che.properties @@ -447,7 +447,7 @@ che.infra.kubernetes.client.http.connection_pool.keep_alive_min=5 che.infra.openshift.project= # Create routes with Transport Layer Security (TLS) enabled -che.infra.openshift.tls_enabled=false +che.infra.kubernetes.tls_enabled=false # Single port mode wildcard domain host & port. nip.io is used by default diff --git a/dockerfiles/init/manifests/che.env b/dockerfiles/init/manifests/che.env index 818d41e169d..b5ef08e5012 100644 --- a/dockerfiles/init/manifests/che.env +++ b/dockerfiles/init/manifests/che.env @@ -433,6 +433,10 @@ CHE_SINGLE_PORT=false ##### Kubernetes Infrastructure ##### ##### ##### # + +# Create routes with Transport Layer Security (TLS) enabled +CHE_INFRA_KUBERNETES_TLS_ENABLED=false + #Configuration of Kubernetes client that Infra will use #CHE_INFRA_KUBERNETES_MASTER__URL= #CHE_INFRA_KUBERNETES_USERNAME= @@ -511,9 +515,6 @@ CHE_SINGLE_PORT=false # If not set, every workspace will be created in a new project, where project name = workspace id #CHE_INFRA_OPENSHIFT_PROJECT= -# Create routes with Transport Layer Security (TLS) enabled -CHE_INFRA_OPENSHIFT_TLS_ENABLED=false - ######################################################################################## ##### ##### diff --git a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/deployment.yaml b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/deployment.yaml index 6bafdd82216..dd70f84b111 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/deployment.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/deployment.yaml @@ -46,6 +46,9 @@ spec: - name: CHE_HOST value: {{ template "cheHost" . }} image: {{ .Values.image }} + securityContext: + runAsUser: 0 + fsGroup: 0 imagePullPolicy: Always name: keycloak livenessProbe: diff --git a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/ingress.yaml b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/ingress.yaml index 6041ab57d57..04a953cf45e 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/ingress.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-keycloak/templates/ingress.yaml @@ -14,27 +14,33 @@ metadata: {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-read-timeout: "3600" {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-connect-timeout: "3600" {{- if .Values.global.tlsEnabled }} + {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect: "true" kubernetes.io/tls-acme: "true" {{- else }} {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect: "false" {{- end }} spec: -{{- if .Values.global.tlsEnabled }} +{{- if .Values.global.tls.enabled }} tls: - hosts: - {{ template "keycloakHost" . }} secretName: keycloak-tls {{- end }} rules: -{{- if .Values.global.isHostBased }} +{{- if eq .Values.global.serverStrategy "default-host" }} + - http: + paths: + - path: /auth/ +{{- else if eq .Values.global.serverStrategy "single-host" }} - host: {{ template "keycloakHost" . }} http: paths: - - path: / + - path: /auth/ {{- else }} - - http: + - host: {{ template "keycloakHost" . }} + http: paths: - - path: /auth/ + - path: / {{- end }} backend: serviceName: keycloak diff --git a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-postgres/templates/deployment.yaml b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-postgres/templates/deployment.yaml index 5014f0391f0..5befe009e91 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/charts/che-postgres/templates/deployment.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/charts/che-postgres/templates/deployment.yaml @@ -36,6 +36,7 @@ spec: image: {{ .Values.image }} securityContext: runAsUser: 26 + fsGroup: 26 imagePullPolicy: Always name: postgres livenessProbe: diff --git a/dockerfiles/init/modules/che-kubernetes-helm/readme.md b/dockerfiles/init/modules/che-kubernetes-helm/readme.md index 993ea444bd2..8fdb86cd073 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/readme.md +++ b/dockerfiles/init/modules/che-kubernetes-helm/readme.md @@ -17,7 +17,7 @@ In production, you should specify a hostname (see [here](https://github.com/eclipse/che/issues/8694) why). In case you don't have a hostname (e.g. during development), and would still want to use a host-based configuration, you can use services such as nip.io or xip.io. -In case you're specifying a hostname, simply pass it as the value of the `cheDomain` parameter below. +In case you're specifying a hostname, simply pass it as the value of the `ingressDomain` parameter below. If you must use an ip address (e.g. your corporate policy prevents you from using nip.io), you would also have to set `isHostBased` to `false`. @@ -32,55 +32,53 @@ The context of the commands below is the directory in which this readme file res - Or, you can override default values during installation, using the `--set` flag: ```bash - helm upgrade --install --namespace --set global.cheDomain= --set cheImage= ./ + helm upgrade --install --namespace --set global.ingressDomain= --set cheImage= ./ ``` -#### Deployment types -Currenty, only minikube deployment is supported. +#### Deployment Options ##### Single User Only Che will be deployed. ```bash - helm upgrade --install --namespace --set global.cheDomain= ./ + helm upgrade --install --namespace --set global.ingressDomain= ./ ``` ##### Multi User Che, KeyCloak and Postgres will be deployed. ```bash - helm upgrade --install --namespace --set global.multiuser=true --set global.cheDomain= ./ + helm upgrade --install --namespace -f ./values/multi-user.yaml --set global.ingressDomain= ./ ``` - -##### No Host: - Ingress will serve requests on minikube-ip. - Path based routing to Che, Secondary servers (KeyCloak) and Workspace servers. +#### Default Host +All Ingress specs are created without a host attribute (defaults to *). +Path based routing to all components. +Multi User configuration is enabled. + ```bash - helm upgrade --install --namespace --set global.isHostbased=false --set global.cheDomain= ./ - Master: http:/// - Workspaces: http:/// - Keycloak (if multiuser) : http:///auth/ + helm upgrade --install --namespace -f ./values/default-host.yaml --set global.ingressDomain= ./ ``` - -##### Host (partial): - WS Master Ingress will serve requests on provided domain - Workspaces: Ingress will serve requests on minikube-ip, Path Based routing to workspaces. - KeyCloak : dedicated hostname - - ```bash - helm upgrade --install --namespace --set global.cheDomain=.xip.io ./ - Master: http://master..xip.io - Workspaces: http:/// - Keycloak (if multiuser): http://keycloak..xip.io/ - ``` +* Master: `http:///` +* Keycloak: `http:///auth/` +* Workspaces servers: `http:///` + +#### TLS-enabled +Cert-Manager is used to issue LetsEncrypt certificates. +To avoid rate-limit issues, we use a single hostname for all ingresses. +Path based routing to all components. +Multi User configuration is enabled. + + ```bash + helm install --name stable/cert-manager + helm upgrade --install --namespace -f ./values/tls.yaml --set global.ingressDomain= ./ + ``` + +* Master: `https://che-.your-domain/` +* Keycloak: `https://che-.your-domain/auth/` +* Workspaces servers: `https://.your-domain/` -##### Future options: -- Path Based: single hostname for all components (che, keycloak, WS servers) -- Host Based: unique host for each component -- TLS - ## Deleting a Deployment You can delete a deployment using the following command: ``` bash diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/_hostHelper.tpl b/dockerfiles/init/modules/che-kubernetes-helm/templates/_hostHelper.tpl index 59371f5b0ca..cfaf5b0a60d 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/_hostHelper.tpl +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/_hostHelper.tpl @@ -1,7 +1,9 @@ {{- define "cheHost" }} -{{- if .Values.global.isHostBased }} -{{- printf "master.%s" .Values.global.cheDomain }} +{{- if eq .Values.global.serverStrategy "default-host" }} +{{- printf "%s" .Values.global.ingressDomain }} +{{- else if eq .Values.global.serverStrategy "single-host" }} +{{- printf "che-%s.%s" .Release.Namespace .Values.global.ingressDomain }} {{- else }} -{{- printf "%s" .Values.global.cheDomain }} +{{- printf "che-%s.%s" .Release.Namespace .Values.global.ingressDomain }} {{- end }} {{- end }} diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakAuthUrlHelper.tpl b/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakAuthUrlHelper.tpl index db73ece70a7..0f2206045b1 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakAuthUrlHelper.tpl +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakAuthUrlHelper.tpl @@ -1,15 +1,21 @@ {{- define "keycloakAuthUrl" }} -{{- if .Values.global.isHostBased }} -{{- if .Values.global.tlsEnabled }} -{{- printf "https://keycloak.%s/auth" .Values.global.cheDomain }} -{{- else }} -{{- printf "http://keycloak.%s/auth" .Values.global.cheDomain }} -{{- end }} -{{- else }} -{{- if .Values.global.tlsEnabled }} -{{- printf "https://%s/auth" .Values.global.cheDomain }} -{{- else }} -{{- printf "http://%s/auth" .Values.global.cheDomain }} -{{- end }} -{{- end }} + {{- if eq .Values.global.serverStrategy "default-host" }} + {{- if .Values.global.tls.enabled }} + {{- printf "https://%s/auth" .Values.global.ingressDomain }} + {{- else }} + {{- printf "http://%s/auth" .Values.global.ingressDomain }} + {{- end }} + {{- else if eq .Values.global.serverStrategy "single-host" }} + {{- if .Values.global.tls.enabled }} + {{- printf "https://che-%s.%s/auth" .Release.Namespace .Values.global.ingressDomain }} + {{- else }} + {{- printf "http:/che-%s./%s/auth" .Release.Namespace .Values.global.ingressDomain }} + {{- end }} + {{- else }} + {{- if .Values.global.tls.enabled }} + {{- printf "https://keycloak-%s.%s/auth" .Release.Namespace .Values.global.ingressDomain }} + {{- else }} + {{- printf "http://keycloak-%s.%s/auth" .Release.Namespace .Values.global.ingressDomain }} + {{- end }} + {{- end }} {{- end }} diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakHostHelper.tpl b/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakHostHelper.tpl index 71e0654db1d..6e21748c2d4 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakHostHelper.tpl +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/_keycloakHostHelper.tpl @@ -1,7 +1,9 @@ {{- define "keycloakHost" }} -{{- if .Values.global.isHostBased }} -{{- printf "keycloak.%s" .Values.global.cheDomain }} +{{- if eq .Values.global.serverStrategy "default-host" }} +{{- printf "%s" .Values.global.ingressDomain }} +{{- else if eq .Values.global.serverStrategy "single-host" }} +{{- printf "che-%s.%s" .Release.Namespace .Values.global.ingressDomain }} {{- else }} -{{- printf "%s" .Values.global.cheDomain }} -{{- end }} +{{- printf "keycloak-%s.%s" .Release.Namespace .Values.global.ingressDomain }} {{- end }} +{{- end }} \ No newline at end of file diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/cert-issuer.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/cert-issuer.yaml index f055e38ecf1..1afc9445bc2 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/cert-issuer.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/cert-issuer.yaml @@ -5,15 +5,20 @@ # http://www.eclipse.org/legal/epl-v10.html # -{{- if .Values.global.tlsEnabled }} +{{- if .Values.global.tls }} +{{- if and .Values.global.tls.enabled .Values.global.tls.useCertManager }} apiVersion: certmanager.k8s.io/v1alpha1 -kind: Issuer +kind: ClusterIssuer metadata: name: letsencrypt spec: acme: # The ACME server URL +{{- if .Values.global.tls.useStaging }} + server: https://acme-staging.api.letsencrypt.org/directory +{{- else }} server: https://acme-v01.api.letsencrypt.org/directory +{{- end }} # Email address used for ACME registration email: eyal.barlev@sap.com # Name of a secret used to store the ACME account private key @@ -22,3 +27,4 @@ spec: # Enable the HTTP-01 challenge provider http01: {} {{- end }} +{{- end }} \ No newline at end of file diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/certificate.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/certificate.yaml deleted file mode 100644 index 4312c777623..00000000000 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/certificate.yaml +++ /dev/null @@ -1,26 +0,0 @@ -# Copyright (c) 2012-2017 Red Hat, Inc -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v1.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v10.html -# - -{{- if .Values.global.tlsEnabled }} -apiVersion: certmanager.k8s.io/v1alpha1 -kind: Certificate -metadata: - name: che-host-cert -spec: - secretName: che-tls - issuerRef: - name: letsencrypt - commonName: {{ .Values.global.cheDomain }} - dnsNames: - - {{ .Values.global.cheDomain }} - acme: - config: - - http01: - ingressClass: nginx - domains: - - {{ .Values.global.cheDomain }} -{{- end }} diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/configmap.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/configmap.yaml index 1ddc3b29023..0f1500f5334 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/configmap.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/configmap.yaml @@ -12,10 +12,9 @@ metadata: app: che name: che data: - CHE_DOMAIN: {{ .Values.global.cheDomain }} CHE_HOST: {{ template "cheHost" . }} CHE_PORT: "8080" -{{- if .Values.global.tlsEnabled }} +{{- if and .Values.global.tls .Values.global.tls.enabled }} CHE_API: https://{{ template "cheHost" . }}/api CHE_WEBSOCKET_ENDPOINT: wss://{{ template "cheHost" . }}/api/websocket CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL: https://{{ template "cheHost" . }}/agent-binaries/linux_amd64/bootstrapper/bootstrapper @@ -25,7 +24,8 @@ data: CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL: http://{{ template "cheHost" . }}/agent-binaries/linux_amd64/bootstrapper/bootstrapper {{- end }} CHE_DEBUG_SERVER: "true" - CHE_INFRASTRUCTURE_ACTIVE: kubernetes + CHE_INFRASTRUCTURE_ACTIVE: "kubernetes" + CHE_INFRA_KUBERNETES_INGRESS_DOMAIN: {{ .Values.global.ingressDomain }} CHE_INFRA_KUBERNETES_MACHINE__START__TIMEOUT__MIN: "5" CHE_INFRA_KUBERNETES_MASTER__URL: "" CHE_INFRA_KUBERNETES_OAUTH__TOKEN: "" @@ -36,7 +36,14 @@ data: CHE_KEYCLOAK_CLIENT__ID: {{ .Values.cheKeycloakClientId }} CHE_KEYCLOAK_REALM: {{ .Values.cheKeycloakRealm }} {{- end }} - CHE_INFRA_KUBERNETES_NAMESPACE: "" + CHE_INFRA_KUBERNETES_NAMESPACE: {{ .Values.global.cheNamespace }} +{{- if and .Values.global.tls .Values.global.tls.enabled }} + CHE_INFRA_KUBERNETES_TLS__ENABLED: {{ .Values.global.tls.enabled | quote}} + CHE_INFRA_KUBERNETES_TLS__SECRET: {{ .Values.global.tls.secretName }} +{{- else }} + CHE_INFRA_KUBERNETES_TLS__ENABLED: "false" + CHE_INFRA_KUBERNETES_TLS__SECRET: "" +{{- end }} CHE_INFRA_KUBERNETES_TRUST__CERTS: "false" CHE_INFRA_KUBERNETES_PVC_STRATEGY: "common" CHE_INFRA_KUBERNETES_PVC_PRECREATE__SUBPATHS: "false" @@ -51,8 +58,10 @@ data: CHE_PREDEFINED_STACKS_RELOAD__ON__START: "false" JAVA_OPTS: "-XX:MaxRAMFraction=2 -XX:+UseParallelGC -XX:MinHeapFreeRatio=10 -XX:MaxHeapFreeRatio=20 -XX:GCTimeRatio=4 -XX:AdaptiveSizePolicyWeight=90 -XX:+UnlockExperimentalVMOptions -XX:+UseCGroupMemoryLimitForHeap -Dsun.zip.disableMemoryMapping=true -Xms20m " CHE_WORKSPACE_AUTO_START: "false" -{{- if .Values.global.tlsEnabled }} +{{- if .Values.global.tls.enabled }} CHE_INFRA_KUBERNETES_INGRESS_ANNOTATIONS__JSON: '{"kubernetes.io/ingress.class": "nginx", "kubernetes.io/tls-acme": "true", "{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/rewrite-target": "/","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect": "true","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-connect-timeout": "3600","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-read-timeout": "3600"}' {{- else }} CHE_INFRA_KUBERNETES_INGRESS_ANNOTATIONS__JSON: '{"kubernetes.io/ingress.class": "nginx", "{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/rewrite-target": "/","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect": "false","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-connect-timeout": "3600","{{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-read-timeout": "3600"}' {{- end }} + CHE_INFRA_KUBERNETES_SERVER__STRATEGY: {{ .Values.global.serverStrategy }} + diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/deployment.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/deployment.yaml index 96ea2947ff3..db4b32b8a8e 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/deployment.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/deployment.yaml @@ -44,11 +44,6 @@ spec: }] containers: - env: - - name: CHE_DOMAIN - valueFrom: - configMapKeyRef: - key: CHE_DOMAIN - name: che - name: CHE_HOST valueFrom: configMapKeyRef: @@ -79,6 +74,11 @@ spec: configMapKeyRef: key: CHE_INFRASTRUCTURE_ACTIVE name: che + - name: CHE_INFRA_KUBERNETES_INGRESS_DOMAIN + valueFrom: + configMapKeyRef: + key: CHE_INFRA_KUBERNETES_INGRESS_DOMAIN + name: che - name: CHE_INFRA_KUBERNETES_BOOTSTRAPPER_BINARY__URL valueFrom: configMapKeyRef: @@ -156,6 +156,16 @@ spec: key: CHE_KEYCLOAK_REALM name: che {{- end }} + - name: CHE_INFRA_KUBERNETES_TLS__ENABLED + valueFrom: + configMapKeyRef: + key: CHE_INFRA_KUBERNETES_TLS__ENABLED + name: che + - name: CHE_INFRA_KUBERNETES_TLS__SECRET + valueFrom: + configMapKeyRef: + key: CHE_INFRA_KUBERNETES_TLS__SECRET + name: che - name: CHE_INFRA_KUBERNETES_NAMESPACE valueFrom: configMapKeyRef: @@ -206,6 +216,11 @@ spec: configMapKeyRef: key: CHE_INFRA_KUBERNETES_INGRESS_ANNOTATIONS__JSON name: che + - name: CHE_INFRA_KUBERNETES_SERVER__STRATEGY + valueFrom: + configMapKeyRef: + key: CHE_INFRA_KUBERNETES_SERVER__STRATEGY + name: che image: {{ .Values.cheImage }} imagePullPolicy: {{ .Values.cheImagePullPolicy }} livenessProbe: diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/ingress.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/ingress.yaml index 9aaf5351c3a..5f744f5c121 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/ingress.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/templates/ingress.yaml @@ -13,20 +13,23 @@ metadata: kubernetes.io/ingress.class: "nginx" {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-read-timeout: "3600" {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/proxy-connect-timeout: "3600" -{{- if .Values.global.tlsEnabled }} - kubernetes.io/tls-acme: "true" +{{- if and .Values.global.tls .Values.global.tls.enabled }} + {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect: "true" {{- else }} {{ .Values.global.ingressAnnotationsPrefix }}ingress.kubernetes.io/ssl-redirect: "false" {{- end }} +{{- if and .Values.global.tls .Values.global.tls.enabled .Values.global.tls.useCertManager }} + certmanager.k8s.io/cluster-issuer: "letsencrypt" +{{- end }} spec: -{{- if .Values.global.tlsEnabled }} +{{- if .Values.global.tls.enabled }} tls: - hosts: - {{ template "cheHost" . }} secretName: che-tls {{- end }} rules: -{{- if .Values.global.isHostBased }} +{{- if ne .Values.global.serverStrategy "default-host" }} - host: {{ template "cheHost" . }} http: {{- else }} @@ -36,4 +39,4 @@ spec: - path: / backend: serviceName: che-host - servicePort: 8080 + servicePort: 8080 \ No newline at end of file diff --git a/dockerfiles/init/modules/che-kubernetes-helm/templates/staging-cert-issuer.yaml b/dockerfiles/init/modules/che-kubernetes-helm/templates/staging-cert-issuer.yaml deleted file mode 100644 index b8a7bcf34df..00000000000 --- a/dockerfiles/init/modules/che-kubernetes-helm/templates/staging-cert-issuer.yaml +++ /dev/null @@ -1,24 +0,0 @@ -# Copyright (c) 2012-2017 Red Hat, Inc -# All rights reserved. This program and the accompanying materials -# are made available under the terms of the Eclipse Public License v1.0 -# which accompanies this distribution, and is available at -# http://www.eclipse.org/legal/epl-v10.html -# - -# {{- if .Values.global.tlsEnabled }} -# apiVersion: certmanager.k8s.io/v1alpha1 -# kind: Issuer -# metadata: -# name: letsencrypt-staging -# spec: -# acme: -# # The ACME server URL -# server: https://acme-staging.api.letsencrypt.org/directory -# # Email address used for ACME registration -# email: eyal.barlev@sap.com -# # Name of a secret used to store the ACME account private key -# privateKeySecretRef: -# name: letsencrypt-staging -# # Enable the HTTP-01 challenge provider -# http01: {} -# {{- end }} diff --git a/dockerfiles/init/modules/che-kubernetes-helm/values.yaml b/dockerfiles/init/modules/che-kubernetes-helm/values.yaml index 1730904ad3c..73fddaee44c 100644 --- a/dockerfiles/init/modules/che-kubernetes-helm/values.yaml +++ b/dockerfiles/init/modules/che-kubernetes-helm/values.yaml @@ -8,20 +8,24 @@ # the following section is for secure registries. when uncommented, a pull secret will be created #registry: # host: my-secure-private-registry.com -# host: my-secure-private-registry.com # username: myUser # password: myPass cheImage: eclipse/che-server:nightly cheImagePullPolicy: Always - cheKeycloakClientId: "che-public" cheKeycloakRealm: "che" global: - tlsEnabled: false + cheNamespace: "" multiuser: false - isHostBased: true - cheDomain: 192.168.99.100.nip.io + ingressDomain: 192.168.99.100.nip.io # See --annotations-prefix flag (https://github.com/kubernetes/ingress-nginx/blob/master/docs/user-guide/cli-arguments.md) ingressAnnotationsPrefix: "nginx." + # options: default-host, single-host, multi-host + serverStrategy: multi-host + tls: + enabled: false + useCertManager: true + useStaging: true + secretName: che-tls diff --git a/dockerfiles/init/modules/che-kubernetes-helm/values/default-host.yaml b/dockerfiles/init/modules/che-kubernetes-helm/values/default-host.yaml new file mode 100644 index 00000000000..4fd514e9654 --- /dev/null +++ b/dockerfiles/init/modules/che-kubernetes-helm/values/default-host.yaml @@ -0,0 +1,11 @@ +# Copyright (c) 2012-2017 Red Hat, Inc +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + +global: + multiuser: true + ingressDomain: 192.168.99.100 + serverStrategy: default-host diff --git a/dockerfiles/init/modules/che-kubernetes-helm/values/multi-user.yaml b/dockerfiles/init/modules/che-kubernetes-helm/values/multi-user.yaml new file mode 100644 index 00000000000..96e87bcbecc --- /dev/null +++ b/dockerfiles/init/modules/che-kubernetes-helm/values/multi-user.yaml @@ -0,0 +1,10 @@ +# Copyright (c) 2012-2017 Red Hat, Inc +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + +global: + multiuser: true + diff --git a/dockerfiles/init/modules/che-kubernetes-helm/values/tls.yaml b/dockerfiles/init/modules/che-kubernetes-helm/values/tls.yaml new file mode 100644 index 00000000000..143035bc073 --- /dev/null +++ b/dockerfiles/init/modules/che-kubernetes-helm/values/tls.yaml @@ -0,0 +1,18 @@ +# Copyright (c) 2012-2017 Red Hat, Inc +# All rights reserved. This program and the accompanying materials +# are made available under the terms of the Eclipse Public License v1.0 +# which accompanies this distribution, and is available at +# http://www.eclipse.org/legal/epl-v10.html +# + +# Currently, TLS support is enabled only for single-host deployments + +global: + multiuser: true + ingressDomain: + serverStrategy: single-host + tls: + enabled: true + useCertManager: true + useStaging: false + secretName: che-tls diff --git a/dockerfiles/init/modules/openshift/files/scripts/che-config b/dockerfiles/init/modules/openshift/files/scripts/che-config index d5b23f80a2a..abd8e6eecfb 100644 --- a/dockerfiles/init/modules/openshift/files/scripts/che-config +++ b/dockerfiles/init/modules/openshift/files/scripts/che-config @@ -14,7 +14,7 @@ CHE_INFRA_KUBERNETES_PASSWORD: ${CHE_INFRA_KUBERNETES_PASSWORD} CHE_INFRA_OPENSHIFT_PROJECT: ${CHE_INFRA_OPENSHIFT_PROJECT} CHE_INFRA_KUBERNETES_PVC_STRATEGY: ${CHE_INFRA_KUBERNETES_PVC_STRATEGY} CHE_INFRA_KUBERNETES_PVC_PRECREATE__SUBPATHS: ${CHE_INFRA_KUBERNETES_PVC_PRECREATE__SUBPATHS} -CHE_INFRA_OPENSHIFT_TLS__ENABLED: ${ENABLE_SSL} +CHE_INFRA_KUBERNETES_TLS__ENABLED: ${ENABLE_SSL} CHE_INFRA_KUBERNETES_TRUST__CERTS: "false" CHE_INFRA_KUBERNETES_USERNAME: ${CHE_INFRA_KUBERNETES_USERNAME} CHE_KEYCLOAK_AUTH__SERVER__URL: ${CHE_KEYCLOAK_AUTH__SERVER__URL} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisioner.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisioner.java index 66d42c48d63..e6101e020c3 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisioner.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisioner.java @@ -17,6 +17,7 @@ import org.eclipse.che.api.workspace.server.spi.InfrastructureException; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy; +import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.LogsVolumeMachineProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecurityContextProvisioner; @@ -46,6 +47,7 @@ public class KubernetesEnvironmentProvisioner { private final InstallerServersPortProvisioner installerServersPortProvisioner; private final LogsVolumeMachineProvisioner logsVolumeMachineProvisioner; private final SecurityContextProvisioner securityContextProvisioner; + private final IngressTlsProvisioner externalServerIngressTlsProvisioner; @Inject public KubernetesEnvironmentProvisioner( @@ -58,7 +60,8 @@ public KubernetesEnvironmentProvisioner( RamLimitProvisioner ramLimitProvisioner, InstallerServersPortProvisioner installerServersPortProvisioner, LogsVolumeMachineProvisioner logsVolumeMachineProvisioner, - SecurityContextProvisioner securityContextProvisioner) { + SecurityContextProvisioner securityContextProvisioner, + IngressTlsProvisioner externalServerIngressTlsProvisioner) { this.pvcEnabled = pvcEnabled; this.volumesStrategy = volumesStrategy; this.uniqueNamesProvisioner = uniqueNamesProvisioner; @@ -69,6 +72,7 @@ public KubernetesEnvironmentProvisioner( this.installerServersPortProvisioner = installerServersPortProvisioner; this.logsVolumeMachineProvisioner = logsVolumeMachineProvisioner; this.securityContextProvisioner = securityContextProvisioner; + this.externalServerIngressTlsProvisioner = externalServerIngressTlsProvisioner; } public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) @@ -90,6 +94,7 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) restartPolicyRewriter.provision(k8sEnv, identity); uniqueNamesProvisioner.provision(k8sEnv, identity); ramLimitProvisioner.provision(k8sEnv, identity); + externalServerIngressTlsProvisioner.provision(k8sEnv, identity); securityContextProvisioner.provision(k8sEnv, identity); } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java index 596077ada51..93db3b0a91f 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesInfraModule.java @@ -12,6 +12,9 @@ import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.CommonPVCStrategy.COMMON_STRATEGY; import static org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.UniqueWorkspacePVCStrategy.UNIQUE_STRATEGY; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.DefaultHostIngressExternalServerExposer.DEFAULT_HOST_STRATEGY; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.MultiHostIngressExternalServerExposer.MULTI_HOST_STRATEGY; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.SingleHostIngressExternalServerExposer.SINGLE_HOST_STRATEGY; import com.google.inject.AbstractModule; import com.google.inject.TypeLiteral; @@ -36,7 +39,12 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.LogsRootEnvVariableProvider; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.DefaultHostIngressExternalServerExposer; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.ExternalServerExposerStrategy; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.ExternalServerExposerStrategyProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.server.IngressAnnotationsProvider; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.MultiHostIngressExternalServerExposer; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.SingleHostIngressExternalServerExposer; /** @author Sergii Leshchenko */ public class KubernetesInfraModule extends AbstractModule { @@ -65,6 +73,20 @@ protected void configure() { volumesStrategies.addBinding(UNIQUE_STRATEGY).to(UniqueWorkspacePVCStrategy.class); bind(WorkspaceVolumesStrategy.class).toProvider(WorkspaceVolumeStrategyProvider.class); + MapBinder ingressStrategies = + MapBinder.newMapBinder(binder(), String.class, ExternalServerExposerStrategy.class); + ingressStrategies + .addBinding(MULTI_HOST_STRATEGY) + .to(MultiHostIngressExternalServerExposer.class); + ingressStrategies + .addBinding(SINGLE_HOST_STRATEGY) + .to(SingleHostIngressExternalServerExposer.class); + ingressStrategies + .addBinding(DEFAULT_HOST_STRATEGY) + .to(DefaultHostIngressExternalServerExposer.class); + bind(ExternalServerExposerStrategy.class) + .toProvider(ExternalServerExposerStrategyProvider.class); + Multibinder envVarProviders = Multibinder.newSetBinder(binder(), EnvVarProvider.class); envVarProviders.addBinding().to(LogsRootEnvVariableProvider.class); diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisioner.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisioner.java new file mode 100644 index 00000000000..5a9eadfbb57 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisioner.java @@ -0,0 +1,99 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.provision; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import io.fabric8.kubernetes.api.model.extensions.IngressTLS; +import io.fabric8.kubernetes.api.model.extensions.IngressTLSBuilder; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collection; +import java.util.List; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; +import org.eclipse.che.workspace.infrastructure.kubernetes.KubernetesInfrastructureException; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; + +/** + * Enables Transport Layer Security (TLS) for external server ingresses + * + * @author Guy Daich + */ +public class IngressTlsProvisioner implements ConfigurationProvisioner { + + protected final boolean isTlsEnabled; + protected final String tlsSecretName; + + @Inject + public IngressTlsProvisioner( + @Named("che.infra.kubernetes.tls_enabled") boolean isTlsEnabled, + @Named("che.infra.kubernetes.tls_secret") String tlsSecretName) { + this.isTlsEnabled = isTlsEnabled; + this.tlsSecretName = tlsSecretName; + } + + @Override + public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) + throws KubernetesInfrastructureException { + if (!isTlsEnabled) { + return; + } + + Collection ingresses = k8sEnv.getIngresses().values(); + for (Ingress ingress : ingresses) { + useSecureProtocolForServers(ingress); + enableTLS(ingress); + } + } + + private void enableTLS(Ingress ingress) { + String host = ingress.getSpec().getRules().get(0).getHost(); + + IngressTLSBuilder ingressTLSBuilder = + new IngressTLSBuilder().withHosts(host).withSecretName(tlsSecretName); + + // according to ingress tls spec, secret name is optional + // when working in single-host mode, nginx controller wil reuse the che-master secret + // https://github.com/kubernetes/kubernetes/blob/master/staging/src/k8s.io/api/extensions/v1beta1/types.go + if (!isNullOrEmpty(tlsSecretName)) { + ingressTLSBuilder.withSecretName(tlsSecretName); + } + + IngressTLS ingressTLS = ingressTLSBuilder.build(); + List ingressTLSList = new ArrayList<>(Arrays.asList(ingressTLS)); + ingress.getSpec().setTls(ingressTLSList); + } + + private void useSecureProtocolForServers(final Ingress ingress) { + Map servers = + Annotations.newDeserializer(ingress.getMetadata().getAnnotations()).servers(); + + servers.values().forEach(s -> s.setProtocol(getSecureProtocol(s.getProtocol()))); + + Map annotations = Annotations.newSerializer().servers(servers).annotations(); + + ingress.getMetadata().getAnnotations().putAll(annotations); + } + + private String getSecureProtocol(final String protocol) { + if ("ws".equals(protocol)) { + return "wss"; + } else if ("http".equals(protocol)) { + return "https"; + } else return protocol; + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java index 511dccf4686..c41f87907b5 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/server/ServersConverter.java @@ -15,7 +15,6 @@ import io.fabric8.kubernetes.api.model.PodSpec; import java.util.Map; import javax.inject.Inject; -import javax.inject.Named; import javax.inject.Singleton; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; @@ -24,6 +23,7 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.Names; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.ExternalServerExposerStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer; /** @@ -38,12 +38,11 @@ @Singleton public class ServersConverter implements ConfigurationProvisioner { - private final Map ingressAnnotations; + private final ExternalServerExposerStrategy externalServerExposerStrategy; @Inject - public ServersConverter( - @Named("infra.kubernetes.ingress.annotations") Map ingressAnnotations) { - this.ingressAnnotations = ingressAnnotations; + public ServersConverter(ExternalServerExposerStrategy externalServerExposerStrategy) { + this.externalServerExposerStrategy = externalServerExposerStrategy; } @Override @@ -58,7 +57,7 @@ public void provision(KubernetesEnvironment k8sEnv, RuntimeIdentity identity) if (!machineConfig.getServers().isEmpty()) { KubernetesServerExposer kubernetesServerExposer = new KubernetesServerExposer<>( - ingressAnnotations, machineName, podConfig, containerConfig, k8sEnv); + externalServerExposerStrategy, machineName, podConfig, containerConfig, k8sEnv); kubernetesServerExposer.expose(machineConfig.getServers()); } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposer.java new file mode 100644 index 00000000000..e7c07be8b96 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposer.java @@ -0,0 +1,112 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.lang.Integer.parseInt; +import static java.util.stream.Collectors.toMap; + +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides a path-based strategy for exposing service ports outside the cluster using Ingress + * Ingresses will be created without an explicit host (defaulting to *). + * + *

This strategy uses different Ingress path entries
+ * Each external server is exposed with a unique path prefix. + * + *

This strategy imposes limitation on user-developed applications.
+ * It should only be used for local development with a single IP address + * + *

+ *   Path-Based Ingress exposing service's port:
+ * Ingress
+ * ...
+ * spec:
+ *   rules:
+ *     - http:
+ *         paths:
+ *           - path: service123/webapp        ---->> Service.metadata.name + / + Service.spec.ports[0].name
+ *             backend:
+ *               serviceName: service123      ---->> Service.metadata.name
+ *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
+ * 
+ * + * @author Sergii Leshchenko + * @author Guy Daich + */ +public class DefaultHostIngressExternalServerExposer + implements ExternalServerExposerStrategy { + + public static final String DEFAULT_HOST_STRATEGY = "default-host"; + private final Map ingressAnnotations; + private static final Logger LOG = + LoggerFactory.getLogger(DefaultHostIngressExternalServerExposer.class); + + @Inject + public DefaultHostIngressExternalServerExposer( + @Named("infra.kubernetes.ingress.annotations") Map ingressAnnotations) { + if (ingressAnnotations == null) { + LOG.warn( + "Ingresses annotations are absent. Make sure that workspace ingresses don't need " + + "to be configured according to ingress controller."); + } + this.ingressAnnotations = ingressAnnotations; + } + + @Override + public void exposeExternalServers( + KubernetesEnvironment k8sEnv, + String machineName, + String serviceName, + Map portToServicePort, + Map externalServers) { + + for (ServicePort servicePort : portToServicePort.values()) { + int port = servicePort.getTargetPort().getIntVal(); + + Map ingressesServers = + externalServers + .entrySet() + .stream() + .filter(e -> parseInt(e.getValue().getPort().split("/")[0]) == port) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Ingress ingress = + new ExternalServerIngressBuilder() + .withPath(generateExternalServerIngressPath(serviceName, servicePort)) + .withName(generateExternalServerIngressName(serviceName, servicePort)) + .withMachineName(machineName) + .withServiceName(serviceName) + .withAnnotations(ingressAnnotations) + .withServicePort(servicePort.getName()) + .withServers(ingressesServers) + .build(); + + k8sEnv.getIngresses().put(ingress.getMetadata().getName(), ingress); + } + } + + private String generateExternalServerIngressName(String serviceName, ServicePort servicePort) { + return serviceName + '-' + servicePort.getName(); + } + + private String generateExternalServerIngressPath(String serviceName, ServicePort servicePort) { + return "/" + serviceName + "/" + servicePort.getName(); + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategy.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategy.java new file mode 100644 index 00000000000..6e068771ca4 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategy.java @@ -0,0 +1,42 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import io.fabric8.kubernetes.api.model.ServicePort; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; + +/** + * Defines a basic set of operations for exposing external servers. + * + * @author Guy Daich + */ +public interface ExternalServerExposerStrategy { + + /** + * Exposes service ports on given service externally (outside kubernetes cluster). Each exposed + * service port is associated with a specific Server configuration. Server configuration should be + * encoded in the exposing object's annotations, to be used by {@link KubernetesServerResolver}. + * + * @param k8sEnv Kubernetes environment + * @param machineName machine containing servers + * @param serviceName service associated with machine, mapping all machine server ports + * @param portToServicePort specific service ports to be exposed externally + * @param externalServers server configs of servers to be exposed externally + */ + public void exposeExternalServers( + T k8sEnv, + String machineName, + String serviceName, + Map portToServicePort, + Map externalServers); +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategyProvider.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategyProvider.java new file mode 100644 index 00000000000..854394af3cf --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerExposerStrategyProvider.java @@ -0,0 +1,49 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.lang.String.format; + +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import javax.inject.Provider; +import javax.inject.Singleton; + +/** + * Provides implementation of {@link ExternalServerExposerStrategy} for configured value. + * + * @author Guy Daich + */ +@Singleton +public class ExternalServerExposerStrategyProvider + implements Provider { + + private final ExternalServerExposerStrategy externalServerExposerStrategy; + + @Inject + public ExternalServerExposerStrategyProvider( + @Named("che.infra.kubernetes.server_strategy") String strategy, + Map strategies) { + final ExternalServerExposerStrategy externalServerExposerStrategy = strategies.get(strategy); + if (externalServerExposerStrategy != null) { + this.externalServerExposerStrategy = externalServerExposerStrategy; + } else { + throw new IllegalArgumentException( + format("Unsupported Ingress strategy '%s' configured", strategy)); + } + } + + @Override + public ExternalServerExposerStrategy get() { + return externalServerExposerStrategy; + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilder.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilder.java new file mode 100644 index 00000000000..d38fe1a8366 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilder.java @@ -0,0 +1,130 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static com.google.common.base.Strings.isNullOrEmpty; + +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPath; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPathBuilder; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressRuleValue; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressRuleValueBuilder; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import io.fabric8.kubernetes.api.model.extensions.IngressBackend; +import io.fabric8.kubernetes.api.model.extensions.IngressBackendBuilder; +import io.fabric8.kubernetes.api.model.extensions.IngressRule; +import io.fabric8.kubernetes.api.model.extensions.IngressRuleBuilder; +import io.fabric8.kubernetes.api.model.extensions.IngressSpec; +import io.fabric8.kubernetes.api.model.extensions.IngressSpecBuilder; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; + +/** @author Guy Daich */ + +/** + * helper class for builder ingresses. Creates an ingress with a single rule, based on hostname, + * http path. Ingress maps path to a specific service and service port. + */ +public class ExternalServerIngressBuilder { + + private String host; + private String path; + private String name; + private String serviceName; + private IntOrString servicePort; + private Map serversConfigs; + private String machineName; + private Map annotations; + + public ExternalServerIngressBuilder withHost(String host) { + this.host = host; + return this; + } + + public ExternalServerIngressBuilder withPath(String path) { + this.path = path; + return this; + } + + public ExternalServerIngressBuilder withName(String name) { + this.name = name; + return this; + } + + public ExternalServerIngressBuilder withServiceName(String serviceName) { + this.serviceName = serviceName; + return this; + } + + public ExternalServerIngressBuilder withAnnotations(Map annotations) { + this.annotations = annotations; + return this; + } + + public ExternalServerIngressBuilder withServicePort(String targetPortName) { + this.servicePort = new IntOrString(targetPortName); + return this; + } + + public ExternalServerIngressBuilder withServers( + Map serversConfigs) { + this.serversConfigs = serversConfigs; + return this; + } + + public ExternalServerIngressBuilder withMachineName(String machineName) { + this.machineName = machineName; + return this; + } + + public Ingress build() { + + IngressBackend ingressBackend = + new IngressBackendBuilder() + .withServiceName(serviceName) + .withNewServicePort(servicePort.getStrVal()) + .build(); + + HTTPIngressPathBuilder httpIngressPathBuilder = + new HTTPIngressPathBuilder().withBackend(ingressBackend); + + if (!isNullOrEmpty(path)) { + httpIngressPathBuilder.withPath(path); + } + + HTTPIngressPath httpIngressPath = httpIngressPathBuilder.build(); + + HTTPIngressRuleValue httpIngressRuleValue = + new HTTPIngressRuleValueBuilder().withPaths(httpIngressPath).build(); + IngressRuleBuilder ingressRuleBuilder = new IngressRuleBuilder().withHttp(httpIngressRuleValue); + + if (!isNullOrEmpty(host)) { + ingressRuleBuilder.withHost(host); + } + + IngressRule ingressRule = ingressRuleBuilder.build(); + + IngressSpec ingressSpec = new IngressSpecBuilder().withRules(ingressRule).build(); + + Map ingressAnnotations = new HashMap<>(annotations); + ingressAnnotations.putAll( + Annotations.newSerializer().servers(serversConfigs).machineName(machineName).annotations()); + + return new io.fabric8.kubernetes.api.model.extensions.IngressBuilder() + .withSpec(ingressSpec) + .withMetadata( + new ObjectMetaBuilder().withName(name).withAnnotations(ingressAnnotations).build()) + .build(); + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java index 3db5001ef4a..878afe7d1f6 100644 --- a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposer.java @@ -11,7 +11,6 @@ package org.eclipse.che.workspace.infrastructure.kubernetes.server; import static java.lang.Integer.parseInt; -import static java.util.stream.Collectors.toMap; import static org.eclipse.che.api.core.model.workspace.config.ServerConfig.INTERNAL_SERVER_ATTRIBUTE; import static org.eclipse.che.commons.lang.NameGenerator.generate; import static org.eclipse.che.workspace.infrastructure.kubernetes.Constants.CHE_ORIGINAL_NAME_LABEL; @@ -19,23 +18,11 @@ import io.fabric8.kubernetes.api.model.Container; import io.fabric8.kubernetes.api.model.ContainerPort; import io.fabric8.kubernetes.api.model.ContainerPortBuilder; -import io.fabric8.kubernetes.api.model.IntOrString; -import io.fabric8.kubernetes.api.model.ObjectMetaBuilder; import io.fabric8.kubernetes.api.model.Pod; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; import io.fabric8.kubernetes.api.model.ServicePortBuilder; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPath; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPathBuilder; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressRuleValue; -import io.fabric8.kubernetes.api.model.extensions.HTTPIngressRuleValueBuilder; import io.fabric8.kubernetes.api.model.extensions.Ingress; -import io.fabric8.kubernetes.api.model.extensions.IngressBackend; -import io.fabric8.kubernetes.api.model.extensions.IngressBackendBuilder; -import io.fabric8.kubernetes.api.model.extensions.IngressRule; -import io.fabric8.kubernetes.api.model.extensions.IngressRuleBuilder; -import io.fabric8.kubernetes.api.model.extensions.IngressSpec; -import io.fabric8.kubernetes.api.model.extensions.IngressSpecBuilder; import java.util.ArrayList; import java.util.Collection; import java.util.Collections; @@ -45,7 +32,6 @@ import java.util.Optional; import java.util.Set; import java.util.stream.Collectors; -import javax.inject.Named; import org.eclipse.che.api.core.model.workspace.config.ServerConfig; import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; @@ -99,20 +85,8 @@ * protocol: TCP ---->> Pod.spec.ports[0].protocol * * - * Then corresponding ingress expose one of the service's port: - * - *
- * Ingress
- * ...
- * spec:
- *   rules:
- *     - http:
- *         paths:
- *           - path: service123/webapp        ---->> Service.metadata.name + / + Service.spec.ports[0].name
- *             backend:
- *               serviceName: service123      ---->> Service.metadata.name
- *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
- * 
+ * Then, a server exposer strategy is used to expose one of the service's ports, to outside of the + * cluster. Currently, Host-Based and Path-Based Ingresses can be used to expose service ports. * *

For accessing publicly accessible server user will use ingress host or its load balancer IP. * For accessing workspace-wide accessible server user will use service name. Information about @@ -130,24 +104,19 @@ public class KubernetesServerExposer { public static final int SERVER_UNIQUE_PART_SIZE = 8; public static final String SERVER_PREFIX = "server"; - private final Map ingressAnnotations; + protected final ExternalServerExposerStrategy kubernetesExternalServerExposerStrategy; protected final String machineName; protected final Container container; protected final Pod pod; protected final T kubernetesEnvironment; public KubernetesServerExposer( - @Named("infra.kubernetes.ingress.annotations") Map ingressAnnotations, + ExternalServerExposerStrategy kubernetesExternalServerExposerStrategy, String machineName, Pod pod, Container container, T kubernetesEnvironment) { - if (ingressAnnotations == null) { - LOG.warn( - "Ingresses annotations are absent. Make sure that workspace ingresses don't need " - + "to be configured according to ingress controller."); - } - this.ingressAnnotations = ingressAnnotations; + this.kubernetesExternalServerExposerStrategy = kubernetesExternalServerExposerStrategy; this.machineName = machineName; this.pod = pod; this.container = container; @@ -189,38 +158,9 @@ public void expose(Map servers) { String serviceName = service.getMetadata().getName(); kubernetesEnvironment.getServices().put(serviceName, service); - exposeExternalServers(serviceName, portToServicePort, externalServers); } - protected void exposeExternalServers( - String serviceName, - Map portToServicePort, - Map externalServers) { - for (ServicePort servicePort : portToServicePort.values()) { - int port = servicePort.getTargetPort().getIntVal(); - - Map ingressesServers = - externalServers - .entrySet() - .stream() - .filter(e -> parseInt(e.getValue().getPort().split("/")[0]) == port) - .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); - - Ingress ingress = - new IngressBuilder() - .withName(serviceName + '-' + servicePort.getName()) - .withMachineName(machineName) - .withServiceName(serviceName) - .withAnnotations(ingressAnnotations) - .withServicePort(servicePort.getName()) - .withServers(ingressesServers) - .build(); - - kubernetesEnvironment.getIngresses().put(ingress.getMetadata().getName(), ingress); - } - } - private Map exposePort(Collection serverConfig) { Map exposedPorts = new HashMap<>(); Set portsToExpose = @@ -258,6 +198,15 @@ private Map exposePort(Collection s return exposedPorts; } + protected void exposeExternalServers( + String serviceName, + Map portToServicePort, + Map externalServers) { + + kubernetesExternalServerExposerStrategy.exposeExternalServers( + kubernetesEnvironment, machineName, serviceName, portToServicePort, externalServers); + } + private static class ServiceBuilder { private String name; private String machineName; @@ -309,74 +258,4 @@ public ServiceBuilder withMachineName(String machineName) { return this; } } - - private static class IngressBuilder { - private String name; - private String serviceName; - private IntOrString servicePort; - private Map serversConfigs; - private String machineName; - private Map annotations; - - private IngressBuilder withName(String name) { - this.name = name; - return this; - } - - private IngressBuilder withServiceName(String serviceName) { - this.serviceName = serviceName; - return this; - } - - private IngressBuilder withAnnotations(Map annotations) { - this.annotations = annotations; - return this; - } - - private IngressBuilder withServicePort(String targetPortName) { - this.servicePort = new IntOrString(targetPortName); - return this; - } - - private IngressBuilder withServers(Map serversConfigs) { - this.serversConfigs = serversConfigs; - return this; - } - - public IngressBuilder withMachineName(String machineName) { - this.machineName = machineName; - return this; - } - - private Ingress build() { - - IngressBackend ingressBackend = - new IngressBackendBuilder() - .withServiceName(serviceName) - .withNewServicePort(servicePort.getStrVal()) - .build(); - - String serverPath = "/" + serviceName + "/" + servicePort.getStrVal(); - HTTPIngressPath httpIngressPath = - new HTTPIngressPathBuilder().withPath(serverPath).withBackend(ingressBackend).build(); - - HTTPIngressRuleValue httpIngressRuleValue = - new HTTPIngressRuleValueBuilder().withPaths(httpIngressPath).build(); - IngressRule ingressRule = new IngressRuleBuilder().withHttp(httpIngressRuleValue).build(); - IngressSpec ingressSpec = new IngressSpecBuilder().withRules(ingressRule).build(); - - Map ingressAnnotations = new HashMap<>(annotations); - ingressAnnotations.putAll( - Annotations.newSerializer() - .servers(serversConfigs) - .machineName(machineName) - .annotations()); - - return new io.fabric8.kubernetes.api.model.extensions.IngressBuilder() - .withSpec(ingressSpec) - .withMetadata( - new ObjectMetaBuilder().withName(name).withAnnotations(ingressAnnotations).build()) - .build(); - } - } } diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposer.java new file mode 100644 index 00000000000..380f4cf9a6b --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposer.java @@ -0,0 +1,118 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.lang.Integer.parseInt; +import static java.util.stream.Collectors.toMap; + +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides a host-based strategy for exposing service ports outside the cluster using Ingress + * + *

This strategy uses different Ingress host entries
+ * Each external server is exposed with a unique subdomain of CHE_DOMAIN. + * + *

+ *   Host-Based Ingress exposing service's port:
+ * Ingress
+ * ...
+ * spec:
+ *   rules:
+ *     - host: service123-webapp.che-domain   ---->> Service.metadata.name + - + Service.spec.ports[0].name + . + CHE_DOMAIN
+ *     - http:
+ *         paths:
+ *           - path: /
+ *             backend:
+ *               serviceName: service123      ---->> Service.metadata.name
+ *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
+ * 
+ * + * @author Sergii Leshchenko + * @author Guy Daich + */ +public class MultiHostIngressExternalServerExposer + implements ExternalServerExposerStrategy { + + public static final String MULTI_HOST_STRATEGY = "multi-host"; + private final String domain; + private final Map ingressAnnotations; + private static final Logger LOG = + LoggerFactory.getLogger(MultiHostIngressExternalServerExposer.class); + + @Inject + public MultiHostIngressExternalServerExposer( + @Named("infra.kubernetes.ingress.annotations") Map ingressAnnotations, + @Named("che.infra.kubernetes.ingress.domain") String domain) { + if (ingressAnnotations == null) { + LOG.warn( + "Ingresses annotations are absent. Make sure that workspace ingresses don't need " + + "to be configured according to ingress controller."); + } + this.ingressAnnotations = ingressAnnotations; + this.domain = domain; + } + + @Override + public void exposeExternalServers( + KubernetesEnvironment k8sEnv, + String machineName, + String serviceName, + Map portToServicePort, + Map externalServers) { + + for (ServicePort servicePort : portToServicePort.values()) { + int port = servicePort.getTargetPort().getIntVal(); + + Map ingressesServers = + externalServers + .entrySet() + .stream() + .filter(e -> parseInt(e.getValue().getPort().split("/")[0]) == port) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Ingress ingress = + new ExternalServerIngressBuilder() + .withHost(generateExternalServerIngressHostname(serviceName, servicePort)) + .withPath(generateExternalServerIngressPath()) + .withName(generateExternalServerIngressName(serviceName, servicePort)) + .withMachineName(machineName) + .withServiceName(serviceName) + .withAnnotations(ingressAnnotations) + .withServicePort(servicePort.getName()) + .withServers(ingressesServers) + .build(); + + k8sEnv.getIngresses().put(ingress.getMetadata().getName(), ingress); + } + } + + private String generateExternalServerIngressPath() { + return "/"; + } + + private String generateExternalServerIngressName(String serviceName, ServicePort servicePort) { + return serviceName + '-' + servicePort.getName(); + } + + private String generateExternalServerIngressHostname( + String serviceName, ServicePort servicePort) { + return serviceName + "-" + servicePort.getName() + "." + domain; + } +} diff --git a/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/SingleHostIngressExternalServerExposer.java b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/SingleHostIngressExternalServerExposer.java new file mode 100644 index 00000000000..1ce9832d718 --- /dev/null +++ b/infrastructures/kubernetes/src/main/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/SingleHostIngressExternalServerExposer.java @@ -0,0 +1,116 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.lang.Integer.parseInt; +import static java.util.stream.Collectors.toMap; + +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import java.util.Map; +import javax.inject.Inject; +import javax.inject.Named; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.slf4j.Logger; +import org.slf4j.LoggerFactory; + +/** + * Provides a path-based strategy for exposing service ports outside the cluster using Ingress + * Ingresses will be created with a common host name for all workspaces. + * + *

This strategy uses different Ingress path entries
+ * Each external server is exposed with a unique path prefix. + * + *

This strategy imposes limitation on user-developed applications.
+ * + *

+ *   Path-Based Ingress exposing service's port:
+ * Ingress
+ * ...
+ * spec:
+ *   rules:
+ *     - host: CHE_HOST
+ *       http:
+ *         paths:
+ *           - path: service123/webapp        ---->> Service.metadata.name + / + Service.spec.ports[0].name
+ *             backend:
+ *               serviceName: service123      ---->> Service.metadata.name
+ *               servicePort: [8080|web-app]  ---->> Service.spec.ports[0].[port|name]
+ * 
+ * + * @author Sergii Leshchenko + * @author Guy Daich + */ +public class SingleHostIngressExternalServerExposer + implements ExternalServerExposerStrategy { + + public static final String SINGLE_HOST_STRATEGY = "single-host"; + private final Map ingressAnnotations; + private final String cheHost; + private static final Logger LOG = + LoggerFactory.getLogger(SingleHostIngressExternalServerExposer.class); + + @Inject + public SingleHostIngressExternalServerExposer( + @Named("infra.kubernetes.ingress.annotations") Map ingressAnnotations, + @Named("che.host") String cheHost) { + if (ingressAnnotations == null) { + LOG.warn( + "Ingresses annotations are absent. Make sure that workspace ingresses don't need " + + "to be configured according to ingress controller."); + } + this.ingressAnnotations = ingressAnnotations; + this.cheHost = cheHost; + } + + @Override + public void exposeExternalServers( + KubernetesEnvironment k8sEnv, + String machineName, + String serviceName, + Map portToServicePort, + Map externalServers) { + + for (ServicePort servicePort : portToServicePort.values()) { + int port = servicePort.getTargetPort().getIntVal(); + + Map ingressesServers = + externalServers + .entrySet() + .stream() + .filter(e -> parseInt(e.getValue().getPort().split("/")[0]) == port) + .collect(toMap(Map.Entry::getKey, Map.Entry::getValue)); + + Ingress ingress = + new ExternalServerIngressBuilder() + .withHost(cheHost) + .withPath(generateExternalServerIngressPath(serviceName, servicePort)) + .withName(generateExternalServerIngressName(serviceName, servicePort)) + .withMachineName(machineName) + .withServiceName(serviceName) + .withAnnotations(ingressAnnotations) + .withServicePort(servicePort.getName()) + .withServers(ingressesServers) + .build(); + + k8sEnv.getIngresses().put(ingress.getMetadata().getName(), ingress); + } + } + + private String generateExternalServerIngressName(String serviceName, ServicePort servicePort) { + return serviceName + '-' + servicePort.getName(); + } + + private String generateExternalServerIngressPath(String serviceName, ServicePort servicePort) { + return "/" + serviceName + "/" + servicePort.getName(); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisionerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisionerTest.java index 2e91492862f..e121f36d942 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisionerTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/KubernetesEnvironmentProvisionerTest.java @@ -16,6 +16,7 @@ import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy; +import org.eclipse.che.workspace.infrastructure.kubernetes.provision.IngressTlsProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.InstallerServersPortProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.LogsVolumeMachineProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.SecurityContextProvisioner; @@ -50,6 +51,7 @@ public class KubernetesEnvironmentProvisionerTest { @Mock private RamLimitProvisioner ramLimitProvisioner; @Mock private LogsVolumeMachineProvisioner logsVolumeMachineProvisioner; @Mock private SecurityContextProvisioner securityContextProvisioner; + @Mock private IngressTlsProvisioner externalServerIngressTlsProvisioner; private KubernetesEnvironmentProvisioner osInfraProvisioner; @@ -68,7 +70,8 @@ public void setUp() { ramLimitProvisioner, installerServersPortProvisioner, logsVolumeMachineProvisioner, - securityContextProvisioner); + securityContextProvisioner, + externalServerIngressTlsProvisioner); provisionOrder = inOrder( installerServersPortProvisioner, @@ -79,6 +82,7 @@ public void setUp() { envVarsProvisioner, restartPolicyRewriter, ramLimitProvisioner, + externalServerIngressTlsProvisioner, securityContextProvisioner); } @@ -96,6 +100,9 @@ public void performsOrderedProvisioning() throws Exception { provisionOrder.verify(restartPolicyRewriter).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(uniqueNamesProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(ramLimitProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); + provisionOrder + .verify(externalServerIngressTlsProvisioner) + .provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verify(securityContextProvisioner).provision(eq(k8sEnv), eq(runtimeIdentity)); provisionOrder.verifyNoMoreInteractions(); } diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisionerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisionerTest.java new file mode 100644 index 00000000000..7874406c391 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/provision/IngressTlsProvisionerTest.java @@ -0,0 +1,106 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.provision; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.mockito.Mockito.never; +import static org.mockito.Mockito.verify; +import static org.mockito.Mockito.when; +import static org.testng.Assert.assertEquals; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import java.util.HashMap; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.ExternalServerIngressBuilder; +import org.mockito.Mock; +import org.mockito.testng.MockitoTestNGListener; +import org.testng.annotations.Listeners; +import org.testng.annotations.Test; + +/** + * Tests {@link IngressTlsProvisioner}. + * + * @author Ilya Buziuk + * @author Sergii Leshchenko + * @author Guy Daich + */ +@Listeners(MockitoTestNGListener.class) +public class IngressTlsProvisionerTest { + + @Mock private KubernetesEnvironment k8sEnv; + @Mock private RuntimeIdentity runtimeIdentity; + + @Test + public void doNothingWhenTlsDisabled() throws Exception { + // given + IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(false, ""); + + // when + ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); + + // then + verify(k8sEnv, never()).getIngresses(); + } + + @Test + public void provisionTlsForRoutes() throws Exception { + // given + IngressTlsProvisioner ingressTlsProvisioner = new IngressTlsProvisioner(true, ""); + + Map attributesMap = singletonMap("key", "value"); + ServerConfigImpl httpServer = new ServerConfigImpl("8080/tpc", "http", "/api", emptyMap()); + ServerConfigImpl wsServer = new ServerConfigImpl("8080/tpc", "ws", "/ws", emptyMap()); + Map servers = + ImmutableMap.of("http-server", httpServer, "ws-server", wsServer); + Map annotations = singletonMap("annotation-key", "annotation-value"); + String machine = "machine"; + String name = "IngressName"; + String serviceName = "ServiceName"; + String servicePort = "server-port"; + String host = "server-host"; + + final Map ingresses = new HashMap<>(); + + ExternalServerIngressBuilder externalServerIngressBuilder = new ExternalServerIngressBuilder(); + Ingress ingress = + externalServerIngressBuilder + .withHost(host) + .withAnnotations(annotations) + .withMachineName(machine) + .withName(name) + .withServers(servers) + .withServiceName(serviceName) + .withServicePort(servicePort) + .build(); + ingresses.put("ingress", ingress); + when(k8sEnv.getIngresses()).thenReturn(ingresses); + + // when + ingressTlsProvisioner.provision(k8sEnv, runtimeIdentity); + + // then + assertEquals(ingress.getSpec().getTls().size(), 1); + assertEquals(ingress.getSpec().getTls().get(0).getHosts().size(), 1); + assertEquals(ingress.getSpec().getTls().get(0).getHosts().get(0), host); + + Map ingressServers = + Annotations.newDeserializer(ingress.getMetadata().getAnnotations()).servers(); + assertEquals(ingressServers.get("http-server").getProtocol(), "https"); + assertEquals(ingressServers.get("ws-server").getProtocol(), "wss"); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposerTest.java new file mode 100644 index 00000000000..eb7d4c3b00c --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/DefaultHostIngressExternalServerExposerTest.java @@ -0,0 +1,218 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; +import static org.testng.Assert.*; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.ServicePortBuilder; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import io.fabric8.kubernetes.api.model.extensions.IngressBackend; +import io.fabric8.kubernetes.api.model.extensions.IngressRule; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** @author Guy Daich */ +public class DefaultHostIngressExternalServerExposerTest { + + private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); + public static final String MACHINE_NAME = "pod/main"; + public static final String SERVICE_NAME = SERVER_PREFIX + "12345678" + "-" + MACHINE_NAME; + + private DefaultHostIngressExternalServerExposer externalServerExposer; + private KubernetesEnvironment kubernetesEnvironment; + private Container container; + + @BeforeMethod + public void setUp() throws Exception { + container = new ContainerBuilder().withName("main").build(); + Pod pod = + new PodBuilder() + .withNewMetadata() + .withName("pod") + .endMetadata() + .withNewSpec() + .withContainers(container) + .endSpec() + .build(); + + kubernetesEnvironment = + KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); + externalServerExposer = new DefaultHostIngressExternalServerExposer(emptyMap()); + } + + @Test + public void shouldCreateIngressForServer() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + ServicePort servicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8080)) + .build(); + Map portToServicePort = ImmutableMap.of("8080/tcp", servicePort); + Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + @Test + public void shouldCreateIngressForServerWhenTwoServersHasTheSamePort() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + ServerConfigImpl wsServerConfig = + new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); + ServicePort servicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8080)) + .build(); + Map portToServicePort = ImmutableMap.of("8080/tcp", servicePort); + + Map serversToExpose = + ImmutableMap.of( + "http-server", httpServerConfig, + "ws-server", wsServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertEquals(kubernetesEnvironment.getIngresses().size(), 1); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "ws-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + @Test + public void shouldCreateIngressesForServerWhenTwoServersHasDifferentPorts() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + ServerConfigImpl wsServerConfig = + new ServerConfigImpl("8081/tcp", "ws", "/connect", ATTRIBUTES_MAP); + ServicePort httpServicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8080)) + .build(); + ServicePort wsServicePort = + new ServicePortBuilder() + .withName("server-8081") + .withPort(8081) + .withProtocol("TCP") + .withTargetPort(new IntOrString(8081)) + .build(); + Map portToServicePort = + ImmutableMap.of("8080/tcp", httpServicePort, "8081/tcp", wsServicePort); + + Map serversToExpose = + ImmutableMap.of( + "http-server", httpServerConfig, + "ws-server", wsServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertEquals(kubernetesEnvironment.getIngresses().size(), 2); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + httpServicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "ws-server", + "tcp", + 8081, + wsServicePort, + new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + private void assertThatExternalServerIsExposed( + String machineName, + String serviceName, + String serverNameRegex, + String portProtocol, + Integer port, + ServicePort servicePort, + ServerConfigImpl expected) { + + // ensure that required ingress is created + Ingress ingress = kubernetesEnvironment.getIngresses().get(serviceName + "-server-" + port); + IngressRule ingressRule = ingress.getSpec().getRules().get(0); + IngressBackend backend = ingressRule.getHttp().getPaths().get(0).getBackend(); + assertEquals(backend.getServiceName(), serviceName); + assertEquals(backend.getServicePort().getStrVal(), servicePort.getName()); + + Annotations.Deserializer ingressAnnotations = + Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); + Map servers = ingressAnnotations.servers(); + ServerConfig serverConfig = servers.get(serverNameRegex); + assertEquals(serverConfig, expected); + + assertEquals(ingressAnnotations.machineName(), machineName); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilderTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilderTest.java new file mode 100644 index 00000000000..08929b39eb3 --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/ExternalServerIngressBuilderTest.java @@ -0,0 +1,132 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.util.Collections.singletonMap; +import static org.testng.Assert.*; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.extensions.HTTPIngressPath; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** @author Guy Daich */ +public class ExternalServerIngressBuilderTest { + + private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); + private static final ServerConfig SERVER_CONFIG = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + Map SERVERS = ImmutableMap.of("http-server", SERVER_CONFIG); + private static final Map ANNOTATIONS = + singletonMap("annotation-key", "annotation-value"); + private static final String MACHINE_NAME = "machine"; + private static final String NAME = "IngressName"; + private static final String SERVICE_NAME = "ServiceName"; + private static final String SERVICE_PORT = "server-port"; + + ExternalServerIngressBuilder externalServerIngressBuilder; + + @BeforeMethod + public void setUp() throws Exception { + this.externalServerIngressBuilder = new ExternalServerIngressBuilder(); + } + + @Test + public void shouldCreateIngress() { + // given + final String path = "/path/to/server"; + final String host = "host-to-server"; + + // when + Ingress ingress = + externalServerIngressBuilder + .withPath(path) + .withHost(host) + .withAnnotations(ANNOTATIONS) + .withMachineName(MACHINE_NAME) + .withName(NAME) + .withServers(SERVERS) + .withServiceName(SERVICE_NAME) + .withServicePort(SERVICE_PORT) + .build(); + + // then + AssertIngressSpec(path, host, ingress); + } + + @Test + public void shouldCreateIngressWithNoPath() { + // given + final String host = "host-to-server"; + + // when + Ingress ingress = + externalServerIngressBuilder + .withHost(host) + .withAnnotations(ANNOTATIONS) + .withMachineName(MACHINE_NAME) + .withName(NAME) + .withServers(SERVERS) + .withServiceName(SERVICE_NAME) + .withServicePort(SERVICE_PORT) + .build(); + + // then + AssertIngressSpec(null, host, ingress); + } + + @Test + public void shouldCreateIngressWithNoHost() { + // given + final String path = "/path/to/server"; + + // when + Ingress ingress = + externalServerIngressBuilder + .withPath(path) + .withAnnotations(ANNOTATIONS) + .withMachineName(MACHINE_NAME) + .withName(NAME) + .withServers(SERVERS) + .withServiceName(SERVICE_NAME) + .withServicePort(SERVICE_PORT) + .build(); + + // then + AssertIngressSpec(path, null, ingress); + } + + private void AssertIngressSpec(String path, String host, Ingress ingress) { + assertEquals(ingress.getSpec().getRules().get(0).getHost(), host); + HTTPIngressPath httpIngressPath = + ingress.getSpec().getRules().get(0).getHttp().getPaths().get(0); + assertEquals(httpIngressPath.getPath(), path); + assertEquals(httpIngressPath.getBackend().getServiceName(), SERVICE_NAME); + assertEquals(httpIngressPath.getBackend().getServicePort().getStrVal(), SERVICE_PORT); + + assertEquals(ingress.getMetadata().getName(), NAME); + assertTrue(ingress.getMetadata().getAnnotations().containsKey("annotation-key")); + assertEquals(ingress.getMetadata().getAnnotations().get("annotation-key"), "annotation-value"); + + Annotations.Deserializer ingressAnnotations = + Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); + Map servers = ingressAnnotations.servers(); + ServerConfig serverConfig = servers.get("http-server"); + assertEquals(serverConfig, SERVER_CONFIG); + + assertEquals(ingressAnnotations.machineName(), MACHINE_NAME); + } +} diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java index 3912962f5d3..3d8ab71c312 100644 --- a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/KubernetesServerExposerTest.java @@ -10,7 +10,6 @@ */ package org.eclipse.che.workspace.infrastructure.kubernetes.server; -import static java.util.Collections.emptyMap; import static java.util.Collections.singletonList; import static java.util.Collections.singletonMap; import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; @@ -27,9 +26,6 @@ import io.fabric8.kubernetes.api.model.PodBuilder; import io.fabric8.kubernetes.api.model.Service; import io.fabric8.kubernetes.api.model.ServicePort; -import io.fabric8.kubernetes.api.model.extensions.Ingress; -import io.fabric8.kubernetes.api.model.extensions.IngressBackend; -import io.fabric8.kubernetes.api.model.extensions.IngressRule; import java.util.ArrayList; import java.util.Map; import java.util.Map.Entry; @@ -39,6 +35,7 @@ import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.mockito.Mock; import org.mockito.testng.MockitoTestNGListener; import org.testng.annotations.BeforeMethod; import org.testng.annotations.Listeners; @@ -52,6 +49,7 @@ @Listeners(MockitoTestNGListener.class) public class KubernetesServerExposerTest { + @Mock private ExternalServerExposerStrategy externalServerExposerStrategy; private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); private static final Map INTERNAL_SERVER_ATTRIBUTE_MAP = singletonMap(ServerConfig.INTERNAL_SERVER_ATTRIBUTE, Boolean.TRUE.toString()); @@ -81,11 +79,11 @@ public void setUp() throws Exception { KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); this.serverExposer = new KubernetesServerExposer<>( - emptyMap(), MACHINE_NAME, pod, container, kubernetesEnvironment); + externalServerExposerStrategy, MACHINE_NAME, pod, container, kubernetesEnvironment); } @Test - public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { + public void shouldExposeContainerPortAndCreateServiceForServer() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); @@ -105,8 +103,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { } @Test - public void - shouldExposeContainerPortAndCreateServiceAndIngressForServerWhenTwoServersHasTheSamePort() { + public void shouldExposeContainerPortAndCreateServiceAndForServerWhenTwoServersHasTheSamePort() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); @@ -122,7 +119,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { // then assertEquals(kubernetesEnvironment.getServices().size(), 1); - assertEquals(kubernetesEnvironment.getIngresses().size(), 1); + assertThatExternalServerIsExposed( MACHINE_NAME, "http-server", @@ -138,8 +135,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { } @Test - public void - shouldExposeContainerPortsAndCreateServiceAndIngressesForServerWhenTwoServersHasDifferentPorts() { + public void shouldExposeContainerPortsAndCreateServiceForServerWhenTwoServersHasDifferentPorts() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); @@ -155,7 +151,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { // then assertEquals(kubernetesEnvironment.getServices().size(), 1); - assertEquals(kubernetesEnvironment.getIngresses().size(), 2); + assertThatExternalServerIsExposed( MACHINE_NAME, "http-server", @@ -172,7 +168,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { @Test public void - shouldExposeTcpContainerPortsAndCreateServiceAndIngressForServerWhenProtocolIsMissedInPort() { + shouldExposeTcpContainerPortsAndCreateServiceAndForServerWhenProtocolIsMissedInPort() { // given ServerConfigImpl httpServerConfig = new ServerConfigImpl("8080", "http", "/api", ATTRIBUTES_MAP); @@ -184,7 +180,7 @@ public void shouldExposeContainerPortAndCreateServiceAndIngressForServer() { // then assertEquals(kubernetesEnvironment.getServices().size(), 1); - assertEquals(kubernetesEnvironment.getIngresses().size(), 1); + assertThatExternalServerIsExposed( MACHINE_NAME, "http-server", @@ -341,24 +337,6 @@ private void assertThatExternalServerIsExposed( Annotations.Deserializer serviceAnnotations = Annotations.newDeserializer(service.getMetadata().getAnnotations()); assertEquals(serviceAnnotations.machineName(), machineName); - - // ensure that required ingress is created - Ingress ingress = - kubernetesEnvironment - .getIngresses() - .get(service.getMetadata().getName() + "-server-" + port); - IngressRule ingressRule = ingress.getSpec().getRules().get(0); - IngressBackend backend = ingressRule.getHttp().getPaths().get(0).getBackend(); - assertEquals(backend.getServiceName(), service.getMetadata().getName()); - assertEquals(backend.getServicePort().getStrVal(), servicePort.getName()); - - Annotations.Deserializer ingressAnnotations = - Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); - Map servers = ingressAnnotations.servers(); - ServerConfig serverConfig = servers.get(serverNameRegex); - assertEquals(serverConfig, expected); - - assertEquals(ingressAnnotations.machineName(), machineName); } private void assertThatInternalServerIsExposed( diff --git a/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposerTest.java b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposerTest.java new file mode 100644 index 00000000000..a3ab6507bef --- /dev/null +++ b/infrastructures/kubernetes/src/test/java/org/eclipse/che/workspace/infrastructure/kubernetes/server/MultiHostIngressExternalServerExposerTest.java @@ -0,0 +1,226 @@ +/* + * Copyright (c) 2012-2018 Red Hat, Inc. + * All rights reserved. This program and the accompanying materials + * are made available under the terms of the Eclipse Public License v1.0 + * which accompanies this distribution, and is available at + * http://www.eclipse.org/legal/epl-v10.html + * + * Contributors: + * Red Hat, Inc. - initial API and implementation + */ +package org.eclipse.che.workspace.infrastructure.kubernetes.server; + +import static java.util.Collections.emptyMap; +import static java.util.Collections.singletonMap; +import static org.eclipse.che.workspace.infrastructure.kubernetes.server.KubernetesServerExposer.SERVER_PREFIX; +import static org.testng.Assert.assertEquals; + +import com.google.common.collect.ImmutableMap; +import io.fabric8.kubernetes.api.model.Container; +import io.fabric8.kubernetes.api.model.ContainerBuilder; +import io.fabric8.kubernetes.api.model.IntOrString; +import io.fabric8.kubernetes.api.model.Pod; +import io.fabric8.kubernetes.api.model.PodBuilder; +import io.fabric8.kubernetes.api.model.ServicePort; +import io.fabric8.kubernetes.api.model.ServicePortBuilder; +import io.fabric8.kubernetes.api.model.extensions.Ingress; +import io.fabric8.kubernetes.api.model.extensions.IngressBackend; +import io.fabric8.kubernetes.api.model.extensions.IngressRule; +import java.util.Map; +import org.eclipse.che.api.core.model.workspace.config.ServerConfig; +import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; +import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; +import org.eclipse.che.workspace.infrastructure.kubernetes.environment.KubernetesEnvironment; +import org.testng.annotations.BeforeMethod; +import org.testng.annotations.Test; + +/** @author Guy Daich */ +public class MultiHostIngressExternalServerExposerTest { + + private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); + public static final String MACHINE_NAME = "pod/main"; + public static final String SERVICE_NAME = SERVER_PREFIX + "12345678" + "-" + MACHINE_NAME; + public static final String DOMAIN = "che.com"; + + private MultiHostIngressExternalServerExposer externalServerExposer; + private KubernetesEnvironment kubernetesEnvironment; + private Container container; + + @BeforeMethod + public void setUp() throws Exception { + container = new ContainerBuilder().withName("main").build(); + Pod pod = + new PodBuilder() + .withNewMetadata() + .withName("pod") + .endMetadata() + .withNewSpec() + .withContainers(container) + .endSpec() + .build(); + + kubernetesEnvironment = + KubernetesEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); + externalServerExposer = new MultiHostIngressExternalServerExposer(emptyMap(), DOMAIN); + } + + @Test + public void shouldCreateIngressForServer() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + IntOrString targetPort = new IntOrString(8080); + ServicePort servicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(targetPort) + .build(); + Map portToServicePort = ImmutableMap.of("8080/tcp", servicePort); + Map serversToExpose = ImmutableMap.of("http-server", httpServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + @Test + public void shouldCreateIngressForServerWhenTwoServersHasTheSamePort() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + ServerConfigImpl wsServerConfig = + new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); + IntOrString targetPort = new IntOrString(8080); + + ServicePort servicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(targetPort) + .build(); + Map portToServicePort = ImmutableMap.of("8080/tcp", servicePort); + + Map serversToExpose = + ImmutableMap.of( + "http-server", httpServerConfig, + "ws-server", wsServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertEquals(kubernetesEnvironment.getIngresses().size(), 1); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "ws-server", + "tcp", + 8080, + servicePort, + new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + @Test + public void shouldCreateIngressesForServerWhenTwoServersHasDifferentPorts() { + // given + ServerConfigImpl httpServerConfig = + new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); + ServerConfigImpl wsServerConfig = + new ServerConfigImpl("8081/tcp", "ws", "/connect", ATTRIBUTES_MAP); + IntOrString httpTargetPort = new IntOrString(8080); + IntOrString wsTargetPort = new IntOrString(8081); + ServicePort httpServicePort = + new ServicePortBuilder() + .withName("server-8080") + .withPort(8080) + .withProtocol("TCP") + .withTargetPort(httpTargetPort) + .build(); + ServicePort wsServicePort = + new ServicePortBuilder() + .withName("server-8081") + .withPort(8081) + .withProtocol("TCP") + .withTargetPort(wsTargetPort) + .build(); + Map portToServicePort = + ImmutableMap.of("8080/tcp", httpServicePort, "8081/tcp", wsServicePort); + + Map serversToExpose = + ImmutableMap.of( + "http-server", httpServerConfig, + "ws-server", wsServerConfig); + + // when + externalServerExposer.exposeExternalServers( + kubernetesEnvironment, MACHINE_NAME, SERVICE_NAME, portToServicePort, serversToExpose); + + // then + assertEquals(kubernetesEnvironment.getIngresses().size(), 2); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "http-server", + "tcp", + 8080, + httpServicePort, + new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); + assertThatExternalServerIsExposed( + MACHINE_NAME, + SERVICE_NAME, + "ws-server", + "tcp", + 8081, + wsServicePort, + new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); + } + + private void assertThatExternalServerIsExposed( + String machineName, + String serviceName, + String serverNameRegex, + String portProtocol, + Integer port, + ServicePort servicePort, + ServerConfigImpl expected) { + + // ensure that required ingress is created + Ingress ingress = kubernetesEnvironment.getIngresses().get(serviceName + "-server-" + port); + IngressRule ingressRule = ingress.getSpec().getRules().get(0); + assertEquals(ingressRule.getHost(), serviceName + "-" + servicePort.getName() + "." + DOMAIN); + assertEquals(ingressRule.getHttp().getPaths().get(0).getPath(), "/"); + IngressBackend backend = ingressRule.getHttp().getPaths().get(0).getBackend(); + assertEquals(backend.getServiceName(), serviceName); + assertEquals(backend.getServicePort().getStrVal(), servicePort.getName()); + + Annotations.Deserializer ingressAnnotations = + Annotations.newDeserializer(ingress.getMetadata().getAnnotations()); + Map servers = ingressAnnotations.servers(); + ServerConfig serverConfig = servers.get(serverNameRegex); + assertEquals(serverConfig, expected); + + assertEquals(ingressAnnotations.machineName(), machineName); + } +} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java index 1f503442a83..6533c4ea6ac 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisioner.java @@ -22,8 +22,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.RamLimitProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; +import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; -import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; @@ -41,7 +41,7 @@ public class OpenShiftEnvironmentProvisioner { private final WorkspaceVolumesStrategy volumesStrategy; private final UniqueNamesProvisioner uniqueNamesProvisioner; private final RouteTlsProvisioner routeTlsProvisioner; - private final OpenShiftServersConverter openShiftServersConverter; + private final ServersConverter serversConverter; private final EnvVarsConverter envVarsConverter; private final RestartPolicyRewriter restartPolicyRewriter; private final RamLimitProvisioner ramLimitProvisioner; @@ -53,7 +53,7 @@ public OpenShiftEnvironmentProvisioner( @Named("che.infra.kubernetes.pvc.enabled") boolean pvcEnabled, OpenShiftUniqueNamesProvisioner uniqueNamesProvisioner, RouteTlsProvisioner routeTlsProvisioner, - OpenShiftServersConverter openShiftServersConverter, + ServersConverter serversConverter, EnvVarsConverter envVarsConverter, RestartPolicyRewriter restartPolicyRewriter, WorkspaceVolumesStrategy volumesStrategy, @@ -64,7 +64,7 @@ public OpenShiftEnvironmentProvisioner( this.volumesStrategy = volumesStrategy; this.uniqueNamesProvisioner = uniqueNamesProvisioner; this.routeTlsProvisioner = routeTlsProvisioner; - this.openShiftServersConverter = openShiftServersConverter; + this.serversConverter = serversConverter; this.envVarsConverter = envVarsConverter; this.restartPolicyRewriter = restartPolicyRewriter; this.ramLimitProvisioner = ramLimitProvisioner; @@ -81,7 +81,7 @@ public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) } // 2 stage - converting Che model env to OpenShift env - openShiftServersConverter.provision(osEnv, identity); + serversConverter.provision(osEnv, identity); envVarsConverter.provision(osEnv, identity); if (pvcEnabled) { volumesStrategy.provision(osEnv, identity); diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java index f23bb0d20a6..7955fcc8582 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftInfraModule.java @@ -32,10 +32,12 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.namespace.pvc.WorkspaceVolumesStrategy; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.KubernetesCheApiEnvVarProvider; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.LogsRootEnvVariableProvider; +import org.eclipse.che.workspace.infrastructure.kubernetes.server.ExternalServerExposerStrategy; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironmentFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.OpenShiftProjectFactory; import org.eclipse.che.workspace.infrastructure.openshift.project.RemoveProjectOnWorkspaceRemove; +import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftExternalServerExposer; /** @author Sergii Leshchenko */ public class OpenShiftInfraModule extends AbstractModule { @@ -66,6 +68,8 @@ protected void configure() { volumesStrategies.addBinding(UNIQUE_STRATEGY).to(UniqueWorkspacePVCStrategy.class); bind(WorkspaceVolumesStrategy.class).toProvider(WorkspaceVolumeStrategyProvider.class); + bind(ExternalServerExposerStrategy.class).to(OpenShiftExternalServerExposer.class); + Multibinder envVarProviders = Multibinder.newSetBinder(binder(), EnvVarProvider.class); envVarProviders.addBinding().to(LogsRootEnvVariableProvider.class); diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftServersConverter.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftServersConverter.java deleted file mode 100644 index a57203c6e15..00000000000 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/OpenShiftServersConverter.java +++ /dev/null @@ -1,56 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.workspace.infrastructure.openshift.provision; - -import io.fabric8.kubernetes.api.model.Container; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.PodSpec; -import java.util.Map; -import javax.inject.Singleton; -import org.eclipse.che.api.core.model.workspace.config.ServerConfig; -import org.eclipse.che.api.core.model.workspace.runtime.RuntimeIdentity; -import org.eclipse.che.api.workspace.server.spi.InfrastructureException; -import org.eclipse.che.api.workspace.server.spi.environment.InternalMachineConfig; -import org.eclipse.che.workspace.infrastructure.kubernetes.Names; -import org.eclipse.che.workspace.infrastructure.kubernetes.provision.ConfigurationProvisioner; -import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; -import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerExposer; - -/** - * Converts {@link ServerConfig} to OpenShift related objects to add a server into OpenShift - * runtime. - * - *

Adds OpenShift objects by calling {@link OpenShiftServerExposer#expose(Map)} on each machine - * with servers. - * - * @author Alexander Garagatyi - */ -@Singleton -public class OpenShiftServersConverter implements ConfigurationProvisioner { - - @Override - public void provision(OpenShiftEnvironment osEnv, RuntimeIdentity identity) - throws InfrastructureException { - - for (Pod podConfig : osEnv.getPods().values()) { - final PodSpec podSpec = podConfig.getSpec(); - for (Container containerConfig : podSpec.getContainers()) { - String machineName = Names.machineName(podConfig, containerConfig); - InternalMachineConfig machineConfig = osEnv.getMachines().get(machineName); - if (!machineConfig.getServers().isEmpty()) { - OpenShiftServerExposer openShiftServerExposer = - new OpenShiftServerExposer(machineName, podConfig, containerConfig, osEnv); - openShiftServerExposer.expose(machineConfig.getServers()); - } - } - } - } -} diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisioner.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisioner.java index 9653dccc416..dc319d5108a 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisioner.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/provision/RouteTlsProvisioner.java @@ -39,7 +39,7 @@ public class RouteTlsProvisioner implements ConfigurationProvisioner { - - private final OpenShiftEnvironment openShiftEnvironment; - - public OpenShiftServerExposer( - String machineName, Pod pod, Container container, OpenShiftEnvironment openShiftEnvironment) { - super(Collections.emptyMap(), machineName, pod, container, openShiftEnvironment); - this.openShiftEnvironment = openShiftEnvironment; - } +public class OpenShiftExternalServerExposer + implements ExternalServerExposerStrategy { @Override - protected void exposeExternalServers( + public void exposeExternalServers( + OpenShiftEnvironment openShiftEnvironment, + String machineName, String serviceName, Map portToServicePort, Map externalServers) { diff --git a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerResolver.java b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerResolver.java index d0604a6ffb5..5a4b3fce51a 100644 --- a/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerResolver.java +++ b/infrastructures/openshift/src/main/java/org/eclipse/che/workspace/infrastructure/openshift/server/OpenShiftServerResolver.java @@ -30,7 +30,7 @@ * * @author Sergii Leshchenko * @author Alexander Garagatyi - * @see OpenShiftServerExposer + * @see OpenShiftExternalServerExposer * @see Annotations */ public class OpenShiftServerResolver extends KubernetesServerResolver { diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java index 20ab71b96ea..d97ecb19279 100644 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java +++ b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftEnvironmentProvisionerTest.java @@ -20,8 +20,8 @@ import org.eclipse.che.workspace.infrastructure.kubernetes.provision.env.EnvVarsConverter; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.limits.ram.RamLimitProvisioner; import org.eclipse.che.workspace.infrastructure.kubernetes.provision.restartpolicy.RestartPolicyRewriter; +import org.eclipse.che.workspace.infrastructure.kubernetes.provision.server.ServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; -import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftServersConverter; import org.eclipse.che.workspace.infrastructure.openshift.provision.OpenShiftUniqueNamesProvisioner; import org.eclipse.che.workspace.infrastructure.openshift.provision.RouteTlsProvisioner; import org.mockito.InOrder; @@ -46,7 +46,7 @@ public class OpenShiftEnvironmentProvisionerTest { @Mock private RuntimeIdentity runtimeIdentity; @Mock private RouteTlsProvisioner tlsRouteProvisioner; @Mock private EnvVarsConverter envVarsProvisioner; - @Mock private OpenShiftServersConverter serversProvisioner; + @Mock private ServersConverter serversProvisioner; @Mock private RestartPolicyRewriter restartPolicyRewriter; @Mock private RamLimitProvisioner ramLimitProvisioner; @Mock private LogsVolumeMachineProvisioner logsVolumeMachineProvisioner; diff --git a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftServerExposerTest.java b/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftServerExposerTest.java deleted file mode 100644 index 395a93a9466..00000000000 --- a/infrastructures/openshift/src/test/java/org/eclipse/che/workspace/infrastructure/openshift/OpenShiftServerExposerTest.java +++ /dev/null @@ -1,417 +0,0 @@ -/* - * Copyright (c) 2012-2018 Red Hat, Inc. - * All rights reserved. This program and the accompanying materials - * are made available under the terms of the Eclipse Public License v1.0 - * which accompanies this distribution, and is available at - * http://www.eclipse.org/legal/epl-v10.html - * - * Contributors: - * Red Hat, Inc. - initial API and implementation - */ -package org.eclipse.che.workspace.infrastructure.openshift; - -import static java.util.Collections.singletonList; -import static java.util.Collections.singletonMap; -import static org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerExposer.SERVER_PREFIX; -import static org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerExposer.SERVER_UNIQUE_PART_SIZE; -import static org.testng.Assert.assertEquals; -import static org.testng.Assert.assertNotNull; -import static org.testng.Assert.assertNull; -import static org.testng.Assert.assertTrue; - -import com.google.common.collect.ImmutableMap; -import io.fabric8.kubernetes.api.model.Container; -import io.fabric8.kubernetes.api.model.ContainerBuilder; -import io.fabric8.kubernetes.api.model.ContainerPortBuilder; -import io.fabric8.kubernetes.api.model.Pod; -import io.fabric8.kubernetes.api.model.PodBuilder; -import io.fabric8.kubernetes.api.model.Service; -import io.fabric8.kubernetes.api.model.ServicePort; -import io.fabric8.openshift.api.model.Route; -import java.util.ArrayList; -import java.util.Map; -import java.util.Map.Entry; -import java.util.Optional; -import java.util.regex.Pattern; -import org.eclipse.che.api.core.model.workspace.config.ServerConfig; -import org.eclipse.che.api.workspace.server.model.impl.ServerConfigImpl; -import org.eclipse.che.workspace.infrastructure.kubernetes.Annotations; -import org.eclipse.che.workspace.infrastructure.openshift.environment.OpenShiftEnvironment; -import org.eclipse.che.workspace.infrastructure.openshift.server.OpenShiftServerExposer; -import org.testng.annotations.BeforeMethod; -import org.testng.annotations.Test; - -/** - * Test for {@link OpenShiftServerExposer}. - * - * @author Sergii Leshchenko - */ -public class OpenShiftServerExposerTest { - - private static final Map ATTRIBUTES_MAP = singletonMap("key", "value"); - private static final Map INTERNAL_SERVER_ATTRIBUTE_MAP = - singletonMap(ServerConfig.INTERNAL_SERVER_ATTRIBUTE, Boolean.TRUE.toString()); - - private static final Pattern SERVER_PREFIX_REGEX = - Pattern.compile('^' + SERVER_PREFIX + "[A-z0-9]{" + SERVER_UNIQUE_PART_SIZE + "}-pod-main$"); - public static final String MACHINE_NAME = "pod/main"; - - private OpenShiftServerExposer serverExposer; - private OpenShiftEnvironment openShiftEnvironment; - private Container container; - - @BeforeMethod - public void setUp() throws Exception { - container = new ContainerBuilder().withName("main").build(); - Pod pod = - new PodBuilder() - .withNewMetadata() - .withName("pod") - .endMetadata() - .withNewSpec() - .withContainers(container) - .endSpec() - .build(); - - openShiftEnvironment = - OpenShiftEnvironment.builder().setPods(ImmutableMap.of("pod", pod)).build(); - this.serverExposer = - new OpenShiftServerExposer(MACHINE_NAME, pod, container, openShiftEnvironment); - } - - @Test - public void shouldExposeContainerPortAndCreateServiceAndRouteForServer() { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of("http-server", httpServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertThatExternalServerIsExposed( - MACHINE_NAME, - "http-server", - "tcp", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void - shouldExposeContainerPortAndCreateServiceAndRouteForServerWhenTwoServersHasTheSamePort() { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); - ServerConfigImpl wsServerConfig = - new ServerConfigImpl("8080/tcp", "ws", "/connect", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of( - "http-server", httpServerConfig, - "ws-server", wsServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertEquals(openShiftEnvironment.getServices().size(), 1); - assertEquals(openShiftEnvironment.getRoutes().size(), 1); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "http-server", - "tcp", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "ws-server", - "tcp", - 8080, - new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void - shouldExposeContainerPortsAndCreateServiceAndRoutesForServerWhenTwoServersHasDifferentPorts() { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); - ServerConfigImpl wsServerConfig = - new ServerConfigImpl("8081/tcp", "ws", "/connect", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of( - "http-server", httpServerConfig, - "ws-server", wsServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertEquals(openShiftEnvironment.getServices().size(), 1); - assertEquals(openShiftEnvironment.getRoutes().size(), 2); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "http-server", - "tcp", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "ws-server", - "tcp", - 8081, - new ServerConfigImpl(wsServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void - shouldExposeTcpContainerPortsAndCreateServiceAndRouteForServerWhenProtocolIsMissedInPort() { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080", "http", "/api", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of("http-server", httpServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertEquals(openShiftEnvironment.getServices().size(), 1); - assertEquals(openShiftEnvironment.getRoutes().size(), 1); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "http-server", - "TCP", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void shouldNotAddAdditionalContainerPortWhenItIsAlreadyExposed() { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of("http-server", httpServerConfig); - container.setPorts( - singletonList( - new ContainerPortBuilder() - .withName("port-8080") - .withContainerPort(8080) - .withProtocol("TCP") - .build())); - - // when - serverExposer.expose(serversToExpose); - - // then - assertThatExternalServerIsExposed( - MACHINE_NAME, - "http-server", - "tcp", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void shouldAddAdditionalContainerPortWhenThereIsTheSameButWithDifferentProtocol() { - // given - ServerConfigImpl udpServerConfig = - new ServerConfigImpl("8080/udp", "udp", "/api", ATTRIBUTES_MAP); - Map serversToExpose = ImmutableMap.of("server", udpServerConfig); - container.setPorts( - new ArrayList<>( - singletonList( - new ContainerPortBuilder() - .withName("port-8080") - .withContainerPort(8080) - .withProtocol("TCP") - .build()))); - - // when - serverExposer.expose(serversToExpose); - - // then - assertEquals(container.getPorts().size(), 2); - assertEquals(container.getPorts().get(1).getContainerPort(), new Integer(8080)); - assertEquals(container.getPorts().get(1).getProtocol(), "UDP"); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "server", - "udp", - 8080, - new ServerConfigImpl(udpServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - @Test - public void shouldExposeContainerPortAndCreateServiceForInternalServer() throws Exception { - // given - ServerConfigImpl httpServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", INTERNAL_SERVER_ATTRIBUTE_MAP); - Map serversToExpose = - ImmutableMap.of("http-server", httpServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertThatInternalServerIsExposed( - MACHINE_NAME, - "http-server", - "tcp", - 8080, - new ServerConfigImpl(httpServerConfig).withAttributes(INTERNAL_SERVER_ATTRIBUTE_MAP)); - } - - @Test - public void shouldExposeInternalAndExternalServers() throws Exception { - // given - ServerConfigImpl internalServerConfig = - new ServerConfigImpl("8080/tcp", "http", "/api", INTERNAL_SERVER_ATTRIBUTE_MAP); - ServerConfigImpl externalServerConfig = - new ServerConfigImpl("9090/tcp", "http", "/api", ATTRIBUTES_MAP); - Map serversToExpose = - ImmutableMap.of("int-server", internalServerConfig, "ext-server", externalServerConfig); - - // when - serverExposer.expose(serversToExpose); - - // then - assertThatInternalServerIsExposed( - MACHINE_NAME, - "int-server", - "tcp", - 8080, - new ServerConfigImpl(internalServerConfig).withAttributes(INTERNAL_SERVER_ATTRIBUTE_MAP)); - assertThatExternalServerIsExposed( - MACHINE_NAME, - "ext-server", - "tcp", - 9090, - new ServerConfigImpl(externalServerConfig).withAttributes(ATTRIBUTES_MAP)); - } - - private void assertThatExternalServerIsExposed( - String machineName, - String serverNameRegex, - String portProtocol, - Integer port, - ServerConfigImpl expected) { - // then - assertTrue( - container - .getPorts() - .stream() - .anyMatch( - p -> - p.getContainerPort().equals(port) - && p.getProtocol().equals(portProtocol.toUpperCase()))); - // ensure that service is created - - Service service = null; - for (Entry entry : openShiftEnvironment.getServices().entrySet()) { - if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) { - service = entry.getValue(); - break; - } - } - assertNotNull(service); - - // ensure that required service port is exposed - Optional servicePortOpt = - service - .getSpec() - .getPorts() - .stream() - .filter(p -> p.getTargetPort().getIntVal().equals(port)) - .findAny(); - assertTrue(servicePortOpt.isPresent()); - ServicePort servicePort = servicePortOpt.get(); - assertEquals(servicePort.getTargetPort().getIntVal(), port); - assertEquals(servicePort.getPort(), port); - assertEquals(servicePort.getName(), SERVER_PREFIX + "-" + port); - - Annotations.Deserializer serviceAnnotations = - Annotations.newDeserializer(service.getMetadata().getAnnotations()); - assertEquals(serviceAnnotations.machineName(), machineName); - - // ensure that required route is created - Route route = - openShiftEnvironment.getRoutes().get(service.getMetadata().getName() + "-server-" + port); - assertEquals(route.getSpec().getTo().getName(), service.getMetadata().getName()); - assertEquals(route.getSpec().getPort().getTargetPort().getStrVal(), servicePort.getName()); - - Annotations.Deserializer routeAnnotations = - Annotations.newDeserializer(route.getMetadata().getAnnotations()); - Map servers = routeAnnotations.servers(); - ServerConfig serverConfig = servers.get(serverNameRegex); - assertEquals(serverConfig, expected); - - assertEquals(routeAnnotations.machineName(), machineName); - } - - private void assertThatInternalServerIsExposed( - String machineName, - String serverNameRegex, - String portProtocol, - Integer port, - ServerConfigImpl expected) { - // then - assertTrue( - container - .getPorts() - .stream() - .anyMatch( - p -> - p.getContainerPort().equals(port) - && p.getProtocol().equals(portProtocol.toUpperCase()))); - // ensure that service is created - - Service service = null; - for (Entry entry : openShiftEnvironment.getServices().entrySet()) { - if (SERVER_PREFIX_REGEX.matcher(entry.getKey()).matches()) { - service = entry.getValue(); - break; - } - } - assertNotNull(service); - - // ensure that required service port is exposed - Optional servicePortOpt = - service - .getSpec() - .getPorts() - .stream() - .filter(p -> p.getTargetPort().getIntVal().equals(port)) - .findAny(); - assertTrue(servicePortOpt.isPresent()); - ServicePort servicePort = servicePortOpt.get(); - assertEquals(servicePort.getTargetPort().getIntVal(), port); - assertEquals(servicePort.getPort(), port); - assertEquals(servicePort.getName(), SERVER_PREFIX + "-" + port); - - Annotations.Deserializer serviceAnnotations = - Annotations.newDeserializer(service.getMetadata().getAnnotations()); - assertEquals(serviceAnnotations.machineName(), machineName); - - Map servers = serviceAnnotations.servers(); - ServerConfig serverConfig = servers.get(serverNameRegex); - assertEquals(serverConfig, expected); - - // ensure that required route is created - Route route = - openShiftEnvironment.getRoutes().get(service.getMetadata().getName() + "-server-" + port); - assertEquals(route.getSpec().getTo().getName(), service.getMetadata().getName()); - assertEquals(route.getSpec().getPort().getTargetPort().getStrVal(), servicePort.getName()); - - Annotations.Deserializer routeAnnotations = - Annotations.newDeserializer(route.getMetadata().getAnnotations()); - Map routeServers = routeAnnotations.servers(); - ServerConfig routeServerConfig = routeServers.get(serverNameRegex); - assertNull(routeServerConfig); - - assertEquals(routeAnnotations.machineName(), machineName); - } -}