github.com/axw/juju@v0.0.0-20161005053422-4bd6544d08d4/doc/api.txt (about)

     1  * API Design
     2  
     3  The overall aim is to make agents and clients connect through a network
     4  API rather than directly to the underlying database as is the case
     5  currently.
     6  
     7  This will have a few advantages:
     8  
     9  - Operations that involve multiple round trips to the database can be
    10  made more efficient because the API server is likely to be closer to
    11  the database server.
    12  
    13  - We can decide on, and enforce, an appropriate authorization policy
    14  for each operation.
    15  
    16  - The API can be made easy to use from multiple languages and client
    17  types.
    18  
    19  There are two general kinds of operation on the API: simple
    20  requests and watch requests. I'll deal with simple requests
    21  first.
    22  
    23  * Simple requests
    24  
    25  A simple request takes some parameters, possibly makes some changes to
    26  the state database, and returns some results or an error.
    27  
    28  When the request returns no data, it would theoretically be possible to
    29  have the API server operate on the request without returning a reply,
    30  but then the client would not know when the request has completed or if
    31  it completed successfully. Therefore, I think it's better if all requests
    32  return a reply.
    33  
    34  Here is the list of all the State requests that are currently used by the
    35  juju agents:
    36  	XXX link
    37  We will need to implement at least these requests (possibly
    38  slightly changed, but hopefully as little as possible, to ensure as
    39  little churn as possible in the agent code when moving to using the API)
    40  
    41  41 out of the 59 requests are operating directly on a single state
    42  entity, expressed as the receiver object in the Go API. For this reason
    43  I believe it's appropriate to phrase the API requests in this way -
    44  as requests on particular entities in the state. This leads to certain
    45  implementation advantages (see "Implementation" below) and means there's
    46  a close correspondence between the API protocol and the API as implemented
    47  in Go (and hopefully other languages too).
    48  
    49  To make the protocol accessible, we define all messages to be in JSON
    50  format and we use a secure websocket for transport.
    51  For security, we currently rely on a server-side certificate
    52  and passwords sent over the connection to identify the client,
    53  but it should be straightforward to enable the server to
    54  do client certificate checking if desired.
    55  
    56  Here's a sample request to change the instance id
    57  associated with a machine, and its reply.  (I'll show JSON in rjson form, to keep the
    58  noise down, see http://godoc.org/launchpad.net/rjson).
    59  
    60  Client->Server
    61  	{
    62  		RequestId: 1234
    63  		Type: "Machine"
    64  		Id: "99"
    65  		Request: "SetInstanceId"
    66  		Params: {
    67  			InstanceId: "i-43e55e5"
    68  		}
    69  	}
    70  Server->Client
    71  	{
    72  		RequestId: 1234
    73  		Error: ""
    74  		Result: {
    75  		}
    76  	}
    77  
    78  We use the RequestId field to associate the request and its
    79  reply. The API client must not re-use a request id until
    80  it has received the request's reply (the easiest way
    81  to do that is simply to increment the request id each time).
    82  We allow multiple requests to be outstanding on a connection
    83  at once, and their replies can be received in any order.
    84  
    85  In the request, the Id field may be omitted to specify
    86  an empty Id, and Params may be omitted
    87  to specify no request parameters. Similarly, in the
    88  response, the Error field may be omitted to
    89  signify no error, and the Result field may be
    90  omitted to signify no result. To save space below,
    91  I've omitted fields accordingly.
    92  
    93  The Type field identifies the type of entity to act on,
    94  and the Id field its identifier. Currently I envisage
    95  the following types of entities:
    96  
    97  	Admin
    98  		Admin (a singleton) is used by a client when identifying itself
    99  		to the server. It is the only thing that can be accessed
   100  		before the client has authenticated.
   101  
   102  	Client
   103  	ClientWatcher
   104  		Client (a singleton) is the access point for all the GUI client
   105  		and other user-facing methods. This is only
   106  		usable by clients, not by agents.
   107  
   108  	State
   109  	Machine
   110  	Unit
   111  	Relation
   112  	RelationUnit
   113  	Service
   114  	Pinger
   115  	MachineWatcher
   116  	UnitWatcher
   117  	LifecycleWatcher
   118  	ServiceUnitsWatcher
   119  	ServiceRelationsWatcher
   120  	RelationScopeWatcher
   121  	UnitsWatcher
   122  	ConfigWatcher
   123  	NotifyWatcher
   124  	MachineUnitsWatcher
   125  		These correspond directly to types exported by the
   126  		juju state package.  They are usable only by agents,
   127  		not clients.
   128  
   129  The Request field specifies the action to perform, and Params holds the
   130  parameters to that request.
   131  
   132  In the reply message, the RequestId field must match that of the
   133  request. If the request failed, then the Error field holds the description
   134  of the error (it is possible we might add a Code field later, to help
   135  diagnosing specific kinds of error).
   136  
   137  The Result field holds the results of the request (in this case there
   138  are none, so it's empty).
   139  
   140  That completes the overview of simple requests,
   141  so on to watching.
   142  
   143  * Watching
   144  
   145  To watch something in the state, we invoke a Watch request, which
   146  returns a handle to a watcher object, that can then be used to find
   147  out when changes happen by calling its Next method. To stop a watcher,
   148  we call Stop on it.
   149  
   150  For example, if an agent wishes to watch machine 99, the conversation
   151  with the API server looks something like this:
   152  
   153  Client->Server
   154  	{
   155  		RequestId: 1000
   156  		Type: "Machine"
   157  		Id: "99"
   158  		Request: "Watch"
   159  	}
   160  Server->Client
   161  	{
   162  		RequestId: 1000
   163  		Response: {
   164  			NotifyWatcherId: "1"
   165  		}
   166  	}
   167  
   168  At this point, the watcher is registered.  Subsequent Next calls will
   169  only return when the entity has changed.
   170  
   171  Client->Server
   172  	{
   173  		RequestId: 1001
   174  		Type: "NotifyWatcher"
   175  		Id: "1"
   176  		Request: "Next"
   177  	}
   178  
   179  This reply will only sent when something has changed.  Note that for this
   180  particular watcher, no data is sent with the Next response. This can vary
   181  according to the particular kind of watcher - some watchers may return
   182  deltas, for example, or the latest value of the thing being watched.
   183  
   184  Server->Client
   185  	{
   186  		RequestId: 1001
   187  	}
   188  
   189  The client can carry on sending Next requests for
   190  as long as it chooses, each one returning only
   191  when the machine has changed since the previous
   192  Next request.
   193  
   194  Client->Server
   195  	{
   196  		RequestId: 1002
   197  		Type: "NotifyWatcher"
   198  		Id: "1"
   199  		Request: "Next"
   200  	}
   201  
   202  Finally, the client decides to stop the watcher. This
   203  causes any outstanding Next request to return too -
   204  in no particular order with respect to the Stop reply.
   205  
   206  Client->Server
   207  	{
   208  		RequestId: 1003
   209  		Type: "NotifyWatcher"
   210  		Id: "1"
   211  		Request: "Stop"
   212  	}
   213  Server->Client
   214  	{
   215  		RequestId: 1002
   216  	}
   217  Server->Client
   218  	{
   219  		RequestId: 1003
   220  	}
   221  
   222  As you can see, we use exactly the same RPC mechanism for watching as
   223  for simple requests. An alternative would have been to push watch change
   224  notifications to clients without waiting for an explicit request.
   225  
   226  Both schemes have advantages and disadvantages.  I've gone with the
   227  above scheme mainly because it makes the protocol more obviously correct
   228  in the face of clients that are not reading data fast enough - in the
   229  face of a client with a slow network connection, we will not continue
   230  saturating its link with changes that cannot be passed through the
   231  pipe fast enough. Because Juju is state-based rather than event-based,
   232  the number of possible changes is bounded by the size of the system,
   233  so even if a client is very slow at reading the number of changes pushed
   234  down to it will not grow without bound.
   235  
   236  Allocating a watcher per client also implies that the server must
   237  keep some per-client state, but preliminary measurements indicate
   238  that the cost of that is unlikely to be prohibitive.
   239  
   240  Using exactly the same mechanism for all interactions with the API has
   241  advantages in simplicity too.
   242  
   243  * Authentication and authorization
   244  
   245  The API server is authenticated by TLS handshake before the
   246  websocket connection is initiated; the client should check that
   247  the server's certificate is signed by a trusted CA (in particular
   248  the CA that's created as a part of the bootstrap process).
   249  
   250  One wrinkle here is that before bootstrapping, we don't
   251  know the DNS name of the API server (in general, from
   252  a high-availability standpoint, we want to be able to serve the API from
   253  any number of servers), so we cannot put it into
   254  the certificate that we generate for the API server.
   255  This doesn't sit well with the way that www authentication
   256  usually works - hopefully there's a way around it in node.
   257  
   258  The client authenticates to the server currently by providing
   259  a user name and password in a Login request:
   260  
   261  Client->Server
   262  	{
   263  		RequestId: 1
   264  		Type: "Admin"
   265  		Request: "Login"
   266  		Params: {
   267  			"Tag": "machine-1",
   268  			Password: "a2eaa54323ae",
   269  		}
   270  	}
   271  Server->Client
   272  	{
   273  		RequestId: 1
   274  	}
   275  
   276  Until the user has successfully logged in, the Login
   277  request is the only one that the server will respond
   278  to - all other requests yield a "permission denied"
   279  error.
   280  
   281  The exact form of the Login request is subject to change,
   282  depending on what kind user authentication we might
   283  end up with - it may even end up as two or more requests,
   284  going through different stages of some authentication
   285  process.
   286  
   287  When logged in, requests are authorized both at the
   288  type level (to filter out obviously inappropriate requests,
   289  such as a client trying to access the agent API) and
   290  at the request level (allowing a more fine-grained
   291  approach).
   292  
   293  * Versioning
   294  
   295  I'm not currently sure of the best approach to versioning.
   296  One possibility is to have a Version request that
   297  allows the client to specify a desired version number;
   298  the server could then reply with a lower (or
   299  equal) version. The server would then serve the
   300  version of the protocol that it replied with.
   301  
   302  Unfortunately, this adds an extra round trip to the
   303  session setup. This could be mitigated by sending
   304  both the Version and the Login requests at the same
   305  time.
   306  
   307  * Implementation
   308  
   309  The Go stack consists of the following levels (high to low):
   310  
   311  	client interface ("github.com/juju/juju/state/api".State)
   312  	rpc package ("github.com/juju/juju/rpc".Client)
   313  	----- (json transport over secure websockets, implemented by 3rd party code)
   314  	rpc package ("github.com/juju/juju/rpc".Server)
   315  	server implementation ("github.com/juju/juju/state/api".Server)
   316  	server backend ("github.com/juju/juju/state")
   317  	mongo data store
   318  
   319