~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/agent/agent.go

  • Committer: Nicholas Skaggs
  • Date: 2016-10-24 20:56:05 UTC
  • Revision ID: nicholas.skaggs@canonical.com-20161024205605-z8lta0uvuhtxwzwl
Initi with beta15

Show diffs side-by-side

added added

removed removed

Lines of Context:
 
1
// Copyright 2013 Canonical Ltd.
 
2
// Licensed under the AGPLv3, see LICENCE file for details.
 
3
 
 
4
package agent
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "io/ioutil"
 
10
        "net"
 
11
        "os"
 
12
        "path"
 
13
        "path/filepath"
 
14
        "regexp"
 
15
        "strconv"
 
16
 
 
17
        "github.com/juju/errors"
 
18
        "github.com/juju/loggo"
 
19
        "github.com/juju/utils"
 
20
        "github.com/juju/utils/series"
 
21
        "github.com/juju/utils/shell"
 
22
        "github.com/juju/version"
 
23
        "gopkg.in/juju/names.v2"
 
24
 
 
25
        "github.com/juju/juju/api"
 
26
        "github.com/juju/juju/apiserver/params"
 
27
        "github.com/juju/juju/juju/paths"
 
28
        "github.com/juju/juju/mongo"
 
29
        "github.com/juju/juju/network"
 
30
        "github.com/juju/juju/state/multiwatcher"
 
31
)
 
32
 
 
33
var logger = loggo.GetLogger("juju.agent")
 
34
 
 
35
const (
 
36
        // BootstrapNonce is used as a nonce for the initial controller machine.
 
37
        BootstrapNonce = "user-admin:bootstrap"
 
38
 
 
39
        // BootstrapMachineId is the ID of the initial controller machine.
 
40
        BootstrapMachineId = "0"
 
41
 
 
42
        // MachineLockName is the name of the mutex that the agent creates to
 
43
        // ensure serialization of tasks such as uniter hook executions, juju-run,
 
44
        // and others.
 
45
        MachineLockName = "machine-lock"
 
46
)
 
47
 
 
48
// These are base values used for the corresponding defaults.
 
49
var (
 
50
        logDir          = paths.MustSucceed(paths.LogDir(series.HostSeries()))
 
51
        dataDir         = paths.MustSucceed(paths.DataDir(series.HostSeries()))
 
52
        confDir         = paths.MustSucceed(paths.ConfDir(series.HostSeries()))
 
53
        metricsSpoolDir = paths.MustSucceed(paths.MetricsSpoolDir(series.HostSeries()))
 
54
)
 
55
 
 
56
// Agent exposes the agent's configuration to other components. This
 
57
// interface should probably be segregated (agent.ConfigGetter and
 
58
// agent.ConfigChanger?) but YAGNI *currently* advises against same.
 
59
type Agent interface {
 
60
 
 
61
        // CurrentConfig returns a copy of the agent's configuration. No
 
62
        // guarantees regarding ongoing correctness are made.
 
63
        CurrentConfig() Config
 
64
 
 
65
        // ChangeConfig allows clients to change the agent's configuration
 
66
        // by supplying a callback that applies the changes.
 
67
        ChangeConfig(ConfigMutator) error
 
68
}
 
69
 
 
70
// APIHostPortsSetter trivially wraps an Agent to implement
 
71
// worker/apiaddressupdater/APIAddressSetter.
 
72
type APIHostPortsSetter struct {
 
73
        Agent
 
74
}
 
75
 
 
76
// SetAPIHostPorts is the APIAddressSetter interface.
 
77
func (s APIHostPortsSetter) SetAPIHostPorts(servers [][]network.HostPort) error {
 
78
        return s.ChangeConfig(func(c ConfigSetter) error {
 
79
                c.SetAPIHostPorts(servers)
 
80
                return nil
 
81
        })
 
82
}
 
83
 
 
84
// StateServingInfoSetter trivially wraps an Agent to implement
 
85
// worker/certupdater/SetStateServingInfo.
 
86
type StateServingInfoSetter struct {
 
87
        Agent
 
88
}
 
89
 
 
90
// SetStateServingInfo is the SetStateServingInfo interface.
 
91
func (s StateServingInfoSetter) SetStateServingInfo(info params.StateServingInfo) error {
 
92
        return s.ChangeConfig(func(c ConfigSetter) error {
 
93
                c.SetStateServingInfo(info)
 
94
                return nil
 
95
        })
 
96
}
 
97
 
 
98
// Paths holds the directory paths used by the agent.
 
99
type Paths struct {
 
100
        // DataDir is the data directory where each agent has a subdirectory
 
101
        // containing the configuration files.
 
102
        DataDir string
 
103
        // LogDir is the log directory where all logs from all agents on
 
104
        // the machine are written.
 
105
        LogDir string
 
106
        // MetricsSpoolDir is the spool directory where workloads store
 
107
        // collected metrics.
 
108
        MetricsSpoolDir string
 
109
        // ConfDir is the directory where all  config file for
 
110
        // Juju agents are stored.
 
111
        ConfDir string
 
112
}
 
113
 
 
114
// Migrate assigns the directory locations specified from the new path configuration.
 
115
func (p *Paths) Migrate(newPaths Paths) {
 
116
        if newPaths.DataDir != "" {
 
117
                p.DataDir = newPaths.DataDir
 
118
        }
 
119
        if newPaths.LogDir != "" {
 
120
                p.LogDir = newPaths.LogDir
 
121
        }
 
122
        if newPaths.MetricsSpoolDir != "" {
 
123
                p.MetricsSpoolDir = newPaths.MetricsSpoolDir
 
124
        }
 
125
        if newPaths.ConfDir != "" {
 
126
                p.ConfDir = newPaths.ConfDir
 
127
        }
 
128
}
 
129
 
 
130
// NewPathsWithDefaults returns a Paths struct initialized with default locations if not otherwise specified.
 
131
func NewPathsWithDefaults(p Paths) Paths {
 
132
        paths := DefaultPaths
 
133
        if p.DataDir != "" {
 
134
                paths.DataDir = p.DataDir
 
135
        }
 
136
        if p.LogDir != "" {
 
137
                paths.LogDir = p.LogDir
 
138
        }
 
139
        if p.MetricsSpoolDir != "" {
 
140
                paths.MetricsSpoolDir = p.MetricsSpoolDir
 
141
        }
 
142
        if p.ConfDir != "" {
 
143
                paths.ConfDir = p.ConfDir
 
144
        }
 
145
        return paths
 
146
}
 
147
 
 
148
var (
 
149
        // DefaultPaths defines the default paths for an agent.
 
150
        DefaultPaths = Paths{
 
151
                DataDir:         dataDir,
 
152
                LogDir:          path.Join(logDir, "juju"),
 
153
                MetricsSpoolDir: metricsSpoolDir,
 
154
                ConfDir:         confDir,
 
155
        }
 
156
)
 
157
 
 
158
// SystemIdentity is the name of the file where the environment SSH key is kept.
 
159
const SystemIdentity = "system-identity"
 
160
 
 
161
const (
 
162
        LxcBridge         = "LXC_BRIDGE"
 
163
        LxdBridge         = "LXD_BRIDGE"
 
164
        ProviderType      = "PROVIDER_TYPE"
 
165
        ContainerType     = "CONTAINER_TYPE"
 
166
        Namespace         = "NAMESPACE"
 
167
        AgentServiceName  = "AGENT_SERVICE_NAME"
 
168
        MongoOplogSize    = "MONGO_OPLOG_SIZE"
 
169
        NumaCtlPreference = "NUMA_CTL_PREFERENCE"
 
170
)
 
171
 
 
172
// The Config interface is the sole way that the agent gets access to the
 
173
// configuration information for the machine and unit agents.  There should
 
174
// only be one instance of a config object for any given agent, and this
 
175
// interface is passed between multiple go routines.  The mutable methods are
 
176
// protected by a mutex, and it is expected that the caller doesn't modify any
 
177
// slice that may be returned.
 
178
//
 
179
// NOTE: should new mutating methods be added to this interface, consideration
 
180
// is needed around the synchronisation as a single instance is used in
 
181
// multiple go routines.
 
182
type Config interface {
 
183
        // DataDir returns the data directory. Each agent has a subdirectory
 
184
        // containing the configuration files.
 
185
        DataDir() string
 
186
 
 
187
        // LogDir returns the log directory. All logs from all agents on
 
188
        // the machine are written to this directory.
 
189
        LogDir() string
 
190
 
 
191
        // SystemIdentityPath returns the path of the file where the environment
 
192
        // SSH key is kept.
 
193
        SystemIdentityPath() string
 
194
 
 
195
        // Jobs returns a list of MachineJobs that need to run.
 
196
        Jobs() []multiwatcher.MachineJob
 
197
 
 
198
        // Tag returns the tag of the entity on whose behalf the state connection
 
199
        // will be made.
 
200
        Tag() names.Tag
 
201
 
 
202
        // Dir returns the agent's directory.
 
203
        Dir() string
 
204
 
 
205
        // Nonce returns the nonce saved when the machine was provisioned
 
206
        // TODO: make this one of the key/value pairs.
 
207
        Nonce() string
 
208
 
 
209
        // CACert returns the CA certificate that is used to validate the state or
 
210
        // API server's certificate.
 
211
        CACert() string
 
212
 
 
213
        // APIAddresses returns the addresses needed to connect to the api server
 
214
        APIAddresses() ([]string, error)
 
215
 
 
216
        // WriteCommands returns shell commands to write the agent configuration.
 
217
        // It returns an error if the configuration does not have all the right
 
218
        // elements.
 
219
        WriteCommands(renderer shell.Renderer) ([]string, error)
 
220
 
 
221
        // StateServingInfo returns the details needed to run
 
222
        // a controller and reports whether those details
 
223
        // are available
 
224
        StateServingInfo() (params.StateServingInfo, bool)
 
225
 
 
226
        // APIInfo returns details for connecting to the API server and
 
227
        // reports whether the details are available.
 
228
        APIInfo() (*api.Info, bool)
 
229
 
 
230
        // MongoInfo returns details for connecting to the controller's mongo
 
231
        // database and reports whether those details are available
 
232
        MongoInfo() (*mongo.MongoInfo, bool)
 
233
 
 
234
        // OldPassword returns the fallback password when connecting to the
 
235
        // API server.
 
236
        OldPassword() string
 
237
 
 
238
        // UpgradedToVersion returns the version for which all upgrade steps have been
 
239
        // successfully run, which is also the same as the initially deployed version.
 
240
        UpgradedToVersion() version.Number
 
241
 
 
242
        // Value returns the value associated with the key, or an empty string if
 
243
        // the key is not found.
 
244
        Value(key string) string
 
245
 
 
246
        // Model returns the tag for the model that the agent belongs to.
 
247
        Model() names.ModelTag
 
248
 
 
249
        // MetricsSpoolDir returns the spool directory where workloads store
 
250
        // collected metrics.
 
251
        MetricsSpoolDir() string
 
252
 
 
253
        // MongoVersion returns the version of mongo that the state server
 
254
        // is using.
 
255
        MongoVersion() mongo.Version
 
256
}
 
257
 
 
258
type configSetterOnly interface {
 
259
        // Clone returns a copy of the configuration that
 
260
        // is unaffected by subsequent calls to the Set*
 
261
        // methods
 
262
        Clone() Config
 
263
 
 
264
        // SetOldPassword sets the password that is currently
 
265
        // valid but needs to be changed. This is used as
 
266
        // a fallback.
 
267
        SetOldPassword(oldPassword string)
 
268
 
 
269
        // SetPassword sets the password to be used when
 
270
        // connecting to the state.
 
271
        SetPassword(newPassword string)
 
272
 
 
273
        // SetValue updates the value for the specified key.
 
274
        SetValue(key, value string)
 
275
 
 
276
        // SetUpgradedToVersion sets the version that
 
277
        // the agent has successfully upgraded to.
 
278
        SetUpgradedToVersion(newVersion version.Number)
 
279
 
 
280
        // SetAPIHostPorts sets the API host/port addresses to connect to.
 
281
        SetAPIHostPorts(servers [][]network.HostPort)
 
282
 
 
283
        // SetCACert sets the CA cert used for validating API connections.
 
284
        SetCACert(string)
 
285
 
 
286
        // Migrate takes an existing agent config and applies the given
 
287
        // parameters to change it.
 
288
        //
 
289
        // Only non-empty fields in newParams are used
 
290
        // to change existing config settings. All changes are written
 
291
        // atomically. UpgradedToVersion cannot be changed here, because
 
292
        // Migrate is most likely called during an upgrade, so it will be
 
293
        // changed at the end of the upgrade anyway, if successful.
 
294
        //
 
295
        // Migrate does not actually write the new configuration.
 
296
        //
 
297
        // Note that if the configuration file moves location,
 
298
        // (if DataDir is set), the the caller is responsible for removing
 
299
        // the old configuration.
 
300
        Migrate(MigrateParams) error
 
301
 
 
302
        // SetStateServingInfo sets the information needed
 
303
        // to run a controller
 
304
        SetStateServingInfo(info params.StateServingInfo)
 
305
 
 
306
        // SetMongoVersion sets the passed version as currently in use.
 
307
        SetMongoVersion(mongo.Version)
 
308
}
 
309
 
 
310
// LogFileName returns the filename for the Agent's log file.
 
311
func LogFilename(c Config) string {
 
312
        return filepath.Join(c.LogDir(), c.Tag().String()+".log")
 
313
}
 
314
 
 
315
type ConfigMutator func(ConfigSetter) error
 
316
 
 
317
type ConfigWriter interface {
 
318
        // Write writes the agent configuration.
 
319
        Write() error
 
320
}
 
321
 
 
322
type ConfigSetter interface {
 
323
        Config
 
324
        configSetterOnly
 
325
}
 
326
 
 
327
type ConfigSetterWriter interface {
 
328
        Config
 
329
        configSetterOnly
 
330
        ConfigWriter
 
331
}
 
332
 
 
333
// MigrateParams holds agent config values to change in a
 
334
// Migrate call. Empty fields will be ignored. DeleteValues
 
335
// specifies a list of keys to delete.
 
336
type MigrateParams struct {
 
337
        Paths        Paths
 
338
        Jobs         []multiwatcher.MachineJob
 
339
        DeleteValues []string
 
340
        Values       map[string]string
 
341
        Model        names.ModelTag
 
342
}
 
343
 
 
344
// Ensure that the configInternal struct implements the Config interface.
 
345
var _ Config = (*configInternal)(nil)
 
346
 
 
347
type connectionDetails struct {
 
348
        addresses []string
 
349
        password  string
 
350
}
 
351
 
 
352
func (d *connectionDetails) clone() *connectionDetails {
 
353
        if d == nil {
 
354
                return nil
 
355
        }
 
356
        newd := *d
 
357
        newd.addresses = append([]string{}, d.addresses...)
 
358
        return &newd
 
359
}
 
360
 
 
361
type configInternal struct {
 
362
        configFilePath    string
 
363
        paths             Paths
 
364
        tag               names.Tag
 
365
        nonce             string
 
366
        model             names.ModelTag
 
367
        jobs              []multiwatcher.MachineJob
 
368
        upgradedToVersion version.Number
 
369
        caCert            string
 
370
        stateDetails      *connectionDetails
 
371
        apiDetails        *connectionDetails
 
372
        oldPassword       string
 
373
        servingInfo       *params.StateServingInfo
 
374
        values            map[string]string
 
375
        mongoVersion      string
 
376
}
 
377
 
 
378
// AgentConfigParams holds the parameters required to create
 
379
// a new AgentConfig.
 
380
type AgentConfigParams struct {
 
381
        Paths             Paths
 
382
        Jobs              []multiwatcher.MachineJob
 
383
        UpgradedToVersion version.Number
 
384
        Tag               names.Tag
 
385
        Password          string
 
386
        Nonce             string
 
387
        Model             names.ModelTag
 
388
        StateAddresses    []string
 
389
        APIAddresses      []string
 
390
        CACert            string
 
391
        Values            map[string]string
 
392
        MongoVersion      mongo.Version
 
393
}
 
394
 
 
395
// NewAgentConfig returns a new config object suitable for use for a
 
396
// machine or unit agent.
 
397
func NewAgentConfig(configParams AgentConfigParams) (ConfigSetterWriter, error) {
 
398
        if configParams.Paths.DataDir == "" {
 
399
                return nil, errors.Trace(requiredError("data directory"))
 
400
        }
 
401
        if configParams.Tag == nil {
 
402
                return nil, errors.Trace(requiredError("entity tag"))
 
403
        }
 
404
        switch configParams.Tag.(type) {
 
405
        case names.MachineTag, names.UnitTag:
 
406
                // these are the only two type of tags that can represent an agent
 
407
        default:
 
408
                return nil, errors.Errorf("entity tag must be MachineTag or UnitTag, got %T", configParams.Tag)
 
409
        }
 
410
        if configParams.UpgradedToVersion == version.Zero {
 
411
                return nil, errors.Trace(requiredError("upgradedToVersion"))
 
412
        }
 
413
        if configParams.Password == "" {
 
414
                return nil, errors.Trace(requiredError("password"))
 
415
        }
 
416
        if uuid := configParams.Model.Id(); uuid == "" {
 
417
                return nil, errors.Trace(requiredError("model"))
 
418
        } else if !names.IsValidModel(uuid) {
 
419
                return nil, errors.Errorf("%q is not a valid model uuid", uuid)
 
420
        }
 
421
        if len(configParams.CACert) == 0 {
 
422
                return nil, errors.Trace(requiredError("CA certificate"))
 
423
        }
 
424
        // Note that the password parts of the state and api information are
 
425
        // blank.  This is by design: we want to generate a secure password
 
426
        // for new agents. So, we create this config without a current password
 
427
        // which signals to apicaller worker that it should try to connect using old password.
 
428
        // When/if this connection is successful, apicaller worker will generate
 
429
        // a new secure password and update this agent's config.
 
430
        config := &configInternal{
 
431
                paths:             NewPathsWithDefaults(configParams.Paths),
 
432
                jobs:              configParams.Jobs,
 
433
                upgradedToVersion: configParams.UpgradedToVersion,
 
434
                tag:               configParams.Tag,
 
435
                nonce:             configParams.Nonce,
 
436
                model:             configParams.Model,
 
437
                caCert:            configParams.CACert,
 
438
                oldPassword:       configParams.Password,
 
439
                values:            configParams.Values,
 
440
                mongoVersion:      configParams.MongoVersion.String(),
 
441
        }
 
442
 
 
443
        if len(configParams.StateAddresses) > 0 {
 
444
                config.stateDetails = &connectionDetails{
 
445
                        addresses: configParams.StateAddresses,
 
446
                }
 
447
        }
 
448
        if len(configParams.APIAddresses) > 0 {
 
449
                config.apiDetails = &connectionDetails{
 
450
                        addresses: configParams.APIAddresses,
 
451
                }
 
452
        }
 
453
        if err := config.check(); err != nil {
 
454
                return nil, err
 
455
        }
 
456
        if config.values == nil {
 
457
                config.values = make(map[string]string)
 
458
        }
 
459
        config.configFilePath = ConfigPath(config.paths.DataDir, config.tag)
 
460
        return config, nil
 
461
}
 
462
 
 
463
// NewStateMachineConfig returns a configuration suitable for
 
464
// a machine running the controller.
 
465
func NewStateMachineConfig(configParams AgentConfigParams, serverInfo params.StateServingInfo) (ConfigSetterWriter, error) {
 
466
        if serverInfo.Cert == "" {
 
467
                return nil, errors.Trace(requiredError("controller cert"))
 
468
        }
 
469
        if serverInfo.PrivateKey == "" {
 
470
                return nil, errors.Trace(requiredError("controller key"))
 
471
        }
 
472
        if serverInfo.CAPrivateKey == "" {
 
473
                return nil, errors.Trace(requiredError("ca cert key"))
 
474
        }
 
475
        if serverInfo.StatePort == 0 {
 
476
                return nil, errors.Trace(requiredError("state port"))
 
477
        }
 
478
        if serverInfo.APIPort == 0 {
 
479
                return nil, errors.Trace(requiredError("api port"))
 
480
        }
 
481
        config, err := NewAgentConfig(configParams)
 
482
        if err != nil {
 
483
                return nil, err
 
484
        }
 
485
        config.SetStateServingInfo(serverInfo)
 
486
        return config, nil
 
487
}
 
488
 
 
489
// BaseDir returns the directory containing the data directories for
 
490
// all the agents.
 
491
func BaseDir(dataDir string) string {
 
492
        // Note: must use path, not filepath, as this function is
 
493
        // (indirectly) used by the client on Windows.
 
494
        return path.Join(dataDir, "agents")
 
495
}
 
496
 
 
497
// Dir returns the agent-specific data directory.
 
498
func Dir(dataDir string, tag names.Tag) string {
 
499
        // Note: must use path, not filepath, as this
 
500
        // function is used by the client on Windows.
 
501
        return path.Join(BaseDir(dataDir), tag.String())
 
502
}
 
503
 
 
504
// ConfigPath returns the full path to the agent config file.
 
505
// NOTE: Delete this once all agents accept --config instead
 
506
// of --data-dir - it won't be needed anymore.
 
507
func ConfigPath(dataDir string, tag names.Tag) string {
 
508
        return filepath.Join(Dir(dataDir, tag), agentConfigFilename)
 
509
}
 
510
 
 
511
// ReadConfig reads configuration data from the given location.
 
512
func ReadConfig(configFilePath string) (ConfigSetterWriter, error) {
 
513
        var (
 
514
                format formatter
 
515
                config *configInternal
 
516
        )
 
517
        configData, err := ioutil.ReadFile(configFilePath)
 
518
        if err != nil {
 
519
                return nil, fmt.Errorf("cannot read agent config %q: %v", configFilePath, err)
 
520
        }
 
521
        format, config, err = parseConfigData(configData)
 
522
        if err != nil {
 
523
                return nil, err
 
524
        }
 
525
        logger.Debugf("read agent config, format %q", format.version())
 
526
        config.configFilePath = configFilePath
 
527
        return config, nil
 
528
}
 
529
 
 
530
func (c0 *configInternal) Clone() Config {
 
531
        c1 := *c0
 
532
        // Deep copy only fields which may be affected
 
533
        // by ConfigSetter methods.
 
534
        c1.stateDetails = c0.stateDetails.clone()
 
535
        c1.apiDetails = c0.apiDetails.clone()
 
536
        c1.jobs = append([]multiwatcher.MachineJob{}, c0.jobs...)
 
537
        c1.values = make(map[string]string, len(c0.values))
 
538
        for key, val := range c0.values {
 
539
                c1.values[key] = val
 
540
        }
 
541
        return &c1
 
542
}
 
543
 
 
544
func (config *configInternal) Migrate(newParams MigrateParams) error {
 
545
        config.paths.Migrate(newParams.Paths)
 
546
        config.configFilePath = ConfigPath(config.paths.DataDir, config.tag)
 
547
        if len(newParams.Jobs) > 0 {
 
548
                config.jobs = make([]multiwatcher.MachineJob, len(newParams.Jobs))
 
549
                copy(config.jobs, newParams.Jobs)
 
550
        }
 
551
        for _, key := range newParams.DeleteValues {
 
552
                delete(config.values, key)
 
553
        }
 
554
        for key, value := range newParams.Values {
 
555
                if config.values == nil {
 
556
                        config.values = make(map[string]string)
 
557
                }
 
558
                config.values[key] = value
 
559
        }
 
560
        if newParams.Model.Id() != "" {
 
561
                config.model = newParams.Model
 
562
        }
 
563
        if err := config.check(); err != nil {
 
564
                return fmt.Errorf("migrated agent config is invalid: %v", err)
 
565
        }
 
566
        return nil
 
567
}
 
568
 
 
569
func (c *configInternal) SetUpgradedToVersion(newVersion version.Number) {
 
570
        c.upgradedToVersion = newVersion
 
571
}
 
572
 
 
573
func (c *configInternal) SetAPIHostPorts(servers [][]network.HostPort) {
 
574
        if c.apiDetails == nil {
 
575
                return
 
576
        }
 
577
        var addrs []string
 
578
        for _, serverHostPorts := range servers {
 
579
                hps := network.PrioritizeInternalHostPorts(serverHostPorts, false)
 
580
                addrs = append(addrs, hps...)
 
581
        }
 
582
        c.apiDetails.addresses = addrs
 
583
        logger.Infof("API server address details %q written to agent config as %q", servers, addrs)
 
584
}
 
585
 
 
586
func (c *configInternal) SetCACert(cert string) {
 
587
        c.caCert = cert
 
588
}
 
589
 
 
590
func (c *configInternal) SetValue(key, value string) {
 
591
        if value == "" {
 
592
                delete(c.values, key)
 
593
        } else {
 
594
                c.values[key] = value
 
595
        }
 
596
}
 
597
 
 
598
func (c *configInternal) SetOldPassword(oldPassword string) {
 
599
        c.oldPassword = oldPassword
 
600
}
 
601
 
 
602
func (c *configInternal) SetPassword(newPassword string) {
 
603
        if c.stateDetails != nil {
 
604
                c.stateDetails.password = newPassword
 
605
        }
 
606
        if c.apiDetails != nil {
 
607
                c.apiDetails.password = newPassword
 
608
        }
 
609
}
 
610
 
 
611
func (c *configInternal) Write() error {
 
612
        data, err := c.fileContents()
 
613
        if err != nil {
 
614
                return err
 
615
        }
 
616
        // Make sure the config dir gets created.
 
617
        configDir := filepath.Dir(c.configFilePath)
 
618
        if err := os.MkdirAll(configDir, 0755); err != nil {
 
619
                return fmt.Errorf("cannot create agent config dir %q: %v", configDir, err)
 
620
        }
 
621
        return utils.AtomicWriteFile(c.configFilePath, data, 0600)
 
622
}
 
623
 
 
624
func requiredError(what string) error {
 
625
        return fmt.Errorf("%s not found in configuration", what)
 
626
}
 
627
 
 
628
func (c *configInternal) File(name string) string {
 
629
        return path.Join(c.Dir(), name)
 
630
}
 
631
 
 
632
func (c *configInternal) DataDir() string {
 
633
        return c.paths.DataDir
 
634
}
 
635
 
 
636
func (c *configInternal) MetricsSpoolDir() string {
 
637
        return c.paths.MetricsSpoolDir
 
638
}
 
639
 
 
640
func (c *configInternal) LogDir() string {
 
641
        return c.paths.LogDir
 
642
}
 
643
 
 
644
func (c *configInternal) SystemIdentityPath() string {
 
645
        return filepath.Join(c.paths.DataDir, SystemIdentity)
 
646
}
 
647
 
 
648
func (c *configInternal) Jobs() []multiwatcher.MachineJob {
 
649
        return c.jobs
 
650
}
 
651
 
 
652
func (c *configInternal) Nonce() string {
 
653
        return c.nonce
 
654
}
 
655
 
 
656
func (c *configInternal) UpgradedToVersion() version.Number {
 
657
        return c.upgradedToVersion
 
658
}
 
659
 
 
660
func (c *configInternal) CACert() string {
 
661
        return c.caCert
 
662
}
 
663
 
 
664
func (c *configInternal) Value(key string) string {
 
665
        return c.values[key]
 
666
}
 
667
 
 
668
func (c *configInternal) StateServingInfo() (params.StateServingInfo, bool) {
 
669
        if c.servingInfo == nil {
 
670
                return params.StateServingInfo{}, false
 
671
        }
 
672
        return *c.servingInfo, true
 
673
}
 
674
 
 
675
func (c *configInternal) SetStateServingInfo(info params.StateServingInfo) {
 
676
        c.servingInfo = &info
 
677
}
 
678
 
 
679
func (c *configInternal) APIAddresses() ([]string, error) {
 
680
        if c.apiDetails == nil {
 
681
                return []string{}, errors.New("No apidetails in config")
 
682
        }
 
683
        return append([]string{}, c.apiDetails.addresses...), nil
 
684
}
 
685
 
 
686
func (c *configInternal) OldPassword() string {
 
687
        return c.oldPassword
 
688
}
 
689
 
 
690
func (c *configInternal) Tag() names.Tag {
 
691
        return c.tag
 
692
}
 
693
 
 
694
func (c *configInternal) Model() names.ModelTag {
 
695
        return c.model
 
696
}
 
697
 
 
698
func (c *configInternal) Dir() string {
 
699
        return Dir(c.paths.DataDir, c.tag)
 
700
}
 
701
 
 
702
func (c *configInternal) check() error {
 
703
        if c.stateDetails == nil && c.apiDetails == nil {
 
704
                return errors.Trace(requiredError("state or API addresses"))
 
705
        }
 
706
        if c.stateDetails != nil {
 
707
                if err := checkAddrs(c.stateDetails.addresses, "controller address"); err != nil {
 
708
                        return err
 
709
                }
 
710
        }
 
711
        if c.apiDetails != nil {
 
712
                if err := checkAddrs(c.apiDetails.addresses, "API server address"); err != nil {
 
713
                        return err
 
714
                }
 
715
        }
 
716
        return nil
 
717
}
 
718
 
 
719
// MongoVersion implements Config.
 
720
func (c *configInternal) MongoVersion() mongo.Version {
 
721
        v, err := mongo.NewVersion(c.mongoVersion)
 
722
        if err != nil {
 
723
                return mongo.Mongo24
 
724
        }
 
725
        return v
 
726
}
 
727
 
 
728
// SetMongoVersion implements configSetterOnly.
 
729
func (c *configInternal) SetMongoVersion(v mongo.Version) {
 
730
        c.mongoVersion = v.String()
 
731
}
 
732
 
 
733
var validAddr = regexp.MustCompile("^.+:[0-9]+$")
 
734
 
 
735
func checkAddrs(addrs []string, what string) error {
 
736
        if len(addrs) == 0 {
 
737
                return errors.Trace(requiredError(what))
 
738
        }
 
739
        for _, a := range addrs {
 
740
                if !validAddr.MatchString(a) {
 
741
                        return errors.Errorf("invalid %s %q", what, a)
 
742
                }
 
743
        }
 
744
        return nil
 
745
}
 
746
 
 
747
func (c *configInternal) fileContents() ([]byte, error) {
 
748
        data, err := currentFormat.marshal(c)
 
749
        if err != nil {
 
750
                return nil, err
 
751
        }
 
752
        var buf bytes.Buffer
 
753
        fmt.Fprintf(&buf, "%s%s\n", formatPrefix, currentFormat.version())
 
754
        buf.Write(data)
 
755
        return buf.Bytes(), nil
 
756
}
 
757
 
 
758
// WriteCommands is defined on Config interface.
 
759
func (c *configInternal) WriteCommands(renderer shell.Renderer) ([]string, error) {
 
760
        data, err := c.fileContents()
 
761
        if err != nil {
 
762
                return nil, errors.Trace(err)
 
763
        }
 
764
        commands := renderer.MkdirAll(c.Dir())
 
765
        filename := c.File(agentConfigFilename)
 
766
        commands = append(commands, renderer.WriteFile(filename, data)...)
 
767
        commands = append(commands, renderer.Chmod(filename, 0600)...)
 
768
        return commands, nil
 
769
}
 
770
 
 
771
// APIInfo is defined on Config interface.
 
772
func (c *configInternal) APIInfo() (*api.Info, bool) {
 
773
        if c.apiDetails == nil || c.apiDetails.addresses == nil {
 
774
                return nil, false
 
775
        }
 
776
        servingInfo, isController := c.StateServingInfo()
 
777
        addrs := c.apiDetails.addresses
 
778
        if isController {
 
779
                port := servingInfo.APIPort
 
780
                localAPIAddr := net.JoinHostPort("localhost", strconv.Itoa(port))
 
781
                addrInAddrs := false
 
782
                for _, addr := range addrs {
 
783
                        if addr == localAPIAddr {
 
784
                                addrInAddrs = true
 
785
                                break
 
786
                        }
 
787
                }
 
788
                if !addrInAddrs {
 
789
                        addrs = append(addrs, localAPIAddr)
 
790
                }
 
791
        }
 
792
        return &api.Info{
 
793
                Addrs:    addrs,
 
794
                Password: c.apiDetails.password,
 
795
                CACert:   c.caCert,
 
796
                Tag:      c.tag,
 
797
                Nonce:    c.nonce,
 
798
                ModelTag: c.model,
 
799
        }, true
 
800
}
 
801
 
 
802
// MongoInfo is defined on Config interface.
 
803
func (c *configInternal) MongoInfo() (info *mongo.MongoInfo, ok bool) {
 
804
        ssi, ok := c.StateServingInfo()
 
805
        if !ok {
 
806
                return nil, false
 
807
        }
 
808
        addr := net.JoinHostPort("127.0.0.1", strconv.Itoa(ssi.StatePort))
 
809
        return &mongo.MongoInfo{
 
810
                Info: mongo.Info{
 
811
                        Addrs:  []string{addr},
 
812
                        CACert: c.caCert,
 
813
                },
 
814
                Password: c.stateDetails.password,
 
815
                Tag:      c.tag,
 
816
        }, true
 
817
}