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