Skip to content

Commit

Permalink
Merge pull request #142 from mhilton/012-add-websocket-handlers
Browse files Browse the repository at this point in the history
Add websocket endpoint

The endpoint doesn't do much yet, it just returns a "no such model" error.
  • Loading branch information
jujugui authored Jul 5, 2016
2 parents 6186e43 + fa6bc80 commit 7bcb61a
Show file tree
Hide file tree
Showing 5 changed files with 179 additions and 0 deletions.
33 changes: 33 additions & 0 deletions internal/jujuapi/api.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
// Copyright 2016 Canonical Ltd.

// Package jujuapi implements API endpoints for the juju API.
package jujuapi

import (
"net/http"

"github.com/juju/httprequest"
"github.com/julienschmidt/httprouter"

"github.com/CanonicalLtd/jem/internal/jem"
"github.com/CanonicalLtd/jem/internal/jemserver"
)

func NewAPIHandler(jp *jem.Pool, sp jemserver.Params) ([]httprequest.Handler, error) {
return []httprequest.Handler{
newWebSocketHandler(jp),
}, nil
}

func newWebSocketHandler(jp *jem.Pool) httprequest.Handler {
return httprequest.Handler{
Method: "GET",
Path: "/model/:modeluuid/api",
Handle: func(w http.ResponseWriter, r *http.Request, p httprouter.Params) {
j := jp.JEM()
defer j.Close()
wsServer := newWSServer(j, p.ByName("modeluuid"))
wsServer.ServeHTTP(w, r)
},
}
}
13 changes: 13 additions & 0 deletions internal/jujuapi/package_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
// Copyright 2015 Canonical Ltd.

package jujuapi_test

import (
"testing"

jujutesting "github.com/juju/juju/testing"
)

func TestPackage(t *testing.T) {
jujutesting.MgoTestPackage(t)
}
65 changes: 65 additions & 0 deletions internal/jujuapi/websocket.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,65 @@
// Copyright 2016 Canonical Ltd.

package jujuapi

import (
"github.com/juju/juju/apiserver/common"
"github.com/juju/juju/apiserver/observer"
"github.com/juju/juju/rpc"
"github.com/juju/juju/rpc/jsoncodec"
"github.com/juju/juju/rpc/rpcreflect"
"golang.org/x/net/websocket"

"github.com/CanonicalLtd/jem/internal/jem"
)

// newWSServer creates a new websocket server suitible for handling the api for modelUUID.
func newWSServer(jem *jem.JEM, modelUUID string) websocket.Server {
hnd := wsHandler{
jem: jem,
modelUUID: modelUUID,
}
return websocket.Server{
Handler: hnd.handle,
}
}

// wsHandler is a handler for a particular websocket connection.
type wsHandler struct {
jem *jem.JEM
modelUUID string
}

// handle handles the connection.
func (h *wsHandler) handle(wsConn *websocket.Conn) {
codec := jsoncodec.NewWebsocket(wsConn)
conn := rpc.NewConn(codec, observer.None())

// TODO(mhilton) serve something useful on this connection.
err := common.UnknownModelError(h.modelUUID)
conn.ServeFinder(&errRoot{err}, serverError)
conn.Start()
select {
case <-conn.Dead():
}
conn.Close()
}

func serverError(err error) error {
if err := common.ServerError(err); err != nil {
return err
}
return nil
}

// errRoot implements the API that a client first sees
// when connecting to the API. It exposes the same API as initialRoot, except
// it returns the requested error when the client makes any request.
type errRoot struct {
err error
}

// FindMethod conforms to the same API as initialRoot, but we'll always return (nil, err)
func (r *errRoot) FindMethod(rootName string, version int, methodName string) (rpcreflect.MethodCaller, error) {
return nil, r.err
}
66 changes: 66 additions & 0 deletions internal/jujuapi/websocket_test.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
// Copyright 2016 Canonical Ltd.

package jujuapi_test

import (
"bytes"
"encoding/pem"
"net/http/httptest"
"net/url"

"github.com/juju/juju/api"
jc "github.com/juju/testing/checkers"
gc "gopkg.in/check.v1"
"gopkg.in/juju/names.v2"

"github.com/CanonicalLtd/jem/internal/apitest"
)

type websocketSuite struct {
apitest.Suite
wsServer *httptest.Server
connection api.Connection
}

var _ = gc.Suite(&websocketSuite{})

func (s *websocketSuite) SetUpTest(c *gc.C) {
s.Suite.SetUpTest(c)
s.wsServer = httptest.NewTLSServer(s.JEMSrv)
}

func (s *websocketSuite) TearDownTest(c *gc.C) {
s.wsServer.Close()
s.Suite.TearDownTest(c)
}

func (s *websocketSuite) TestUnknownModel(c *gc.C) {
conn := s.open(c, &api.Info{
ModelTag: names.NewModelTag("00000000-0000-0000-0000-000000000000"),
SkipLogin: true,
})
defer conn.Close()
err := conn.Login(names.NewUserTag("test-user"), "", "", nil)
c.Assert(err, gc.ErrorMatches, `unknown model: "00000000-0000-0000-0000-000000000000" \(not found\)`)
}

func (s *websocketSuite) open(c *gc.C, info *api.Info) api.Connection {
inf := *info
u, err := url.Parse(s.wsServer.URL)
c.Assert(err, jc.ErrorIsNil)
inf.Addrs = []string{
u.Host,
}
w := new(bytes.Buffer)
err = pem.Encode(w, &pem.Block{
Type: "CERTIFICATE",
Bytes: s.wsServer.TLS.Certificates[0].Certificate[0],
})
c.Assert(err, jc.ErrorIsNil)
inf.CACert = w.String()
conn, err := api.Open(&inf, api.DialOpts{
InsecureSkipVerify: true,
})
c.Assert(err, jc.ErrorIsNil)
return conn
}
2 changes: 2 additions & 0 deletions server.go
Original file line number Diff line number Diff line change
Expand Up @@ -12,13 +12,15 @@ import (

"github.com/CanonicalLtd/jem/internal/debugapi"
"github.com/CanonicalLtd/jem/internal/jemserver"
"github.com/CanonicalLtd/jem/internal/jujuapi"
"github.com/CanonicalLtd/jem/internal/v2"
"github.com/CanonicalLtd/jem/params"
)

var versions = map[string]jemserver.NewAPIHandlerFunc{
"v2": v2.NewAPIHandler,
"debug": debugapi.NewAPIHandler,
"juju": jujuapi.NewAPIHandler,
}

// ServerParams holds configuration for a new API server.
Expand Down

0 comments on commit 7bcb61a

Please sign in to comment.