github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/apiserver/testing/fakeapi.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package testing 5 6 import ( 7 "context" 8 "crypto/tls" 9 "net/http" 10 "net/http/httptest" 11 "net/url" 12 "reflect" 13 14 "github.com/bmizerany/pat" 15 "github.com/gorilla/websocket" 16 jujuhttp "github.com/juju/http/v2" 17 "github.com/juju/rpcreflect" 18 19 "github.com/juju/juju/apiserver/observer" 20 "github.com/juju/juju/apiserver/observer/fakeobserver" 21 "github.com/juju/juju/rpc" 22 "github.com/juju/juju/rpc/jsoncodec" 23 "github.com/juju/juju/testing" 24 ) 25 26 // Server represents a fake API server. It must be closed 27 // after use. 28 type Server struct { 29 // Addrs holds the address used for the 30 // server, suitable for including in api.Info.Addrs 31 Addrs []string 32 33 *httptest.Server 34 newRoot func(modelUUID string) interface{} 35 } 36 37 // NewAPIServer serves RPC methods on a localhost HTTP server. 38 // When a connection is made to the API, the newRoot function 39 // is called with the requested model UUID and the returned 40 // value defines the API (see the juju/rpc package). 41 // 42 // Note that the root value accepts any facade version number - it 43 // is not currently possible to use this to serve several different 44 // facade versions. 45 // 46 // The server uses testing.ServerCert and testing.ServerKey 47 // to host the server. 48 // 49 // The returned server must be closed after use. 50 func NewAPIServer(newRoot func(modelUUID string) interface{}) *Server { 51 tlsCert, err := tls.X509KeyPair([]byte(testing.ServerCert), []byte(testing.ServerKey)) 52 if err != nil { 53 panic("bad key pair") 54 } 55 56 srv := &Server{ 57 newRoot: newRoot, 58 } 59 pmux := pat.New() 60 pmux.Get("/model/:modeluuid/api", http.HandlerFunc(srv.serveAPI)) 61 62 srv.Server = httptest.NewUnstartedServer(pmux) 63 64 tlsConfig := jujuhttp.SecureTLSConfig() 65 tlsConfig.Certificates = []tls.Certificate{tlsCert} 66 srv.Server.TLS = tlsConfig 67 68 srv.StartTLS() 69 u, _ := url.Parse(srv.URL) 70 srv.Addrs = []string{u.Host} 71 return srv 72 } 73 74 func (srv *Server) serveAPI(w http.ResponseWriter, req *http.Request) { 75 var websocketUpgrader = websocket.Upgrader{} 76 conn, err := websocketUpgrader.Upgrade(w, req, nil) 77 if err != nil { 78 return 79 } 80 srv.serveConn(req.Context(), conn, req.URL.Query().Get(":modeluuid")) 81 } 82 83 func (srv *Server) serveConn(ctx context.Context, wsConn *websocket.Conn, modelUUID string) { 84 codec := jsoncodec.NewWebsocket(wsConn) 85 conn := rpc.NewConn(codec, observer.NewRecorderFactory( 86 &fakeobserver.Instance{}, nil, observer.NoCaptureArgs)) 87 88 root := allVersions{ 89 rpcreflect.ValueOf(reflect.ValueOf(srv.newRoot(modelUUID))), 90 } 91 conn.ServeRoot(root, nil, nil) 92 conn.Start(ctx) 93 <-conn.Dead() 94 conn.Close() 95 } 96 97 // allVersions serves the same methods as would be served 98 // by rpc.Conn.Serve except that the facade version is ignored. 99 type allVersions struct { 100 rpcreflect.Value 101 } 102 103 func (av allVersions) FindMethod(rootMethodName string, version int, objMethodName string) (rpcreflect.MethodCaller, error) { 104 return av.Value.FindMethod(rootMethodName, 0, objMethodName) 105 }