diff --git a/.github/workflows/dockerimage.yaml b/.github/workflows/dockerimage.yaml new file mode 100644 index 0000000..164dc4c --- /dev/null +++ b/.github/workflows/dockerimage.yaml @@ -0,0 +1,35 @@ +name: '[build] docker image' + +env: + DOCKER_REPO: docker-registry.ebrains.eu + DOCKER_NAMESPACE: hbp-spatial-backend + DOCKER_IMAGE: server + DOCKER_TAG: latest + DOCKER_USERNAME: ${{ secrets.DOCKER_USERNAME }} + DOCKER_PASSWORD: ${{ secrets.DOCKER_PASSWORD }} + +on: + push: + branches: + - main +jobs: + build-server-image: + if: github.repository_owner == 'HumanBrainProject' and ${{ env.DOCKER_USERNAME != '' }} and ${{ env.DOCKER_PASSWORD != '' }} + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: 'Build and Push' + run: | + FULL_TAG=${{ env.DOCKER_REPO }}/${{ env.DOCKER_NAMESPACE }}/${{ env.DOCKER_IMAGE }}:${{ env.DOCKER_TAG }} + echo FULL_TAG: $FULL_TAG + docker build -t $FULL_TAG -f Dockerfile.server . + echo Build successful + echo Login + docker login \ + -u '${{ env.DOCKER_USERNAME }}' \ + -P '${{ env.DOCKER_PASSWORD }}' \ + ${{ env.DOCKER_REPO }} + echo Login Successful + echo Pushing image + docker push $FULL_TAG + echo Pushing image successful diff --git a/.github/workflows/tox.yaml b/.github/workflows/tox.yaml index d08b3e1..9f6223c 100644 --- a/.github/workflows/tox.yaml +++ b/.github/workflows/tox.yaml @@ -3,13 +3,18 @@ name: '[tox]' on: [push] jobs: build: - runs-on: ubuntu-latest + runs-on: ${{ matrix.os }} strategy: fail-fast: false matrix: # Python versions used in BrainVISA base images (i.e. Ubuntu LTS, # at the moment 18.04 and 20.04) + the latest stable Python version - python-version: [ '3.10', '3.8', '3.6' ] + python-version: [ '3.10', '3.8' ] + os: ['ubuntu-latest'] + include: + - python-version: '3.6' + # see https://github.com/actions/setup-python/issues/355#issuecomment-1335042510 + os: 'ubuntu-20.04' steps: - uses: actions/checkout@v2 - name: Set up Python ${{ matrix.python-version }} diff --git a/.helm/README.md b/.helm/README.md new file mode 100644 index 0000000..f0a141d --- /dev/null +++ b/.helm/README.md @@ -0,0 +1,26 @@ +# Deploying to production + +This document is intended for a documentation on how hbp-spatial-backend can be deployed to a [kubernetes (k8s)](https://kubernetes.io/) cluster via [helm](https://helm.sh/). + +Whilst the helm chart is produced with a rancher installation at https://rancher.tc.humanbrainproject.eu/ , the concept should be applicable to other k8s installations. + +## Get started + +1/ Create resources listed in adhoc (they may need to be adjusted based on the cluster you are working on) with: + +```sh +kubectl apply -f .helm/adhoc/*.yaml +``` +2/ cp the static files needed to startup the service: + +```sh +pod_name=$(kubectl get pod -l app=busy-box -o jsonpath="{.items[0].metadata.name}") + +for f in $(find /volatile/hbp-spatial-transformations-data/) +do + + kubectl cp $f $pod_name:/static-data/${f#/volatile/hbp-spatial-transformations-data/} +done +``` + +3/ Start application with `helm install prod .helm/hbp_spatial_backend` diff --git a/.helm/adhoc/certificate.yaml b/.helm/adhoc/certificate.yaml new file mode 100644 index 0000000..329e79e --- /dev/null +++ b/.helm/adhoc/certificate.yaml @@ -0,0 +1,20 @@ +apiVersion: cert-manager.io/v1 +kind: Certificate +metadata: + name: siibra-spatial-backend +spec: + commonName: siibra-spatial-backend.apps.tc.humanbrainproject.eu + isCA: false + dnsNames: + - siibra-spatial-backend.apps.tc.humanbrainproject.eu + issuerRef: + kind: ClusterIssuer + name: letsencrypt-production-issuer-1 + privateKey: + algorithm: RSA + encoding: PKCS1 + size: 2048 + renewBefore: 120h + secretName: siibra-spatial-backend-secret + usages: + - server auth diff --git a/.helm/adhoc/deployment-busybox.yaml b/.helm/adhoc/deployment-busybox.yaml new file mode 100644 index 0000000..d365b6a --- /dev/null +++ b/.helm/adhoc/deployment-busybox.yaml @@ -0,0 +1,35 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: busy-box + labels: + app: busy-box +spec: + replicas: 1 + selector: + matchLabels: + app: busy-box + template: + metadata: + labels: + app: busy-box + spec: + volumes: + - name: data-volume + persistentVolumeClaim: + claimName: data-volume-claim + containers: + - name: busybox + image: busybox:latest + command: ["tail"] + args: ["-f", "/dev/null"] + resources: + limits: + cpu: 100m + memory: 128Mi + requests: + cpu: 100m + memory: 128Mi + volumeMounts: + - mountPath: /static-data + name: data-volume diff --git a/.helm/adhoc/pvc-data.yaml b/.helm/adhoc/pvc-data.yaml new file mode 100644 index 0000000..7f27f8c --- /dev/null +++ b/.helm/adhoc/pvc-data.yaml @@ -0,0 +1,14 @@ +apiVersion: v1 +kind: PersistentVolumeClaim +metadata: + name: data-volume-claim + labels: + type: longhorn-pvc +spec: + # https://wiki.ebrains.eu/bin/view/Collabs/migration-faq/EBRAINS%20Kubernetes%20README/?srid=01ZnoA2n#HDownloadandconfigureyourkubeconfigfile + storageClassName: longhorn-1 + resources: + requests: + storage: 40Gi + accessModes: + - ReadWriteMany diff --git a/.helm/hbp_spatial_backend/.helmignore b/.helm/hbp_spatial_backend/.helmignore new file mode 100644 index 0000000..0e8a0eb --- /dev/null +++ b/.helm/hbp_spatial_backend/.helmignore @@ -0,0 +1,23 @@ +# Patterns to ignore when building packages. +# This supports shell glob matching, relative path matching, and +# negation (prefixed with !). Only one pattern per line. +.DS_Store +# Common VCS dirs +.git/ +.gitignore +.bzr/ +.bzrignore +.hg/ +.hgignore +.svn/ +# Common backup files +*.swp +*.bak +*.tmp +*.orig +*~ +# Various IDEs +.project +.idea/ +*.tmproj +.vscode/ diff --git a/.helm/hbp_spatial_backend/Chart.yaml b/.helm/hbp_spatial_backend/Chart.yaml new file mode 100644 index 0000000..44e3d28 --- /dev/null +++ b/.helm/hbp_spatial_backend/Chart.yaml @@ -0,0 +1,24 @@ +apiVersion: v2 +name: hbp-spatial-backend +description: A Helm chart for Kubernetes + +# A chart can be either an 'application' or a 'library' chart. +# +# Application charts are a collection of templates that can be packaged into versioned archives +# to be deployed. +# +# Library charts provide useful utilities or functions for the chart developer. They're included as +# a dependency of application charts to inject those utilities and functions into the rendering +# pipeline. Library charts do not define any templates and therefore cannot be deployed. +type: application + +# This is the chart version. This version number should be incremented each time you make changes +# to the chart and its templates, including the app version. +# Versions are expected to follow Semantic Versioning (https://semver.org/) +version: 0.1.0 + +# This is the version number of the application being deployed. This version number should be +# incremented each time you make changes to the application. Versions are not expected to +# follow Semantic Versioning. They should reflect the version the application is using. +# It is recommended to use it with quotes. +appVersion: "0.0.3" diff --git a/.helm/hbp_spatial_backend/templates/NOTES.txt b/.helm/hbp_spatial_backend/templates/NOTES.txt new file mode 100644 index 0000000..b7f348d --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/NOTES.txt @@ -0,0 +1,22 @@ +1. Get the application URL by running these commands: +{{- if .Values.ingress.enabled }} +{{- range $host := .Values.ingress.hosts }} + {{- range .paths }} + http{{ if $.Values.ingress.tls }}s{{ end }}://{{ $host.host }}{{ .path }} + {{- end }} +{{- end }} +{{- else if contains "NodePort" .Values.service.type }} + export NODE_PORT=$(kubectl get --namespace {{ .Release.Namespace }} -o jsonpath="{.spec.ports[0].nodePort}" services {{ include "hbp_spatial_backend.fullname" . }}) + export NODE_IP=$(kubectl get nodes --namespace {{ .Release.Namespace }} -o jsonpath="{.items[0].status.addresses[0].address}") + echo http://$NODE_IP:$NODE_PORT +{{- else if contains "LoadBalancer" .Values.service.type }} + NOTE: It may take a few minutes for the LoadBalancer IP to be available. + You can watch the status of by running 'kubectl get --namespace {{ .Release.Namespace }} svc -w {{ include "hbp_spatial_backend.fullname" . }}' + export SERVICE_IP=$(kubectl get svc --namespace {{ .Release.Namespace }} {{ include "hbp_spatial_backend.fullname" . }} --template "{{"{{ range (index .status.loadBalancer.ingress 0) }}{{.}}{{ end }}"}}") + echo http://$SERVICE_IP:{{ .Values.service.port }} +{{- else if contains "ClusterIP" .Values.service.type }} + export POD_NAME=$(kubectl get pods --namespace {{ .Release.Namespace }} -l "app.kubernetes.io/name={{ include "hbp_spatial_backend.name" . }},app.kubernetes.io/instance={{ .Release.Name }}" -o jsonpath="{.items[0].metadata.name}") + export CONTAINER_PORT=$(kubectl get pod --namespace {{ .Release.Namespace }} $POD_NAME -o jsonpath="{.spec.containers[0].ports[0].containerPort}") + echo "Visit http://127.0.0.1:8080 to use your application" + kubectl --namespace {{ .Release.Namespace }} port-forward $POD_NAME 8080:$CONTAINER_PORT +{{- end }} diff --git a/.helm/hbp_spatial_backend/templates/_helpers.tpl b/.helm/hbp_spatial_backend/templates/_helpers.tpl new file mode 100644 index 0000000..815f17d --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/_helpers.tpl @@ -0,0 +1,62 @@ +{{/* +Expand the name of the chart. +*/}} +{{- define "hbp_spatial_backend.name" -}} +{{- default .Chart.Name .Values.nameOverride | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Create a default fully qualified app name. +We truncate at 63 chars because some Kubernetes name fields are limited to this (by the DNS naming spec). +If release name contains chart name it will be used as a full name. +*/}} +{{- define "hbp_spatial_backend.fullname" -}} +{{- if .Values.fullnameOverride }} +{{- .Values.fullnameOverride | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- $name := default .Chart.Name .Values.nameOverride }} +{{- if contains $name .Release.Name }} +{{- .Release.Name | trunc 63 | trimSuffix "-" }} +{{- else }} +{{- printf "%s-%s" .Release.Name $name | trunc 63 | trimSuffix "-" }} +{{- end }} +{{- end }} +{{- end }} + +{{/* +Create chart name and version as used by the chart label. +*/}} +{{- define "hbp_spatial_backend.chart" -}} +{{- printf "%s-%s" .Chart.Name .Chart.Version | replace "+" "_" | trunc 63 | trimSuffix "-" }} +{{- end }} + +{{/* +Common labels +*/}} +{{- define "hbp_spatial_backend.labels" -}} +helm.sh/chart: {{ include "hbp_spatial_backend.chart" . }} +{{ include "hbp_spatial_backend.selectorLabels" . }} +{{- if .Chart.AppVersion }} +app.kubernetes.io/version: {{ .Chart.AppVersion | quote }} +{{- end }} +app.kubernetes.io/managed-by: {{ .Release.Service }} +{{- end }} + +{{/* +Selector labels +*/}} +{{- define "hbp_spatial_backend.selectorLabels" -}} +app.kubernetes.io/name: {{ include "hbp_spatial_backend.name" . }} +app.kubernetes.io/instance: {{ .Release.Name }} +{{- end }} + +{{/* +Create the name of the service account to use +*/}} +{{- define "hbp_spatial_backend.serviceAccountName" -}} +{{- if .Values.serviceAccount.create }} +{{- default (include "hbp_spatial_backend.fullname" .) .Values.serviceAccount.name }} +{{- else }} +{{- default "default" .Values.serviceAccount.name }} +{{- end }} +{{- end }} diff --git a/.helm/hbp_spatial_backend/templates/configmap.yaml b/.helm/hbp_spatial_backend/templates/configmap.yaml new file mode 100644 index 0000000..77f3f87 --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/configmap.yaml @@ -0,0 +1,16 @@ +apiVersion: v1 +data: + config.py: | + CORS_ORIGINS = '*' + PROXY_FIX = { + 'x_for': 1, + 'x_host': 1, + 'x_port': 1, + 'x_proto': 1, + } + DEFAULT_TRANSFORM_GRAPH = '/static-data/DISCO_20181004_sigV30_DARTEL_20181004_reg_x4/graph.yaml' +kind: ConfigMap +metadata: + name: siibra-spatial-backend + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} diff --git a/.helm/hbp_spatial_backend/templates/deployment.yaml b/.helm/hbp_spatial_backend/templates/deployment.yaml new file mode 100644 index 0000000..2c6607f --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/deployment.yaml @@ -0,0 +1,72 @@ +apiVersion: apps/v1 +kind: Deployment +metadata: + name: {{ include "hbp_spatial_backend.fullname" . }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} +spec: + {{- if not .Values.autoscaling.enabled }} + replicas: {{ .Values.replicaCount }} + {{- end }} + selector: + matchLabels: + {{- include "hbp_spatial_backend.selectorLabels" . | nindent 6 }} + template: + metadata: + {{- with .Values.podAnnotations }} + annotations: + {{- toYaml . | nindent 8 }} + {{- end }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 8 }} + {{- with .Values.podLabels }} + {{- toYaml . | nindent 8 }} + {{- end }} + spec: + {{- with .Values.imagePullSecrets }} + imagePullSecrets: + {{- toYaml . | nindent 8 }} + {{- end }} + serviceAccountName: {{ include "hbp_spatial_backend.serviceAccountName" . }} + securityContext: + {{- toYaml .Values.podSecurityContext | nindent 8 }} + containers: + - name: {{ .Chart.Name }} + securityContext: + {{- toYaml .Values.securityContext | nindent 12 }} + image: "{{ .Values.image.repository }}:{{ .Values.image.tag | default .Chart.AppVersion }}" + imagePullPolicy: {{ .Values.image.pullPolicy }} + ports: + - name: http + containerPort: {{ .Values.service.port }} + protocol: TCP + livenessProbe: + httpGet: + path: /health + port: http + readinessProbe: + httpGet: + path: /health + port: http + resources: + {{- toYaml .Values.resources | nindent 12 }} + {{- with .Values.volumeMounts }} + volumeMounts: + {{- toYaml . | nindent 12 }} + {{- end }} + {{- with .Values.volumes }} + volumes: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.nodeSelector }} + nodeSelector: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.affinity }} + affinity: + {{- toYaml . | nindent 8 }} + {{- end }} + {{- with .Values.tolerations }} + tolerations: + {{- toYaml . | nindent 8 }} + {{- end }} diff --git a/.helm/hbp_spatial_backend/templates/hpa.yaml b/.helm/hbp_spatial_backend/templates/hpa.yaml new file mode 100644 index 0000000..254aed4 --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/hpa.yaml @@ -0,0 +1,32 @@ +{{- if .Values.autoscaling.enabled }} +apiVersion: autoscaling/v2 +kind: HorizontalPodAutoscaler +metadata: + name: {{ include "hbp_spatial_backend.fullname" . }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} +spec: + scaleTargetRef: + apiVersion: apps/v1 + kind: Deployment + name: {{ include "hbp_spatial_backend.fullname" . }} + minReplicas: {{ .Values.autoscaling.minReplicas }} + maxReplicas: {{ .Values.autoscaling.maxReplicas }} + metrics: + {{- if .Values.autoscaling.targetCPUUtilizationPercentage }} + - type: Resource + resource: + name: cpu + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetCPUUtilizationPercentage }} + {{- end }} + {{- if .Values.autoscaling.targetMemoryUtilizationPercentage }} + - type: Resource + resource: + name: memory + target: + type: Utilization + averageUtilization: {{ .Values.autoscaling.targetMemoryUtilizationPercentage }} + {{- end }} +{{- end }} diff --git a/.helm/hbp_spatial_backend/templates/ingress.yaml b/.helm/hbp_spatial_backend/templates/ingress.yaml new file mode 100644 index 0000000..8dc266a --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/ingress.yaml @@ -0,0 +1,61 @@ +{{- if .Values.ingress.enabled -}} +{{- $fullName := include "hbp_spatial_backend.fullname" . -}} +{{- $svcPort := .Values.service.port -}} +{{- if and .Values.ingress.className (not (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion)) }} + {{- if not (hasKey .Values.ingress.annotations "kubernetes.io/ingress.class") }} + {{- $_ := set .Values.ingress.annotations "kubernetes.io/ingress.class" .Values.ingress.className}} + {{- end }} +{{- end }} +{{- if semverCompare ">=1.19-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1 +{{- else if semverCompare ">=1.14-0" .Capabilities.KubeVersion.GitVersion -}} +apiVersion: networking.k8s.io/v1beta1 +{{- else -}} +apiVersion: extensions/v1beta1 +{{- end }} +kind: Ingress +metadata: + name: {{ $fullName }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} + {{- with .Values.ingress.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +spec: + {{- if and .Values.ingress.className (semverCompare ">=1.18-0" .Capabilities.KubeVersion.GitVersion) }} + ingressClassName: {{ .Values.ingress.className }} + {{- end }} + {{- if .Values.ingress.tls }} + tls: + {{- range .Values.ingress.tls }} + - hosts: + {{- range .hosts }} + - {{ . | quote }} + {{- end }} + secretName: {{ .secretName }} + {{- end }} + {{- end }} + rules: + {{- range .Values.ingress.hosts }} + - host: {{ .host | quote }} + http: + paths: + {{- range .paths }} + - path: {{ .path }} + {{- if and .pathType (semverCompare ">=1.18-0" $.Capabilities.KubeVersion.GitVersion) }} + pathType: {{ .pathType }} + {{- end }} + backend: + {{- if semverCompare ">=1.19-0" $.Capabilities.KubeVersion.GitVersion }} + service: + name: {{ $fullName }} + port: + number: {{ $svcPort }} + {{- else }} + serviceName: {{ $fullName }} + servicePort: {{ $svcPort }} + {{- end }} + {{- end }} + {{- end }} +{{- end }} diff --git a/.helm/hbp_spatial_backend/templates/service.yaml b/.helm/hbp_spatial_backend/templates/service.yaml new file mode 100644 index 0000000..2adad1d --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/service.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Service +metadata: + name: {{ include "hbp_spatial_backend.fullname" . }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} +spec: + type: {{ .Values.service.type }} + ports: + - port: {{ .Values.service.port }} + targetPort: http + protocol: TCP + name: http + selector: + {{- include "hbp_spatial_backend.selectorLabels" . | nindent 4 }} diff --git a/.helm/hbp_spatial_backend/templates/serviceaccount.yaml b/.helm/hbp_spatial_backend/templates/serviceaccount.yaml new file mode 100644 index 0000000..29560b3 --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/serviceaccount.yaml @@ -0,0 +1,13 @@ +{{- if .Values.serviceAccount.create -}} +apiVersion: v1 +kind: ServiceAccount +metadata: + name: {{ include "hbp_spatial_backend.serviceAccountName" . }} + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} + {{- with .Values.serviceAccount.annotations }} + annotations: + {{- toYaml . | nindent 4 }} + {{- end }} +automountServiceAccountToken: {{ .Values.serviceAccount.automount }} +{{- end }} diff --git a/.helm/hbp_spatial_backend/templates/tests/test-connection.yaml b/.helm/hbp_spatial_backend/templates/tests/test-connection.yaml new file mode 100644 index 0000000..9ced2ea --- /dev/null +++ b/.helm/hbp_spatial_backend/templates/tests/test-connection.yaml @@ -0,0 +1,15 @@ +apiVersion: v1 +kind: Pod +metadata: + name: "{{ include "hbp_spatial_backend.fullname" . }}-test-connection" + labels: + {{- include "hbp_spatial_backend.labels" . | nindent 4 }} + annotations: + "helm.sh/hook": test +spec: + containers: + - name: wget + image: busybox + command: ['wget'] + args: ['{{ include "hbp_spatial_backend.fullname" . }}:{{ .Values.service.port }}'] + restartPolicy: Never diff --git a/.helm/hbp_spatial_backend/values.yaml b/.helm/hbp_spatial_backend/values.yaml new file mode 100644 index 0000000..5f92081 --- /dev/null +++ b/.helm/hbp_spatial_backend/values.yaml @@ -0,0 +1,102 @@ +# Default values for hbp_spatial_backend. +# This is a YAML-formatted file. +# Declare variables to be passed into your templates. + +replicaCount: 3 + +image: + repository: docker-registry.ebrains.eu/hbp-spatial-backend/server + pullPolicy: Always + # Overrides the image tag whose default is the chart appVersion. + tag: "latest" + +imagePullSecrets: [] +nameOverride: "" +fullnameOverride: "" + +serviceAccount: + # Specifies whether a service account should be created + create: false + # Automatically mount a ServiceAccount's API credentials? + automount: true + # Annotations to add to the service account + annotations: {} + # The name of the service account to use. + # If not set and create is true, a name is generated using the fullname template + name: "" + +podAnnotations: {} +podLabels: {} + +podSecurityContext: {} + # fsGroup: 2000 + +securityContext: {} + # capabilities: + # drop: + # - ALL + # readOnlyRootFilesystem: true + # runAsNonRoot: true + # runAsUser: 1000 + +service: + type: ClusterIP + port: 8080 + +ingress: + enabled: true + className: "" + annotations: {} + # kubernetes.io/ingress.class: nginx + # kubernetes.io/tls-acme: "true" + hosts: + - host: siibra-spatial-backend.apps.tc.humanbrainproject.eu + paths: + - path: / + pathType: ImplementationSpecific + tls: + - secretName: siibra-spatial-backend-secret + hosts: + - siibra-spatial-backend.apps.tc.humanbrainproject.eu + +resources: + # We usually recommend not to specify default resources and to leave this as a conscious + # choice for the user. This also increases chances charts run on environments with little + # resources, such as Minikube. If you do want to specify resources, uncomment the following + # lines, adjust them as necessary, and remove the curly braces after 'resources:'. + limits: + cpu: 300m + memory: 1024Mi + requests: + cpu: 150m + memory: 256Mi + +autoscaling: + enabled: false + minReplicas: 1 + maxReplicas: 100 + targetCPUUtilizationPercentage: 80 + # targetMemoryUtilizationPercentage: 80 + +# Additional volumes on the output Deployment definition. +volumes: + - name: data-volume + persistentVolumeClaim: + claimName: data-volume-claim + - name: config-vol + configMap: + defaultMode: 420 + name: siibra-spatial-backend + +# Additional volumeMounts on the output Deployment definition. +volumeMounts: + - mountPath: /static-data + name: data-volume + - mountPath: /instance + name: config-vol + readOnly: true +nodeSelector: {} + +tolerations: [] + +affinity: {} diff --git a/.pre-commit-config.yaml b/.pre-commit-config.yaml index 27c4b95..8d70b7f 100644 --- a/.pre-commit-config.yaml +++ b/.pre-commit-config.yaml @@ -9,12 +9,13 @@ repos: - id: check-merge-conflict - id: check-symlinks - id: check-yaml + exclude: ^\.helm/hbp_spatial_backend - id: end-of-file-fixer - id: debug-statements - id: trailing-whitespace -- repo: https://gitlab.com/pycqa/flake8 - rev: 3.7.9 +- repo: https://github.com/pycqa/flake8 + rev: 3.9.2 hooks: - id: flake8 additional_dependencies: [pep8-naming] diff --git a/openshift-deployment/README.rst b/openshift-deployment/README.rst deleted file mode 100644 index 3e3f065..0000000 --- a/openshift-deployment/README.rst +++ /dev/null @@ -1,130 +0,0 @@ -Deploying to production -======================= - -As an example, these are the instructions for restoring the production deployment on https://okd.hbp.eu/. - -#. You can use the deployment configuration saved in ``_ provided in the repository as a starting point. Edit the route contained in this file to use the correct URL. -#. Create the project named `hbp-spatial-backend` on https://okd.hbp.eu/ -#. Log in using the command-line ``oc`` tool (https://okd.hbp.eu/console/command-line), switch to the `hbp-spatial-backend` project with ``oc project hbp-spatial-backend`` -#. Import the objects from your edited YAML file using ``oc create -f openshift-prod-export.yaml`` -#. Re-create the Persistent Volume Claims and upload the data (see below). -#. Edit the Config Maps if needed, re-create the needed Secrets (namely ``github-webhook-secret``). -#. Start the build. The deployment should follow automatically. -#. For production, increase the number of replicas in order to be more resilient to node failures: go to `Applications` -> `Deployments` -> `flask` -> `Configuration` and change the number of `Replicas` to 3. -#. Go to `Builds` -> `Builds` -> `flask` -> `Configuration`, copy the GitHub Webhook URL and configure it into the GitHub repository (https://github.com/HumanBrainProject/hbp-spatial-backend/settings/hooks). Make sure to set the Content Type to ``application/json``. - -The deployment configuration is saved to ``_ by running ``oc get -o yaml --export is,bc,dc,svc,route,pvc,cm,horizontalpodautoscaler > openshift-prod-export.yaml`` (`status`, `resourceVersion`, `generation`, `@sha256`, `PersistentVolumeClaim` metadata (`volumeName`, `finalizers`, `annotations`) and `secret` information is stripped manually, see https://collab.humanbrainproject.eu/#/collab/38996/nav/270508 for other edits that may be necessary). - - -Deployment on okd-dev.hbp.eu -============================ - -For the record, here are the steps that were used to create this OpenShift project: - -#. Create the project / navigate to the project on https://okd-dev.hbp.eu/ - -#. Configure the Flask instance - - #. Add to Project -> Browse Catalog - #. Choose Python (does not matter, configuration will be changed later). Hit Next - #. In Step 2 (Configuration), hit `advanced options` and enter these values: - - - `flask` as Name - - `https://github.com/HumanBrainProject/hbp-spatial-backend.git` as Git Repository - - `dev` as Git Reference - - Under Routing, enter `hbp-spatial-backend.apps-dev.hbp.eu` as Hostname - - Under Routing, check `Secure route` - - Under Routing, set `Insecure Traffic` to `Redirect` - - Under `Build Configuration`, uncheck `Launch the first build when the build configuration is created` - - #. Hit `Create` at the bottom of the page - #. Follow the instructions to configure the GitHub webhook - - #. (optional) If you are going to publish the OpenShift deployment configuration, make sure that the webhook secrets refer to a real `Secret` resource (e.g. ``github-webhook-secret``) instead of being stored in clear-text in the `BuildConfig` object. - - #. Change the build configuration to use the `Docker` build strategy: - - #. Go to `Builds` -> `Builds` -> `flask` -> `Actions` -> `Edit YAML` - #. Replace the contents of the `strategy` key by:: - - dockerStrategy: - dockerfilePath: Dockerfile.server - type: Docker - - #. Hit `Save` - - #. Add post-build tests and tweak build configuration - - #. Go to `Builds` -> `Builds` -> `flask` -> `Actions` -> `Edit`. Click on `advanced options`. - #. Under `Image Configuration`, check `Always pull the builder image from the docker registry, even if it is present locally` - #. Under `Post-Commit Hooks`, check `Run build hooks after image is built`. Choose `Hook Type` = `Shell Script` and enter the following Script:: - - set -e - # Without PIP_IGNORE_INSTALLED=0 the Debian version of pip would - # re-install already installed dependencies in the user's home - # directory - # (https://github.com/pypa/pip/issues/4222#issuecomment-417672236) - cd /source - PIP_IGNORE_INSTALLED=0 python3 -m pip install --user -r test-requirements.txt - python3 -m pytest tests/ - - #. Hit `Save` - - #. Trigger the build by hitting `Start Build` - #. Configure the Flask instance - - #. Go to `Applications` -> `Deployments` -> `flask` -> `Configuration` - #. Under `Volumes`, hit `Add Config Files` - #. Click `Create Config Map` - - - `Name` = `instance-dir` - - `Key` = `config.py` - - `Value`:: - - CORS_ORIGINS = '*' - PROXY_FIX = { - 'x_for': 1, - 'x_host': 1, - 'x_port': 1, - 'x_proto': 1, - } - DEFAULT_TRANSFORM_GRAPH = '/static-data/DISCO_20181004_sigV30_DARTEL_20181004_reg_x4/graph.yaml' - - #. Hit `Create` - #. Back in the `Add Config Files` page, choose the newly created `instance-dir` as `Source` - #. Set `Mount Path` = `/instance` - #. Hit `Add` - #. Go to the `Environment` tab and add these variables: - - - `INSTANCE_PATH` = `/instance` - - #. Create a volume to hold the static data (transformation graph and deformation fields) - - #. Go to `Applications` -> `Deployments` -> `flask` -> `Configuration` - #. Under `Volumes`, hit `Add Storage` - #. Hit `Create Storage` - #. Set `Name` = `static-data`, `Size` = `10 GiB` - #. Hit `Create` - #. Set `Mount Path` = `/static-data` - #. Check `Read only` - #. Hit `Add` - - #. Upload the static data (transformation graph and deformation fields). We follow the method described on https://blog.openshift.com/transferring-files-in-and-out-of-containers-in-openshift-part-3/ - - #. Install the OpenShift Command-Line Tools by following the instructions on https://okd-dev.hbp.eu/console/command-line - #. Log in using the CLI (Under your name on the top right corner, hit `Copy Login Command` and paste it into a terminal) - #. Switch to the project (``oc project hbp-spatial-backend``) - #. Run a dummy pod for rsync transfer with ``oc run dummy --image ylep/oc-rsync-transfer`` - #. Mount the volume against the dummy pod ``oc set volume dc/dummy --add --name=tmp-mount --claim-name=static-data --mount-path /static-data`` - #. Wait for the deployment to be complete with ``oc rollout status dc/dummy`` - #. Get the name of the dummy pod with ``oc get pods --selector run=dummy`` - #. Copy the data using ``oc rsync --compress=true --progress=true /volatile/hbp-spatial-transformations-data/ dummy-2-7tdml:/static-data/`` (replace `dummy-2-7tdml` with the pod name from the previous step). - #. Verify the contents of the directory with ``oc rsh dummy-2-7tdml ls -l /static-data`` - #. Delete everything related to the temporary pod with ``oc delete all --selector run=dummy`` - - #. Add Health Checks - - #. Go to `Applications` -> `Deployments` -> `flask` -> `Actions` -> `Edit Health Checks` - #. Add a `Readiness Probe` of type `HTTP GET`, using `Path` = `/health`, setting some `Initial Delay` (e.g. 5 seconds) and `Timeout` (e.g. 10 seconds) - #. Add a `Liveness Probe` of type `HTTP GET`, using `Path` = `/health`, setting a long `Timeout` (e.g. 60 seconds) - #. Hit `Save` diff --git a/openshift-deployment/openshift-prod-export.yaml b/openshift-deployment/openshift-prod-export.yaml deleted file mode 100644 index 1e71f51..0000000 --- a/openshift-deployment/openshift-prod-export.yaml +++ /dev/null @@ -1,244 +0,0 @@ -apiVersion: v1 -items: -- apiVersion: image.openshift.io/v1 - kind: ImageStream - metadata: - annotations: - openshift.io/generated-by: OpenShiftWebConsole - creationTimestamp: 2020-01-29T10:42:20Z - labels: - app: flask - name: flask - namespace: hbp-spatial-backend - selfLink: /apis/image.openshift.io/v1/namespaces/hbp-spatial-backend/imagestreams/flask - uid: 06baee73-4284-11ea-89e4-fa163e061b5f - spec: - lookupPolicy: - local: false -- apiVersion: build.openshift.io/v1 - kind: BuildConfig - metadata: - annotations: - openshift.io/generated-by: OpenShiftWebConsole - creationTimestamp: 2020-01-29T10:42:20Z - labels: - app: flask - name: flask - namespace: hbp-spatial-backend - selfLink: /apis/build.openshift.io/v1/namespaces/hbp-spatial-backend/buildconfigs/flask - uid: 06c5d5dd-4284-11ea-89e4-fa163e061b5f - spec: - failedBuildsHistoryLimit: 5 - nodeSelector: null - output: - to: - kind: ImageStreamTag - name: flask:latest - postCommit: - script: | - set -e - # Without PIP_IGNORE_INSTALLED=0 the Debian version of pip would - # re-install already installed dependencies in the user's home - # directory - # (https://github.com/pypa/pip/issues/4222#issuecomment-417672236) - cd /source - PIP_IGNORE_INSTALLED=0 python3 -m pip install --user -r test-requirements.txt - python3 -m pytest tests/ - resources: {} - runPolicy: Serial - source: - git: - uri: https://github.com/HumanBrainProject/hbp-spatial-backend.git - type: Git - strategy: - dockerStrategy: - dockerfilePath: Dockerfile.server - forcePull: true - type: Docker - successfulBuildsHistoryLimit: 5 - triggers: - - github: - secretReference: - name: github-webhook-secret - type: GitHub -- apiVersion: apps.openshift.io/v1 - kind: DeploymentConfig - metadata: - annotations: - openshift.io/generated-by: OpenShiftWebConsole - creationTimestamp: 2020-01-29T10:42:20Z - labels: - app: flask - name: flask - namespace: hbp-spatial-backend - selfLink: /apis/apps.openshift.io/v1/namespaces/hbp-spatial-backend/deploymentconfigs/flask - uid: 06cef084-4284-11ea-89e4-fa163e061b5f - spec: - replicas: 3 - revisionHistoryLimit: 10 - selector: - deploymentconfig: flask - strategy: - activeDeadlineSeconds: 21600 - resources: {} - rollingParams: - intervalSeconds: 1 - maxSurge: 25% - maxUnavailable: 25% - timeoutSeconds: 600 - updatePeriodSeconds: 1 - type: Rolling - template: - metadata: - creationTimestamp: null - labels: - app: flask - deploymentconfig: flask - spec: - containers: - - env: - - name: INSTANCE_PATH - value: /instance - image: docker-registry.default.svc:5000/hbp-spatial-backend/flask - imagePullPolicy: Always - livenessProbe: - failureThreshold: 3 - httpGet: - path: /health - port: 8080 - scheme: HTTP - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 60 - name: flask - ports: - - containerPort: 8080 - protocol: TCP - readinessProbe: - failureThreshold: 3 - httpGet: - path: /health - port: 8080 - scheme: HTTP - initialDelaySeconds: 5 - periodSeconds: 10 - successThreshold: 1 - timeoutSeconds: 10 - resources: {} - terminationMessagePath: /dev/termination-log - terminationMessagePolicy: File - volumeMounts: - - mountPath: /instance - name: volume-6c4ne - - mountPath: /static-data - name: volume-vjjd7 - readOnly: true - dnsPolicy: ClusterFirst - restartPolicy: Always - schedulerName: default-scheduler - securityContext: {} - terminationGracePeriodSeconds: 30 - volumes: - - configMap: - defaultMode: 420 - name: instance-dir - name: volume-6c4ne - - name: volume-vjjd7 - persistentVolumeClaim: - claimName: static-data - test: false - triggers: - - imageChangeParams: - automatic: true - containerNames: - - flask - from: - kind: ImageStreamTag - name: flask:latest - namespace: hbp-spatial-backend - lastTriggeredImage: docker-registry.default.svc:5000/hbp-spatial-backend/flask - type: ImageChange - - type: ConfigChange -- apiVersion: v1 - kind: Service - metadata: - annotations: - openshift.io/generated-by: OpenShiftWebConsole - creationTimestamp: 2020-01-29T10:42:20Z - labels: - app: flask - name: flask - namespace: hbp-spatial-backend - selfLink: /api/v1/namespaces/hbp-spatial-backend/services/flask - uid: 06d975d4-4284-11ea-89e4-fa163e061b5f - spec: - clusterIP: 172.30.20.217 - ports: - - name: 8080-tcp - port: 8080 - protocol: TCP - targetPort: 8080 - selector: - deploymentconfig: flask - sessionAffinity: None - type: ClusterIP -- apiVersion: route.openshift.io/v1 - kind: Route - metadata: - annotations: - openshift.io/generated-by: OpenShiftWebConsole - creationTimestamp: 2020-01-29T10:42:20Z - labels: - app: flask - name: flask - namespace: hbp-spatial-backend - selfLink: /apis/route.openshift.io/v1/namespaces/hbp-spatial-backend/routes/flask - uid: 0705d9b3-4284-11ea-89e4-fa163e061b5f - spec: - host: hbp-spatial-backend.apps.hbp.eu - port: - targetPort: 8080-tcp - tls: - insecureEdgeTerminationPolicy: Redirect - termination: edge - to: - kind: Service - name: flask - weight: 100 - wildcardPolicy: None -- apiVersion: v1 - kind: PersistentVolumeClaim - metadata: - creationTimestamp: 2020-01-29T12:08:41Z - name: static-data - namespace: hbp-spatial-backend - selfLink: /api/v1/namespaces/hbp-spatial-backend/persistentvolumeclaims/static-data - uid: 16cede9f-4290-11ea-89e4-fa163e061b5f - spec: - accessModes: - - ReadWriteOnce - resources: - requests: - storage: 10Gi -- apiVersion: v1 - data: - config.py: | - CORS_ORIGINS = '*' - PROXY_FIX = { - 'x_for': 1, - 'x_host': 1, - 'x_port': 1, - 'x_proto': 1, - } - DEFAULT_TRANSFORM_GRAPH = '/static-data/DISCO_20181004_sigV30_DARTEL_20181004_reg_x4/graph.yaml' - kind: ConfigMap - metadata: - creationTimestamp: 2020-01-29T10:42:21Z - name: instance-dir - namespace: hbp-spatial-backend - selfLink: /api/v1/namespaces/hbp-spatial-backend/configmaps/instance-dir - uid: 072157df-4284-11ea-89e4-fa163e061b5f -kind: List -metadata: - resourceVersion: "" - selfLink: "" diff --git a/setup.py b/setup.py index 724f70e..310b52a 100644 --- a/setup.py +++ b/setup.py @@ -58,7 +58,12 @@ def find_source_url(*file_paths): "Flask-Cors", "flask-smorest ~= 0.18.4", "marshmallow ~= 3.0", - "PyYAML ~= 5.1", + # see https://github.com/yaml/pyyaml/issues/601 + # see https://github.com/yaml/pyyaml/issues/723 + # see https://github.com/yaml/pyyaml/issues/724 + "PyYAML ~= 6.0.1", + # see https://stackoverflow.com/questions/72191560/importerror-cannot-import-name-soft-unicode-from-markupsafe # noqa: E501 + "markupsafe==2.0.1", ], python_requires="~= 3.5", extras_require={ diff --git a/tox.ini b/tox.ini index 7b43549..1609675 100644 --- a/tox.ini +++ b/tox.ini @@ -29,7 +29,7 @@ deps = [testenv:codestyle] # pre-commit needs to clone Git repositories over https -passenv = http_proxy https_proxy no_proxy +passenv = http_proxy, https_proxy, no_proxy commands = pre-commit run --all-files deps = pre-commit