~dave-cheney/juju-core/153-fix-release-tools-script

« back to all changes in this revision

Viewing changes to environs/manual/provisioner.go

  • Committer: Tarmac
  • Author(s): Andrew Wilkins
  • Date: 2013-08-30 01:48:39 UTC
  • mfrom: (1628.11.22 juju-add-machine)
  • Revision ID: tarmac-20130830014839-infsllif52ywy6yq
[r=axwalk] Update add-machine for manual provisioning

juju add-machine is updated to use a new package,
environs/manual, to manually provision tools and
a machine agent to an existing machine.

When a manually provisioned machine is destroyed
via juju destroy-machine, the machine agent will
detect its termination and remove its upstart
configuration file. There is currently no cleanup
of the data or log directories; this will be done
in a follow-up pending discussion.

When the machine goes to Dead, a provisioner will
remove the machine from state just like any other
machine.

TODO: destroy-environment will currently leak
manually provisioned machines. A follow-up will
address this by requiring users to individually
destroy-machine before destroy-environment will
proceed. Alternatively (or perhaps additionally),
destroy-environment may take a flag to automatically
do this.

https://codereview.appspot.com/12831043/

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
        "errors"
 
8
        "fmt"
 
9
        "strings"
 
10
 
 
11
        "launchpad.net/loggo"
 
12
 
 
13
        "launchpad.net/juju-core/environs"
 
14
        "launchpad.net/juju-core/instance"
 
15
        "launchpad.net/juju-core/juju"
 
16
        "launchpad.net/juju-core/state"
 
17
        "launchpad.net/juju-core/state/api"
 
18
        "launchpad.net/juju-core/tools"
 
19
        "launchpad.net/juju-core/utils"
 
20
        "launchpad.net/juju-core/worker/provisioner"
 
21
)
 
22
 
 
23
const manualInstancePrefix = "manual:"
 
24
 
 
25
var logger = loggo.GetLogger("juju.environs.manual")
 
26
 
 
27
type ProvisionMachineArgs struct {
 
28
        // Host is the SSH host: [user@]host
 
29
        Host string
 
30
 
 
31
        // DataDir is the root directory for juju data.
 
32
        // If left blank, the default location "/var/lib/juju" will be used.
 
33
        DataDir string
 
34
 
 
35
        // State is the *state.State object to register the machine with.
 
36
        State *state.State
 
37
 
 
38
        // Tools to install on the machine. If nil, tools will be automatically
 
39
        // chosen using environs/tools FindInstanceTools.
 
40
        Tools *tools.Tools
 
41
}
 
42
 
 
43
// ErrProvisioned is returned by ProvisionMachine if the target
 
44
// machine has an existing machine agent.
 
45
var ErrProvisioned = errors.New("machine is already provisioned")
 
46
 
 
47
// ProvisionMachine provisions a machine agent to an existing host, via
 
48
// an SSH connection to the specified host. The host may optionally be preceded
 
49
// with a login username, as in [user@]host.
 
50
//
 
51
// On successful completion, this function will return the state.Machine
 
52
// that was entered into state.
 
53
func ProvisionMachine(args ProvisionMachineArgs) (m *state.Machine, err error) {
 
54
        defer func() {
 
55
                if m != nil && err != nil {
 
56
                        m.EnsureDead()
 
57
                        m.Remove()
 
58
                        m = nil
 
59
                }
 
60
        }()
 
61
 
 
62
        var env environs.Environ
 
63
        if conn, err := juju.NewConnFromState(args.State); err != nil {
 
64
                return nil, err
 
65
        } else {
 
66
                env = conn.Environ
 
67
        }
 
68
 
 
69
        sshHostWithoutUser := args.Host
 
70
        if at := strings.Index(sshHostWithoutUser, "@"); at != -1 {
 
71
                sshHostWithoutUser = sshHostWithoutUser[at+1:]
 
72
        }
 
73
        addrs, err := instance.HostAddresses(sshHostWithoutUser)
 
74
        if err != nil {
 
75
                return nil, err
 
76
        }
 
77
 
 
78
        provisioned, err := checkProvisioned(args.Host)
 
79
        if err != nil {
 
80
                err = fmt.Errorf("error checking if provisioned: %v", err)
 
81
                return nil, err
 
82
        }
 
83
        if provisioned {
 
84
                return nil, ErrProvisioned
 
85
        }
 
86
 
 
87
        hc, series, err := detectSeriesAndHardwareCharacteristics(args.Host)
 
88
        if err != nil {
 
89
                err = fmt.Errorf("error detecting hardware characteristics: %v", err)
 
90
                return nil, err
 
91
        }
 
92
 
 
93
        // Generate a unique nonce for the machine.
 
94
        uuid, err := utils.NewUUID()
 
95
        if err != nil {
 
96
                return nil, err
 
97
        }
 
98
 
 
99
        // Inject a new machine into state.
 
100
        //
 
101
        // There will never be a corresponding "instance" that any provider
 
102
        // knows about. This is fine, and works well with the provisioner
 
103
        // task. The provisioner task will happily remove any and all dead
 
104
        // machines from state, but will ignore the associated instance ID
 
105
        // if it isn't one that the environment provider knows about.
 
106
        instanceId := instance.Id(manualInstancePrefix + sshHostWithoutUser)
 
107
        nonce := fmt.Sprintf("%s:%s", instanceId, uuid.String())
 
108
        m, err = injectMachine(injectMachineArgs{
 
109
                st:         args.State,
 
110
                instanceId: instanceId,
 
111
                addrs:      addrs,
 
112
                series:     series,
 
113
                hc:         hc,
 
114
                nonce:      nonce,
 
115
        })
 
116
        if err != nil {
 
117
                return nil, err
 
118
        }
 
119
        stateInfo, apiInfo, err := setupAuthentication(env, m)
 
120
        if err != nil {
 
121
                return m, err
 
122
        }
 
123
 
 
124
        // Finally, provision the machine agent.
 
125
        err = provisionMachineAgent(provisionMachineAgentArgs{
 
126
                host:      args.Host,
 
127
                dataDir:   args.DataDir,
 
128
                env:       env,
 
129
                machine:   m,
 
130
                nonce:     nonce,
 
131
                stateInfo: stateInfo,
 
132
                apiInfo:   apiInfo,
 
133
                series:    series,
 
134
                arch:      *hc.Arch,
 
135
                tools:     args.Tools,
 
136
        })
 
137
        if err != nil {
 
138
                return m, err
 
139
        }
 
140
 
 
141
        logger.Infof("Provisioned machine %v", m)
 
142
        return m, nil
 
143
}
 
144
 
 
145
type injectMachineArgs struct {
 
146
        st         *state.State
 
147
        instanceId instance.Id
 
148
        addrs      []instance.Address
 
149
        series     string
 
150
        hc         instance.HardwareCharacteristics
 
151
        nonce      string
 
152
}
 
153
 
 
154
// injectMachine injects a machine into state with provisioned status.
 
155
func injectMachine(args injectMachineArgs) (m *state.Machine, err error) {
 
156
        defer func() {
 
157
                if m != nil && err != nil {
 
158
                        m.EnsureDead()
 
159
                        m.Remove()
 
160
                }
 
161
        }()
 
162
        m, err = args.st.InjectMachine(&state.AddMachineParams{
 
163
                Series:                  args.series,
 
164
                InstanceId:              args.instanceId,
 
165
                HardwareCharacteristics: args.hc,
 
166
                Nonce: args.nonce,
 
167
                Jobs:  []state.MachineJob{state.JobHostUnits},
 
168
        })
 
169
        if err != nil {
 
170
                return nil, err
 
171
        }
 
172
        if err = m.SetAddresses(args.addrs); err != nil {
 
173
                return nil, err
 
174
        }
 
175
        return m, nil
 
176
}
 
177
 
 
178
func setupAuthentication(env environs.Environ, m *state.Machine) (*state.Info, *api.Info, error) {
 
179
        auth, err := provisioner.NewSimpleAuthenticator(env)
 
180
        if err != nil {
 
181
                return nil, nil, err
 
182
        }
 
183
        return auth.SetupAuthentication(m)
 
184
}