github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/cni/client.go (about)

     1  /*
     2  Copyright 2016 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 cni
    18  
    19  import (
    20  	"fmt"
    21  
    22  	"github.com/containernetworking/cni/libcni"
    23  	cnicurrent "github.com/containernetworking/cni/pkg/types/current"
    24  	"github.com/davecgh/go-spew/spew"
    25  	"github.com/golang/glog"
    26  
    27  	"github.com/Mirantis/virtlet/pkg/nsfix"
    28  	"github.com/Mirantis/virtlet/pkg/utils"
    29  )
    30  
    31  // Client provides an interface to CNI plugins.
    32  type Client interface {
    33  	// AddSandboxToNetwork adds a pod sandbox to the CNI network.
    34  	AddSandboxToNetwork(podID, podName, podNs string) (*cnicurrent.Result, error)
    35  	// RemoveSandboxFromNetwork removes a pod sandbox from the CNI network.
    36  	RemoveSandboxFromNetwork(podID, podName, podNs string) error
    37  	// GetDummyNetwork creates a dummy network using CNI plugin.
    38  	// It's used for making a dummy gateway for Calico CNI plugin.
    39  	// It returns a CNI result and a path to the network namespace.
    40  	GetDummyNetwork() (*cnicurrent.Result, string, error)
    41  }
    42  
    43  // client provides an implementation of Client interface.
    44  type client struct {
    45  	pluginsDir string
    46  	configsDir string
    47  }
    48  
    49  var _ Client = &client{}
    50  
    51  // NewClient returns a client perpared to call plugins in `pluginsDir`
    52  // using configurations found in `configsDir`.
    53  func NewClient(pluginsDir, configsDir string) (*client, error) {
    54  	return &client{
    55  		pluginsDir: pluginsDir,
    56  		configsDir: configsDir,
    57  	}, nil
    58  }
    59  
    60  // GetDummyNetwork implements GetDummyNetwork method of Client interface.
    61  func (c *client) GetDummyNetwork() (*cnicurrent.Result, string, error) {
    62  	// TODO: virtlet pod restarts should not grab another address for
    63  	// the gateway. That's not a big problem usually though
    64  	// as the IPs are not returned to Calico so both old
    65  	// IPs on existing VMs and new ones should work.
    66  	podID := utils.NewUUID()
    67  	if err := CreateNetNS(podID); err != nil {
    68  		return nil, "", fmt.Errorf("couldn't create netns for fake pod %q: %v", podID, err)
    69  	}
    70  	r, err := c.AddSandboxToNetwork(podID, "", "")
    71  	if err != nil {
    72  		return nil, "", fmt.Errorf("couldn't set up CNI for fake pod %q: %v", podID, err)
    73  	}
    74  	return r, PodNetNSPath(podID), nil
    75  }
    76  
    77  // AddSandboxToNetwork implements AddSandboxToNetwork method of Client interface.
    78  func (c *client) AddSandboxToNetwork(podID, podName, podNs string) (*cnicurrent.Result, error) {
    79  	var r cnicurrent.Result
    80  	if err := nsfix.NewCall("cniAddSandboxToNetwork").
    81  		Arg(cniRequest{
    82  			PluginsDir: c.pluginsDir,
    83  			ConfigsDir: c.configsDir,
    84  			PodID:      podID,
    85  			PodName:    podName,
    86  			PodNs:      podNs,
    87  		}).
    88  		SpawnInNamespaces(&r); err != nil {
    89  		return nil, err
    90  	}
    91  	return &r, nil
    92  }
    93  
    94  // RemoveSandboxFromNetwork implements RemoveSandboxFromNetwork method of Client interface.
    95  func (c *client) RemoveSandboxFromNetwork(podID, podName, podNs string) error {
    96  	return nsfix.NewCall("cniRemoveSandboxFromNetwork").
    97  		Arg(cniRequest{
    98  			PluginsDir: c.pluginsDir,
    99  			ConfigsDir: c.configsDir,
   100  			PodID:      podID,
   101  			PodName:    podName,
   102  			PodNs:      podNs,
   103  		}).
   104  		SpawnInNamespaces(nil)
   105  }
   106  
   107  type cniRequest struct {
   108  	PluginsDir string
   109  	ConfigsDir string
   110  	PodID      string
   111  	PodName    string
   112  	PodNs      string
   113  }
   114  
   115  type realClient struct {
   116  	cniConfig     *libcni.CNIConfig
   117  	netConfigList *libcni.NetworkConfigList
   118  }
   119  
   120  func newRealclient(pluginsDir, configsDir string) (*realClient, error) {
   121  	netConfigList, err := ReadConfiguration(configsDir)
   122  	if err != nil {
   123  		return nil, fmt.Errorf("failed to read CNI configuration %q: %v", configsDir, err)
   124  	}
   125  	glog.V(3).Infof("CNI config: name: %q type: %q", netConfigList.Plugins[0].Network.Name, netConfigList.Plugins[0].Network.Type)
   126  
   127  	return &realClient{
   128  		cniConfig:     &libcni.CNIConfig{Path: []string{pluginsDir}},
   129  		netConfigList: netConfigList,
   130  	}, nil
   131  }
   132  
   133  func (c *realClient) cniRuntimeConf(podID, podName, podNs string) *libcni.RuntimeConf {
   134  	r := &libcni.RuntimeConf{
   135  		ContainerID: podID,
   136  		NetNS:       PodNetNSPath(podID),
   137  		IfName:      "virtlet-eth0",
   138  	}
   139  	if podName != "" && podNs != "" {
   140  		r.Args = [][2]string{
   141  			{"IgnoreUnknown", "1"},
   142  			{"K8S_POD_NAMESPACE", podNs},
   143  			{"K8S_POD_NAME", podName},
   144  			{"K8S_POD_INFRA_CONTAINER_ID", podID},
   145  		}
   146  	} else {
   147  		r.Args = [][2]string{
   148  			{"IgnoreUnknown", "1"},
   149  		}
   150  	}
   151  	return r
   152  }
   153  
   154  func handleAddSandboxToNetwork(arg interface{}) (interface{}, error) {
   155  	req := arg.(*cniRequest)
   156  	c, err := newRealclient(req.PluginsDir, req.ConfigsDir)
   157  	if err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	rtConf := c.cniRuntimeConf(req.PodID, req.PodName, req.PodNs)
   162  	// NOTE: this annotation is only need by CNI Genie
   163  	rtConf.Args = append(rtConf.Args, [2]string{
   164  		"K8S_ANNOT", `{"cni": "calico"}`,
   165  	})
   166  	glog.V(3).Infof("AddSandboxToNetwork: PodID %q, PodName %q, PodNs %q, runtime config:\n%s",
   167  		req.PodID, req.PodName, req.PodNs, spew.Sdump(rtConf))
   168  	result, err := c.cniConfig.AddNetworkList(c.netConfigList, rtConf)
   169  	if err == nil {
   170  		glog.V(3).Infof("AddSandboxToNetwork: PodID %q, PodName %q, PodNs %q: result:\n%s",
   171  			req.PodID, req.PodName, req.PodNs, spew.Sdump(result))
   172  	} else {
   173  		glog.Errorf("AddSandboxToNetwork: PodID %q, PodName %q, PodNs %q: error: %v",
   174  			req.PodID, req.PodName, req.PodNs, err)
   175  		return nil, err
   176  	}
   177  	r, err := cnicurrent.NewResultFromResult(result)
   178  	if err != nil {
   179  		return nil, fmt.Errorf("error converting CNI result to the current version: %v", err)
   180  	}
   181  	return r, err
   182  }
   183  
   184  func handleRemoveSandboxFromNetwork(arg interface{}) (interface{}, error) {
   185  	req := arg.(*cniRequest)
   186  	c, err := newRealclient(req.PluginsDir, req.ConfigsDir)
   187  	if err != nil {
   188  		return nil, err
   189  	}
   190  
   191  	glog.V(3).Infof("RemoveSandboxFromNetwork: PodID %q, PodName %q, PodNs %q", req.PodID, req.PodName, req.PodNs)
   192  	err = c.cniConfig.DelNetworkList(c.netConfigList, c.cniRuntimeConf(req.PodID, req.PodName, req.PodNs))
   193  	if err == nil {
   194  		glog.V(3).Infof("RemoveSandboxFromNetwork: PodID %q, PodName %q, PodNs %q: success",
   195  			req.PodID, req.PodName, req.PodNs)
   196  	} else {
   197  		glog.Errorf("RemoveSandboxFromNetwork: PodID %q, PodName %q, PodNs %q: error: %v",
   198  			req.PodID, req.PodName, req.PodNs, err)
   199  	}
   200  	return nil, err
   201  }
   202  
   203  func init() {
   204  	nsfix.RegisterReexec("cniAddSandboxToNetwork", handleAddSandboxToNetwork, cniRequest{})
   205  	nsfix.RegisterReexec("cniRemoveSandboxFromNetwork", handleRemoveSandboxFromNetwork, cniRequest{})
   206  }