github.com/Mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/config/config.go (about)

     1  /*
     2  Copyright 2018 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package config
    18  
    19  import (
    20  	"fmt"
    21  	"math"
    22  	"sort"
    23  
    24  	virtletclient "github.com/Mirantis/virtlet/pkg/client/clientset/versioned"
    25  	flag "github.com/spf13/pflag"
    26  	meta_v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    27  	"k8s.io/client-go/kubernetes"
    28  	"k8s.io/client-go/tools/clientcmd"
    29  
    30  	virtlet_v1 "github.com/Mirantis/virtlet/pkg/api/virtlet.k8s/v1"
    31  )
    32  
    33  const (
    34  	defaultFDServerSocketPath = "/var/lib/virtlet/tapfdserver.sock"
    35  	fdServerSocketPathEnv     = "VIRTLET_FD_SERVER_SOCKET_PATH"
    36  
    37  	defaultDatabasePath = "/var/lib/virtlet/virtlet.db"
    38  	databasePathEnv     = "VIRTLET_DATABASE_PATH"
    39  
    40  	defaultDownloadProtocol  = "https"
    41  	imageDownloadProtocolEnv = "VIRTLET_DOWNLOAD_PROTOCOL"
    42  
    43  	defaultImageDir = "/var/lib/virtlet/images"
    44  	imageDirEnv     = "VIRTLET_IMAGE_DIR"
    45  
    46  	defaultImageTranslationConfigsDir = "/etc/virtlet/images"
    47  	imageTranslationsConfigDirEnv     = "VIRTLET_IMAGE_TRANSLATIONS_DIR"
    48  
    49  	defaultLibvirtURI = "qemu:///system"
    50  	libvirtURIEnv     = "VIRTLET_LIBVIRT_URI"
    51  
    52  	defaultRawDevices = "loop*"
    53  	rawDevicesEnv     = "VIRTLET_RAW_DEVICES"
    54  
    55  	defaultCRISocketPath = "/run/virtlet.sock"
    56  	criSocketPathEnv     = "VIRTLET_CRI_SOCKET_PATH"
    57  
    58  	disableLoggingEnv = "VIRTLET_DISABLE_LOGGING"
    59  	disableKVMEnv     = "VIRTLET_DISABLE_KVM"
    60  	enableSriovEnv    = "VIRTLET_SRIOV_SUPPORT"
    61  
    62  	defaultCNIPluginDir = "/opt/cni/bin"
    63  	cniPluginDirEnv     = "VIRTLET_CNI_PLUGIN_DIR"
    64  
    65  	defaultCNIConfigDir = "/etc/cni/net.d"
    66  	cniConfigDirEnv     = "VIRTLET_CNI_CONFIG_DIR"
    67  
    68  	defaultCalicoSubnet = 24
    69  	calicoSubnetEnv     = "VIRTLET_CALICO_SUBNET"
    70  
    71  	enableRegexpImageTranslationEnv = "IMAGE_REGEXP_TRANSLATION"
    72  	logLevelEnv                     = "VIRTLET_LOGLEVEL"
    73  
    74  	defaultCPUModel = ""
    75  	cpuModelEnv     = "VIRTLET_CPU_MODEL"
    76  
    77  	defaultStreamPort = 10010
    78  	streamPortEnv     = "VIRTLET_STREAM_PORT"
    79  
    80  	kubeletRootDir    = "/var/lib/kubelet/pods"
    81  	kubeletRootDirEnv = "KUBELET_ROOT_DIR"
    82  )
    83  
    84  func configFieldSet(c *virtlet_v1.VirtletConfig) *fieldSet {
    85  	var fs fieldSet
    86  	fs.addStringField("fdServerSocketPath", "fd-server-socket-path", "", "Path to fd server socket", fdServerSocketPathEnv, defaultFDServerSocketPath, &c.FDServerSocketPath)
    87  	fs.addStringField("databasePath", "database-path", "", "Path to the virtlet database", databasePathEnv, defaultDatabasePath, &c.DatabasePath)
    88  	fs.addStringFieldWithPattern("downloadProtocol", "image-download-protocol", "", "Image download protocol. Can be https or http", imageDownloadProtocolEnv, defaultDownloadProtocol, "^https?$", &c.DownloadProtocol)
    89  	fs.addStringField("imageDir", "image-dir", "", "Image directory", imageDirEnv, defaultImageDir, &c.ImageDir)
    90  	fs.addStringField("imageTranslationConfigsDir", "image-translation-configs-dir", "", "Image name translation configs directory", imageTranslationsConfigDirEnv, defaultImageTranslationConfigsDir, &c.ImageTranslationConfigsDir)
    91  	// SkipImageTranslation doesn't have corresponding flag or env var as it's only used by tests
    92  	fs.addBoolField("skipImageTranslation", "", "", "", "", false, &c.SkipImageTranslation)
    93  	fs.addStringField("libvirtURI", "libvirt-uri", "", "Libvirt connection URI", libvirtURIEnv, defaultLibvirtURI, &c.LibvirtURI)
    94  	fs.addStringField("rawDevices", "raw-devices", "", "Comma separated list of raw device glob patterns which VMs can access (without '/dev/' prefix)", rawDevicesEnv, defaultRawDevices, &c.RawDevices)
    95  	fs.addStringField("criSocketPath", "listen", "", "The path to UNIX domain socket for CRI service to listen on", criSocketPathEnv, defaultCRISocketPath, &c.CRISocketPath)
    96  	fs.addBoolField("disableLogging", "disable-logging", "", "Display logging and the streamer", disableLoggingEnv, false, &c.DisableLogging)
    97  	fs.addBoolField("disableKVM", "disable-kvm", "", "Forcibly disable KVM support", disableKVMEnv, false, &c.DisableKVM)
    98  	fs.addBoolField("enableSriov", "enable-sriov", "", "Enable SR-IOV support", enableSriovEnv, false, &c.EnableSriov)
    99  	fs.addStringField("cniPluginDir", "cni-bin-dir", "", "Path to CNI plugin binaries", cniPluginDirEnv, defaultCNIPluginDir, &c.CNIPluginDir)
   100  	fs.addStringField("cniConfigDir", "cni-conf-dir", "", "Path to the CNI configuration directory", cniConfigDirEnv, defaultCNIConfigDir, &c.CNIConfigDir)
   101  	fs.addIntField("calicoSubnetSize", "calico-subnet-size", "", "Calico subnet size to use", calicoSubnetEnv, defaultCalicoSubnet, 0, 32, &c.CalicoSubnetSize)
   102  	fs.addBoolField("enableRegexpImageTranslation", "enable-regexp-image-translation", "", "Enable regexp image name translation", enableRegexpImageTranslationEnv, true, &c.EnableRegexpImageTranslation)
   103  	fs.addStringField("cpuModel", "cpu-model", "", "CPU model to use in libvirt domain definition (libvirt's default value will be used if not set)", cpuModelEnv, defaultCPUModel, &c.CPUModel)
   104  	fs.addIntField("streamPort", "stream-port", "", "configurable port to the virtlet server", streamPortEnv, defaultStreamPort, 1, 65535, &c.StreamPort)
   105  	fs.addStringField("kubeletRootDir", "kubelet-root-dir", "", "Pod's root dir in kubelet", kubeletRootDirEnv, kubeletRootDir, &c.KubeletRootDir)
   106  	// this field duplicates glog's --v, so no option for it, which is signified
   107  	// by "+" here (it's only for doc)
   108  	fs.addIntField("logLevel", "+v", "", "Log level to use", logLevelEnv, 1, 0, math.MaxInt32, &c.LogLevel)
   109  	return &fs
   110  }
   111  
   112  // GetDefaultConfig returns a VirtletConfig with all values set to default
   113  func GetDefaultConfig() *virtlet_v1.VirtletConfig {
   114  	var c virtlet_v1.VirtletConfig
   115  	configFieldSet(&c).applyDefaults()
   116  	return &c
   117  }
   118  
   119  // Override replaces the values in the target config with those
   120  // which are set in the other config.
   121  func Override(target, other *virtlet_v1.VirtletConfig) {
   122  	configFieldSet(target).override(configFieldSet(other))
   123  }
   124  
   125  // DumpEnv returns a string with environment variable settings
   126  // corresponding to the VirtletConfig.
   127  func DumpEnv(c *virtlet_v1.VirtletConfig) string {
   128  	return configFieldSet(c).dumpEnv()
   129  }
   130  
   131  // GenerateDoc generates a markdown document with a table describing
   132  // all the configuration settings.
   133  func GenerateDoc() string {
   134  	return configFieldSet(&virtlet_v1.VirtletConfig{}).generateDoc()
   135  }
   136  
   137  func mappingMatches(cm virtlet_v1.VirtletConfigMapping, nodeName string, nodeLabels map[string]string) bool {
   138  	if cm.Spec.Config == nil {
   139  		return false
   140  	}
   141  	if cm.Spec.NodeName != "" && cm.Spec.NodeName != nodeName {
   142  		return false
   143  	}
   144  	for label, value := range cm.Spec.NodeSelector {
   145  		actual, found := nodeLabels[label]
   146  		if !found || actual != value {
   147  			return false
   148  		}
   149  	}
   150  	return true
   151  }
   152  
   153  // MergeConfigs merges several Virtlet configs together, with
   154  // configs going later taking precedence.
   155  func MergeConfigs(configs []*virtlet_v1.VirtletConfig) *virtlet_v1.VirtletConfig {
   156  	var cfg *virtlet_v1.VirtletConfig
   157  	for _, cur := range configs {
   158  		if cfg == nil {
   159  			cfg = cur
   160  		} else {
   161  			Override(cfg, cur)
   162  		}
   163  	}
   164  	return cfg
   165  }
   166  
   167  // Binder is used to extract Virtlet config from a FlagSet.
   168  type Binder struct {
   169  	flagSet   *flag.FlagSet
   170  	config    *virtlet_v1.VirtletConfig
   171  	fieldSet  *fieldSet
   172  	lookupEnv envLookup
   173  }
   174  
   175  // NewBinder returns a new Binder.
   176  func NewBinder(flagSet *flag.FlagSet) *Binder {
   177  	config := &virtlet_v1.VirtletConfig{}
   178  	fs := configFieldSet(config)
   179  	fs.applyDefaults()
   180  	if flagSet != nil {
   181  		fs.addFlags(flagSet)
   182  	}
   183  	return &Binder{
   184  		flagSet:  flagSet,
   185  		config:   config,
   186  		fieldSet: fs,
   187  	}
   188  }
   189  
   190  // GetConfig returns the config that only includes the fields that
   191  // were explicitly set in the flags. It should be called after parsing
   192  // the flags.
   193  func (b *Binder) GetConfig() *virtlet_v1.VirtletConfig {
   194  	b.fieldSet.clearFieldsNotInFlagSet(b.flagSet)
   195  	b.fieldSet.setFromEnv(b.lookupEnv)
   196  	return b.config
   197  }
   198  
   199  // configForNode gets virtlet_v1.VirtletConfig for the specified node name and labels.
   200  func configForNode(mappings []virtlet_v1.VirtletConfigMapping, localConfig *virtlet_v1.VirtletConfig, nodeName string, nodeLabels map[string]string) *virtlet_v1.VirtletConfig {
   201  	cfg := GetDefaultConfig()
   202  	var sortedMappings []virtlet_v1.VirtletConfigMapping
   203  	for _, m := range mappings {
   204  		if mappingMatches(m, nodeName, nodeLabels) {
   205  			sortedMappings = append(sortedMappings, m)
   206  		}
   207  	}
   208  	sort.Slice(sortedMappings, func(i, j int) bool {
   209  		a, b := sortedMappings[i], sortedMappings[j]
   210  		// Items that go later in the list take precedence.
   211  		return a.Spec.Priority < b.Spec.Priority
   212  	})
   213  
   214  	configs := []*virtlet_v1.VirtletConfig{cfg}
   215  	for _, m := range sortedMappings {
   216  		configs = append(configs, m.Spec.Config)
   217  	}
   218  	if localConfig != nil {
   219  		configs = append(configs, localConfig)
   220  	}
   221  	return MergeConfigs(configs)
   222  }
   223  
   224  // NodeConfig is used to retrieve Virtlet configuration for the current
   225  // node.
   226  type NodeConfig struct {
   227  	clientCfg     clientcmd.ClientConfig
   228  	kubeClient    kubernetes.Interface
   229  	virtletClient virtletclient.Interface
   230  }
   231  
   232  // NewNodeConfig creates a new NodeConfig
   233  func NewNodeConfig(clientCfg clientcmd.ClientConfig) *NodeConfig {
   234  	return &NodeConfig{clientCfg: clientCfg}
   235  }
   236  
   237  func (nc *NodeConfig) setup() error {
   238  	if nc.kubeClient != nil {
   239  		return nil
   240  	}
   241  
   242  	config, err := nc.clientCfg.ClientConfig()
   243  	if err != nil {
   244  		return err
   245  	}
   246  
   247  	kubeClient, err := kubernetes.NewForConfig(config)
   248  	if err != nil {
   249  		return fmt.Errorf("can't create kubernetes api client: %v", err)
   250  	}
   251  
   252  	virtletClient, err := virtletclient.NewForConfig(config)
   253  	if err != nil {
   254  		return fmt.Errorf("can't create Virtlet api client: %v", err)
   255  	}
   256  
   257  	nc.kubeClient = kubeClient
   258  	nc.virtletClient = virtletClient
   259  	return nil
   260  }
   261  
   262  // LoadConfig loads the configuration for the specified node.
   263  func (nc *NodeConfig) LoadConfig(localConfig *virtlet_v1.VirtletConfig, nodeName string) (*virtlet_v1.VirtletConfig, error) {
   264  	if err := nc.setup(); err != nil {
   265  		return nil, err
   266  	}
   267  
   268  	node, err := nc.kubeClient.CoreV1().Nodes().Get(nodeName, meta_v1.GetOptions{})
   269  	if err != nil {
   270  		return nil, fmt.Errorf("can't get node info for node %q: %v", nodeName, err)
   271  	}
   272  
   273  	mappingList, err := nc.virtletClient.VirtletV1().VirtletConfigMappings("kube-system").List(meta_v1.ListOptions{})
   274  	if err != nil {
   275  		return nil, fmt.Errorf("failed to list Virtlet config mappings: %v", err)
   276  	}
   277  
   278  	return configForNode(mappingList.Items, localConfig, nodeName, node.Labels), nil
   279  }