diff --git a/common/deployments/hub-staging.yaml b/common/deployments/hub-staging.yaml new file mode 100644 index 0000000000000000000000000000000000000000..4c75a56d4a019d503524b81cbd5d97efb03ba5a8 --- /dev/null +++ b/common/deployments/hub-staging.yaml @@ -0,0 +1,449 @@ +--- +proxy: + service: + type: NodePort + +ingress: + enabled: true + annotations: + kubernetes.io/ingress.class: "nginx" + kubernetes.io/tls-acme: "true" + hosts: + - "{{ notebooks_hostname }}" + tls: + - hosts: + - "{{ notebooks_hostname }}" + secretName: acme-tls-hub + +singleuser: + # keep resource limits in sync with: + # - profileList + storage: + type: none + extraVolumes: + - name: cvmfs-host + hostPath: + path: /cvmfs + type: Directory + - name: owncloud-home + empty_dir: + # - name: scratch + # ephemeral: + # volumeClaimTemplate: + # spec: + # accessModes: [ "ReadWriteOnce" ] + # storageClassName: local-path + # resources: + # requests: + # storage: "10Gi" + extraVolumeMounts: + - name: cvmfs-host + mountPath: "/cvmfs:shared" + - name: owncloud-home + mountPath: '/home/jovyan:shared' + # - name: scratch + # mountPath: '/scratch' + memory: + limit: 4G + guarantee: 512M + cpu: + limit: 2 + guarantee: .2 + defaultUrl: "/lab" + image: + name: eginotebooks/single-user + tag: "sha-b94a3ef" + profileList: + - display_name: Small Environment - 2 vCPU / 4 GB RAM + description: > + The notebook environment includes Python, R, Julia and Octave kernels. + default: true + kubespawner_override: + args: + - "--CondaKernelSpecManager.env_filter='/opt/conda$'" + extra_annotations: + "egi.eu/flavor": "small-environment-2-vcpu-4-gb-ram" + - display_name: Medium Environment - 4 vCPU / 8 GB RAM + description: > + The notebook environment includes Python, R, Julia and Octave kernels. + kubespawner_override: + args: + - "--CondaKernelSpecManager.env_filter='/opt/conda$'" + extra_annotations: + "egi.eu/flavor": "medium-environment-4-vcpu-8-gb-ram" + cpu_guarantee: 0.4 + cpu_limit: 4 + mem_guarantee: 1G + mem_limit: 8G + - display_name: Large Environment - 8 vCPU / 16 GB RAM / GPU + description: > + The notebook environment includes Python, R, Julia and Octave kernels with GPU. + kubespawner_override: + args: + - "--CondaKernelSpecManager.env_filter='/opt/conda$'" + cpu_guarantee: 0.8 + cpu_limit: 8 + mem_guarantee: 2G + mem_limit: 16G + extra_annotations: + "egi.eu/flavor": "large-environment-8-vcpu-16-gb-ram-gpu" + extra_resource_guarantees: + nvidia.com/gpu: 1 + extra_resource_limits: + nvidia.com/gpu: 1 + cmd: jupyterhub-singleuser-webdav-wrapper + extraFiles: + wait-remote-home.sh: + mode: 0755 + mountPath: /usr/local/bin/jupyterhub-wait-remote-home + stringData: |- + #! /bin/sh + i=0 + while ! grep '^webdav-fs: /home/jovyan ' /proc/mounts && test $i -lt 30; do + echo 'Waiting for ownClound mount...' + sleep 0.5 + i=$((i+1)) + done + singleuser-webdav-wrapper.sh: + mode: 0755 + mountPath: /usr/local/bin/jupyterhub-singleuser-webdav-wrapper + stringData: |- + #! /bin/sh + # + # Dirty hack to make remote mount on home directory working properly: + # + # 1) wait for webdav sidecar image to kick in + # 2) change directory to the mounted version of itself + # 3) launch notebook server + # + /usr/local/bin/jupyterhub-wait-remote-home + + cd . + + exec jupyterhub-singleuser \ + --FileCheckpoints.checkpoint_dir='/home/jovyan/.notebookCheckpoints' \ + --LabApp.news_url=None \ + "$@" + +hub: + services: + status: + url: "http://status-web/" + admin: true + jwt: + url: "http://jwt/" + display: false + # recommended to keep in sync with common/playbooks/files/jupyterhub-jwt.yaml + image: + name: eginotebooks/hub + tag: "sha-323c75e" + config: + Authenticator: + enable_auth_state: true + admin_users: + # valtri@civ.zcu.cz + - 94d3cde7-3121-4b33-b4c2-526c67e8cb38@eosc-federation.eu + allowed_groups: + - urn:geant:eosc-federation.eu:staging:group:eosc#staging.eosc-federation.eu + auto_login: true + claim_groups_key: "entitlements" + EGICheckinAuthenticator: + checkin_host: "{{ secret['checkin_host'] }}" + authorize_url: "https://{{ secret['checkin_host'] }}/OIDC/authorization" + token_url: "https://{{ secret['checkin_host'] }}/OIDC/token" + userdata_url: "https://{{ secret['checkin_host'] }}/OIDC/userinfo" + client_id: "{{ secret['client_id'] }}" + client_secret: "{{ secret['client_secret'] }}" + oauth_callback_url: "https://{{ notebooks_hostname }}/hub/oauth_callback" + openid_configuration_url: "https://proxy.testing.eosc-federation.eu/.well-known/openid-configuration" + scope: ["openid", "profile", "email", "offline_access", "entitlements"] + username_claim: "sub" + extra_authorize_params: + prompt: consent + JupyterHub: + admin_access: true + authenticate_prometheus: false + authenticator_class: egi_notebooks_hub.egiauthenticator.EOSCNodeAuthenticator + # spawner_class: (in egi-notebooks-b2drop) + LabApp: + check_for_updates_class: jupyterlab.NeverCheckForUpdate + extraConfig: + egi-notebooks-welcome: |- + from egi_notebooks_hub.welcome import WelcomeHandler + c.JupyterHub.default_url = "/welcome" + c.JupyterHub.extra_handlers = [(r'/welcome', WelcomeHandler)] + egi-notebooks-b2drop: |- +{%- raw %} + import base64 + import json + from jinja2 import BaseLoader + from jinja2 import Environment + from egi_notebooks_hub.onedata import OnedataSpawner + from kubernetes_asyncio.client.rest import ApiException + from tornado.httpclient import AsyncHTTPClient, HTTPClientError, HTTPRequest + + + class B2DropSpawner(OnedataSpawner): + async def auth_state_hook(self, spawner, auth_state): + await super().auth_state_hook(spawner, auth_state) + self.b2drop_ready = False + self.b2drop_user = "" + self.b2drop_pwd = "" + try: + secret = await self.api.read_namespaced_secret(self.token_secret_name, self.namespace) + except ApiException: + return + if secret and secret.data: + self.b2drop_user = base64.b64decode(secret.data.get("b2drop-user", "")).decode() + self.b2drop_pwd = base64.b64decode(secret.data.get("b2drop-pwd", "")).decode() + self.b2drop_ready = (self.b2drop_user and self.b2drop_pwd) + + def _render_options_form(self, profile_list): + # old:self._profile_list = self._init_profile_list(profile_list) + self._profile_list = self._get_initialized_profile_list(profile_list) + + profile_form_template = Environment(loader=BaseLoader).from_string( + self.profile_form_template + ) + return profile_form_template.render(profile_list=self._profile_list, b2drop_ready=self.b2drop_ready, b2drop_user=self.b2drop_user, b2drop_pwd=self.b2drop_pwd) + + async def pre_spawn_hook(self, spawner): + await super(B2DropSpawner, self).pre_spawn_hook(spawner) + b2drop_user = self.user_options.get("b2drop-user", "") + b2drop_pwd = self.user_options.get("b2drop-pwd", "") + if not (b2drop_user and b2drop_pwd): + secret = await self.api.read_namespaced_secret(self.token_secret_name, self.namespace) + if secret and secret.data: + b2drop_user = base64.b64decode(secret.data.get("b2drop-user", "")).decode() + b2drop_pwd = base64.b64decode(secret.data.get("b2drop-pwd", "")).decode() + if b2drop_user and b2drop_pwd: + volume_mounts = [ + {"mountPath": "/owncloud:shared", "name": "owncloud-home"}, + ] + spawner.extra_containers.append( + { + "name": "b2drop", + "image": "eginotebooks/webdav-rclone-sidecar:sha-0a62679", + "env": [ + {"name": "WEBDAV_URL", "value": "https://b2drop.eudat.eu/remote.php/webdav"}, + {"name": "WEBDAV_PWD", "value": b2drop_pwd}, + {"name": "WEBDAV_USER", "value": b2drop_user}, + {"name": "WEBDAV_VENDOR", "value": "other"}, + {"name": "MOUNT_PATH", "value": "/owncloud/b2drop"}, + {"name": "MOUNT_WAIT_POINT", "value": "webdav-fs: /owncloud fuse.rclone"}, + ], + "resources": self.sidecar_resources, + "securityContext": { + "runAsUser": 1000, + "fsUser": 1000, + "fsGroup": 100, + "privileged": True, + "capabilities": {"add": ["SYS_ADMIN"]}, + }, + "volumeMounts": volume_mounts, + } + ) + + def options_from_form(self, formdata): + data = super(B2DropSpawner, self)._options_from_form(formdata) + data.update({'b2drop-user': formdata.get('b2drop-user', [None])[0], + 'b2drop-pwd': formdata.get('b2drop-pwd', [None])[0]}) + return data + + + class WebDavOIDCSpawner(B2DropSpawner): + # ownCloud Infinite Scale parameters + # (https://owncloud.dev/apis/http/graph/spaces/#list-my-spaces-get-medrives) + OCIS_URL = "https://ocis-staging.apps.bst2-test.paas.psnc.pl" + # personal space + OCIS_PERSONAL_SPACE = "/graph/v1.0/me/drives?%24filter=driveType+eq+personal" + # shared space + OCIS_SHARED_WITH_ME = "/graph/v1.0/me/drives?%24filter=driveType+eq+virtual" + # otter spaces + OCIS_SPACES = "/graph/v1.0/me/drives?%24filter=driveType+eq+project" + + async def append_owncloud_sidecar(self, spawner, type, query, fallback_url=None, headers={}): + owncloud_url = fallback_url + http_client = AsyncHTTPClient() + req = HTTPRequest( + self.OCIS_URL + query, + headers=headers, + method="GET", + ) + try: + resp = await http_client.fetch(req) + body = json.loads(resp.body.decode("utf8", "replace")) + self.log.debug("OCIS response: %s", body) + if "value" in body: + ocis_infos = body["value"] + if len(ocis_infos) >= 1 and "root" in ocis_infos[0]: + owncloud_url = ocis_infos[0]["root"].get("webDavUrl", None) + except HTTPClientError as e: + self.log.error("can't query ownCloud: %s", e) + self.log.info("ownCloud %s URL: %s", type, owncloud_url) + + if owncloud_url is None: + return + + if type == "home": + subpath = "" + else: + subpath = "/" + type.capitalize() + env = [ + {"name": "WEBDAV_URL", "value": owncloud_url}, + {"name": "WEBDAV_VENDOR", "value": "owncloud"}, + # XXX: strict permissions needed for .local/share/jupyter/runtime/jupyter_cookie_secret + # quicker directory cache and polling + {"name": "MOUNT_OPTS", "value": "--file-perms=0600 --dir-perms=0770 --dir-cache-time=1m0s --poll-interval=0m20s"}, + {"name": "MOUNT_PATH", "value": "/owncloud" + subpath}, + # default mode is "full" + {"name": "VFS_CACHE_MODE", "value": "full"}, + ] + if type != "home": + env.append({"name": "MOUNT_WAIT_POINT", "value": "webdav-fs: /owncloud fuse.rclone"}) + volume_mounts = [ + {"mountPath": "/owncloud:shared", "name": "owncloud-home"}, + {"mountPath": self.token_mount_path, "name": self.token_secret_volume_name, "readOnly": True}, + ] + spawner.extra_containers.append( + { + "name": "owncloud-" + type, + "image": "eginotebooks/webdav-rclone-sidecar:sha-0a62679", + "args": ["bearer_token_command=cat " + self.token_path], + "env": env, + "resources": self.sidecar_resources, + "securityContext": { + "runAsUser": 1000, + "fsUser": 1000, + "fsGroup": 100, + "privileged": True, + "capabilities": {"add": ["SYS_ADMIN"]}, + }, + "volumeMounts": volume_mounts, + } + ) + + async def pre_spawn_hook(self, spawner): + await super(WebDavOIDCSpawner, self).pre_spawn_hook(spawner) + auth_state = await self.user.get_auth_state() + # volume name as in EGI spawner + self.token_secret_volume_name = self._expand_user_properties( + self.token_secret_volume_name_template + ) + self.token_path = os.path.join(self.token_mount_path, "access_token") + + if auth_state: + access_token = auth_state.get("access_token", None) + headers = { + "Accept": "application/json", + "User-Agent": "JupyterHub", + "Authorization": "Bearer %s" % access_token, + } + + await self.append_owncloud_sidecar(spawner, "home", self.OCIS_PERSONAL_SPACE, headers=headers) + await self.append_owncloud_sidecar(spawner, "shares", self.OCIS_SHARED_WITH_ME, headers=headers) + await self.append_owncloud_sidecar(spawner, "spaces", self.OCIS_SPACES, headers=headers) + else: + self.log.info("No auth state, skipping ownCloud") + + + c.JupyterHub.spawner_class = WebDavOIDCSpawner + c.B2DropSpawner.http_timeout = 90 + c.B2DropSpawner.profile_form_template = """ + <style> + /* + .profile divs holds two div tags: one for a radio button, and one + for the profile's content. + */ + #kubespawner-profiles-list .profile { + display: flex; + flex-direction: row; + font-weight: normal; + border-bottom: 1px solid #ccc; + padding-bottom: 12px; + } + + #kubespawner-profiles-list .profile .radio { + padding: 12px; + } + + /* .option divs holds a label and a select tag */ + #kubespawner-profiles-list .profile .option { + display: flex; + flex-direction: row; + align-items: center; + padding-bottom: 12px; + } + + #kubespawner-profiles-list .profile .option label { + font-weight: normal; + margin-right: 8px; + min-width: 96px; + } + </style> + + <div class='form-group' id='kubespawner-profiles-list'> + {%- for profile in profile_list %} + {#- Wrap everything in a <label> so clicking anywhere selects the option #} + <label for='profile-item-{{ profile.slug }}' class='profile'> + <div class='radio'> + <input type='radio' name='profile' id='profile-item-{{ profile.slug }}' value='{{ profile.slug }}' {% if profile.default %}checked{% endif %} /> + </div> + <div> + <h3>{{ profile.display_name }}</h3> + + {%- if profile.description %} + <p>{{ profile.description }}</p> + {%- endif %} + + {%- if profile.profile_options %} + <div> + {%- for k, option in profile.profile_options.items() %} + <div class='option'> + <label for='profile-option-{{profile.slug}}-{{k}}'>{{option.display_name}}</label> + <select name="profile-option-{{profile.slug}}-{{k}}" class="form-control"> + {%- for k, choice in option['choices'].items() %} + <option value="{{ k }}" {% if choice.default %}selected{%endif %}>{{ choice.display_name }}</option> + {%- endfor %} + </select> + </div> + {%- endfor %} + </div> + {%- endif %} + </div> + </label> + {%- endfor %} + <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> + <div class="panel panel-default"> + <div class="panel-heading" role="tab" id="headingOne"> + <h4 class="panel-title"> + <a class="collabpsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"> + B2DROP connection + </a> + {%if b2drop_ready %}<span class="label label-success">Already configured!</span>{% endif %} + </h4> + </div> + <div id="collapseOne" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne"> + <div class="panel-body"> + <div class='form-group'> + <label for="b2drop-user" class="form-label">B2DROP app Username</label> + <input type="text" class="form-control" name="b2drop-user" id="b2drop-user" aria-describedby="b2drop-user-help" value="{{ b2drop_user }}"> + <div id="b2drop-user-help" class="form-text">Create new app password at <a href="https://b2drop.eudat.eu/settings/user/security">B2DROP security configuration</a></div> + </div> + <div class='form-group'> + <label for="b2drop-pwd" class="form-label">B2DROP app Password</label> + <input type="password" class="form-control" name="b2drop-pwd" id="b2drop-pwd" value="{{ b2drop_pwd }}"> + </div> + </div> + </div> + </div> + </div> + """ +{% endraw %} + extraFiles: + welcome.html: + mountPath: /usr/local/share/jupyterhub/templates/welcome.html + stringData: |- +{%- raw %} + {% extends "login.html" %} +{% endraw %} diff --git a/staging1/deployments/hub.yaml b/staging1/deployments/hub.yaml deleted file mode 100644 index 4c75a56d4a019d503524b81cbd5d97efb03ba5a8..0000000000000000000000000000000000000000 --- a/staging1/deployments/hub.yaml +++ /dev/null @@ -1,449 +0,0 @@ ---- -proxy: - service: - type: NodePort - -ingress: - enabled: true - annotations: - kubernetes.io/ingress.class: "nginx" - kubernetes.io/tls-acme: "true" - hosts: - - "{{ notebooks_hostname }}" - tls: - - hosts: - - "{{ notebooks_hostname }}" - secretName: acme-tls-hub - -singleuser: - # keep resource limits in sync with: - # - profileList - storage: - type: none - extraVolumes: - - name: cvmfs-host - hostPath: - path: /cvmfs - type: Directory - - name: owncloud-home - empty_dir: - # - name: scratch - # ephemeral: - # volumeClaimTemplate: - # spec: - # accessModes: [ "ReadWriteOnce" ] - # storageClassName: local-path - # resources: - # requests: - # storage: "10Gi" - extraVolumeMounts: - - name: cvmfs-host - mountPath: "/cvmfs:shared" - - name: owncloud-home - mountPath: '/home/jovyan:shared' - # - name: scratch - # mountPath: '/scratch' - memory: - limit: 4G - guarantee: 512M - cpu: - limit: 2 - guarantee: .2 - defaultUrl: "/lab" - image: - name: eginotebooks/single-user - tag: "sha-b94a3ef" - profileList: - - display_name: Small Environment - 2 vCPU / 4 GB RAM - description: > - The notebook environment includes Python, R, Julia and Octave kernels. - default: true - kubespawner_override: - args: - - "--CondaKernelSpecManager.env_filter='/opt/conda$'" - extra_annotations: - "egi.eu/flavor": "small-environment-2-vcpu-4-gb-ram" - - display_name: Medium Environment - 4 vCPU / 8 GB RAM - description: > - The notebook environment includes Python, R, Julia and Octave kernels. - kubespawner_override: - args: - - "--CondaKernelSpecManager.env_filter='/opt/conda$'" - extra_annotations: - "egi.eu/flavor": "medium-environment-4-vcpu-8-gb-ram" - cpu_guarantee: 0.4 - cpu_limit: 4 - mem_guarantee: 1G - mem_limit: 8G - - display_name: Large Environment - 8 vCPU / 16 GB RAM / GPU - description: > - The notebook environment includes Python, R, Julia and Octave kernels with GPU. - kubespawner_override: - args: - - "--CondaKernelSpecManager.env_filter='/opt/conda$'" - cpu_guarantee: 0.8 - cpu_limit: 8 - mem_guarantee: 2G - mem_limit: 16G - extra_annotations: - "egi.eu/flavor": "large-environment-8-vcpu-16-gb-ram-gpu" - extra_resource_guarantees: - nvidia.com/gpu: 1 - extra_resource_limits: - nvidia.com/gpu: 1 - cmd: jupyterhub-singleuser-webdav-wrapper - extraFiles: - wait-remote-home.sh: - mode: 0755 - mountPath: /usr/local/bin/jupyterhub-wait-remote-home - stringData: |- - #! /bin/sh - i=0 - while ! grep '^webdav-fs: /home/jovyan ' /proc/mounts && test $i -lt 30; do - echo 'Waiting for ownClound mount...' - sleep 0.5 - i=$((i+1)) - done - singleuser-webdav-wrapper.sh: - mode: 0755 - mountPath: /usr/local/bin/jupyterhub-singleuser-webdav-wrapper - stringData: |- - #! /bin/sh - # - # Dirty hack to make remote mount on home directory working properly: - # - # 1) wait for webdav sidecar image to kick in - # 2) change directory to the mounted version of itself - # 3) launch notebook server - # - /usr/local/bin/jupyterhub-wait-remote-home - - cd . - - exec jupyterhub-singleuser \ - --FileCheckpoints.checkpoint_dir='/home/jovyan/.notebookCheckpoints' \ - --LabApp.news_url=None \ - "$@" - -hub: - services: - status: - url: "http://status-web/" - admin: true - jwt: - url: "http://jwt/" - display: false - # recommended to keep in sync with common/playbooks/files/jupyterhub-jwt.yaml - image: - name: eginotebooks/hub - tag: "sha-323c75e" - config: - Authenticator: - enable_auth_state: true - admin_users: - # valtri@civ.zcu.cz - - 94d3cde7-3121-4b33-b4c2-526c67e8cb38@eosc-federation.eu - allowed_groups: - - urn:geant:eosc-federation.eu:staging:group:eosc#staging.eosc-federation.eu - auto_login: true - claim_groups_key: "entitlements" - EGICheckinAuthenticator: - checkin_host: "{{ secret['checkin_host'] }}" - authorize_url: "https://{{ secret['checkin_host'] }}/OIDC/authorization" - token_url: "https://{{ secret['checkin_host'] }}/OIDC/token" - userdata_url: "https://{{ secret['checkin_host'] }}/OIDC/userinfo" - client_id: "{{ secret['client_id'] }}" - client_secret: "{{ secret['client_secret'] }}" - oauth_callback_url: "https://{{ notebooks_hostname }}/hub/oauth_callback" - openid_configuration_url: "https://proxy.testing.eosc-federation.eu/.well-known/openid-configuration" - scope: ["openid", "profile", "email", "offline_access", "entitlements"] - username_claim: "sub" - extra_authorize_params: - prompt: consent - JupyterHub: - admin_access: true - authenticate_prometheus: false - authenticator_class: egi_notebooks_hub.egiauthenticator.EOSCNodeAuthenticator - # spawner_class: (in egi-notebooks-b2drop) - LabApp: - check_for_updates_class: jupyterlab.NeverCheckForUpdate - extraConfig: - egi-notebooks-welcome: |- - from egi_notebooks_hub.welcome import WelcomeHandler - c.JupyterHub.default_url = "/welcome" - c.JupyterHub.extra_handlers = [(r'/welcome', WelcomeHandler)] - egi-notebooks-b2drop: |- -{%- raw %} - import base64 - import json - from jinja2 import BaseLoader - from jinja2 import Environment - from egi_notebooks_hub.onedata import OnedataSpawner - from kubernetes_asyncio.client.rest import ApiException - from tornado.httpclient import AsyncHTTPClient, HTTPClientError, HTTPRequest - - - class B2DropSpawner(OnedataSpawner): - async def auth_state_hook(self, spawner, auth_state): - await super().auth_state_hook(spawner, auth_state) - self.b2drop_ready = False - self.b2drop_user = "" - self.b2drop_pwd = "" - try: - secret = await self.api.read_namespaced_secret(self.token_secret_name, self.namespace) - except ApiException: - return - if secret and secret.data: - self.b2drop_user = base64.b64decode(secret.data.get("b2drop-user", "")).decode() - self.b2drop_pwd = base64.b64decode(secret.data.get("b2drop-pwd", "")).decode() - self.b2drop_ready = (self.b2drop_user and self.b2drop_pwd) - - def _render_options_form(self, profile_list): - # old:self._profile_list = self._init_profile_list(profile_list) - self._profile_list = self._get_initialized_profile_list(profile_list) - - profile_form_template = Environment(loader=BaseLoader).from_string( - self.profile_form_template - ) - return profile_form_template.render(profile_list=self._profile_list, b2drop_ready=self.b2drop_ready, b2drop_user=self.b2drop_user, b2drop_pwd=self.b2drop_pwd) - - async def pre_spawn_hook(self, spawner): - await super(B2DropSpawner, self).pre_spawn_hook(spawner) - b2drop_user = self.user_options.get("b2drop-user", "") - b2drop_pwd = self.user_options.get("b2drop-pwd", "") - if not (b2drop_user and b2drop_pwd): - secret = await self.api.read_namespaced_secret(self.token_secret_name, self.namespace) - if secret and secret.data: - b2drop_user = base64.b64decode(secret.data.get("b2drop-user", "")).decode() - b2drop_pwd = base64.b64decode(secret.data.get("b2drop-pwd", "")).decode() - if b2drop_user and b2drop_pwd: - volume_mounts = [ - {"mountPath": "/owncloud:shared", "name": "owncloud-home"}, - ] - spawner.extra_containers.append( - { - "name": "b2drop", - "image": "eginotebooks/webdav-rclone-sidecar:sha-0a62679", - "env": [ - {"name": "WEBDAV_URL", "value": "https://b2drop.eudat.eu/remote.php/webdav"}, - {"name": "WEBDAV_PWD", "value": b2drop_pwd}, - {"name": "WEBDAV_USER", "value": b2drop_user}, - {"name": "WEBDAV_VENDOR", "value": "other"}, - {"name": "MOUNT_PATH", "value": "/owncloud/b2drop"}, - {"name": "MOUNT_WAIT_POINT", "value": "webdav-fs: /owncloud fuse.rclone"}, - ], - "resources": self.sidecar_resources, - "securityContext": { - "runAsUser": 1000, - "fsUser": 1000, - "fsGroup": 100, - "privileged": True, - "capabilities": {"add": ["SYS_ADMIN"]}, - }, - "volumeMounts": volume_mounts, - } - ) - - def options_from_form(self, formdata): - data = super(B2DropSpawner, self)._options_from_form(formdata) - data.update({'b2drop-user': formdata.get('b2drop-user', [None])[0], - 'b2drop-pwd': formdata.get('b2drop-pwd', [None])[0]}) - return data - - - class WebDavOIDCSpawner(B2DropSpawner): - # ownCloud Infinite Scale parameters - # (https://owncloud.dev/apis/http/graph/spaces/#list-my-spaces-get-medrives) - OCIS_URL = "https://ocis-staging.apps.bst2-test.paas.psnc.pl" - # personal space - OCIS_PERSONAL_SPACE = "/graph/v1.0/me/drives?%24filter=driveType+eq+personal" - # shared space - OCIS_SHARED_WITH_ME = "/graph/v1.0/me/drives?%24filter=driveType+eq+virtual" - # otter spaces - OCIS_SPACES = "/graph/v1.0/me/drives?%24filter=driveType+eq+project" - - async def append_owncloud_sidecar(self, spawner, type, query, fallback_url=None, headers={}): - owncloud_url = fallback_url - http_client = AsyncHTTPClient() - req = HTTPRequest( - self.OCIS_URL + query, - headers=headers, - method="GET", - ) - try: - resp = await http_client.fetch(req) - body = json.loads(resp.body.decode("utf8", "replace")) - self.log.debug("OCIS response: %s", body) - if "value" in body: - ocis_infos = body["value"] - if len(ocis_infos) >= 1 and "root" in ocis_infos[0]: - owncloud_url = ocis_infos[0]["root"].get("webDavUrl", None) - except HTTPClientError as e: - self.log.error("can't query ownCloud: %s", e) - self.log.info("ownCloud %s URL: %s", type, owncloud_url) - - if owncloud_url is None: - return - - if type == "home": - subpath = "" - else: - subpath = "/" + type.capitalize() - env = [ - {"name": "WEBDAV_URL", "value": owncloud_url}, - {"name": "WEBDAV_VENDOR", "value": "owncloud"}, - # XXX: strict permissions needed for .local/share/jupyter/runtime/jupyter_cookie_secret - # quicker directory cache and polling - {"name": "MOUNT_OPTS", "value": "--file-perms=0600 --dir-perms=0770 --dir-cache-time=1m0s --poll-interval=0m20s"}, - {"name": "MOUNT_PATH", "value": "/owncloud" + subpath}, - # default mode is "full" - {"name": "VFS_CACHE_MODE", "value": "full"}, - ] - if type != "home": - env.append({"name": "MOUNT_WAIT_POINT", "value": "webdav-fs: /owncloud fuse.rclone"}) - volume_mounts = [ - {"mountPath": "/owncloud:shared", "name": "owncloud-home"}, - {"mountPath": self.token_mount_path, "name": self.token_secret_volume_name, "readOnly": True}, - ] - spawner.extra_containers.append( - { - "name": "owncloud-" + type, - "image": "eginotebooks/webdav-rclone-sidecar:sha-0a62679", - "args": ["bearer_token_command=cat " + self.token_path], - "env": env, - "resources": self.sidecar_resources, - "securityContext": { - "runAsUser": 1000, - "fsUser": 1000, - "fsGroup": 100, - "privileged": True, - "capabilities": {"add": ["SYS_ADMIN"]}, - }, - "volumeMounts": volume_mounts, - } - ) - - async def pre_spawn_hook(self, spawner): - await super(WebDavOIDCSpawner, self).pre_spawn_hook(spawner) - auth_state = await self.user.get_auth_state() - # volume name as in EGI spawner - self.token_secret_volume_name = self._expand_user_properties( - self.token_secret_volume_name_template - ) - self.token_path = os.path.join(self.token_mount_path, "access_token") - - if auth_state: - access_token = auth_state.get("access_token", None) - headers = { - "Accept": "application/json", - "User-Agent": "JupyterHub", - "Authorization": "Bearer %s" % access_token, - } - - await self.append_owncloud_sidecar(spawner, "home", self.OCIS_PERSONAL_SPACE, headers=headers) - await self.append_owncloud_sidecar(spawner, "shares", self.OCIS_SHARED_WITH_ME, headers=headers) - await self.append_owncloud_sidecar(spawner, "spaces", self.OCIS_SPACES, headers=headers) - else: - self.log.info("No auth state, skipping ownCloud") - - - c.JupyterHub.spawner_class = WebDavOIDCSpawner - c.B2DropSpawner.http_timeout = 90 - c.B2DropSpawner.profile_form_template = """ - <style> - /* - .profile divs holds two div tags: one for a radio button, and one - for the profile's content. - */ - #kubespawner-profiles-list .profile { - display: flex; - flex-direction: row; - font-weight: normal; - border-bottom: 1px solid #ccc; - padding-bottom: 12px; - } - - #kubespawner-profiles-list .profile .radio { - padding: 12px; - } - - /* .option divs holds a label and a select tag */ - #kubespawner-profiles-list .profile .option { - display: flex; - flex-direction: row; - align-items: center; - padding-bottom: 12px; - } - - #kubespawner-profiles-list .profile .option label { - font-weight: normal; - margin-right: 8px; - min-width: 96px; - } - </style> - - <div class='form-group' id='kubespawner-profiles-list'> - {%- for profile in profile_list %} - {#- Wrap everything in a <label> so clicking anywhere selects the option #} - <label for='profile-item-{{ profile.slug }}' class='profile'> - <div class='radio'> - <input type='radio' name='profile' id='profile-item-{{ profile.slug }}' value='{{ profile.slug }}' {% if profile.default %}checked{% endif %} /> - </div> - <div> - <h3>{{ profile.display_name }}</h3> - - {%- if profile.description %} - <p>{{ profile.description }}</p> - {%- endif %} - - {%- if profile.profile_options %} - <div> - {%- for k, option in profile.profile_options.items() %} - <div class='option'> - <label for='profile-option-{{profile.slug}}-{{k}}'>{{option.display_name}}</label> - <select name="profile-option-{{profile.slug}}-{{k}}" class="form-control"> - {%- for k, choice in option['choices'].items() %} - <option value="{{ k }}" {% if choice.default %}selected{%endif %}>{{ choice.display_name }}</option> - {%- endfor %} - </select> - </div> - {%- endfor %} - </div> - {%- endif %} - </div> - </label> - {%- endfor %} - <div class="panel-group" id="accordion" role="tablist" aria-multiselectable="true"> - <div class="panel panel-default"> - <div class="panel-heading" role="tab" id="headingOne"> - <h4 class="panel-title"> - <a class="collabpsed" role="button" data-toggle="collapse" data-parent="#accordion" href="#collapseOne" aria-expanded="false" aria-controls="collapseOne"> - B2DROP connection - </a> - {%if b2drop_ready %}<span class="label label-success">Already configured!</span>{% endif %} - </h4> - </div> - <div id="collapseOne" class="panel-collapse collapse" role="tabpanel" aria-labelledby="headingOne"> - <div class="panel-body"> - <div class='form-group'> - <label for="b2drop-user" class="form-label">B2DROP app Username</label> - <input type="text" class="form-control" name="b2drop-user" id="b2drop-user" aria-describedby="b2drop-user-help" value="{{ b2drop_user }}"> - <div id="b2drop-user-help" class="form-text">Create new app password at <a href="https://b2drop.eudat.eu/settings/user/security">B2DROP security configuration</a></div> - </div> - <div class='form-group'> - <label for="b2drop-pwd" class="form-label">B2DROP app Password</label> - <input type="password" class="form-control" name="b2drop-pwd" id="b2drop-pwd" value="{{ b2drop_pwd }}"> - </div> - </div> - </div> - </div> - </div> - """ -{% endraw %} - extraFiles: - welcome.html: - mountPath: /usr/local/share/jupyterhub/templates/welcome.html - stringData: |- -{%- raw %} - {% extends "login.html" %} -{% endraw %} diff --git a/staging1/deployments/hub.yaml b/staging1/deployments/hub.yaml new file mode 120000 index 0000000000000000000000000000000000000000..3fda46201a3ef90140fba6b49d1b2415cc3c3481 --- /dev/null +++ b/staging1/deployments/hub.yaml @@ -0,0 +1 @@ +/home/valtri/notebooks-operations.eosc/common/deployments/hub-staging.yaml \ No newline at end of file diff --git a/staging2/deployments/hub.yaml b/staging2/deployments/hub.yaml new file mode 120000 index 0000000000000000000000000000000000000000..3fda46201a3ef90140fba6b49d1b2415cc3c3481 --- /dev/null +++ b/staging2/deployments/hub.yaml @@ -0,0 +1 @@ +/home/valtri/notebooks-operations.eosc/common/deployments/hub-staging.yaml \ No newline at end of file diff --git a/staging2/inventory/1-safespring.yaml b/staging2/inventory/1-safespring.yaml new file mode 100644 index 0000000000000000000000000000000000000000..6d6bc5706d721fdbbf919cf947a9ad93c96b83b2 --- /dev/null +++ b/staging2/inventory/1-safespring.yaml @@ -0,0 +1,31 @@ +--- +fip: + hosts: + 89.47.191.176 + +master: + hosts: + 2001:6b0:7d:40::80: + # must be IPv4 address or hostname + kube_server: 89.47.191.61 + +ingress: + hosts: + 2001:6b0:7d:40::12: + +nfs: + hosts: + 2001:6b0:7d:40::17c: + +worker: + hosts: + 2001:6b0:7d:40::81: + +gpu: + hosts: + +# using public IP of kube_server for ansible delegate_to +kube_server: + hosts: + 89.47.191.61: + ansible_host: 2001:6b0:7d:40::80 diff --git a/staging2/inventory/99-all.yaml b/staging2/inventory/99-all.yaml index 75f4d04a23df302db193d79b618a1e9d880be7cd..3c59b9ce122b7c720fd13616212dc812e49a69e3 100644 --- a/staging2/inventory/99-all.yaml +++ b/staging2/inventory/99-all.yaml @@ -11,7 +11,8 @@ all: ansible_become: yes ansible_user: egi - site_name: safespring-staging + mail_local: true + site_name: safespring-staging2 vault_mount_point: secrets/users/e1662e20-e34b-468c-b0ce-d899bc878364@egi.eu/eosc-staging notebooks_hostname: notebooks-stg2.cloud.cesnet.cz diff --git a/staging2/playbooks/cvmfs.yaml b/staging2/playbooks/cvmfs.yaml new file mode 120000 index 0000000000000000000000000000000000000000..2e82cca6c387556209aad5ff66ea41eba8f28082 --- /dev/null +++ b/staging2/playbooks/cvmfs.yaml @@ -0,0 +1 @@ +../../common/playbooks/cvmfs.yaml \ No newline at end of file diff --git a/staging2/playbooks/files/calico.yaml b/staging2/playbooks/files/calico.yaml new file mode 120000 index 0000000000000000000000000000000000000000..732c864b90be9b3f8aaefe227ad0da7a7685b763 --- /dev/null +++ b/staging2/playbooks/files/calico.yaml @@ -0,0 +1 @@ +../../../common/playbooks/files/calico.yaml \ No newline at end of file diff --git a/staging2/playbooks/files/etc b/staging2/playbooks/files/etc new file mode 120000 index 0000000000000000000000000000000000000000..ed53b8742792e16bb4bae2ed49d02c79d79de146 --- /dev/null +++ b/staging2/playbooks/files/etc @@ -0,0 +1 @@ +../../../common/playbooks/files/etc \ No newline at end of file diff --git a/staging2/playbooks/files/jupyterhub-jwt.yaml b/staging2/playbooks/files/jupyterhub-jwt.yaml new file mode 120000 index 0000000000000000000000000000000000000000..59f9ac23b488d899df25d4ea582405ec6998d793 --- /dev/null +++ b/staging2/playbooks/files/jupyterhub-jwt.yaml @@ -0,0 +1 @@ +../../../common/playbooks/files/jupyterhub-jwt.yaml \ No newline at end of file diff --git a/staging2/playbooks/files/usr b/staging2/playbooks/files/usr new file mode 120000 index 0000000000000000000000000000000000000000..b034223ec617fbae8cd736f29cf1c61394fa7ebd --- /dev/null +++ b/staging2/playbooks/files/usr @@ -0,0 +1 @@ +../../../common/playbooks/files/usr \ No newline at end of file diff --git a/staging2/playbooks/k8s.yaml b/staging2/playbooks/k8s.yaml new file mode 120000 index 0000000000000000000000000000000000000000..117aed694c9e6d26908aec62da2dc03cdbbacc7d --- /dev/null +++ b/staging2/playbooks/k8s.yaml @@ -0,0 +1 @@ +../../common/playbooks/k8s.yaml \ No newline at end of file diff --git a/staging2/playbooks/notebooks.yaml b/staging2/playbooks/notebooks.yaml new file mode 120000 index 0000000000000000000000000000000000000000..3f1a33f7bdd6b5a2381afcd25e41f8a051fde965 --- /dev/null +++ b/staging2/playbooks/notebooks.yaml @@ -0,0 +1 @@ +../../common/playbooks/notebooks.yaml \ No newline at end of file diff --git a/staging2/playbooks/public_keys b/staging2/playbooks/public_keys new file mode 120000 index 0000000000000000000000000000000000000000..6ef4918a9eb6aba6c6076f8e4d42570f35735d86 --- /dev/null +++ b/staging2/playbooks/public_keys @@ -0,0 +1 @@ +../../common/playbooks/public_keys \ No newline at end of file diff --git a/staging2/playbooks/squid.yaml b/staging2/playbooks/squid.yaml new file mode 120000 index 0000000000000000000000000000000000000000..114c327c31c35f2362f124c93f50c31d9e28b589 --- /dev/null +++ b/staging2/playbooks/squid.yaml @@ -0,0 +1 @@ +../../common/playbooks/squid.yaml \ No newline at end of file diff --git a/staging2/playbooks/templates/etc/exports b/staging2/playbooks/templates/etc/exports new file mode 120000 index 0000000000000000000000000000000000000000..3ef288e7f957972605d82cccffdb528b3992e2d7 --- /dev/null +++ b/staging2/playbooks/templates/etc/exports @@ -0,0 +1 @@ +../../../../common/playbooks/templates/etc/exports.ipv46 \ No newline at end of file diff --git a/staging2/playbooks/templates/etc/mailutils.conf b/staging2/playbooks/templates/etc/mailutils.conf new file mode 120000 index 0000000000000000000000000000000000000000..dbd8a1f31599952201aa0ce602fc80b047c07c16 --- /dev/null +++ b/staging2/playbooks/templates/etc/mailutils.conf @@ -0,0 +1 @@ +../../../../common/playbooks/templates/etc/mailutils.conf \ No newline at end of file diff --git a/staging2/playbooks/templates/etc/squid b/staging2/playbooks/templates/etc/squid new file mode 120000 index 0000000000000000000000000000000000000000..352b5984beddb28543e7e6e2a84e5d5152a155ec --- /dev/null +++ b/staging2/playbooks/templates/etc/squid @@ -0,0 +1 @@ +../../../../common/playbooks/templates/etc/squid \ No newline at end of file diff --git a/staging2/playbooks/upgrade.yaml b/staging2/playbooks/upgrade.yaml new file mode 120000 index 0000000000000000000000000000000000000000..0f9e3f4182cd474bfd384ba28fb5166eaeed8110 --- /dev/null +++ b/staging2/playbooks/upgrade.yaml @@ -0,0 +1 @@ +../../common/playbooks/upgrade.yaml \ No newline at end of file diff --git a/staging2/terraform/terraform.tfvars b/staging2/terraform/terraform.tfvars index 41038d01cc206113bc167c16ea5121cd54bd8ebb..fbd4191efd03608116030fd5c273124ec20ffcf1 100644 --- a/staging2/terraform/terraform.tfvars +++ b/staging2/terraform/terraform.tfvars @@ -6,7 +6,7 @@ site_name = "staging2" # These may need some adjustment for your provider master_flavor_name = "l2.c4r8.100" -worker_flavor_name = "l2.c8r16.100" +worker_flavor_name = "l2.c16r32.100" # XXX: replace this for GPU flavor, once available gpu_flavor_name = "l2.c2r4.100" @@ -20,10 +20,10 @@ gpu_workers = 0 docker_volumes_size = 384 # NFS volume -nfs_volume_size = 256 +nfs_volume_size = 200 # scratch volume -scratch_volumes_size = 128 +scratch_volumes_size = 0 # squid volume squid_volume_size = 128 diff --git a/staging2/terraform/vms.tf b/staging2/terraform/vms.tf index 94976b011edfd02226f022656e166d356bcad31e..d46b46e93b250c14fc18931c3b80ad4e1cfb1db5 100644 --- a/staging2/terraform/vms.tf +++ b/staging2/terraform/vms.tf @@ -42,7 +42,7 @@ resource "openstack_networking_secgroup_rule_v2" "ping6" { ethertype = "IPv6" port_range_min = 128 port_range_max = 0 - protocol = "icmp" + protocol = "icmp" # icmp / ipv6-icmp remote_ip_prefix = "::/0" security_group_id = openstack_networking_secgroup_v2.ping.id }