~rogpeppe/juju-core/azure

« back to all changes in this revision

Viewing changes to environs/local/environ.go

[r=thumper] Add the initial bootstrap implementation.

This isn't all that is needed to bootstrap, but it is the start.
In particular, this adds the mongo upstart service. This branch
also adds a very simple instance implementation for the local
instances.

https://codereview.appspot.com/11325043/

Show diffs side-by-side

added added

removed removed

Lines of Context:
5
5
 
6
6
import (
7
7
        "fmt"
 
8
        "io/ioutil"
8
9
        "net"
9
10
        "os"
 
11
        "path/filepath"
10
12
        "sync"
 
13
        "time"
11
14
 
 
15
        "launchpad.net/juju-core/agent"
12
16
        "launchpad.net/juju-core/constraints"
13
17
        "launchpad.net/juju-core/environs"
14
18
        "launchpad.net/juju-core/environs/config"
16
20
        "launchpad.net/juju-core/instance"
17
21
        "launchpad.net/juju-core/state"
18
22
        "launchpad.net/juju-core/state/api"
 
23
        "launchpad.net/juju-core/upstart"
 
24
        "launchpad.net/juju-core/utils"
19
25
)
20
26
 
 
27
// lxcBridgeName is the name of the network interface that the local provider
 
28
// uses to determine the ip address to use for machine-0 such that the
 
29
// containers being created are able to communicate with it simply.
 
30
const lxcBridgeName = "lxcbr0"
 
31
 
 
32
// upstartScriptLocation is parameterised purely for testing purposes as we
 
33
// don't really want to be installing and starting scripts as root for
 
34
// testing.
 
35
var upstartScriptLocation = "/etc/init"
 
36
 
 
37
// A request may fail to due "eventual consistency" semantics, which
 
38
// should resolve fairly quickly.  A request may also fail due to a slow
 
39
// state transition (for instance an instance taking a while to release
 
40
// a security group after termination).  The former failure mode is
 
41
// dealt with by shortAttempt, the latter by longAttempt.
 
42
var shortAttempt = utils.AttemptStrategy{
 
43
        Total: 1 * time.Second,
 
44
        Delay: 50 * time.Millisecond,
 
45
}
 
46
 
21
47
// localEnviron implements Environ.
22
48
var _ environs.Environ = (*localEnviron)(nil)
23
49
 
34
60
        return env.name
35
61
}
36
62
 
 
63
func (env *localEnviron) mongoServiceName() string {
 
64
        return "juju-db-" + env.config.namespace()
 
65
}
 
66
 
 
67
// ensureCertOwner checks to make sure that the cert files created
 
68
// by the bootstrap command are owned by the user and not root.
 
69
func (env *localEnviron) ensureCertOwner() error {
 
70
        files := []string{
 
71
                config.JujuHomePath(env.name + "-cert.pem"),
 
72
                config.JujuHomePath(env.name + "-private-key.pem"),
 
73
        }
 
74
 
 
75
        uid, gid, err := sudoCallerIds()
 
76
        if err != nil {
 
77
                return err
 
78
        }
 
79
        if uid != 0 || gid != 0 {
 
80
                for _, filename := range files {
 
81
                        if err := os.Chown(filename, uid, gid); err != nil {
 
82
                                return err
 
83
                        }
 
84
                }
 
85
        }
 
86
        return nil
 
87
}
 
88
 
37
89
// Bootstrap is specified in the Environ interface.
38
90
func (env *localEnviron) Bootstrap(cons constraints.Value) error {
39
 
        return fmt.Errorf("not implemented")
 
91
        logger.Infof("bootstrapping environment %q", env.name)
 
92
        if !env.config.runningAsRoot {
 
93
                return fmt.Errorf("bootstrapping a local environment must be done as root")
 
94
        }
 
95
        if err := env.config.createDirs(); err != nil {
 
96
                logger.Errorf("failed to create necessary directories: %v", err)
 
97
                return err
 
98
        }
 
99
 
 
100
        if err := env.ensureCertOwner(); err != nil {
 
101
                logger.Errorf("failed to reassign ownership of the certs to the user: %v", err)
 
102
                return err
 
103
        }
 
104
        // TODO(thumper): check that the constraints don't include "container=lxc" for now.
 
105
 
 
106
        var noRetry = utils.AttemptStrategy{}
 
107
        if err := environs.VerifyBootstrapInit(env, noRetry); err != nil {
 
108
                return err
 
109
        }
 
110
 
 
111
        cert, key, err := env.setupLocalMongoService()
 
112
        if err != nil {
 
113
                return err
 
114
        }
 
115
 
 
116
        // Work out the ip address of the lxc bridge, and use that for the mongo config.
 
117
        bridgeAddress, err := env.findBridgeAddress()
 
118
        if err != nil {
 
119
                return err
 
120
        }
 
121
        logger.Debugf("found %q as address for %q", bridgeAddress, lxcBridgeName)
 
122
 
 
123
        // Before we write the agent config file, we need to make sure the
 
124
        // instance is saved in the StateInfo.
 
125
        bootstrapId := instance.Id("localhost")
 
126
        if err := environs.SaveState(env.Storage(), &environs.BootstrapState{[]instance.Id{bootstrapId}}); err != nil {
 
127
                logger.Errorf("failed to save state instances: %v", err)
 
128
                return err
 
129
        }
 
130
 
 
131
        // Need to write out the agent file for machine-0 before initializing
 
132
        // state, as as part of that process, it will reset the password in the
 
133
        // agent file.
 
134
        if err := env.writeBootstrapAgentConfFile(cert, key); err != nil {
 
135
                return err
 
136
        }
 
137
 
 
138
        // Have to initialize the state configuration with localhost so we get
 
139
        // "special" permissions.
 
140
        stateConnection, err := env.initialStateConfiguration("localhost", cons)
 
141
        if err != nil {
 
142
                return err
 
143
        }
 
144
        defer stateConnection.Close()
 
145
 
 
146
        // TODO(thumper): upload tools into the storage
 
147
 
 
148
        // TODO(thumper): start the machine agent for machine-0
 
149
 
 
150
        return nil
40
151
}
41
152
 
42
153
// StateInfo is specified in the Environ interface.
43
154
func (env *localEnviron) StateInfo() (*state.Info, *api.Info, error) {
44
 
        return nil, nil, fmt.Errorf("not implemented")
 
155
        return environs.StateInfo(env)
45
156
}
46
157
 
47
158
// Config is specified in the Environ interface.
118
229
 
119
230
// Instances is specified in the Environ interface.
120
231
func (env *localEnviron) Instances(ids []instance.Id) ([]instance.Instance, error) {
121
 
        return nil, fmt.Errorf("not implemented")
 
232
        // NOTE: do we actually care about checking the existance of the instances?
 
233
        // I posit that here we don't really care, and that we are only called with
 
234
        // instance ids that we know exist.
 
235
        if len(ids) == 0 {
 
236
                return nil, nil
 
237
        }
 
238
        insts := make([]instance.Instance, len(ids))
 
239
        for i, id := range ids {
 
240
                insts[i] = &localInstance{id, env}
 
241
        }
 
242
        return insts, nil
122
243
}
123
244
 
124
245
// AllInstances is specified in the Environ interface.
125
 
func (env *localEnviron) AllInstances() ([]instance.Instance, error) {
126
 
        return nil, fmt.Errorf("not implemented")
 
246
func (env *localEnviron) AllInstances() (instances []instance.Instance, err error) {
 
247
        // TODO(thumper): get all the instances from the container manager
 
248
        instances = append(instances, &localInstance{"localhost", env})
 
249
        return instances, nil
127
250
}
128
251
 
129
252
// Storage is specified in the Environ interface.
138
261
 
139
262
// Destroy is specified in the Environ interface.
140
263
func (env *localEnviron) Destroy(insts []instance.Instance) error {
141
 
        return fmt.Errorf("not implemented")
 
264
        if !env.config.runningAsRoot {
 
265
                return fmt.Errorf("destroying a local environment must be done as root")
 
266
        }
 
267
 
 
268
        logger.Infof("removing service %s", env.mongoServiceName())
 
269
        mongo := upstart.NewService(env.mongoServiceName())
 
270
        mongo.InitDir = upstartScriptLocation
 
271
        if err := mongo.Remove(); err != nil {
 
272
                logger.Errorf("could not remove mongo service: %v", err)
 
273
                return err
 
274
        }
 
275
 
 
276
        // Remove the rootdir.
 
277
        logger.Infof("removing state dir %s", env.config.rootDir())
 
278
        if err := os.RemoveAll(env.config.rootDir()); err != nil {
 
279
                logger.Errorf("could not remove local state dir: %v", err)
 
280
                return err
 
281
        }
 
282
 
 
283
        return nil
142
284
}
143
285
 
144
286
// OpenPorts is specified in the Environ interface.
160
302
func (env *localEnviron) Provider() environs.EnvironProvider {
161
303
        return &provider
162
304
}
 
305
 
 
306
// setupLocalMongoService returns the cert and key if there was no error.
 
307
func (env *localEnviron) setupLocalMongoService() ([]byte, []byte, error) {
 
308
        journalDir := filepath.Join(env.config.mongoDir(), "journal")
 
309
        logger.Debugf("create mongo journal dir: %v", journalDir)
 
310
        if err := os.MkdirAll(journalDir, 0755); err != nil {
 
311
                logger.Errorf("failed to make mongo journal dir %s: %v", journalDir, err)
 
312
                return nil, nil, err
 
313
        }
 
314
 
 
315
        logger.Debugf("generate server cert")
 
316
        cert, key, err := env.config.GenerateStateServerCertAndKey()
 
317
        if err != nil {
 
318
                logger.Errorf("failed to generate server cert: %v", err)
 
319
                return nil, nil, err
 
320
        }
 
321
        if err := ioutil.WriteFile(
 
322
                env.config.configFile("server.pem"),
 
323
                append(cert, key...),
 
324
                0600); err != nil {
 
325
                logger.Errorf("failed to write server.pem: %v", err)
 
326
                return nil, nil, err
 
327
        }
 
328
 
 
329
        mongo := upstart.MongoUpstartService(
 
330
                env.mongoServiceName(),
 
331
                env.config.rootDir(),
 
332
                env.config.mongoDir(),
 
333
                env.config.StatePort())
 
334
        mongo.InitDir = upstartScriptLocation
 
335
        logger.Infof("installing service %s to %s", env.mongoServiceName(), mongo.InitDir)
 
336
        if err := mongo.Install(); err != nil {
 
337
                logger.Errorf("could not install mongo service: %v", err)
 
338
                return nil, nil, err
 
339
        }
 
340
        return cert, key, nil
 
341
}
 
342
 
 
343
func (env *localEnviron) findBridgeAddress() (string, error) {
 
344
        bridge, err := net.InterfaceByName(lxcBridgeName)
 
345
        if err != nil {
 
346
                logger.Errorf("cannot find network interface %q: %v", lxcBridgeName, err)
 
347
                return "", err
 
348
        }
 
349
        addrs, err := bridge.Addrs()
 
350
        if err != nil {
 
351
                logger.Errorf("cannot get addresses for network interface %q: %v", lxcBridgeName, err)
 
352
                return "", err
 
353
        }
 
354
        return utils.GetIPv4Address(addrs)
 
355
}
 
356
 
 
357
func (env *localEnviron) writeBootstrapAgentConfFile(cert, key []byte) error {
 
358
        info, apiInfo, err := env.StateInfo()
 
359
        if err != nil {
 
360
                logger.Errorf("failed to get state info to write bootstrap agent file: %v", err)
 
361
                return err
 
362
        }
 
363
        tag := state.MachineTag("0")
 
364
        info.Tag = tag
 
365
        apiInfo.Tag = tag
 
366
        conf := &agent.Conf{
 
367
                DataDir:         env.config.rootDir(),
 
368
                StateInfo:       info,
 
369
                APIInfo:         apiInfo,
 
370
                StateServerCert: cert,
 
371
                StateServerKey:  key,
 
372
                StatePort:       env.config.StatePort(),
 
373
                APIPort:         env.config.StatePort(),
 
374
                MachineNonce:    state.BootstrapNonce,
 
375
        }
 
376
        if err := conf.Write(); err != nil {
 
377
                logger.Errorf("failed to write bootstrap agent file: %v", err)
 
378
                return err
 
379
        }
 
380
        return nil
 
381
}
 
382
 
 
383
func (env *localEnviron) initialStateConfiguration(addr string, cons constraints.Value) (*state.State, error) {
 
384
        // We don't check the existance of the CACert here as if it wasn't set, we
 
385
        // wouldn't get this far.
 
386
        cfg := env.config.Config
 
387
        caCert, _ := cfg.CACert()
 
388
        addr = fmt.Sprintf("%s:%d", addr, cfg.StatePort())
 
389
        info := &state.Info{
 
390
                Addrs:  []string{addr},
 
391
                CACert: caCert,
 
392
        }
 
393
        timeout := state.DialOpts{10 * time.Second}
 
394
        bootstrap, err := environs.BootstrapConfig(cfg)
 
395
        if err != nil {
 
396
                return nil, err
 
397
        }
 
398
        st, err := state.Initialize(info, bootstrap, timeout)
 
399
        if err != nil {
 
400
                logger.Errorf("failed to initialize state: %v", err)
 
401
                return nil, err
 
402
        }
 
403
        logger.Debugf("state initialized")
 
404
 
 
405
        passwordHash := utils.PasswordHash(cfg.AdminSecret())
 
406
        if err := environs.BootstrapUsers(st, cfg, passwordHash); err != nil {
 
407
                st.Close()
 
408
                return nil, err
 
409
        }
 
410
        jobs := []state.MachineJob{state.JobManageEnviron, state.JobManageState}
 
411
 
 
412
        if err := environs.ConfigureBootstrapMachine(st, cfg, cons, env.config.rootDir(), jobs); err != nil {
 
413
                st.Close()
 
414
                return nil, err
 
415
        }
 
416
 
 
417
        // Return an open state reference.
 
418
        return st, nil
 
419
}