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 }