Skip to content

Commit d1fffb3

Browse files
authored
multipass: ensure instance does not exist (#3714)
Before the mutipass provisioner tries to launch an instance, if it already exists, it tries to delete and purge the instance.
1 parent b272a93 commit d1fffb3

File tree

2 files changed

+96
-10
lines changed

2 files changed

+96
-10
lines changed

pkg/testing/multipass/provisioner.go

+85-3
Original file line numberDiff line numberDiff line change
@@ -7,6 +7,7 @@ package multipass
77
import (
88
"bytes"
99
"context"
10+
"encoding/json"
1011
"fmt"
1112
"os"
1213
"os/exec"
@@ -122,6 +123,12 @@ func (p *provisioner) Clean(ctx context.Context, _ runner.Config, instances []ru
122123

123124
// launch creates an instance.
124125
func (p *provisioner) launch(ctx context.Context, cfg runner.Config, batch runner.OSBatch) error {
126+
// check if instance already exists
127+
err := p.ensureInstanceNotExist(ctx, batch)
128+
if err != nil {
129+
p.logger.Logf(
130+
"could not check multipass instance %q does not exists, moving on anyway. Err: %v", err)
131+
}
125132
args := []string{
126133
"launch",
127134
"-c", "2",
@@ -145,9 +152,14 @@ func (p *provisioner) launch(ctx context.Context, cfg runner.Config, batch runne
145152
return fmt.Errorf("failed to marshal cloud-init configuration: %w", err)
146153
}
147154

155+
p.logger.Logf("Launching multipass instance %s", batch.ID)
148156
var output bytes.Buffer
149-
p.logger.Logf("Launching multipass image %s", batch.ID)
150-
proc, err := process.Start("multipass", process.WithContext(ctx), process.WithArgs(args), process.WithCmdOptions(runner.AttachOut(&output), runner.AttachErr(&output)))
157+
proc, err := process.Start("multipass",
158+
process.WithContext(ctx),
159+
process.WithArgs(args),
160+
process.WithCmdOptions(
161+
runner.AttachOut(&output),
162+
runner.AttachErr(&output)))
151163
if err != nil {
152164
return fmt.Errorf("failed to run multipass launch: %w", err)
153165
}
@@ -162,14 +174,84 @@ func (p *provisioner) launch(ctx context.Context, cfg runner.Config, batch runne
162174
}
163175
_ = proc.Stdin.Close()
164176
ps := <-proc.Wait()
165-
if ps.ExitCode() != 0 {
177+
if !ps.Success() {
166178
// print the output so its clear what went wrong
167179
fmt.Fprintf(os.Stdout, "%s\n", output.Bytes())
168180
return fmt.Errorf("failed to run multipass launch: exited with code: %d", ps.ExitCode())
169181
}
170182
return nil
171183
}
172184

185+
func (p *provisioner) ensureInstanceNotExist(ctx context.Context, batch runner.OSBatch) error {
186+
var output bytes.Buffer
187+
var stdErr bytes.Buffer
188+
proc, err := process.Start("multipass",
189+
process.WithContext(ctx),
190+
process.WithArgs([]string{"list", "--format", "json"}),
191+
process.WithCmdOptions(
192+
runner.AttachOut(&output),
193+
runner.AttachErr(&stdErr)))
194+
if err != nil {
195+
return fmt.Errorf("multipass list failed to run: %w", err)
196+
}
197+
198+
state := <-proc.Wait()
199+
if !state.Success() {
200+
msg := fmt.Sprintf("multipass list exited with non-zero status: %s",
201+
state.String())
202+
p.logger.Logf(msg)
203+
p.logger.Logf("output: %s", output.String())
204+
p.logger.Logf("stderr: %s", stdErr.String())
205+
return fmt.Errorf(msg)
206+
}
207+
list := struct {
208+
List []struct {
209+
Ipv4 []string `json:"ipv4"`
210+
Name string `json:"name"`
211+
Release string `json:"release"`
212+
State string `json:"state"`
213+
} `json:"list"`
214+
}{}
215+
err = json.NewDecoder(&output).Decode(&list)
216+
if err != nil {
217+
return fmt.Errorf("could not decode mutipass list output: %w", err)
218+
}
219+
220+
for _, i := range list.List {
221+
if i.Name == batch.ID {
222+
p.logger.Logf("multipass trying to delete instance %s", batch.ID)
223+
224+
output.Reset()
225+
stdErr.Reset()
226+
proc, err = process.Start("multipass",
227+
process.WithContext(ctx),
228+
process.WithArgs([]string{"delete", "--purge", batch.ID}),
229+
process.WithCmdOptions(
230+
runner.AttachOut(&output),
231+
runner.AttachErr(&stdErr)))
232+
if err != nil {
233+
return fmt.Errorf(
234+
"multipass instance %q already exist, state %q. Could not delete it: %w",
235+
batch.ID, i.State, err)
236+
}
237+
state = <-proc.Wait()
238+
if !state.Success() {
239+
msg := fmt.Sprintf("failed to delete and purge multipass instance %s: %s",
240+
batch.ID,
241+
state.String())
242+
p.logger.Logf(msg)
243+
p.logger.Logf("output: %s", output.String())
244+
p.logger.Logf("stderr: %s", stdErr.String())
245+
return fmt.Errorf(msg)
246+
}
247+
248+
break
249+
}
250+
}
251+
252+
return nil
253+
}
254+
173255
// delete deletes an instance.
174256
func (p *provisioner) delete(ctx context.Context, instance runner.Instance) error {
175257
args := []string{

testing/integration/logs_ingestion_test.go

+11-7
Original file line numberDiff line numberDiff line change
@@ -104,11 +104,15 @@ func testMonitoringLogsAreShipped(
104104
) {
105105
// Stage 1: Make sure metricbeat logs are populated
106106
t.Log("Making sure metricbeat logs are populated")
107-
docs := findESDocs(t, func() (estools.Documents, error) {
108-
return estools.GetLogsForDataset(info.ESClient, "elastic_agent.metricbeat")
109-
})
110-
t.Logf("metricbeat: Got %d documents", len(docs.Hits.Hits))
111-
require.NotZero(t, len(docs.Hits.Hits))
107+
require.Eventually(t,
108+
func() bool {
109+
docs := findESDocs(t, func() (estools.Documents, error) {
110+
return estools.GetLogsForDataset(info.ESClient, "elastic_agent.metricbeat")
111+
})
112+
return len(docs.Hits.Hits) > 0
113+
},
114+
1*time.Minute, 500*time.Millisecond,
115+
"there should be metricbeats logs by now")
112116

113117
// Stage 2: make sure all components are healthy
114118
t.Log("Making sure all components are healthy")
@@ -123,7 +127,7 @@ func testMonitoringLogsAreShipped(
123127

124128
// Stage 3: Make sure there are no errors in logs
125129
t.Log("Making sure there are no error logs")
126-
docs = findESDocs(t, func() (estools.Documents, error) {
130+
docs := findESDocs(t, func() (estools.Documents, error) {
127131
return estools.CheckForErrorsInLogs(info.ESClient, info.Namespace, []string{
128132
// acceptable error messages (include reason)
129133
"Error dialing dial tcp 127.0.0.1:9200: connect: connection refused", // beat is running default config before its config gets updated
@@ -134,7 +138,7 @@ func testMonitoringLogsAreShipped(
134138
"elastic-agent-client error: rpc error: code = Canceled desc = context canceled", // can happen on restart
135139
})
136140
})
137-
t.Logf("errors: Got %d documents", len(docs.Hits.Hits))
141+
t.Logf("error logs: Got %d documents", len(docs.Hits.Hits))
138142
for _, doc := range docs.Hits.Hits {
139143
t.Logf("%#v", doc.Source)
140144
}

0 commit comments

Comments
 (0)