github.com/mhilton/juju-juju@v0.0.0-20150901100907-a94dd2c73455/doc/architectural-overview.txt (about) 1 2 # Juju Architectural Overview 3 4 ## Audience 5 6 This document is targeted at new developers of Juju, and may be useful to experienced 7 developers who need a refresher on some aspect of juju's operation. It is deliberately 8 light on detail, because the precise mechanisms of various components' operation are 9 expected to change much faster than the general interactions between components. 10 11 12 ## The View From Space 13 14 A Juju environment is a distributed system comprising: 15 16 * A data store (mongodb) which describes the desired state of the world, in terms 17 of running workloads or *services*, and the *relations* between them; and of the 18 *units* that comprise those services, and the *machines* on which those units run. 19 20 * A bunch of *agents*, each of which runs the same `jujud` binary, and which are 21 variously responsible for causing reality to converge towards the idealised world- 22 state encoded in the data store. 23 24 * Some number of *clients* which talk over an API, implemented by the agents, to 25 update the desired world-state (and thereby cause the agents to update the world 26 to match). The `juju` binary is one of many possible clients; the `juju-gui` web 27 application, and the `juju-deployer` python tool, are other examples. 28 29 The whole system depends upon a substrate, or *provider*, which supplies the compute, 30 storage, and network resources used by the workloads (and by juju itself; but never 31 forget that *everything* described in this document is merely supporting infrastructure 32 geared towards the successful deployment and configuration of the workloads that solve 33 actual problems for actual users). 34 35 36 ## The Data Store 37 38 There's a lot of *detail* to cover, but there's not much to say from an architectural 39 standpoint. We use a mongodb replicaset to support HA; we use the `mgo` package from 40 `labix.org` to implement multi-document transactions; we make use of the transaction 41 log to detect changes to particular documents, and convert them into business-object- 42 level events that get sent over the API to interested parties. 43 44 The mongodb databases run on machines we refer to as *state servers*, and are only 45 accessed by agents running on those machines; it's important to keep it locked down 46 (and, honestly, to lock it down further and better than we currently have). 47 48 There's some documentation on how to work with [the state package](hacking-state); 49 and plenty more on the [state entities](lifecycles) and the details of their 50 [creation](entity-creation) and [destruction](death-and-destruction) from various 51 perspectives; but there's not a lot more to say in this context. 52 53 It *is* important to understand that the transaction-log watching is not an ideal 54 solution, and we'll be retiring it at some point, in favour of an in-memory model 55 of state and a pub-sub system for watchers; we *know* it's a scalability problem, 56 but we're not devoting resources to it until it becomes more pressing. 57 58 Code for dealing with mongodb is found primarily in the `state`, `state/watcher`, 59 `replicaset`, and `worker/peergrouper` packages. 60 61 62 ## The Agents 63 64 Agents all use the same `jujud` binary, and all follow roughly the same model. 65 When starting up, they authenticate with an API server; possibly reset their 66 password, if the one they used has been stored persistently somewhere and is 67 thus vulnerable; determine their responsibilities; and run a set of tasks in 68 parallel until one of those tasks returns an error indicating that the agent 69 should either restart or terminate completely. Tasks that return any other error 70 will be automatically restarted after a short delay; tasks that return nil are 71 considered to be complete, and will not be restarted until the whole process is. 72 73 When comparing the unit agent with the machine agent, the truth of the above 74 may not be immediately apparent, because the responsibilities of the unit 75 agent are so much less varied than those of the machine agent; but we have 76 scheduled work to integrate the unit agent into the machine agent, rendering 77 each unit agent a single worker task within its responsible machine agent. It's 78 still better to consider a unit agent to be a simplistic and/or degenerate 79 implementation of a machine agent than to attach too much importance to the 80 differences. 81 82 83 ### Jobs, Runners, and Workers 84 85 Machine agents all have at least one of two jobs: JobHostUnits and JobManageEnviron. 86 Each of these jobs represents a number of tasks the agent needs to execute to 87 fulfil its responsibilities; in addition, there are a number of tasks that are 88 executed by every machine agent. The terms *task* and *worker* are generally used 89 interchangeably in this document and in the source code; it's possible but not 90 generally helpful to draw the distinction that a worker executes a task. All 91 tasks are implemented by code in some subpackage of the `worker` package, and the 92 `worker.Runner` type implements the retry behaviour described above. 93 94 It's useful to note that the Runner type is itself a worker, so we can and do 95 nest Runners inside one another; the details of *exactly* how and where a given 96 worker comes to be executed are generally elided in this document; but it's worth 97 being aware of the fact that all the workers that use an API connection share a 98 single one, mediated by a single Runner, such that when the API connection fails 99 that single Runner can stop all its workers; shut itself down; be restarted by 100 its parent worker; and set up a new API connection, which it then uses to start 101 all its child workers. 102 103 Please note that the lists of workers below should *not* be assumed to be 104 exhaustive. Juju evolves, and the workers evolve with it. 105 106 107 ### Common workers 108 109 All agents run workers with the following responsibilities: 110 111 * Check for scheduled upgrades for their binaries, and replace themselves 112 (implemented in `worker/upgrader`) 113 * Watch logging config, and reconfigure the local logger (`worker/logger`; yes, 114 we know; it is not the stupidest name in the codebase) 115 * Watch and store the latest known addresses for the state servers 116 (`worker/apiaddressupdater`) 117 * Watch and store rsyslog targets (`worker/rsyslog`) -- this *could* probably be 118 reasonably combined with the apiaddressupdater, but there's no guarantee that 119 120 ### Machine Agent Workers 121 122 Machine agents additionally do the following: 123 124 * Run upgrade code in the new binaries once they're replaced themselves 125 (implemented directly in the machine agent's `upgradeWorker` method) 126 * Handle SIGABRT and permanently stop the agent (`worker/terminationworker`) 127 * Handle the machine entity's death and permanently stop the agent (`worker/machiner`) 128 * Watch proxy config, and reconfigure the local machine (`worker/machineenvironmentworker`) 129 the two sets of machines will be the same for ever. 130 * Watch for contained LXC or KVM machines and provision/decommission them 131 (`worker/provisioner`) 132 133 *Almost* all machine agents have JobHostUnits -- the sole exception is the state 134 server in a local environment, which runs directly on the user's machine (not 135 in a container); we don't want to pollute their day-to-day working environment 136 by deploying charms there (especially because the local provider is explicitly 137 a development tool, and charms deployed there are disproportionately likely to 138 be flawed or incomplete). Those that do run the `worker/deployer` code which 139 watches for units assigned to the machine, and deploys/recalls upstart configs 140 for their respective unit agents as the units are assigned/removed. We expect 141 the deployer implementation to change to just directly run the unit agents' 142 workers in its own Runner. 143 144 There remain a couple of abominations in which the machine agent looks up 145 information it really shouldn't have access to -- ie the running provider type 146 -- and uses that information to decide whether to start other workers. These 147 instances should be killed with fire if you get any opportunity to do so; 148 basicaly all the information in agent config which *isn't* about contacting an 149 API server represents a layering violation that (1) confuses us and slows us 150 down and (2) just encourages worse layering violations as we progress. 151 152 153 ### State Server Workers 154 155 Machines with JobManageEnviron also run a number of other workers, which do 156 the following. 157 158 * Run the API server used by all other workers (in this, and other, agents: 159 `state/apiserver`) 160 * Provision/decommission provider instances in response to the creation/ 161 destruction of machine entities (`worker/provisioner`, just like the 162 container provisioners run in all machine agents anyway) 163 * Manipulate provider networks in response to units opening/closing ports, 164 and users exposing/unexposing services (`worker/firewaller`) 165 * Update network addresses and associated information for provider instances 166 (`worker/instancepoller`) 167 * Respond to queued DB cleanup events (`worker/cleaner`) 168 * Maintain the MongoDB replica set (`worker/peergrouper`) 169 * Resume incomplete MongoDB transactions (`worker/resumer`) 170 171 Many of these workers (more than strictly need to be) are wrapped as "singular" 172 workers, which only run on the same machine as the current MongoDB replicaset 173 master. When the master changes, the state connection is dropped, causing all 174 those workers to also be stopped; when they're restarted, they won't run because 175 they're no longer running on the master. 176 177 178 ### Unit Agents 179 180 Unit agents run all the common workers, and the `worker/uniter` task as well; 181 this task is probably the single most forbiddingly complex part of Juju. (Side 182 note: It's a unit-er because it deals with units, and we're bad at names; but 183 it's also a unite-r because it's where all the various components of juju come 184 together to run actual workloads.) It's sufficiently large that it deserves its 185 own top-level heading, below. 186 187 188 ## The Uniter 189 190 At the highest level, the Uniter is a state machine. After a "little bit" of setup, 191 it runs a tight loop in which it calls `Mode` functions one after another, with the 192 next mode run determined by the result of its predecessor. All mode functions are 193 implemented in `worker/uniter/modes.go`, which is actually pretty small: just a hair 194 over 400 lines. 195 196 It's deliberately implemented as conceptually single-threaded (just like almost 197 everything else in juju -- rampaging concurrency is the root of much evil, and so 198 we save ourselves a huge number of headaches by hiding concurrency behind event 199 channels and handling a single event at a time), but this property has degraded 200 over time; in particular, the `RunListener` code can inject events at unhelpful 201 times, and while the `hookLock` *probably* renders this safe it's still deeply 202 suboptimal, because the fact of the concurrency requires that we be *extremely* 203 careful with further modifications, lest they subtly break assumptions. We hope 204 to address this by retiring the current implementation of `juju run`, but it's 205 not entirely clear how to do this; in the meantime, Here Be Dragons. 206 207 Leaving these woes aside, the mode functions make use of two fundamental components, 208 which are glommed together until someone refactors it to make more sense. There's 209 the `Filter`, which is responsible for communicating with the API server (and the 210 rest of the outside world) such that relevant events can be delivered to the mode 211 func via channels exposed on the filter; and then there's the `Uniter` itself, which 212 exposes a number of methods that are expected to be called by the mode funcs. 213 214 215 ### Uniter Modes 216 217 XXXX 218 219 220 ### Hook contexts 221 222 XXXX 223 224 225 ### The Relation Model 226 227 XXXX 228 229 230 ## The APIs 231 232 State servers expose an API endpoint over a websocket connection. The methods 233 available over the API are broken down by client; there's a `Client` facade that 234 exposes the methods used by clients, an `Agent` facade that exposes the methods 235 common to all agents, and a wide range of worker-specific *facades* that individually 236 deal with particular chunks of functionality implemented by one agent or another 237 (for example, `Provisioner`, `Upgrader`, and `Uniter`, each used by the eponymous 238 worker types). 239 240 Various facades share functionality; for example, the Life method is used by many 241 worker facades. In these cases, the method is implemented on a spearate type, which 242 is embedded in the facade implementation. 243 244 All APIs *should* be implemented such that they can be called in bulk, but not 245 all of them are. The agent facades are (almost?) all implemented correctly, but 246 the Client facade is almost exclusively not. As functionality evolves, and new 247 versions of the client APIs are implemented, we must take care to implement them 248 consistently -- this means both implementing bulk calls *and* splitting the 249 monolithic Client facade into smaller service-specific facades, such that we 250 can evolve interaction with (say) users without bumping global API versions 251 across the board). 252 253 254 ## The Providers 255 256 Each provider represents a different possible kind of substrate on which a juju 257 environment can run, and (as far as possible) abstracts away the differences 258 between them, by making them all conform to the Environ interface. The most 259 important thing to understand about the various providers is that they're all 260 implemented without reference to broader juju concepts; they are squeezed into 261 a shape that's convenient WRT allowing juju to make use of them, but if we allow 262 juju-level concepts to infect the providers we will suffer greatly, because we 263 will open a path by which changes to *juju* end up causing changes to *all the 264 providers at once*. 265 266 However, we lack the ability to enforce this at present, because the package 267 dependency flows in the wrong direction, thanks primarily (purely?) to the 268 StateInfo method on Environ; and we jam all sorts of gymnastics into the state 269 package to allow us to use Environs without doing so explicitly (see the 270 state.Policy interface, and its many somewhat-inelegant uses). In other places, 271 we have (quite reasonably) moved code out of the environs package (see both 272 environs/config.Config, and instance.Instance). 273 274 Environ implementations are expected to be goroutine-safe; we don't currently 275 make much use of that property at the moment, but we will be coming to depend 276 upon it as we move to eliminate the wasteful proliferation of Environ instances 277 in the API server. 278 279 It's important to note that an environ Config will generally contain sensitive 280 information -- a user's authentication keys for a cloud provider -- and so we 281 must always be careful to avoid spreading those around further than we need to. 282 Basically, if an environ config gets off a state server, we've screwed up. 283 284 285 ## Bootstrapping 286 287 XXXX