~ubuntu-branches/ubuntu/trusty/juju-core/trusty-proposed

« back to all changes in this revision

Viewing changes to src/launchpad.net/juju-core/environs/manual/provisioner.go

  • Committer: Package Import Robot
  • Author(s): James Page
  • Date: 2014-01-29 11:40:20 UTC
  • mfrom: (23.1.1 trusty-proposed)
  • Revision ID: package-import@ubuntu.com-20140129114020-ejieitm8smtt5vln
Tags: 1.17.1-0ubuntu2
d/tests/local-provider: Don't fail tests if ~/.juju is present as its
created by the juju version command. 

Show diffs side-by-side

added added

removed removed

Lines of Context:
14
14
 
15
15
        coreCloudinit "launchpad.net/juju-core/cloudinit"
16
16
        "launchpad.net/juju-core/cloudinit/sshinit"
17
 
        "launchpad.net/juju-core/constraints"
18
 
        "launchpad.net/juju-core/environs"
19
17
        "launchpad.net/juju-core/environs/cloudinit"
20
18
        "launchpad.net/juju-core/environs/config"
21
19
        "launchpad.net/juju-core/instance"
23
21
        "launchpad.net/juju-core/state"
24
22
        "launchpad.net/juju-core/state/api"
25
23
        "launchpad.net/juju-core/state/api/params"
 
24
        "launchpad.net/juju-core/state/statecmd"
26
25
        "launchpad.net/juju-core/tools"
27
26
        "launchpad.net/juju-core/utils"
28
27
)
72
71
        if err != nil {
73
72
                return "", err
74
73
        }
 
74
        // Used for fallback to 1.16 code
 
75
        var stateConn *juju.Conn
75
76
        defer func() {
76
77
                if machineId != "" && err != nil {
77
78
                        logger.Errorf("provisioning failed, removing machine %v: %v", machineId, err)
78
 
                        client.DestroyMachines(machineId)
 
79
                        // If we have stateConn, then we are in 1.16
 
80
                        // compatibility mode and we should issue
 
81
                        // DestroyMachines directly on the state, rather than
 
82
                        // via API (because DestroyMachine *also* didn't exist
 
83
                        // in 1.16, though it will be in 1.16.5).
 
84
                        // TODO: When this compatibility code is removed, we
 
85
                        // should remove the method in state as well (as long
 
86
                        // as destroy-machine also no longer needs it.)
 
87
                        var cleanupErr error
 
88
                        if stateConn != nil {
 
89
                                cleanupErr = statecmd.DestroyMachines1dot16(stateConn.State, machineId)
 
90
                        } else {
 
91
                                cleanupErr = client.DestroyMachines(machineId)
 
92
                        }
 
93
                        if cleanupErr != nil {
 
94
                                logger.Warningf("error cleaning up machine: %s", cleanupErr)
 
95
                        }
79
96
                        machineId = ""
80
97
                }
 
98
                if stateConn != nil {
 
99
                        stateConn.Close()
 
100
                        stateConn = nil
 
101
                }
81
102
                client.Close()
82
103
        }()
83
104
 
84
 
        // Generate a unique nonce for the machine.
85
 
        uuid, err := utils.NewUUID()
 
105
        // Create the "ubuntu" user and initialise passwordless sudo. We populate
 
106
        // the ubuntu user's authorized_keys file with the public keys in the current
 
107
        // user's ~/.ssh directory. The authenticationworker will later update the
 
108
        // ubuntu user's authorized_keys.
 
109
        user, hostname := splitUserHost(args.Host)
 
110
        authorizedKeys, err := config.ReadAuthorizedKeys("")
 
111
        if err := InitUbuntuUser(hostname, user, authorizedKeys, args.Stdin, args.Stdout); err != nil {
 
112
                return "", err
 
113
        }
 
114
 
 
115
        machineParams, err := gatherMachineParams(hostname)
86
116
        if err != nil {
87
117
                return "", err
88
118
        }
89
 
        instanceId := instance.Id(manualInstancePrefix + hostWithoutUser(args.Host))
90
 
        nonce := fmt.Sprintf("%s:%s", instanceId, uuid.String())
91
119
 
92
120
        // Inform Juju that the machine exists.
93
 
        machineId, series, arch, err := recordMachineInState(client, args.Host, nonce, instanceId)
 
121
        machineId, err = recordMachineInState(client, *machineParams)
 
122
        if params.IsCodeNotImplemented(err) {
 
123
                logger.Infof("AddMachines not supported by the API server, " +
 
124
                        "falling back to 1.16 compatibility mode (direct DB access)")
 
125
                stateConn, err = juju.NewConnFromName(args.EnvName)
 
126
                if err == nil {
 
127
                        machineId, err = recordMachineInState1dot16(stateConn, *machineParams)
 
128
                }
 
129
        }
94
130
        if err != nil {
95
131
                return "", err
96
132
        }
97
133
 
 
134
        var configParameters params.MachineConfig
 
135
        if stateConn == nil {
 
136
                configParameters, err = client.MachineConfig(machineId)
 
137
        } else {
 
138
                configParameters, err = statecmd.MachineConfig(stateConn.State, machineId)
 
139
        }
 
140
        if err != nil {
 
141
                return "", err
 
142
        }
98
143
        // Gather the information needed by the machine agent to run the provisioning script.
99
 
        mcfg, err := createMachineConfig(client, machineId, series, arch, nonce, args.DataDir)
 
144
        mcfg, err := statecmd.FinishMachineConfig(configParameters, machineId, machineParams.Nonce, args.DataDir)
100
145
        if err != nil {
101
146
                return machineId, err
102
147
        }
103
148
 
104
149
        // Finally, provision the machine agent.
105
 
        err = provisionMachineAgent(args.Host, mcfg, args.Stdin, args.Stdout, args.Stderr)
 
150
        err = provisionMachineAgent(hostname, mcfg, args.Stderr)
106
151
        if err != nil {
107
152
                return machineId, err
108
153
        }
111
156
        return machineId, nil
112
157
}
113
158
 
114
 
func hostWithoutUser(host string) string {
115
 
        hostWithoutUser := host
116
 
        if at := strings.Index(hostWithoutUser, "@"); at != -1 {
117
 
                hostWithoutUser = hostWithoutUser[at+1:]
 
159
func splitUserHost(host string) (string, string) {
 
160
        if at := strings.Index(host, "@"); at != -1 {
 
161
                return host[:at], host[at+1:]
118
162
        }
119
 
        return hostWithoutUser
 
163
        return "", host
120
164
}
121
165
 
122
166
func recordMachineInState(
123
 
        client *api.Client, host, nonce string, instanceId instance.Id) (machineId, series, arch string, err error) {
124
 
 
 
167
        client *api.Client, machineParams params.AddMachineParams) (machineId string, err error) {
 
168
        results, err := client.AddMachines([]params.AddMachineParams{machineParams})
 
169
        if err != nil {
 
170
                return "", err
 
171
        }
 
172
        // Currently, only one machine is added, but in future there may be several added in one call.
 
173
        machineInfo := results[0]
 
174
        if machineInfo.Error != nil {
 
175
                return "", machineInfo.Error
 
176
        }
 
177
        return machineInfo.Machine, nil
 
178
}
 
179
 
 
180
// convertToStateJobs takes a slice of params.MachineJob and makes them a slice of state.MachineJob
 
181
func convertToStateJobs(jobs []params.MachineJob) ([]state.MachineJob, error) {
 
182
        outJobs := make([]state.MachineJob, len(jobs))
 
183
        var err error
 
184
        for j, job := range jobs {
 
185
                if outJobs[j], err = state.MachineJobFromParams(job); err != nil {
 
186
                        return nil, err
 
187
                }
 
188
        }
 
189
        return outJobs, nil
 
190
}
 
191
 
 
192
func recordMachineInState1dot16(
 
193
        stateConn *juju.Conn, machineParams params.AddMachineParams) (machineId string, err error) {
 
194
        stateJobs, err := convertToStateJobs(machineParams.Jobs)
 
195
        if err != nil {
 
196
                return "", err
 
197
        }
 
198
        //if p.Series == "" {
 
199
        //      p.Series = defaultSeries
 
200
        //}
 
201
        template := state.MachineTemplate{
 
202
                Series:      machineParams.Series,
 
203
                Constraints: machineParams.Constraints,
 
204
                InstanceId:  machineParams.InstanceId,
 
205
                Jobs:        stateJobs,
 
206
                Nonce:       machineParams.Nonce,
 
207
                HardwareCharacteristics: machineParams.HardwareCharacteristics,
 
208
                Addresses:               machineParams.Addrs,
 
209
        }
 
210
        machine, err := stateConn.State.AddOneMachine(template)
 
211
        if err != nil {
 
212
                return "", err
 
213
        }
 
214
        return machine.Id(), nil
 
215
}
 
216
 
 
217
// gatherMachineParams collects all the information we know about the machine
 
218
// we are about to provision. It will SSH into that machine as the ubuntu user.
 
219
// The hostname supplied should not include a username.
 
220
// If we can, we will reverse lookup the hostname by its IP address, and use
 
221
// the DNS resolved name, rather than the name that was supplied
 
222
func gatherMachineParams(hostname string) (*params.AddMachineParams, error) {
 
223
 
 
224
        // Generate a unique nonce for the machine.
 
225
        uuid, err := utils.NewUUID()
 
226
        if err != nil {
 
227
                return nil, err
 
228
        }
125
229
        // First, gather the parameters needed to inject the existing host into state.
126
 
        sshHostWithoutUser := hostWithoutUser(host)
127
 
        if ip := net.ParseIP(sshHostWithoutUser); ip != nil {
 
230
        if ip := net.ParseIP(hostname); ip != nil {
128
231
                // Do a reverse-lookup on the IP. The IP may not have
129
232
                // a DNS entry, so just log a warning if this fails.
130
233
                names, err := net.LookupAddr(ip.String())
132
235
                        logger.Infof("failed to resolve %v: %v", ip, err)
133
236
                } else {
134
237
                        logger.Infof("resolved %v to %v", ip, names)
135
 
                        sshHostWithoutUser = names[0]
 
238
                        hostname = names[0]
 
239
                        // TODO: jam 2014-01-09 https://bugs.launchpad.net/bugs/1267387
 
240
                        // We change what 'hostname' we are using here (rather
 
241
                        // than an IP address we use the DNS name). I'm not
 
242
                        // sure why that is better, but if we are changing the
 
243
                        // host, we should probably be returning the hostname
 
244
                        // to the parent function.
 
245
                        // Also, we don't seem to try and compare if 'ip' is in
 
246
                        // the list of addrs returned from
 
247
                        // instance.HostAddresses in case you might get
 
248
                        // multiple and one of them is what you are supposed to
 
249
                        // be using.
136
250
                }
137
251
        }
138
 
        addrs, err := instance.HostAddresses(sshHostWithoutUser)
 
252
        addrs, err := instance.HostAddresses(hostname)
139
253
        if err != nil {
140
 
                return "", "", "", err
 
254
                return nil, err
141
255
        }
142
 
        logger.Infof("addresses for %v: %v", sshHostWithoutUser, addrs)
 
256
        logger.Infof("addresses for %v: %v", hostname, addrs)
143
257
 
144
 
        provisioned, err := checkProvisioned(host)
 
258
        provisioned, err := checkProvisioned(hostname)
145
259
        if err != nil {
146
260
                err = fmt.Errorf("error checking if provisioned: %v", err)
147
 
                return "", "", "", err
 
261
                return nil, err
148
262
        }
149
263
        if provisioned {
150
 
                return "", "", "", ErrProvisioned
 
264
                return nil, ErrProvisioned
151
265
        }
152
266
 
153
 
        hc, series, err := DetectSeriesAndHardwareCharacteristics(host)
 
267
        hc, series, err := DetectSeriesAndHardwareCharacteristics(hostname)
154
268
        if err != nil {
155
269
                err = fmt.Errorf("error detecting hardware characteristics: %v", err)
156
 
                return "", "", "", err
 
270
                return nil, err
157
271
        }
158
272
 
159
 
        // Inject a new machine into state.
160
 
        //
161
273
        // There will never be a corresponding "instance" that any provider
162
274
        // knows about. This is fine, and works well with the provisioner
163
275
        // task. The provisioner task will happily remove any and all dead
164
276
        // machines from state, but will ignore the associated instance ID
165
277
        // if it isn't one that the environment provider knows about.
166
 
        machineParams := params.AddMachineParams{
 
278
 
 
279
        instanceId := instance.Id(manualInstancePrefix + hostname)
 
280
        nonce := fmt.Sprintf("%s:%s", instanceId, uuid.String())
 
281
        machineParams := &params.AddMachineParams{
167
282
                Series:                  series,
168
283
                HardwareCharacteristics: hc,
169
284
                InstanceId:              instanceId,
171
286
                Addrs:                   addrs,
172
287
                Jobs:                    []params.MachineJob{params.JobHostUnits},
173
288
        }
174
 
        results, err := client.AddMachines([]params.AddMachineParams{machineParams})
175
 
        if err != nil {
176
 
                return "", "", "", err
177
 
        }
178
 
        // Currently, only one machine is added, but in future there may be several added in one call.
179
 
        machineInfo := results[0]
180
 
        if machineInfo.Error != nil {
181
 
                return "", "", "", machineInfo.Error
182
 
        }
183
 
        return machineInfo.Machine, series, *hc.Arch, nil
184
 
}
185
 
 
186
 
func createMachineConfig(client *api.Client, machineId, series, arch, nonce, dataDir string) (*cloudinit.MachineConfig, error) {
187
 
        configParameters, err := client.MachineConfig(machineId, series, arch)
188
 
        if err != nil {
189
 
                return nil, err
190
 
        }
191
 
        stateInfo := &state.Info{
192
 
                Addrs:    configParameters.StateAddrs,
193
 
                Password: configParameters.Password,
194
 
                Tag:      configParameters.Tag,
195
 
                CACert:   configParameters.CACert,
196
 
        }
197
 
        apiInfo := &api.Info{
198
 
                Addrs:    configParameters.APIAddrs,
199
 
                Password: configParameters.Password,
200
 
                Tag:      configParameters.Tag,
201
 
                CACert:   configParameters.CACert,
202
 
        }
203
 
        environConfig, err := config.New(config.NoDefaults, configParameters.EnvironAttrs)
204
 
        if err != nil {
205
 
                return nil, err
206
 
        }
207
 
        mcfg := environs.NewMachineConfig(machineId, nonce, stateInfo, apiInfo)
208
 
        if dataDir != "" {
209
 
                mcfg.DataDir = dataDir
210
 
        }
211
 
        mcfg.Tools = configParameters.Tools
212
 
        err = environs.FinishMachineConfig(mcfg, environConfig, constraints.Value{})
213
 
        if err != nil {
214
 
                return nil, err
215
 
        }
216
 
        return mcfg, nil
217
 
}
218
 
 
219
 
func provisionMachineAgent(host string, mcfg *cloudinit.MachineConfig, stdin io.Reader, stdout, stderr io.Writer) error {
 
289
        return machineParams, nil
 
290
}
 
291
 
 
292
func provisionMachineAgent(host string, mcfg *cloudinit.MachineConfig, stderr io.Writer) error {
220
293
        cloudcfg := coreCloudinit.New()
221
294
        if err := cloudinit.ConfigureJuju(mcfg, cloudcfg); err != nil {
222
295
                return err
225
298
        // the target machine's existing configuration.
226
299
        cloudcfg.SetAptUpgrade(false)
227
300
        return sshinit.Configure(sshinit.ConfigureParams{
228
 
                Host:   host,
 
301
                Host:   "ubuntu@" + host,
229
302
                Config: cloudcfg,
230
 
                Stdin:  stdin,
231
 
                Stdout: stdout,
232
303
                Stderr: stderr,
233
304
        })
234
305
}