// Copyright (c) 2018 HyperHQ Inc. // // SPDX-License-Identifier: Apache-2.0 // package factory import ( "context" "fmt" "reflect" vc "github.com/kata-containers/runtime/virtcontainers" "github.com/kata-containers/runtime/virtcontainers/factory/base" "github.com/kata-containers/runtime/virtcontainers/factory/cache" "github.com/kata-containers/runtime/virtcontainers/factory/direct" "github.com/kata-containers/runtime/virtcontainers/factory/template" opentracing "github.com/opentracing/opentracing-go" "github.com/sirupsen/logrus" ) var factoryLogger = logrus.FieldLogger(logrus.New()) // Config is a collection of VM factory configurations. type Config struct { Template bool Cache uint VMConfig vc.VMConfig } type factory struct { base base.FactoryBase } func trace(parent context.Context, name string) (opentracing.Span, context.Context) { span, ctx := opentracing.StartSpanFromContext(parent, name) span.SetTag("subsystem", "factory") return span, ctx } // NewFactory returns a working factory. func NewFactory(ctx context.Context, config Config, fetchOnly bool) (vc.Factory, error) { span, _ := trace(ctx, "NewFactory") defer span.Finish() err := config.VMConfig.Valid() if err != nil { return nil, err } if fetchOnly && config.Cache > 0 { return nil, fmt.Errorf("cache factory does not support fetch") } var b base.FactoryBase if config.Template { if fetchOnly { b, err = template.Fetch(config.VMConfig) if err != nil { return nil, err } } else { b = template.New(ctx, config.VMConfig) } } else { b = direct.New(ctx, config.VMConfig) } if config.Cache > 0 { b = cache.New(ctx, config.Cache, b) } return &factory{b}, nil } // SetLogger sets the logger for the factory. func SetLogger(ctx context.Context, logger logrus.FieldLogger) { fields := logrus.Fields{ "source": "virtcontainers", } factoryLogger = logger.WithFields(fields) } func (f *factory) log() *logrus.Entry { return factoryLogger.WithField("subsystem", "factory") } func resetHypervisorConfig(config *vc.VMConfig) { config.HypervisorConfig.NumVCPUs = 0 config.HypervisorConfig.MemorySize = 0 config.HypervisorConfig.BootToBeTemplate = false config.HypervisorConfig.BootFromTemplate = false config.HypervisorConfig.MemoryPath = "" config.HypervisorConfig.DevicesStatePath = "" config.ProxyType = vc.NoopProxyType config.ProxyConfig = vc.ProxyConfig{} } func compareStruct(foo, bar reflect.Value) bool { for i := 0; i < foo.NumField(); i++ { if !deepCompareValue(foo.Field(i), bar.Field(i)) { return false } } return true } func compareMap(foo, bar reflect.Value) bool { if foo.Len() != bar.Len() { return false } for _, k := range foo.MapKeys() { if !deepCompareValue(foo.MapIndex(k), bar.MapIndex(k)) { return false } } return true } func compareSlice(foo, bar reflect.Value) bool { if foo.Len() != bar.Len() { return false } for j := 0; j < foo.Len(); j++ { if !deepCompareValue(foo.Index(j), bar.Index(j)) { return false } } return true } func deepCompareValue(foo, bar reflect.Value) bool { if !foo.IsValid() || !bar.IsValid() { return foo.IsValid() == bar.IsValid() } if foo.Type() != bar.Type() { return false } switch foo.Kind() { case reflect.Map: return compareMap(foo, bar) case reflect.Array: fallthrough case reflect.Slice: return compareSlice(foo, bar) case reflect.Struct: return compareStruct(foo, bar) default: return foo.Interface() == bar.Interface() } } func deepCompare(foo, bar interface{}) bool { v1 := reflect.ValueOf(foo) v2 := reflect.ValueOf(bar) return deepCompareValue(v1, v2) } // It's important that baseConfig and newConfig are passed by value! func checkVMConfig(config1, config2 vc.VMConfig) error { if config1.HypervisorType != config2.HypervisorType { return fmt.Errorf("hypervisor type does not match: %s vs. %s", config1.HypervisorType, config2.HypervisorType) } if config1.AgentType != config2.AgentType { return fmt.Errorf("agent type does not match: %s vs. %s", config1.AgentType, config2.AgentType) } // check hypervisor config details resetHypervisorConfig(&config1) resetHypervisorConfig(&config2) if !deepCompare(config1, config2) { return fmt.Errorf("hypervisor config does not match, base: %+v. new: %+v", config1, config2) } return nil } func (f *factory) checkConfig(config vc.VMConfig) error { baseConfig := f.base.Config() return checkVMConfig(config, baseConfig) } func (f *factory) validateNewVMConfig(config vc.VMConfig) error { if len(config.AgentType.String()) == 0 { return fmt.Errorf("Missing agent type") } if len(config.ProxyType.String()) == 0 { return fmt.Errorf("Missing proxy type") } return config.Valid() } // GetVM returns a working blank VM created by the factory. func (f *factory) GetVM(ctx context.Context, config vc.VMConfig) (*vc.VM, error) { span, _ := trace(ctx, "GetVM") defer span.Finish() hypervisorConfig := config.HypervisorConfig err := f.validateNewVMConfig(config) if err != nil { f.log().WithError(err).Error("invalid hypervisor config") return nil, err } err = f.checkConfig(config) if err != nil { f.log().WithError(err).Info("fallback to direct factory vm") return direct.New(ctx, config).GetBaseVM(ctx, config) } f.log().Info("get base VM") vm, err := f.base.GetBaseVM(ctx, config) if err != nil { f.log().WithError(err).Error("failed to get base VM") return nil, err } // cleanup upon error defer func() { if err != nil { f.log().WithError(err).Error("clean up vm") vm.Stop() } }() err = vm.Resume() if err != nil { return nil, err } // reseed RNG so that shared memory VMs do not generate same random numbers. err = vm.ReseedRNG() if err != nil { return nil, err } // sync guest time since we might have paused it for a long time. err = vm.SyncTime() if err != nil { return nil, err } online := false baseConfig := f.base.Config().HypervisorConfig if baseConfig.NumVCPUs < hypervisorConfig.NumVCPUs { err = vm.AddCPUs(hypervisorConfig.NumVCPUs - baseConfig.NumVCPUs) if err != nil { return nil, err } online = true } if baseConfig.MemorySize < hypervisorConfig.MemorySize { err = vm.AddMemory(hypervisorConfig.MemorySize - baseConfig.MemorySize) if err != nil { return nil, err } online = true } if online { err = vm.OnlineCPUMemory() if err != nil { return nil, err } } return vm, nil } // CloseFactory closes the factory. func (f *factory) CloseFactory(ctx context.Context) { f.base.CloseFactory(ctx) }