cni.go 8.2 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312
  1. /*
  2. Copyright The containerd Authors.
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. package cni
  14. import (
  15. "context"
  16. "fmt"
  17. "os"
  18. "strings"
  19. "sync"
  20. cnilibrary "github.com/containernetworking/cni/libcni"
  21. "github.com/containernetworking/cni/pkg/invoke"
  22. "github.com/containernetworking/cni/pkg/types"
  23. types100 "github.com/containernetworking/cni/pkg/types/100"
  24. "github.com/containernetworking/cni/pkg/version"
  25. )
  26. type CNI interface {
  27. // Setup setup the network for the namespace
  28. Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error)
  29. // SetupSerially sets up each of the network interfaces for the namespace in serial
  30. SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error)
  31. // Remove tears down the network of the namespace.
  32. Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
  33. // Check checks if the network is still in desired state
  34. Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
  35. // Load loads the cni network config
  36. Load(opts ...Opt) error
  37. // Status checks the status of the cni initialization
  38. Status() error
  39. // GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
  40. GetConfig() *ConfigResult
  41. }
  42. type ConfigResult struct {
  43. PluginDirs []string
  44. PluginConfDir string
  45. PluginMaxConfNum int
  46. Prefix string
  47. Networks []*ConfNetwork
  48. }
  49. type ConfNetwork struct {
  50. Config *NetworkConfList
  51. IFName string
  52. }
  53. // NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList
  54. type NetworkConfList struct {
  55. Name string
  56. CNIVersion string
  57. Plugins []*NetworkConf
  58. Source string
  59. }
  60. // NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig
  61. type NetworkConf struct {
  62. Network *types.NetConf
  63. Source string
  64. }
  65. type libcni struct {
  66. config
  67. cniConfig cnilibrary.CNI
  68. networkCount int // minimum network plugin configurations needed to initialize cni
  69. networks []*Network
  70. sync.RWMutex
  71. }
  72. func defaultCNIConfig() *libcni {
  73. return &libcni{
  74. config: config{
  75. pluginDirs: []string{DefaultCNIDir},
  76. pluginConfDir: DefaultNetDir,
  77. pluginMaxConfNum: DefaultMaxConfNum,
  78. prefix: DefaultPrefix,
  79. },
  80. cniConfig: cnilibrary.NewCNIConfig(
  81. []string{
  82. DefaultCNIDir,
  83. },
  84. &invoke.DefaultExec{
  85. RawExec: &invoke.RawExec{Stderr: os.Stderr},
  86. PluginDecoder: version.PluginDecoder{},
  87. },
  88. ),
  89. networkCount: 1,
  90. }
  91. }
  92. // New creates a new libcni instance.
  93. func New(config ...Opt) (CNI, error) {
  94. cni := defaultCNIConfig()
  95. var err error
  96. for _, c := range config {
  97. if err = c(cni); err != nil {
  98. return nil, err
  99. }
  100. }
  101. return cni, nil
  102. }
  103. // Load loads the latest config from cni config files.
  104. func (c *libcni) Load(opts ...Opt) error {
  105. var err error
  106. c.Lock()
  107. defer c.Unlock()
  108. // Reset the networks on a load operation to ensure
  109. // config happens on a clean slate
  110. c.reset()
  111. for _, o := range opts {
  112. if err = o(c); err != nil {
  113. return fmt.Errorf("cni config load failed: %v: %w", err, ErrLoad)
  114. }
  115. }
  116. return nil
  117. }
  118. // Status returns the status of CNI initialization.
  119. func (c *libcni) Status() error {
  120. c.RLock()
  121. defer c.RUnlock()
  122. if len(c.networks) < c.networkCount {
  123. return ErrCNINotInitialized
  124. }
  125. return nil
  126. }
  127. // Networks returns all the configured networks.
  128. // NOTE: Caller MUST NOT modify anything in the returned array.
  129. func (c *libcni) Networks() []*Network {
  130. c.RLock()
  131. defer c.RUnlock()
  132. return append([]*Network{}, c.networks...)
  133. }
  134. // Setup setups the network in the namespace and returns a Result
  135. func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
  136. if err := c.Status(); err != nil {
  137. return nil, err
  138. }
  139. ns, err := newNamespace(id, path, opts...)
  140. if err != nil {
  141. return nil, err
  142. }
  143. result, err := c.attachNetworks(ctx, ns)
  144. if err != nil {
  145. return nil, err
  146. }
  147. return c.createResult(result)
  148. }
  149. // SetupSerially setups the network in the namespace and returns a Result
  150. func (c *libcni) SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
  151. if err := c.Status(); err != nil {
  152. return nil, err
  153. }
  154. ns, err := newNamespace(id, path, opts...)
  155. if err != nil {
  156. return nil, err
  157. }
  158. result, err := c.attachNetworksSerially(ctx, ns)
  159. if err != nil {
  160. return nil, err
  161. }
  162. return c.createResult(result)
  163. }
  164. func (c *libcni) attachNetworksSerially(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
  165. var results []*types100.Result
  166. for _, network := range c.Networks() {
  167. r, err := network.Attach(ctx, ns)
  168. if err != nil {
  169. return nil, err
  170. }
  171. results = append(results, r)
  172. }
  173. return results, nil
  174. }
  175. type asynchAttachResult struct {
  176. index int
  177. res *types100.Result
  178. err error
  179. }
  180. func asynchAttach(ctx context.Context, index int, n *Network, ns *Namespace, wg *sync.WaitGroup, rc chan asynchAttachResult) {
  181. defer wg.Done()
  182. r, err := n.Attach(ctx, ns)
  183. rc <- asynchAttachResult{index: index, res: r, err: err}
  184. }
  185. func (c *libcni) attachNetworks(ctx context.Context, ns *Namespace) ([]*types100.Result, error) {
  186. var wg sync.WaitGroup
  187. var firstError error
  188. results := make([]*types100.Result, len(c.Networks()))
  189. rc := make(chan asynchAttachResult)
  190. for i, network := range c.Networks() {
  191. wg.Add(1)
  192. go asynchAttach(ctx, i, network, ns, &wg, rc)
  193. }
  194. for range c.Networks() {
  195. rs := <-rc
  196. if rs.err != nil && firstError == nil {
  197. firstError = rs.err
  198. }
  199. results[rs.index] = rs.res
  200. }
  201. wg.Wait()
  202. return results, firstError
  203. }
  204. // Remove removes the network config from the namespace
  205. func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
  206. if err := c.Status(); err != nil {
  207. return err
  208. }
  209. ns, err := newNamespace(id, path, opts...)
  210. if err != nil {
  211. return err
  212. }
  213. for _, network := range c.Networks() {
  214. if err := network.Remove(ctx, ns); err != nil {
  215. // Based on CNI spec v0.7.0, empty network namespace is allowed to
  216. // do best effort cleanup. However, it is not handled consistently
  217. // right now:
  218. // https://github.com/containernetworking/plugins/issues/210
  219. // TODO(random-liu): Remove the error handling when the issue is
  220. // fixed and the CNI spec v0.6.0 support is deprecated.
  221. // NOTE(claudiub): Some CNIs could return a "not found" error, which could mean that
  222. // it was already deleted.
  223. if (path == "" && strings.Contains(err.Error(), "no such file or directory")) || strings.Contains(err.Error(), "not found") {
  224. continue
  225. }
  226. return err
  227. }
  228. }
  229. return nil
  230. }
  231. // Check checks if the network is still in desired state
  232. func (c *libcni) Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
  233. if err := c.Status(); err != nil {
  234. return err
  235. }
  236. ns, err := newNamespace(id, path, opts...)
  237. if err != nil {
  238. return err
  239. }
  240. for _, network := range c.Networks() {
  241. err := network.Check(ctx, ns)
  242. if err != nil {
  243. return err
  244. }
  245. }
  246. return nil
  247. }
  248. // GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
  249. func (c *libcni) GetConfig() *ConfigResult {
  250. c.RLock()
  251. defer c.RUnlock()
  252. r := &ConfigResult{
  253. PluginDirs: c.config.pluginDirs,
  254. PluginConfDir: c.config.pluginConfDir,
  255. PluginMaxConfNum: c.config.pluginMaxConfNum,
  256. Prefix: c.config.prefix,
  257. }
  258. for _, network := range c.networks {
  259. conf := &NetworkConfList{
  260. Name: network.config.Name,
  261. CNIVersion: network.config.CNIVersion,
  262. Source: string(network.config.Bytes),
  263. }
  264. for _, plugin := range network.config.Plugins {
  265. conf.Plugins = append(conf.Plugins, &NetworkConf{
  266. Network: plugin.Network,
  267. Source: string(plugin.Bytes),
  268. })
  269. }
  270. r.Networks = append(r.Networks, &ConfNetwork{
  271. Config: conf,
  272. IFName: network.ifName,
  273. })
  274. }
  275. return r
  276. }
  277. func (c *libcni) reset() {
  278. c.networks = nil
  279. }