github.com/kata-containers/runtime@v0.0.0-20210505125100-04f29832a923/virtcontainers/factory/template/template.go (about) 1 // Copyright (c) 2018 HyperHQ Inc. 2 // 3 // SPDX-License-Identifier: Apache-2.0 4 // 5 // template implements base vm factory with vm templating. 6 7 package template 8 9 import ( 10 "context" 11 "fmt" 12 "os" 13 "syscall" 14 "time" 15 16 pb "github.com/kata-containers/runtime/protocols/cache" 17 vc "github.com/kata-containers/runtime/virtcontainers" 18 "github.com/kata-containers/runtime/virtcontainers/factory/base" 19 "github.com/sirupsen/logrus" 20 ) 21 22 type template struct { 23 statePath string 24 config vc.VMConfig 25 } 26 27 var templateWaitForAgent = 2 * time.Second 28 var templateLog = logrus.WithField("source", "virtcontainers/factory") 29 30 // Fetch finds and returns a pre-built template factory. 31 // TODO: save template metadata and fetch from storage. 32 func Fetch(config vc.VMConfig, templatePath string) (base.FactoryBase, error) { 33 t := &template{templatePath, config} 34 35 err := t.checkTemplateVM() 36 if err != nil { 37 return nil, err 38 } 39 40 return t, nil 41 } 42 43 // New creates a new VM template factory. 44 func New(ctx context.Context, config vc.VMConfig, templatePath string) (base.FactoryBase, error) { 45 t := &template{templatePath, config} 46 47 err := t.checkTemplateVM() 48 if err == nil { 49 return nil, fmt.Errorf("There is already a VM template in %s", templatePath) 50 } 51 52 err = t.prepareTemplateFiles() 53 if err != nil { 54 return nil, err 55 } 56 defer func() { 57 if err != nil { 58 t.close() 59 } 60 }() 61 62 err = t.createTemplateVM(ctx) 63 if err != nil { 64 return nil, err 65 } 66 67 return t, nil 68 } 69 70 // Config returns template factory's configuration. 71 func (t *template) Config() vc.VMConfig { 72 return t.config 73 } 74 75 // GetBaseVM creates a new paused VM from the template VM. 76 func (t *template) GetBaseVM(ctx context.Context, config vc.VMConfig) (*vc.VM, error) { 77 return t.createFromTemplateVM(ctx, config) 78 } 79 80 // CloseFactory cleans up the template VM. 81 func (t *template) CloseFactory(ctx context.Context) { 82 t.close() 83 } 84 85 // GetVMStatus is not supported 86 func (t *template) GetVMStatus() []*pb.GrpcVMStatus { 87 panic("ERROR: package template does not support GetVMStatus") 88 } 89 90 func (t *template) close() { 91 if err := syscall.Unmount(t.statePath, syscall.MNT_DETACH); err != nil { 92 t.Logger().WithError(err).Errorf("failed to unmount %s", t.statePath) 93 } 94 95 if err := os.RemoveAll(t.statePath); err != nil { 96 t.Logger().WithError(err).Errorf("failed to remove %s", t.statePath) 97 } 98 } 99 100 func (t *template) prepareTemplateFiles() error { 101 // create and mount tmpfs for the shared memory file 102 err := os.MkdirAll(t.statePath, 0700) 103 if err != nil { 104 return err 105 } 106 flags := uintptr(syscall.MS_NOSUID | syscall.MS_NODEV) 107 opts := fmt.Sprintf("size=%dM", t.config.HypervisorConfig.MemorySize+templateDeviceStateSize) 108 if err = syscall.Mount("tmpfs", t.statePath, "tmpfs", flags, opts); err != nil { 109 t.close() 110 return err 111 } 112 f, err := os.Create(t.statePath + "/memory") 113 if err != nil { 114 t.close() 115 return err 116 } 117 f.Close() 118 119 return nil 120 } 121 122 func (t *template) createTemplateVM(ctx context.Context) error { 123 // create the template vm 124 config := t.config 125 config.HypervisorConfig.BootToBeTemplate = true 126 config.HypervisorConfig.BootFromTemplate = false 127 config.HypervisorConfig.MemoryPath = t.statePath + "/memory" 128 config.HypervisorConfig.DevicesStatePath = t.statePath + "/state" 129 130 vm, err := vc.NewVM(ctx, config) 131 if err != nil { 132 return err 133 } 134 defer vm.Stop() 135 136 if err = vm.Disconnect(); err != nil { 137 return err 138 } 139 140 // Sleep a bit to let the agent grpc server clean up 141 // When we close connection to the agent, it needs sometime to cleanup 142 // and restart listening on the communication( serial or vsock) port. 143 // That time can be saved if we sleep a bit to wait for the agent to 144 // come around and start listening again. The sleep is only done when 145 // creating new vm templates and saves time for every new vm that are 146 // created from template, so it worth the invest. 147 time.Sleep(templateWaitForAgent) 148 149 if err = vm.Pause(); err != nil { 150 return err 151 } 152 153 if err = vm.Save(); err != nil { 154 return err 155 } 156 157 return nil 158 } 159 160 func (t *template) createFromTemplateVM(ctx context.Context, c vc.VMConfig) (*vc.VM, error) { 161 config := t.config 162 config.HypervisorConfig.BootToBeTemplate = false 163 config.HypervisorConfig.BootFromTemplate = true 164 config.HypervisorConfig.MemoryPath = t.statePath + "/memory" 165 config.HypervisorConfig.DevicesStatePath = t.statePath + "/state" 166 config.ProxyType = c.ProxyType 167 config.ProxyConfig = c.ProxyConfig 168 169 return vc.NewVM(ctx, config) 170 } 171 172 func (t *template) checkTemplateVM() error { 173 _, err := os.Stat(t.statePath + "/memory") 174 if err != nil { 175 return err 176 } 177 178 _, err = os.Stat(t.statePath + "/state") 179 return err 180 } 181 182 // Logger returns a logrus logger appropriate for logging template messages 183 func (t *template) Logger() *logrus.Entry { 184 return templateLog.WithFields(logrus.Fields{ 185 "subsystem": "template", 186 }) 187 } 188 189 // SetLogger sets the logger for the factory template. 190 func SetLogger(ctx context.Context, logger logrus.FieldLogger) { 191 fields := templateLog.Data 192 templateLog = logger.WithFields(fields) 193 }