Skip to content

Commit ab1aa1a

Browse files
committed
let users pull VM images from remote location on start
Adds a new option "--pull-template". When specified, every base image is pulled from the given remote location on first use. The template given is used to get the remote location used in virter image pull <base> <remote> It accepts a go template string. A user can use "{{ .Image }}" to generically specify a pull location based on the name of the base image.
1 parent 755e66b commit ab1aa1a

File tree

3 files changed

+117
-1
lines changed

3 files changed

+117
-1
lines changed

cmd/schedule.go

Lines changed: 54 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@ import (
66
"fmt"
77
"net"
88
"os/exec"
9+
"text/template"
910

1011
log "github.com/sirupsen/logrus"
1112
)
@@ -44,6 +45,7 @@ const (
4445

4546
type suiteState struct {
4647
networks map[string]*networkState
48+
baseImage map[string]imageStage
4749
imageStage map[string]imageStage
4850
runStage map[string]runStage
4951
runResults map[string]testResult
@@ -92,6 +94,7 @@ func initializeState(suiteRun *testSuiteRun) *suiteState {
9294

9395
state := suiteState{
9496
networks: make(map[string]*networkState),
97+
baseImage: make(map[string]imageStage),
9598
imageStage: make(map[string]imageStage),
9699
runStage: make(map[string]runStage),
97100
runResults: make(map[string]testResult),
@@ -102,11 +105,18 @@ func initializeState(suiteRun *testSuiteRun) *suiteState {
102105
state.runStage[run.testID] = runNew
103106
}
104107

108+
initialBaseImageState := imageReady
109+
if suiteRun.pullImageTemplate != nil {
110+
initialBaseImageState = imageNone
111+
}
112+
105113
initialImageStage := imageReady
106114
if suiteRun.vmSpec.ProvisionFile != "" {
107115
initialImageStage = imageNone
108116
}
109117
for _, v := range suiteRun.vmSpec.VMs {
118+
state.baseImage[v.BaseImage] = initialBaseImageState
119+
110120
state.imageStage[v.ID()] = initialImageStage
111121
}
112122

@@ -185,6 +195,10 @@ func runStopping(suiteRun *testSuiteRun, state *suiteState) bool {
185195
}
186196

187197
for _, v := range suiteRun.vmSpec.VMs {
198+
if state.baseImage[v.BaseImage] == imageError {
199+
return true
200+
}
201+
188202
if state.imageStage[v.ID()] == imageError {
189203
return true
190204
}
@@ -227,7 +241,7 @@ func chooseNextAction(suiteRun *testSuiteRun, state *suiteState) action {
227241
}
228242

229243
for i, v := range suiteRun.vmSpec.VMs {
230-
if state.imageStage[v.ID()] == imageNone {
244+
if state.baseImage[v.BaseImage] == imageNone || state.imageStage[v.ID()] == imageNone {
231245
return nextActionProvision(suiteRun, state, &suiteRun.vmSpec.VMs[i])
232246
}
233247
}
@@ -264,6 +278,10 @@ func runBetter(state *suiteState, a *testRun, b testRun) bool {
264278

265279
func allImagesReady(state *suiteState, run *testRun) bool {
266280
for _, v := range run.vms {
281+
if state.baseImage[v.BaseImage] != imageReady {
282+
return false
283+
}
284+
267285
if state.imageStage[v.ID()] != imageReady {
268286
return false
269287
}
@@ -389,6 +407,14 @@ func getIDs(suiteRun *testSuiteRun, state *suiteState, n int) []int {
389407
}
390408

391409
func nextActionProvision(suiteRun *testSuiteRun, state *suiteState, v *vm) action {
410+
if state.baseImage[v.BaseImage] == imageNone {
411+
return &pullImageAction{Image: v.BaseImage, PullTemplate: suiteRun.pullImageTemplate}
412+
}
413+
414+
if state.baseImage[v.BaseImage] != imageReady {
415+
return nil
416+
}
417+
392418
network := accessNetwork(false)
393419
networkName := findReadyNetwork(state, nil, network, true)
394420
if networkName == "" {
@@ -475,6 +501,33 @@ func (a *performTestAction) updatePost(state *suiteState) {
475501
}
476502
}
477503

504+
type pullImageAction struct {
505+
Image string
506+
PullTemplate *template.Template
507+
err error
508+
}
509+
510+
func (b *pullImageAction) name() string {
511+
return fmt.Sprintf("Pull base image '%s'", b.Image)
512+
}
513+
514+
func (b *pullImageAction) updatePre(state *suiteState) {
515+
state.baseImage[b.Image] = imageProvision
516+
}
517+
518+
func (b *pullImageAction) exec(ctx context.Context, suiteRun *testSuiteRun) {
519+
b.err = pullImage(ctx, suiteRun, b.Image, b.PullTemplate)
520+
}
521+
522+
func (b *pullImageAction) updatePost(state *suiteState) {
523+
if b.err == nil {
524+
state.baseImage[b.Image] = imageReady
525+
} else {
526+
state.baseImage[b.Image] = imageError
527+
state.errors = append(state.errors, fmt.Errorf("pull image %s: %w", b.Image, b.err))
528+
}
529+
}
530+
478531
type provisionImageAction struct {
479532
v *vm
480533
id int

cmd/vm.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -9,6 +9,7 @@ import (
99
"strconv"
1010
"strings"
1111
"sync"
12+
"text/template"
1213
"time"
1314

1415
log "github.com/sirupsen/logrus"
@@ -52,6 +53,37 @@ func testIDString(test string, vmCount int, variantName string, testIndex int) s
5253
return fmt.Sprintf("%s-%d-%s-%d", test, vmCount, variantName, testIndex)
5354
}
5455

56+
func pullImage(ctx context.Context, suiteRun *testSuiteRun, image string, templ *template.Template) error {
57+
logger := log.WithFields(log.Fields{
58+
"Action": "Pull",
59+
"Image": image,
60+
})
61+
62+
errPath := filepath.Join(suiteRun.outDir, "provision-log", fmt.Sprintf("%s-pull.log", image))
63+
argv := []string{"virter", "image", "pull", image}
64+
65+
if templ != nil {
66+
var buf strings.Builder
67+
err := templ.Execute(&buf, map[string]string{
68+
"Image": image,
69+
})
70+
if err != nil {
71+
return err
72+
}
73+
74+
argv = append(argv, buf.String())
75+
}
76+
77+
cmd := exec.CommandContext(ctx, argv[0], argv[1:]...)
78+
79+
log.Debugf("EXECUTING: %s", argv)
80+
start := time.Now()
81+
err := cmdStderrTerm(ctx, logger, errPath, cmd)
82+
log.Debugf("EXECUTIONTIME: Pull image %s: %v", image, time.Since(start))
83+
84+
return err
85+
}
86+
5587
func provisionImage(ctx context.Context, suiteRun *testSuiteRun, nr int, v *vm, networkName string) error {
5688
newImageName := suiteRun.vmSpec.ImageName(v)
5789
logger := log.WithFields(log.Fields{

cmd/vmshed.go

Lines changed: 31 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -12,6 +12,7 @@ import (
1212
"path/filepath"
1313
"sort"
1414
"strings"
15+
"text/template"
1516
"time"
1617

1718
"github.com/BurntSushi/toml"
@@ -93,6 +94,7 @@ type testSuiteRun struct {
9394
failTest bool
9495
printErrorDetails bool
9596
logFormatVirter string
97+
pullImageTemplate *template.Template
9698
}
9799

98100
type testConfig struct {
@@ -103,6 +105,32 @@ type testConfig struct {
103105
repeats int
104106
}
105107

108+
type TemplateFlag struct {
109+
*template.Template
110+
}
111+
112+
func (t *TemplateFlag) String() string {
113+
if t.Template != nil {
114+
return t.Template.Tree.Root.String()
115+
} else {
116+
return ""
117+
}
118+
}
119+
120+
func (t *TemplateFlag) Set(s string) error {
121+
parsed, err := template.New("image").Parse(s)
122+
if err != nil {
123+
return err
124+
}
125+
126+
t.Template = parsed
127+
return nil
128+
}
129+
130+
func (t *TemplateFlag) Type() string {
131+
return "template"
132+
}
133+
106134
// Execute runs vmshed
107135
func Execute() {
108136
log.SetFormatter(VmshedStandardLogFormatter())
@@ -133,6 +161,7 @@ func rootCommand() *cobra.Command {
133161
var errorDetails bool
134162
var firstv4Subnet string
135163
var firstv6Subnet string
164+
var pullImageTemplate TemplateFlag
136165

137166
rootCmd := &cobra.Command{
138167
Use: "vmshed",
@@ -216,6 +245,7 @@ current user.`,
216245
suiteRun.firstV4Net = firstV4Net
217246
suiteRun.firstV6Net = firstV6Net
218247
suiteRun.logFormatVirter = logFormatVirter
248+
suiteRun.pullImageTemplate = pullImageTemplate.Template
219249

220250
ctx, cancel := signal.NotifyContext(context.Background(), unix.SIGINT, unix.SIGTERM)
221251
defer cancel()
@@ -256,6 +286,7 @@ current user.`,
256286
rootCmd.Flags().BoolVarP(&errorDetails, "error-details", "", true, "Show all test error logs at the end of the run")
257287
rootCmd.Flags().StringVarP(&firstv4Subnet, "first-subnet", "", "10.224.0.0/24", "The first subnet to use for VMs. If more virtual networks are required, the next higher network of the same size will be used")
258288
rootCmd.Flags().StringVarP(&firstv6Subnet, "first-v6-subnet", "", "fd62:a80c:412::/64", "The first ipv6 subnet to use for VMs. If more virtual networks are required, the next higher network of the same size will be used")
289+
rootCmd.Flags().VarP(&pullImageTemplate, "pull-template", "", "Where to pull the base images from. Accepts a go template string, allowing usage like 'registry.example.com/vm/{{ .Image }}:latest'")
259290
return rootCmd
260291
}
261292

0 commit comments

Comments
 (0)