~nskaggs/+junk/xenial-test

« back to all changes in this revision

Viewing changes to src/github.com/juju/juju/provider/manual/environ.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 manual
 
5
 
 
6
import (
 
7
        "bytes"
 
8
        "fmt"
 
9
        "os"
 
10
        "path"
 
11
        "path/filepath"
 
12
        "strings"
 
13
        "sync"
 
14
 
 
15
        "github.com/juju/errors"
 
16
        "github.com/juju/loggo"
 
17
        "github.com/juju/utils"
 
18
        "github.com/juju/utils/ssh"
 
19
 
 
20
        "github.com/juju/juju/agent"
 
21
        "github.com/juju/juju/cloudconfig/instancecfg"
 
22
        "github.com/juju/juju/constraints"
 
23
        "github.com/juju/juju/environs"
 
24
        "github.com/juju/juju/environs/config"
 
25
        "github.com/juju/juju/environs/manual"
 
26
        "github.com/juju/juju/instance"
 
27
        "github.com/juju/juju/juju/names"
 
28
        "github.com/juju/juju/mongo"
 
29
        "github.com/juju/juju/network"
 
30
        "github.com/juju/juju/provider/common"
 
31
        "github.com/juju/juju/worker/terminationworker"
 
32
)
 
33
 
 
34
const (
 
35
        // BootstrapInstanceId is the instance ID used
 
36
        // for the manual provider's bootstrap instance.
 
37
        BootstrapInstanceId instance.Id = "manual:"
 
38
)
 
39
 
 
40
var (
 
41
        logger                                       = loggo.GetLogger("juju.provider.manual")
 
42
        manualCheckProvisioned                       = manual.CheckProvisioned
 
43
        manualDetectSeriesAndHardwareCharacteristics = manual.DetectSeriesAndHardwareCharacteristics
 
44
)
 
45
 
 
46
type manualEnviron struct {
 
47
        host     string
 
48
        cfgmutex sync.Mutex
 
49
        cfg      *environConfig
 
50
}
 
51
 
 
52
var errNoStartInstance = errors.New("manual provider cannot start instances")
 
53
var errNoStopInstance = errors.New("manual provider cannot stop instances")
 
54
 
 
55
// MaintainInstance is specified in the InstanceBroker interface.
 
56
func (*manualEnviron) MaintainInstance(args environs.StartInstanceParams) error {
 
57
        return nil
 
58
}
 
59
 
 
60
func (*manualEnviron) StartInstance(args environs.StartInstanceParams) (*environs.StartInstanceResult, error) {
 
61
        return nil, errNoStartInstance
 
62
}
 
63
 
 
64
func (*manualEnviron) StopInstances(...instance.Id) error {
 
65
        return errNoStopInstance
 
66
}
 
67
 
 
68
func (e *manualEnviron) AllInstances() ([]instance.Instance, error) {
 
69
        return e.Instances([]instance.Id{BootstrapInstanceId})
 
70
}
 
71
 
 
72
func (e *manualEnviron) envConfig() (cfg *environConfig) {
 
73
        e.cfgmutex.Lock()
 
74
        cfg = e.cfg
 
75
        e.cfgmutex.Unlock()
 
76
        return cfg
 
77
}
 
78
 
 
79
func (e *manualEnviron) Config() *config.Config {
 
80
        return e.envConfig().Config
 
81
}
 
82
 
 
83
// PrepareForBootstrap is part of the Environ interface.
 
84
func (e *manualEnviron) PrepareForBootstrap(ctx environs.BootstrapContext) error {
 
85
        if err := ensureBootstrapUbuntuUser(ctx, e.host, e.envConfig()); err != nil {
 
86
                return err
 
87
        }
 
88
        return nil
 
89
}
 
90
 
 
91
// Create is part of the Environ interface.
 
92
func (e *manualEnviron) Create(environs.CreateParams) error {
 
93
        return nil
 
94
}
 
95
 
 
96
// Bootstrap is part of the Environ interface.
 
97
func (e *manualEnviron) Bootstrap(ctx environs.BootstrapContext, args environs.BootstrapParams) (*environs.BootstrapResult, error) {
 
98
        provisioned, err := manualCheckProvisioned(e.host)
 
99
        if err != nil {
 
100
                return nil, errors.Annotate(err, "failed to check provisioned status")
 
101
        }
 
102
        if provisioned {
 
103
                return nil, manual.ErrProvisioned
 
104
        }
 
105
        hc, series, err := manualDetectSeriesAndHardwareCharacteristics(e.host)
 
106
        if err != nil {
 
107
                return nil, err
 
108
        }
 
109
        finalize := func(ctx environs.BootstrapContext, icfg *instancecfg.InstanceConfig, _ environs.BootstrapDialOpts) error {
 
110
                icfg.Bootstrap.BootstrapMachineInstanceId = BootstrapInstanceId
 
111
                icfg.Bootstrap.BootstrapMachineHardwareCharacteristics = &hc
 
112
                if err := instancecfg.FinishInstanceConfig(icfg, e.Config()); err != nil {
 
113
                        return err
 
114
                }
 
115
                return common.ConfigureMachine(ctx, ssh.DefaultClient, e.host, icfg)
 
116
        }
 
117
 
 
118
        result := &environs.BootstrapResult{
 
119
                Arch:     *hc.Arch,
 
120
                Series:   series,
 
121
                Finalize: finalize,
 
122
        }
 
123
        return result, nil
 
124
}
 
125
 
 
126
// ControllerInstances is specified in the Environ interface.
 
127
func (e *manualEnviron) ControllerInstances(controllerUUID string) ([]instance.Id, error) {
 
128
        arg0 := filepath.Base(os.Args[0])
 
129
        if arg0 != names.Jujud {
 
130
                // Not running inside the controller, so we must
 
131
                // verify the host.
 
132
                if err := e.verifyBootstrapHost(); err != nil {
 
133
                        return nil, err
 
134
                }
 
135
        }
 
136
        return []instance.Id{BootstrapInstanceId}, nil
 
137
}
 
138
 
 
139
func (e *manualEnviron) verifyBootstrapHost() error {
 
140
        // First verify that the environment is bootstrapped by checking
 
141
        // if the agents directory exists. Note that we cannot test the
 
142
        // root data directory, as that is created in the process of
 
143
        // initialising sshstorage.
 
144
        agentsDir := path.Join(agent.DefaultPaths.DataDir, "agents")
 
145
        const noAgentDir = "no-agent-dir"
 
146
        stdin := fmt.Sprintf(
 
147
                "test -d %s || echo %s",
 
148
                utils.ShQuote(agentsDir),
 
149
                noAgentDir,
 
150
        )
 
151
        out, err := runSSHCommand(
 
152
                "ubuntu@"+e.host,
 
153
                []string{"/bin/bash"},
 
154
                stdin,
 
155
        )
 
156
        if err != nil {
 
157
                return err
 
158
        }
 
159
        if out = strings.TrimSpace(out); len(out) > 0 {
 
160
                if out == noAgentDir {
 
161
                        return environs.ErrNotBootstrapped
 
162
                }
 
163
                err := errors.Errorf("unexpected output: %q", out)
 
164
                logger.Infof(err.Error())
 
165
                return err
 
166
        }
 
167
        return nil
 
168
}
 
169
 
 
170
func (e *manualEnviron) SetConfig(cfg *config.Config) error {
 
171
        e.cfgmutex.Lock()
 
172
        defer e.cfgmutex.Unlock()
 
173
        _, err := manualProvider{}.validate(cfg, e.cfg.Config)
 
174
        if err != nil {
 
175
                return err
 
176
        }
 
177
        e.cfg = newModelConfig(cfg, cfg.UnknownAttrs())
 
178
        return nil
 
179
}
 
180
 
 
181
// Implements environs.Environ.
 
182
//
 
183
// This method will only ever return an Instance for the Id
 
184
// BootstrapInstanceId. If any others are specified, then
 
185
// ErrPartialInstances or ErrNoInstances will result.
 
186
func (e *manualEnviron) Instances(ids []instance.Id) (instances []instance.Instance, err error) {
 
187
        instances = make([]instance.Instance, len(ids))
 
188
        var found bool
 
189
        for i, id := range ids {
 
190
                if id == BootstrapInstanceId {
 
191
                        instances[i] = manualBootstrapInstance{e.host}
 
192
                        found = true
 
193
                } else {
 
194
                        err = environs.ErrPartialInstances
 
195
                }
 
196
        }
 
197
        if !found {
 
198
                err = environs.ErrNoInstances
 
199
        }
 
200
        return instances, err
 
201
}
 
202
 
 
203
var runSSHCommand = func(host string, command []string, stdin string) (stdout string, err error) {
 
204
        cmd := ssh.Command(host, command, nil)
 
205
        cmd.Stdin = strings.NewReader(stdin)
 
206
        var stdoutBuf, stderrBuf bytes.Buffer
 
207
        cmd.Stdout = &stdoutBuf
 
208
        cmd.Stderr = &stderrBuf
 
209
        if err := cmd.Run(); err != nil {
 
210
                if stderr := strings.TrimSpace(stderrBuf.String()); len(stderr) > 0 {
 
211
                        err = errors.Annotate(err, stderr)
 
212
                }
 
213
                return "", err
 
214
        }
 
215
        return stdoutBuf.String(), nil
 
216
}
 
217
 
 
218
func (e *manualEnviron) Destroy() error {
 
219
        script := `
 
220
set -x
 
221
touch %s
 
222
pkill -%d jujud && exit
 
223
stop %s
 
224
rm -f /etc/init/juju*
 
225
rm -fr %s %s
 
226
exit 0
 
227
`
 
228
        script = fmt.Sprintf(
 
229
                script,
 
230
                // WARNING: this is linked with the use of uninstallFile in
 
231
                // the agent package. Don't change it without extreme care,
 
232
                // and handling for mismatches with already-deployed agents.
 
233
                utils.ShQuote(path.Join(
 
234
                        agent.DefaultPaths.DataDir,
 
235
                        agent.UninstallFile,
 
236
                )),
 
237
                terminationworker.TerminationSignal,
 
238
                mongo.ServiceName,
 
239
                utils.ShQuote(agent.DefaultPaths.DataDir),
 
240
                utils.ShQuote(agent.DefaultPaths.LogDir),
 
241
        )
 
242
        _, err := runSSHCommand(
 
243
                "ubuntu@"+e.host,
 
244
                []string{"sudo", "/bin/bash"}, script,
 
245
        )
 
246
        return err
 
247
}
 
248
 
 
249
// DestroyController implements the Environ interface.
 
250
func (e *manualEnviron) DestroyController(controllerUUID string) error {
 
251
        return e.Destroy()
 
252
}
 
253
 
 
254
func (*manualEnviron) PrecheckInstance(series string, _ constraints.Value, placement string) error {
 
255
        return errors.New(`use "juju add-machine ssh:[user@]<host>" to provision machines`)
 
256
}
 
257
 
 
258
var unsupportedConstraints = []string{
 
259
        constraints.CpuPower,
 
260
        constraints.InstanceType,
 
261
        constraints.Tags,
 
262
        constraints.VirtType,
 
263
}
 
264
 
 
265
// ConstraintsValidator is defined on the Environs interface.
 
266
func (e *manualEnviron) ConstraintsValidator() (constraints.Validator, error) {
 
267
        validator := constraints.NewValidator()
 
268
        validator.RegisterUnsupported(unsupportedConstraints)
 
269
        return validator, nil
 
270
}
 
271
 
 
272
func (e *manualEnviron) OpenPorts(ports []network.PortRange) error {
 
273
        return nil
 
274
}
 
275
 
 
276
func (e *manualEnviron) ClosePorts(ports []network.PortRange) error {
 
277
        return nil
 
278
}
 
279
 
 
280
func (e *manualEnviron) Ports() ([]network.PortRange, error) {
 
281
        return nil, nil
 
282
}
 
283
 
 
284
func (*manualEnviron) Provider() environs.EnvironProvider {
 
285
        return manualProvider{}
 
286
}