diff --git a/.gitignore b/.gitignore new file mode 100644 index 00000000..4c0deeb5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +.Python +*.crt diff --git a/README.md b/README.md index 724b0c21..95f1abef 100644 --- a/README.md +++ b/README.md @@ -1,10 +1,45 @@ -# {{ project_name }} +# PaddlePaddle Cloud ## Getting Started +### Pre-Requirements +- PaddlePaddle Cloud needs python to support `OPENSSL 1.2`. To check it out, simply run: + ```python + >>> import ssl + >>> ssl.OPENSSL_VERSION + 'OpenSSL 1.0.2k 26 Jan 2017' + ``` +- Make sure you have `Python > 2.7.10` installed. + +### Run on kubernetes +```bash +# build docker image +git clone https://github.com/PaddlePaddle/cloud.git +cd cloud/paddlecloud +docker build -t [your_docker_registry]/pcloud . +docker push [your_docker_registry]/pcloud +# submit to kubernetes +kubectl create -f ./k8s +``` + +To test or visit the web site, find out the kubernetes [ingress](https://kubernetes.io/docs/concepts/services-networking/ingress/) ip addresses, and bind it to your `/etc/hosts` file: +``` +# your ingress ip address +192.168.1.100 cloud.paddlepaddle.org +``` + +Then open your browser and visit http://cloud.paddlepaddle.org. + +### Run locally Make sure you are using a virtual environment of some sort (e.g. `virtualenv` or `pyenv`). +``` +virtualenv paddlecloudenv +# enable the virtualenv +source paddlecloudenv/bin/activate +``` +To run for the first time, you need to: ``` npm install pip install -r requirements.txt @@ -13,4 +48,10 @@ pip install -r requirements.txt npm run dev ``` -Browse to http://localhost:3000/ +Browse to http://localhost:8000/ + +If you are starting the server for the second time, just run: +``` +./manage.py runserver +``` + diff --git a/paddlecloud/Dockerfile b/paddlecloud/Dockerfile new file mode 100644 index 00000000..eff2cb3c --- /dev/null +++ b/paddlecloud/Dockerfile @@ -0,0 +1,8 @@ +FROM python:2.7.13-alpine +RUN apk add --update nodejs openssl + +ADD requirements.txt package.json /pcloud/ +RUN cd /pcloud && npm install && pip install -r requirements.txt && npm build +WORKDIR /pcloud + +CMD ["sh", "-c", "./manage.py migrate; ./manage.py loaddata sites; npm run dev"] diff --git a/paddlecloud/ca.crt b/paddlecloud/ca.crt deleted file mode 100644 index 428f5f43..00000000 --- a/paddlecloud/ca.crt +++ /dev/null @@ -1,18 +0,0 @@ ------BEGIN CERTIFICATE----- -MIIC5zCCAc+gAwIBAgIBATANBgkqhkiG9w0BAQsFADAVMRMwEQYDVQQDEwptaW5p -a3ViZUNBMB4XDTE3MDQxODA2MDMxNFoXDTI3MDQxNjA2MDMxNFowFTETMBEGA1UE -AxMKbWluaWt1YmVDQTCCASIwDQYJKoZIhvcNAQEBBQADggEPADCCAQoCggEBAKyM -dvvKvO9q/qkiXhb0cBwyvfW3/wCVttmjjnysA0+eeza/7FVYoHXK8MZBP748Iden -T+DocKxxiyJ8wpa4DLJlbgMOWfWinZvZHeILmzblq/6rcLYe28TdkwUCgkJBhJaH -jcPYg6flYom2NbS2tVxajt3dZiCl6HUF0TTOOnULHK5+ATRu+DaQSKmBW9Ro0SM5 -UDr2Q7gBXcql5dsJDRiqWA2tzn3/GqSTcXT5IAA5GXs3cN1yvxSH/PbNFyx8QrK0 -YDzRzinvzQcrTJcLDV52Oxgo/bkWBqvtZYON4ATxB1gRvKvHMK7hcmQdfXv/nqh5 -IRQOdA35RmleofmfPyUCAwEAAaNCMEAwDgYDVR0PAQH/BAQDAgKkMB0GA1UdJQQW -MBQGCCsGAQUFBwMCBggrBgEFBQcDATAPBgNVHRMBAf8EBTADAQH/MA0GCSqGSIb3 -DQEBCwUAA4IBAQCcfTBVHvVDkImrx5blMqrMH7OMYYQlzZiXaliTgwMBKgSs7zGV -dtYokFRVwHnniK677MU2BQ3DBrQq9SxwdVG4DvuMuOjmfuJWtSIgnmvBXQlKqh+Y -eyPgLAZTjsun+iFHye4KhcmE01A42Gp//zBMUhX9SC48QtsFLG94H/DNcy/zO7Qc -NAlNmazHv27cy4am9Cd/rWSTt3t3NkuDEYM/LW11mIdR8P1MbbsTCQyI2ayKNccU -0FplEqsjFQIGrW00vMPqKIvNK3Z2l1lR9O5s/U8p3k05aVocpMbRZb2A+TxnQcDp -s8fv8SswDNmK1fXxQ7o2DFWM5Ck1jW7ighph ------END CERTIFICATE----- diff --git a/paddlecloud/k8s/cloud_deployment.yaml b/paddlecloud/k8s/cloud_deployment.yaml new file mode 100644 index 00000000..77af650d --- /dev/null +++ b/paddlecloud/k8s/cloud_deployment.yaml @@ -0,0 +1,24 @@ +apiVersion: extensions/v1beta1 +kind: Deployment +metadata: + name: paddle-cloud +spec: + replicas: 1 + template: + metadata: + labels: + app: paddle-cloud + spec: + volumes: + - name: pcloud-volume + hostPath: + path: "/Users/wuyi/go/src/github.com/typhoonzero/PaddleCloud/paddlecloud" + containers: + - name: paddle-cloud + imagePullPolicy: Always + volumeMounts: + - mountPath: /pcloud + name: pcloud-volume + image: docker.paddlepaddlehub.com/pcloud:latest + ports: + - containerPort: 8000 diff --git a/paddlecloud/k8s/cloud_ingress.yaml b/paddlecloud/k8s/cloud_ingress.yaml new file mode 100644 index 00000000..bfe8ae7e --- /dev/null +++ b/paddlecloud/k8s/cloud_ingress.yaml @@ -0,0 +1,13 @@ +apiVersion: extensions/v1beta1 +kind: Ingress +metadata: + name: paddle-cloud-ingress +spec: + rules: + - host: cloud.paddlepaddle.org + http: + paths: + - path: / + backend: + serviceName: paddle-cloud-service + servicePort: 8000 diff --git a/paddlecloud/k8s/cloud_service.yaml b/paddlecloud/k8s/cloud_service.yaml new file mode 100644 index 00000000..a0192013 --- /dev/null +++ b/paddlecloud/k8s/cloud_service.yaml @@ -0,0 +1,11 @@ +kind: Service +apiVersion: v1 +metadata: + name: paddle-cloud-service +spec: + selector: + app: paddle-cloud + ports: + - protocol: TCP + port: 8000 + targetPort: 8000 diff --git a/paddlecloud/nginx.conf b/paddlecloud/nginx.conf index 77adb977..a416b864 100644 --- a/paddlecloud/nginx.conf +++ b/paddlecloud/nginx.conf @@ -37,12 +37,20 @@ server { ssl_protocols TLSv1 TLSv1.1 TLSv1.2; ssl_ciphers HIGH:!aNULL:!MD5; + location ~ ^/notebook/([a-z0-9]+) { + proxy_redirect off; + proxy_set_header Host $host; + proxy_set_header X-Real-IP $remote_addr; + proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; + proxy_pass http://192.168.64.2:80; + } + location / { proxy_redirect off; proxy_set_header Host $host; proxy_set_header X-Real-IP $remote_addr; proxy_set_header X-Forwarded-For $proxy_add_x_forwarded_for; - proxy_pass http://172.20.166.33:8000/; + proxy_pass http://172.20.166.33:8000; } } diff --git a/paddlecloud/notebook/frame_middleware.py b/paddlecloud/notebook/frame_middleware.py new file mode 100644 index 00000000..362d6f29 --- /dev/null +++ b/paddlecloud/notebook/frame_middleware.py @@ -0,0 +1,10 @@ +class NotebookMiddleware: + + def __init__(self, get_response): + self.get_response = get_response + + def __call__(self, request): + response = self.get_response(request) + response["Content-Security-Policy"] = "frame-ancestors 'self' http://notebook.paddlepaddle.org" + response["X-Frame-Options"] = "ALLOW-FROM http://notebook.paddlepaddle.org" + return response diff --git a/paddlecloud/notebook/templates/notebook.html b/paddlecloud/notebook/templates/notebook.html index ec073128..5e2408df 100644 --- a/paddlecloud/notebook/templates/notebook.html +++ b/paddlecloud/notebook/templates/notebook.html @@ -7,9 +7,16 @@ {% block body_class %}home{% endblock %} {% block body_base %} -
- -
- {{ notebook_id }} + + +
+ +
{% endblock %} diff --git a/paddlecloud/notebook/tls.py b/paddlecloud/notebook/tls.py index ad738cdb..5322362f 100644 --- a/paddlecloud/notebook/tls.py +++ b/paddlecloud/notebook/tls.py @@ -1,5 +1,6 @@ import subprocess import os +from django.conf import settings def __check_cert_requirements__(program): def is_exe(fpath): @@ -29,13 +30,13 @@ def create_user_cert(ca_path, username): user_cert_cmds = [] user_cert_dir = os.path.join(settings.USER_CERTS_PATH, username) user_cert_cmds.append("mkdir -p %s" % user_cert_dir) - user_cert_cmd.append("openssl genrsa -out \ + user_cert_cmds.append("openssl genrsa -out \ %s/%s-key.pem 2048"%(user_cert_dir, username)) - user_cert_cmd.append("openssl req -new -key %s/%s-key.pem -out\ + user_cert_cmds.append("openssl req -new -key %s/%s-key.pem -out\ %s/%s.csr -subj \"/CN=%s\""%\ (user_cert_dir, username, user_cert_dir, username, username)) - user_cert_cmd.append("openssl x509 -req -in %s/%s.csr -CA %s -CAkey %s \ + user_cert_cmds.append("openssl x509 -req -in %s/%s.csr -CA %s -CAkey %s \ -CAcreateserial -out %s/%s.pem -days 365"% \ (user_cert_dir, username, settings.CA_PATH, settings.CA_KEY_PATH, diff --git a/paddlecloud/notebook/utils.py b/paddlecloud/notebook/utils.py new file mode 100644 index 00000000..a34f74db --- /dev/null +++ b/paddlecloud/notebook/utils.py @@ -0,0 +1,188 @@ +from django.conf import settings +import os +import kubernetes +import hashlib +import copy + +def email_escape(email): + """ + escape email to a safe string of kubernetes namespace + """ + safe_email = email.replace("@", "-") + safe_email = safe_email.replace(".", "-") + safe_email = safe_email.replace("_", "-") + return safe_email + + +def get_user_api_client(username): + """ + update kubernetes client to use current logined user's crednetials + """ + + conf_obj = kubernetes.client.Configuration() + conf_obj.host = settings.K8S_HOST + conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) + conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, "%s.pem"%username) + conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, "%s-key.pem"%username) + #kubernetes.config.load_kube_config(client_configuration=conf_obj) + api_client = kubernetes.client.ApiClient(config=conf_obj) + return api_client + +# a class for creating jupyter notebook resources +class UserNotebook(): + dep_body = { + "apiVersion": "extensions/v1beta1", + "kind": "Deployment", + "metadata": { + "name": "paddle-book-deployment" + }, + "spec": { + "replicas": 1, + "template": { + "metadata": { + "labels": { + "app": "paddle-book" + } + }, + "spec": { + "containers": [{ + "name": "paddle-book", + "image": settings.PADDLE_BOOK_IMAGE, + "command": ["sh", "-c", + "mkdir -p /root/.jupyter; echo \"c.NotebookApp.base_url = '/notebook/%s'\" > /root/.jupyter/jupyter_notebook_config.py; jupyter notebook --debug --ip=0.0.0.0 --no-browser --allow-root --NotebookApp.token='' --NotebookApp.disable_check_xsrf=True /book/"], + "ports": [{ + "containerPort": settings.PADDLE_BOOK_PORT + }] + }] + } + } + } + } + service_body = { + "apiVersion": "v1", + "kind": "Service", + "metadata": { + "name": "paddle-book-service" + }, + "spec": { + "selector": { + "app": "paddle-book" + }, + "ports": [{ + "protocol": "TCP", + "port": 8888, + "targetPort": 8888 + }] + } + } + ing_body = { + "apiVersion": "extensions/v1beta1", + "kind": "Ingress", + "metadata": { + "name": "paddle-book-ingress" + }, + "spec": { + "rules": [{ + "host": "cloud.paddlepaddle.org", + "http": { + "paths": [{ + "path": "/", + "backend": { + "serviceName": "paddle-book-service", + "servicePort": 8888 + } + }, + ] + } + }] + } + } + def get_notebook_id(self, username): + # notebook id is md5(username) + m = hashlib.md5() + m.update(username) + + return m.hexdigest()[:8] + + def __wait_api_response(self, resp): + print resp.status + + def __find_item(self, resource_list, match_name): + item_found = False + for item in resource_list.items: + if item.metadata.name == match_name: + item_found = True + return item_found + + def __create_deployment(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) + dep_list = v1beta1api.list_namespaced_deployment(namespace) + if not self.__find_item(dep_list, "paddle-book-deployment"): + self.dep_body["spec"]["template"]["spec"]["containers"][0]["command"][2] = \ + self.dep_body["spec"]["template"]["spec"]["containers"][0]["command"][2] % self.get_notebook_id(username) + resp = v1beta1api.create_namespaced_deployment(namespace, body=self.dep_body, pretty=True) + self.__wait_api_response(resp) + + def __create_service(self, username, namespace): + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) + service_list = v1api.list_namespaced_service(namespace) + if not self.__find_item(service_list, "paddle-book-service"): + resp = v1api.create_namespaced_service(namespace, body=self.service_body) + self.__wait_api_response(resp) + + # service for notebook websocket + service_ws = copy.deepcopy(self.service_body) + service_ws["metadata"]["name"] = "paddle-book-service-ws" + if not self.__find_item(service_list, "paddle-book-service-ws"): + resp = v1api.create_namespaced_service(namespace, body=service_ws) + self.__wait_api_response(resp) + + def __create_ingress(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) + ing_list = v1beta1api.list_namespaced_ingress(namespace) + if not self.__find_item(ing_list, "paddle-book-ingress"): + # FIXME: must split this for different users + self.ing_body["spec"]["rules"][0]["http"]["paths"][0]["path"] = "/notebook/" + self.get_notebook_id(username) + #self.ing_body["spec"]["rules"][0]["http"]["paths"][1]["path"] = "/notebook/" + self.get_notebook_id(username) + "/api/kernels/*" + resp = v1beta1api.create_namespaced_ingress(namespace, body=self.ing_body) + self.__wait_api_response(resp) + + def start_all(self, username, namespace): + """ + start deployment, service, ingress to start a notebook service for current user + """ + self.__create_deployment(username, namespace) + self.__create_service(username, namespace) + self.__create_ingress(username, namespace) + + def stop_all(self, username, namespace): + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) + v1beta1api.delete_namespaced_deployment("paddle-book-deployment", namespace) + v1beta1api.delete_namespaced_ingress("paddle-book-ingress", namespace) + v1api.delete_namespaced_service("paddle-book-service", namespace) + + def status(self, username, namespace): + """ + check notebook deployment status + @return: running starting stopped + """ + v1api = kubernetes.client.CoreV1Api(api_client=get_user_api_client(username)) + v1beta1api = kubernetes.client.ExtensionsV1beta1Api(api_client=get_user_api_client(username)) + d, s, i = (True, True, True) + dep_list = v1beta1api.list_namespaced_deployment(namespace) + if not self.__find_item(dep_list, "paddle-book-deployment"): + d = False + service_list = v1api.list_namespaced_service(namespace) + if not self.__find_item(service_list, "paddle-book-service"): + s = False + ing_list = v1beta1api.list_namespaced_ingress(namespace) + if not self.__find_item(ing_list, "paddle-book-ingress"): + i = False + + if d and s and i: + return "running" + elif d or s or i: + return "starting" + else: + return "stopped" diff --git a/paddlecloud/notebook/views.py b/paddlecloud/notebook/views.py index 4dc4c21a..b0145c69 100644 --- a/paddlecloud/notebook/views.py +++ b/paddlecloud/notebook/views.py @@ -19,6 +19,7 @@ import logging import hashlib import kubernetes +import utils @receiver(post_save, sender=User) def handle_user_save(sender, instance, created, **kwargs): @@ -35,11 +36,11 @@ class SignupView(account.views.SignupView): def after_signup(self, form): self.update_profile(form) - try: - logging.info("creating default user certs...") - tls.create_user_cert(settings.CA_PATH, form.get_user()) - except Exception, e: - logging.error("create user certs error: %s", str(e)) + #try: + logging.info("creating default user certs...") + tls.create_user_cert(settings.CA_PATH, form.cleaned_data["email"]) + #except Exception, e: + # logging.error("create user certs error: %s", str(e)) super(SignupView, self).after_signup(form) @@ -57,125 +58,6 @@ def generate_username(self, form): username = form.cleaned_data["email"] return username -# a class for creating jupyter notebook resources -class UserNotebook(): - dep_body = { - "apiVersion": "extensions/v1beta1", - "kind": "Deployment", - "metadata": { - "name": "paddle-book-deployment" - }, - "spec": { - "replicas": 1, - "template": { - "metadata": { - "labels": { - "app": "paddle-book" - } - }, - "spec": { - "containers": [{ - "name": "paddle-book", - "image": settings.PADDLE_BOOK_IMAGE, - "ports": [{ - "containerPort": settings.PADDLE_BOOK_PORT - }] - }] - } - } - } - } - service_body = { - "apiVersion": "v1", - "kind": "Service", - "metadata": { - "name": "paddle-book-service" - }, - "spec": { - "selector": { - "app": "paddle-book" - }, - "ports": [{ - "protocol": "TCP", - "port": 8888, - "targetPort": 8888 - }] - } - } - ing_body = { - "apiVersion": "extensions/v1beta1", - "kind": "Ingress", - "metadata": { - "name": "paddle-book-ingress" - }, - "spec": { - "rules": [{ - "host": "notebook.paddlepaddle.org", - "http": { - "paths": [{ - "path": "/", - "backend": { - "serviceName": "paddle-book-service", - "servicePort": 8888 - } - }] - } - }] - } - } - def get_notebook_id(self, username): - # notebook id is md5(username) - m = hashlib.md5() - m.update(username) - - return m.hexdigest()[:8] - - def __wait_api_response(self, resp): - print resp.status - - def __find_item(self, resource_list, match_name): - item_found = False - for item in resource_list.items: - if item.metadata.name == match_name: - item_found = True - return item_found - - def __create_deployment(self, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() - dep_list = v1beta1api.list_namespaced_deployment(namespace) - if not self.__find_item(dep_list, "paddle-book-deployment"): - resp = v1beta1api.create_namespaced_deployment(namespace, body=self.dep_body, pretty=True) - self.__wait_api_response(resp) - - def __create_service(self, namespace): - v1api = kubernetes.client.CoreV1Api() - service_list = v1api.list_namespaced_service(namespace) - if not self.__find_item(service_list, "paddle-book-service"): - resp = v1api.create_namespaced_service(namespace, body=self.service_body) - self.__wait_api_response(resp) - - def __create_ingress(self, username, namespace): - v1beta1api = kubernetes.client.ExtensionsV1beta1Api() - ing_list = v1beta1api.list_namespaced_ingress(namespace) - if not self.__find_item(ing_list, "paddle-book-ingress"): - # FIXME: must split this for different users - #self.ing_body["spec"]["rules"][0]["http"]["paths"][0]["path"] = "/" + self.get_notebook_id(username) - resp = v1beta1api.create_namespaced_ingress(namespace, body=self.ing_body) - self.__wait_api_response(resp) - - def start_all(self, username, namespace): - self.__create_deployment(namespace) - self.__create_service(namespace) - self.__create_ingress(username, namespace) - -def email_escape(email): - """ - escape email to a safe string of kubernetes namespace - """ - safe_email = email.replace("@", "-") - safe_email = safe_email.replace(".", "-") - safe_email = safe_email.replace("_", "-") - return safe_email @login_required def notebook_view(request): @@ -186,17 +68,12 @@ def notebook_view(request): # NOTICE: username is the same to user's email # NOTICE: escape the username to safe string to create namespaces username = request.user.username - conf_obj = kubernetes.client.Configuration() - conf_obj.host = settings.K8S_HOST - conf_obj.ssl_ca_cert = os.path.join(settings.CA_PATH) - conf_obj.cert_file = os.path.join(settings.USER_CERTS_PATH, username, username, ".pem") - conf_obj.key_file = os.path.join(settings.USER_CERTS_PATH, username, username, "-key.pem") - kubernetes.config.load_kube_config(client_configuration=conf_obj) + # FIXME: notebook must be started under username's namespace - v1api = kubernetes.client.CoreV1Api() + v1api = kubernetes.client.CoreV1Api(utils.get_user_api_client(username)) namespaces = v1api.list_namespace() user_namespace_found = False - user_namespace = email_escape(username) + user_namespace = utils.email_escape(username) for ns in namespaces.items: # must commit to user's namespace if ns.metadata.name == user_namespace: @@ -209,7 +86,16 @@ def notebook_view(request): "name": user_namespace }}) - ub = UserNotebook() + ub = utils.UserNotebook() ub.start_all(username, user_namespace) - return render(request, "notebook.html", context={"notebook_id": ub.get_notebook_id(username)}) + return render(request, "notebook.html", + context={"notebook_id": ub.get_notebook_id(username)}) + +@login_required +def stop_notebook_backend(request): + username = request.user.username + utils.update_user_k8s_config(username) + ub = utils.UserNotebook() + ub.start_all(username, user_namespace) + return HttpResponseRedirect("/") diff --git a/paddlecloud/paddlecloud/settings.py b/paddlecloud/paddlecloud/settings.py index d0622886..1ecf97c1 100644 --- a/paddlecloud/paddlecloud/settings.py +++ b/paddlecloud/paddlecloud/settings.py @@ -15,7 +15,8 @@ } ALLOWED_HOSTS = [ - "localhost", + "127.0.0.1", + "cloud.paddlepaddle.org", ] # Local time zone for this installation. Choices can be found here: @@ -114,6 +115,7 @@ "django.contrib.messages.middleware.MessageMiddleware", "django.middleware.clickjacking.XFrameOptionsMiddleware", "account.middleware.ExpiredPasswordMiddleware", + "notebook.frame_middleware.NotebookMiddleware" ] ROOT_URLCONF = "paddlecloud.urls" @@ -198,10 +200,10 @@ ] -CA_PATH = os.path.join(BASE_DIR, "ca.crt") -CA_KEY_PATH = os.path.join(BASE_DIR, "ca.key") +CA_PATH = os.path.join(BASE_DIR, "..", "ca.crt") +CA_KEY_PATH = os.path.join(BASE_DIR, "..", "ca.key") USER_CERTS_PATH="/tmp" -K8S_HOST = "https://192.168.64.2:8443" +K8S_HOST = "https://192.168.99.100:8443" PADDLE_BOOK_IMAGE="docker.paddlepaddle.org/book:0.10.0rc2" PADDLE_BOOK_PORT=8888 diff --git a/paddlecloud/paddlecloud/templates/homepage.html b/paddlecloud/paddlecloud/templates/homepage.html index 0e53943b..f8d82270 100644 --- a/paddlecloud/paddlecloud/templates/homepage.html +++ b/paddlecloud/paddlecloud/templates/homepage.html @@ -46,7 +46,7 @@

{% blocktrans %}What is PaddlePaddle Cloud?{% endblocktr Starter projects provide project layout, scaffolding, already integrated components and ready-to-go code. - Launch Paddle notebook + Launch Paddle notebook {% endblocktrans %}
diff --git a/paddlecloud/paddlecloud/templates/site_base.html b/paddlecloud/paddlecloud/templates/site_base.html index 00cc5d32..4aed4f69 100644 --- a/paddlecloud/paddlecloud/templates/site_base.html +++ b/paddlecloud/paddlecloud/templates/site_base.html @@ -3,7 +3,27 @@ {% load staticfiles %} {% load pinax_webanalytics_tags %} {% load i18n %} - +{% load notebook_status_tags %} + +{% block nav %} + {% get_user_notebook_status request.user as status %} + +{% endblock %} {% block styles %} diff --git a/paddlecloud/paddlecloud/templatetags/__init__.py b/paddlecloud/paddlecloud/templatetags/__init__.py new file mode 100644 index 00000000..e69de29b diff --git a/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py new file mode 100644 index 00000000..8125be08 --- /dev/null +++ b/paddlecloud/paddlecloud/templatetags/notebook_status_tags.py @@ -0,0 +1,28 @@ +from __future__ import unicode_literals +from django import template +from django.contrib.messages.utils import get_level_tags +from django.utils.encoding import force_text +from notebook.utils import email_escape, UserNotebook, get_user_api_client +import kubernetes + +LEVEL_TAGS = get_level_tags() + +register = template.Library() + + +def _get_notebook_id(self, username): + # notebook id is md5(username) + m = hashlib.md5() + m.update(username) + + return m.hexdigest()[:8] + +@register.simple_tag() +def get_user_notebook_status(user): + if not user.is_authenticated: + return "" + username = user.username + namespace = email_escape(user.email) + ub = UserNotebook() + + return ub.status(username, namespace) diff --git a/paddlecloud/paddlecloud/urls.py b/paddlecloud/paddlecloud/urls.py index 7287a7ed..a2b3db4f 100644 --- a/paddlecloud/paddlecloud/urls.py +++ b/paddlecloud/paddlecloud/urls.py @@ -13,7 +13,8 @@ url(r"^account/signup/$", notebook.views.SignupView.as_view(), name="account_signup"), url(r"^account/login/$", notebook.views.LoginView.as_view(), name="account_login"), url(r"^account/", include("account.urls")), - url(r"^notebook/", notebook.views.notebook_view), + url(r"^notedash/", notebook.views.notebook_view), + url(r"^notestop/", notebook.views.stop_notebook_backend), ] urlpatterns += static(settings.MEDIA_URL, document_root=settings.MEDIA_ROOT)