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  }