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  }