Skip to content

Commit

Permalink
Fix dashboard + proxy incompatibility (#441)
Browse files Browse the repository at this point in the history
* dashboard: dev guide

* fix dashboard + proxy issue

* dashboard: move to hash router

* [dashboard] Add comments about routes and proxying
  • Loading branch information
wbuchwalter authored and k8s-ci-robot committed Mar 20, 2018
1 parent ebb0053 commit 181cb05
Show file tree
Hide file tree
Showing 6 changed files with 47 additions and 7 deletions.
15 changes: 14 additions & 1 deletion dashboard/backend/handler/api_handler.go
Original file line number Diff line number Diff line change
Expand Up @@ -61,7 +61,20 @@ func CreateHTTPAPIHandler(client client.ClientManager) (http.Handler, error) {

apiV1Ws := new(restful.WebService)

apiV1Ws.Path("/api").
// Issue was figuring out which part of the url corresponded to the application itself, and which parts were related to the proxying.
// If we have this url with kubectl proxy + ambassador:
// `127.0.0.1:8001/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/`
// Proxy parts are:
// `127.0.0.1:8001/api/v1/namespaces/kubeflow/services/ambassador:80/proxy`
// Application parts are (depending on how the rewrite is configured in ambassador):
// `/tfjobs/ui/`
// So the way the application handles it, is finding the first occurence of tfjobs in the url and appending /api/ to reach the backend.
// Finally, by rewriting in ambassador to `/tfjobs/` and not `/` and by having the backend listening on
// `/tfjobs/ui` and `/tfjobs/api` we ensure that the logic won't break when not using ambassador, i.e:
// When using a dev server: `127.0.0.1:3000/tfjobs/ui`
// When proxying directly on the dashboard service:
// `http://127.0.0.1:8001/api/v1/namespaces/kubeflow/services/tf-job-dashboard:80/proxy/tfjobs/ui/#/`
apiV1Ws.Path("/tfjobs/api").
Consumes(restful.MIME_JSON).
Produces(restful.MIME_JSON)

Expand Down
6 changes: 3 additions & 3 deletions dashboard/backend/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -20,11 +20,11 @@ func main() {
log.Fatalf("Error while creating the API Handler: %v", err)
}

http.Handle("/api/", apiHandler)
http.Handle("/", http.StripPrefix("/", http.FileServer(http.Dir("/opt/tensorflow_k8s/dashboard/frontend/build/"))))
http.Handle("/tfjobs/ui/", http.StripPrefix("/tfjobs/ui/", http.FileServer(http.Dir("/opt/tensorflow_k8s/dashboard/frontend/build/"))))
http.Handle("/tfjobs/api/", apiHandler)

p := ":8080"
log.Println("Listening on", p)
log.Println("Dashboard available at /tfjobs/ui/ on port", p)

http.ListenAndServe(p, nil)

Expand Down
7 changes: 7 additions & 0 deletions dashboard/frontend/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -2,6 +2,13 @@
"name": "tensorflowk8s-dashboard",
"version": "0.1.0",
"description": "Dashboard for kubeflow/tf-operator.",
"//": [
"Homepage defines where the frontend will query for the static assets.",
"If not specified the static assets will be queried at the root, i.e: 127.0.0.1:8001/static which breaks.",
"'.' means that we query at the path where the application is served. ",
"i.e: http://127.0.0.1:8001/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/static"
],
"homepage": ".",
"private": true,
"dependencies": {
"lodash": "^4.17.4",
Expand Down
13 changes: 12 additions & 1 deletion dashboard/frontend/src/components/App.js
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,18 @@ import React from "react";
import MuiThemeProvider from "material-ui/styles/MuiThemeProvider";
import getMuiTheme from "material-ui/styles/getMuiTheme";
import FlatButton from "material-ui/FlatButton";
import { BrowserRouter as Router, Link } from "react-router-dom";

// BrowserRouter is a traditional router, If I currently am at the root `/` and I navigate to `/new`
// the resulting route will be: `${baseroute}/new`. However this breaks with ambassador:
// If I am at `/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/` and I navigate to `/new` I still end up on `/new`
// And we also can't just figure out the proxy part of the url and preprend it to each link
// such as: `/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/new` since the go backend
// is configured to only handle `/tfjobs/ui` and not `/api/v1/...`
// So instead we can use the HashRouter, which will only use the part of the url located after a # to infer it's current state
// So `/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/#`
// will become `/api/v1/namespaces/kubeflow/services/ambassador:80/proxy/tfjobs/ui/#/new.`
// This works regardless of how the dashboard is accessed. This is also how the k8s dashboard works
import { HashRouter as Router, Link } from "react-router-dom";
import { orange700, orange400 } from "material-ui/styles/colors";
import ContentAdd from "material-ui/svg-icons/content/add";

Expand Down
5 changes: 3 additions & 2 deletions dashboard/frontend/src/services.js
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
//let host = "http://localhost:8080";
let host = "";
import { getHost } from "./utils";

const host = getHost();

export function getTFJobListService(namespace) {
return fetch(`${host}/api/tfjob/${namespace}`).then(r => r.json());
Expand Down
8 changes: 8 additions & 0 deletions dashboard/frontend/src/utils.js
Original file line number Diff line number Diff line change
@@ -0,0 +1,8 @@
export function getHost() {
const prefix = "/tfjobs";
const pLen = prefix.length;

const fullPath = window.location.pathname;
const keepLen = fullPath.indexOf(prefix) + pLen;
return fullPath.substr(0, keepLen);
}

0 comments on commit 181cb05

Please sign in to comment.