74
// Used for fallback to 1.16 code
75
var stateConn *juju.Conn
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.)
89
cleanupErr = statecmd.DestroyMachines1dot16(stateConn.State, machineId)
91
cleanupErr = client.DestroyMachines(machineId)
93
if cleanupErr != nil {
94
logger.Warningf("error cleaning up machine: %s", cleanupErr)
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 {
115
machineParams, err := gatherMachineParams(hostname)
89
instanceId := instance.Id(manualInstancePrefix + hostWithoutUser(args.Host))
90
nonce := fmt.Sprintf("%s:%s", instanceId, uuid.String())
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)
127
machineId, err = recordMachineInState1dot16(stateConn, *machineParams)
134
var configParameters params.MachineConfig
135
if stateConn == nil {
136
configParameters, err = client.MachineConfig(machineId)
138
configParameters, err = statecmd.MachineConfig(stateConn.State, machineId)
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)
101
146
return machineId, err
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)
107
152
return machineId, err
111
156
return machineId, nil
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:]
119
return hostWithoutUser
122
166
func recordMachineInState(
123
client *api.Client, host, nonce string, instanceId instance.Id) (machineId, series, arch string, err error) {
167
client *api.Client, machineParams params.AddMachineParams) (machineId string, err error) {
168
results, err := client.AddMachines([]params.AddMachineParams{machineParams})
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
177
return machineInfo.Machine, nil
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))
184
for j, job := range jobs {
185
if outJobs[j], err = state.MachineJobFromParams(job); err != nil {
192
func recordMachineInState1dot16(
193
stateConn *juju.Conn, machineParams params.AddMachineParams) (machineId string, err error) {
194
stateJobs, err := convertToStateJobs(machineParams.Jobs)
198
//if p.Series == "" {
199
// p.Series = defaultSeries
201
template := state.MachineTemplate{
202
Series: machineParams.Series,
203
Constraints: machineParams.Constraints,
204
InstanceId: machineParams.InstanceId,
206
Nonce: machineParams.Nonce,
207
HardwareCharacteristics: machineParams.HardwareCharacteristics,
208
Addresses: machineParams.Addrs,
210
machine, err := stateConn.State.AddOneMachine(template)
214
return machine.Id(), nil
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) {
224
// Generate a unique nonce for the machine.
225
uuid, err := utils.NewUUID()
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)
134
237
logger.Infof("resolved %v to %v", ip, names)
135
sshHostWithoutUser = 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
138
addrs, err := instance.HostAddresses(sshHostWithoutUser)
252
addrs, err := instance.HostAddresses(hostname)
140
return "", "", "", err
142
logger.Infof("addresses for %v: %v", sshHostWithoutUser, addrs)
256
logger.Infof("addresses for %v: %v", hostname, addrs)
144
provisioned, err := checkProvisioned(host)
258
provisioned, err := checkProvisioned(hostname)
146
260
err = fmt.Errorf("error checking if provisioned: %v", err)
147
return "", "", "", err
150
return "", "", "", ErrProvisioned
264
return nil, ErrProvisioned
153
hc, series, err := DetectSeriesAndHardwareCharacteristics(host)
267
hc, series, err := DetectSeriesAndHardwareCharacteristics(hostname)
155
269
err = fmt.Errorf("error detecting hardware characteristics: %v", err)
156
return "", "", "", err
159
// Inject a new machine into state.
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{
279
instanceId := instance.Id(manualInstancePrefix + hostname)
280
nonce := fmt.Sprintf("%s:%s", instanceId, uuid.String())
281
machineParams := ¶ms.AddMachineParams{
168
283
HardwareCharacteristics: hc,
169
284
InstanceId: instanceId,
172
287
Jobs: []params.MachineJob{params.JobHostUnits},
174
results, err := client.AddMachines([]params.AddMachineParams{machineParams})
176
return "", "", "", err
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
183
return machineInfo.Machine, series, *hc.Arch, nil
186
func createMachineConfig(client *api.Client, machineId, series, arch, nonce, dataDir string) (*cloudinit.MachineConfig, error) {
187
configParameters, err := client.MachineConfig(machineId, series, arch)
191
stateInfo := &state.Info{
192
Addrs: configParameters.StateAddrs,
193
Password: configParameters.Password,
194
Tag: configParameters.Tag,
195
CACert: configParameters.CACert,
197
apiInfo := &api.Info{
198
Addrs: configParameters.APIAddrs,
199
Password: configParameters.Password,
200
Tag: configParameters.Tag,
201
CACert: configParameters.CACert,
203
environConfig, err := config.New(config.NoDefaults, configParameters.EnvironAttrs)
207
mcfg := environs.NewMachineConfig(machineId, nonce, stateInfo, apiInfo)
209
mcfg.DataDir = dataDir
211
mcfg.Tools = configParameters.Tools
212
err = environs.FinishMachineConfig(mcfg, environConfig, constraints.Value{})
219
func provisionMachineAgent(host string, mcfg *cloudinit.MachineConfig, stdin io.Reader, stdout, stderr io.Writer) error {
289
return machineParams, nil
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 {