github.com/mccv1r0/cni@v0.7.0-alpha1/libcni/api.go (about) 1 // Copyright 2015 CNI authors 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 package libcni 16 17 import ( 18 "encoding/json" 19 "fmt" 20 "io/ioutil" 21 "os" 22 "path/filepath" 23 "strings" 24 25 "github.com/containernetworking/cni/pkg/invoke" 26 "github.com/containernetworking/cni/pkg/types" 27 "github.com/containernetworking/cni/pkg/version" 28 ) 29 30 var ( 31 CacheDir = "/var/lib/cni" 32 ) 33 34 // A RuntimeConf holds the arguments to one invocation of a CNI plugin 35 // excepting the network configuration, with the nested exception that 36 // the `runtimeConfig` from the network configuration is included 37 // here. 38 type RuntimeConf struct { 39 ContainerID string 40 NetNS string 41 IfName string 42 Args [][2]string 43 // A dictionary of capability-specific data passed by the runtime 44 // to plugins as top-level keys in the 'runtimeConfig' dictionary 45 // of the plugin's stdin data. libcni will ensure that only keys 46 // in this map which match the capabilities of the plugin are passed 47 // to the plugin 48 CapabilityArgs map[string]interface{} 49 50 // A cache directory in which to library data. Defaults to CacheDir 51 CacheDir string 52 } 53 54 type NetworkConfig struct { 55 Network *types.NetConf 56 Bytes []byte 57 } 58 59 type NetworkConfigList struct { 60 Name string 61 CNIVersion string 62 Plugins []*NetworkConfig 63 Bytes []byte 64 } 65 66 type CNI interface { 67 AddNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) 68 GetNetworkList(net *NetworkConfigList, rt *RuntimeConf) (types.Result, error) 69 DelNetworkList(net *NetworkConfigList, rt *RuntimeConf) error 70 71 AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) 72 GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) 73 DelNetwork(net *NetworkConfig, rt *RuntimeConf) error 74 } 75 76 type CNIConfig struct { 77 Path []string 78 exec invoke.Exec 79 } 80 81 // CNIConfig implements the CNI interface 82 var _ CNI = &CNIConfig{} 83 84 // NewCNIConfig returns a new CNIConfig object that will search for plugins 85 // in the given paths and use the given exec interface to run those plugins, 86 // or if the exec interface is not given, will use a default exec handler. 87 func NewCNIConfig(path []string, exec invoke.Exec) *CNIConfig { 88 return &CNIConfig{ 89 Path: path, 90 exec: exec, 91 } 92 } 93 94 func buildOneConfig(name, cniVersion string, orig *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (*NetworkConfig, error) { 95 var err error 96 97 inject := map[string]interface{}{ 98 "name": name, 99 "cniVersion": cniVersion, 100 } 101 // Add previous plugin result 102 if prevResult != nil { 103 inject["prevResult"] = prevResult 104 } 105 106 // Ensure every config uses the same name and version 107 orig, err = InjectConf(orig, inject) 108 if err != nil { 109 return nil, err 110 } 111 112 return injectRuntimeConfig(orig, rt) 113 } 114 115 // This function takes a libcni RuntimeConf structure and injects values into 116 // a "runtimeConfig" dictionary in the CNI network configuration JSON that 117 // will be passed to the plugin on stdin. 118 // 119 // Only "capabilities arguments" passed by the runtime are currently injected. 120 // These capabilities arguments are filtered through the plugin's advertised 121 // capabilities from its config JSON, and any keys in the CapabilityArgs 122 // matching plugin capabilities are added to the "runtimeConfig" dictionary 123 // sent to the plugin via JSON on stdin. For exmaple, if the plugin's 124 // capabilities include "portMappings", and the CapabilityArgs map includes a 125 // "portMappings" key, that key and its value are added to the "runtimeConfig" 126 // dictionary to be passed to the plugin's stdin. 127 func injectRuntimeConfig(orig *NetworkConfig, rt *RuntimeConf) (*NetworkConfig, error) { 128 var err error 129 130 rc := make(map[string]interface{}) 131 for capability, supported := range orig.Network.Capabilities { 132 if !supported { 133 continue 134 } 135 if data, ok := rt.CapabilityArgs[capability]; ok { 136 rc[capability] = data 137 } 138 } 139 140 if len(rc) > 0 { 141 orig, err = InjectConf(orig, map[string]interface{}{"runtimeConfig": rc}) 142 if err != nil { 143 return nil, err 144 } 145 } 146 147 return orig, nil 148 } 149 150 // ensure we have a usable exec if the CNIConfig was not given one 151 func (c *CNIConfig) ensureExec() invoke.Exec { 152 if c.exec == nil { 153 c.exec = &invoke.DefaultExec{ 154 RawExec: &invoke.RawExec{Stderr: os.Stderr}, 155 PluginDecoder: version.PluginDecoder{}, 156 } 157 } 158 return c.exec 159 } 160 161 func (c *CNIConfig) addOrGetNetwork(command, name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) (types.Result, error) { 162 c.ensureExec() 163 pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) 164 if err != nil { 165 return nil, err 166 } 167 168 newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) 169 if err != nil { 170 return nil, err 171 } 172 173 return invoke.ExecPluginWithResult(pluginPath, newConf.Bytes, c.args(command, rt), c.exec) 174 } 175 176 // Note that only GET requests should pass an initial prevResult 177 func (c *CNIConfig) addOrGetNetworkList(command string, prevResult types.Result, list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { 178 var err error 179 for _, net := range list.Plugins { 180 prevResult, err = c.addOrGetNetwork(command, list.Name, list.CNIVersion, net, prevResult, rt) 181 if err != nil { 182 return nil, err 183 } 184 } 185 186 return prevResult, nil 187 } 188 189 func getResultCacheFilePath(netName string, rt *RuntimeConf) string { 190 cacheDir := rt.CacheDir 191 if cacheDir == "" { 192 cacheDir = CacheDir 193 } 194 return filepath.Join(cacheDir, "results", fmt.Sprintf("%s-%s", netName, rt.ContainerID)) 195 } 196 197 func setCachedResult(result types.Result, netName string, rt *RuntimeConf) error { 198 data, err := json.Marshal(result) 199 if err != nil { 200 return err 201 } 202 fname := getResultCacheFilePath(netName, rt) 203 if err := os.MkdirAll(filepath.Dir(fname), 0700); err != nil { 204 return err 205 } 206 return ioutil.WriteFile(fname, data, 0600) 207 } 208 209 func delCachedResult(netName string, rt *RuntimeConf) error { 210 fname := getResultCacheFilePath(netName, rt) 211 return os.Remove(fname) 212 } 213 214 func getCachedResult(netName, cniVersion string, rt *RuntimeConf) (types.Result, error) { 215 fname := getResultCacheFilePath(netName, rt) 216 data, err := ioutil.ReadFile(fname) 217 if err != nil { 218 // Ignore read errors; the cached result may not exist on-disk 219 return nil, nil 220 } 221 222 // Read the version of the cached result 223 decoder := version.ConfigDecoder{} 224 resultCniVersion, err := decoder.Decode(data) 225 if err != nil { 226 return nil, err 227 } 228 229 // Ensure we can understand the result 230 result, err := version.NewResult(resultCniVersion, data) 231 if err != nil { 232 return nil, err 233 } 234 235 // Convert to the config version to ensure plugins get prevResult 236 // in the same version as the config. The cached result version 237 // should match the config version unless the config was changed 238 // while the container was running. 239 result, err = result.GetAsVersion(cniVersion) 240 if err != nil && resultCniVersion != cniVersion { 241 return nil, fmt.Errorf("failed to convert cached result version %q to config version %q: %v", resultCniVersion, cniVersion, err) 242 } 243 return result, err 244 } 245 246 // AddNetworkList executes a sequence of plugins with the ADD command 247 func (c *CNIConfig) AddNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { 248 result, err := c.addOrGetNetworkList("ADD", nil, list, rt) 249 if err != nil { 250 return nil, err 251 } 252 253 if err = setCachedResult(result, list.Name, rt); err != nil { 254 return nil, fmt.Errorf("failed to set network '%s' cached result: %v", list.Name, err) 255 } 256 257 return result, nil 258 } 259 260 // GetNetworkList executes a sequence of plugins with the GET command 261 func (c *CNIConfig) GetNetworkList(list *NetworkConfigList, rt *RuntimeConf) (types.Result, error) { 262 // GET was added in CNI spec version 0.4.0 and higher 263 if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { 264 return nil, err 265 } else if !gtet { 266 return nil, fmt.Errorf("configuration version %q does not support the GET command", list.CNIVersion) 267 } 268 269 cachedResult, err := getCachedResult(list.Name, list.CNIVersion, rt) 270 if err != nil { 271 return nil, fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) 272 } 273 return c.addOrGetNetworkList("GET", cachedResult, list, rt) 274 } 275 276 func (c *CNIConfig) delNetwork(name, cniVersion string, net *NetworkConfig, prevResult types.Result, rt *RuntimeConf) error { 277 c.ensureExec() 278 pluginPath, err := c.exec.FindInPath(net.Network.Type, c.Path) 279 if err != nil { 280 return err 281 } 282 283 newConf, err := buildOneConfig(name, cniVersion, net, prevResult, rt) 284 if err != nil { 285 return err 286 } 287 288 return invoke.ExecPluginWithoutResult(pluginPath, newConf.Bytes, c.args("DEL", rt), c.exec) 289 } 290 291 // DelNetworkList executes a sequence of plugins with the DEL command 292 func (c *CNIConfig) DelNetworkList(list *NetworkConfigList, rt *RuntimeConf) error { 293 var cachedResult types.Result 294 295 // Cached result on DEL was added in CNI spec version 0.4.0 and higher 296 if gtet, err := version.GreaterThanOrEqualTo(list.CNIVersion, "0.4.0"); err != nil { 297 return err 298 } else if gtet { 299 cachedResult, err = getCachedResult(list.Name, list.CNIVersion, rt) 300 if err != nil { 301 return fmt.Errorf("failed to get network '%s' cached result: %v", list.Name, err) 302 } 303 } 304 305 for i := len(list.Plugins) - 1; i >= 0; i-- { 306 net := list.Plugins[i] 307 if err := c.delNetwork(list.Name, list.CNIVersion, net, cachedResult, rt); err != nil { 308 return err 309 } 310 } 311 _ = delCachedResult(list.Name, rt) 312 313 return nil 314 } 315 316 // AddNetwork executes the plugin with the ADD command 317 func (c *CNIConfig) AddNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { 318 result, err := c.addOrGetNetwork("ADD", net.Network.Name, net.Network.CNIVersion, net, nil, rt) 319 if err != nil { 320 return nil, err 321 } 322 323 if err = setCachedResult(result, net.Network.Name, rt); err != nil { 324 return nil, fmt.Errorf("failed to set network '%s' cached result: %v", net.Network.Name, err) 325 } 326 327 return result, nil 328 } 329 330 // GetNetwork executes the plugin with the GET command 331 func (c *CNIConfig) GetNetwork(net *NetworkConfig, rt *RuntimeConf) (types.Result, error) { 332 // GET was added in CNI spec version 0.4.0 and higher 333 if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { 334 return nil, err 335 } else if !gtet { 336 return nil, fmt.Errorf("configuration version %q does not support the GET command", net.Network.CNIVersion) 337 } 338 339 cachedResult, err := getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) 340 if err != nil { 341 return nil, fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) 342 } 343 return c.addOrGetNetwork("GET", net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt) 344 } 345 346 // DelNetwork executes the plugin with the DEL command 347 func (c *CNIConfig) DelNetwork(net *NetworkConfig, rt *RuntimeConf) error { 348 var cachedResult types.Result 349 350 // Cached result on DEL was added in CNI spec version 0.4.0 and higher 351 if gtet, err := version.GreaterThanOrEqualTo(net.Network.CNIVersion, "0.4.0"); err != nil { 352 return err 353 } else if gtet { 354 cachedResult, err = getCachedResult(net.Network.Name, net.Network.CNIVersion, rt) 355 if err != nil { 356 return fmt.Errorf("failed to get network '%s' cached result: %v", net.Network.Name, err) 357 } 358 } 359 360 if err := c.delNetwork(net.Network.Name, net.Network.CNIVersion, net, cachedResult, rt); err != nil { 361 return err 362 } 363 _ = delCachedResult(net.Network.Name, rt) 364 return nil 365 } 366 367 // GetVersionInfo reports which versions of the CNI spec are supported by 368 // the given plugin. 369 func (c *CNIConfig) GetVersionInfo(pluginType string) (version.PluginInfo, error) { 370 c.ensureExec() 371 pluginPath, err := c.exec.FindInPath(pluginType, c.Path) 372 if err != nil { 373 return nil, err 374 } 375 376 return invoke.GetVersionInfo(pluginPath, c.exec) 377 } 378 379 // ===== 380 func (c *CNIConfig) args(action string, rt *RuntimeConf) *invoke.Args { 381 return &invoke.Args{ 382 Command: action, 383 ContainerID: rt.ContainerID, 384 NetNS: rt.NetNS, 385 PluginArgs: rt.Args, 386 IfName: rt.IfName, 387 Path: strings.Join(c.Path, string(os.PathListSeparator)), 388 } 389 }