ar: refactor network bridge config to use go-cni lib (#6255)
* ar: refactor network bridge config to use go-cni lib * ar: use eth as the iface prefix for bridged network namespaces * vendor: update containerd/go-cni package * ar: update network hook to use TODO contexts when calling configurator * unnecessary conversion
This commit is contained in:
parent
b76de72943
commit
e440ba80f1
|
@ -111,7 +111,10 @@ func (ar *allocRunner) initRunnerHooks(config *clientconfig.Config) error {
|
|||
}
|
||||
|
||||
// create network configurator
|
||||
nc := newNetworkConfigurator(hookLogger, ar.Alloc(), config)
|
||||
nc, err := newNetworkConfigurator(hookLogger, ar.Alloc(), config)
|
||||
if err != nil {
|
||||
return fmt.Errorf("failed to initialize network configurator: %v", err)
|
||||
}
|
||||
|
||||
// Create the alloc directory hook. This is run first to ensure the
|
||||
// directory path exists for other hooks.
|
||||
|
|
|
@ -1,6 +1,7 @@
|
|||
package allocrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
|
@ -70,7 +71,7 @@ func (h *networkHook) Prerun() error {
|
|||
h.setter.SetNetworkIsolation(spec)
|
||||
}
|
||||
|
||||
if err := h.networkConfigurator.Setup(h.alloc, spec); err != nil {
|
||||
if err := h.networkConfigurator.Setup(context.TODO(), h.alloc, spec); err != nil {
|
||||
return fmt.Errorf("failed to configure networking for alloc: %v", err)
|
||||
}
|
||||
return nil
|
||||
|
@ -81,7 +82,7 @@ func (h *networkHook) Postrun() error {
|
|||
return nil
|
||||
}
|
||||
|
||||
if err := h.networkConfigurator.Teardown(h.alloc, h.spec); err != nil {
|
||||
if err := h.networkConfigurator.Teardown(context.TODO(), h.alloc, h.spec); err != nil {
|
||||
h.logger.Error("failed to cleanup network for allocation, resources may have leaked", "alloc", h.alloc.ID, "error", err)
|
||||
}
|
||||
return h.manager.DestroyNetwork(h.alloc.ID, h.spec)
|
||||
|
|
|
@ -1,7 +1,6 @@
|
|||
package allocrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
|
||||
|
@ -122,18 +121,18 @@ func netModeToIsolationMode(netMode string) drivers.NetIsolationMode {
|
|||
}
|
||||
}
|
||||
|
||||
func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) NetworkConfigurator {
|
||||
func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) (NetworkConfigurator, error) {
|
||||
tg := alloc.Job.LookupTaskGroup(alloc.TaskGroup)
|
||||
|
||||
// Check if network stanza is given
|
||||
if len(tg.Networks) == 0 {
|
||||
return &hostNetworkConfigurator{}
|
||||
return &hostNetworkConfigurator{}, nil
|
||||
}
|
||||
|
||||
switch strings.ToLower(tg.Networks[0].Mode) {
|
||||
case "bridge":
|
||||
return newBridgeNetworkConfigurator(log, context.Background(), config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.CNIPath)
|
||||
return newBridgeNetworkConfigurator(log, config.BridgeNetworkName, config.BridgeNetworkAllocSubnet, config.CNIPath)
|
||||
default:
|
||||
return &hostNetworkConfigurator{}
|
||||
return &hostNetworkConfigurator{}, nil
|
||||
}
|
||||
}
|
||||
|
|
|
@ -15,6 +15,6 @@ func newNetworkManager(alloc *structs.Allocation, driverManager drivermanager.Ma
|
|||
return nil, nil
|
||||
}
|
||||
|
||||
func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) NetworkConfigurator {
|
||||
return &hostNetworkConfigurator{}
|
||||
func newNetworkConfigurator(log hclog.Logger, alloc *structs.Allocation, config *clientconfig.Config) (NetworkConfigurator, error) {
|
||||
return &hostNetworkConfigurator{}, nil
|
||||
}
|
||||
|
|
|
@ -1,6 +1,8 @@
|
|||
package allocrunner
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
"github.com/hashicorp/nomad/plugins/drivers"
|
||||
)
|
||||
|
@ -8,8 +10,8 @@ import (
|
|||
// NetworkConfigurator sets up and tears down the interfaces, routes, firewall
|
||||
// rules, etc for the configured networking mode of the allocation.
|
||||
type NetworkConfigurator interface {
|
||||
Setup(*structs.Allocation, *drivers.NetworkIsolationSpec) error
|
||||
Teardown(*structs.Allocation, *drivers.NetworkIsolationSpec) error
|
||||
Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error
|
||||
Teardown(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error
|
||||
}
|
||||
|
||||
// hostNetworkConfigurator is a noop implementation of a NetworkConfigurator for
|
||||
|
@ -17,9 +19,9 @@ type NetworkConfigurator interface {
|
|||
// require further configuration
|
||||
type hostNetworkConfigurator struct{}
|
||||
|
||||
func (h *hostNetworkConfigurator) Setup(*structs.Allocation, *drivers.NetworkIsolationSpec) error {
|
||||
func (h *hostNetworkConfigurator) Setup(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error {
|
||||
return nil
|
||||
}
|
||||
func (h *hostNetworkConfigurator) Teardown(*structs.Allocation, *drivers.NetworkIsolationSpec) error {
|
||||
func (h *hostNetworkConfigurator) Teardown(context.Context, *structs.Allocation, *drivers.NetworkIsolationSpec) error {
|
||||
return nil
|
||||
}
|
||||
|
|
|
@ -8,7 +8,7 @@ import (
|
|||
"path/filepath"
|
||||
"time"
|
||||
|
||||
"github.com/containernetworking/cni/libcni"
|
||||
cni "github.com/containerd/go-cni"
|
||||
"github.com/coreos/go-iptables/iptables"
|
||||
hclog "github.com/hashicorp/go-hclog"
|
||||
"github.com/hashicorp/nomad/nomad/structs"
|
||||
|
@ -28,9 +28,9 @@ const (
|
|||
// the client
|
||||
defaultNomadBridgeName = "nomad"
|
||||
|
||||
// bridgeNetworkAllocIfName is the name that is set for the interface created
|
||||
// inside of the alloc network which is connected to the bridge
|
||||
bridgeNetworkContainerIfName = "eth0"
|
||||
// bridgeNetworkAllocIfPrefix is the prefix that is used for the interface
|
||||
// name created inside of the alloc network which is connected to the bridge
|
||||
bridgeNetworkAllocIfPrefix = "eth"
|
||||
|
||||
// defaultNomadAllocSubnet is the subnet to use for host local ip address
|
||||
// allocation when not specified by the client
|
||||
|
@ -45,8 +45,7 @@ const (
|
|||
// shared bridge, configures masquerading for egress traffic and port mapping
|
||||
// for ingress
|
||||
type bridgeNetworkConfigurator struct {
|
||||
ctx context.Context
|
||||
cniConfig *libcni.CNIConfig
|
||||
cni cni.CNI
|
||||
allocSubnet string
|
||||
bridgeName string
|
||||
|
||||
|
@ -54,9 +53,8 @@ type bridgeNetworkConfigurator struct {
|
|||
logger hclog.Logger
|
||||
}
|
||||
|
||||
func newBridgeNetworkConfigurator(log hclog.Logger, ctx context.Context, bridgeName, ipRange, cniPath string) *bridgeNetworkConfigurator {
|
||||
func newBridgeNetworkConfigurator(log hclog.Logger, bridgeName, ipRange, cniPath string) (*bridgeNetworkConfigurator, error) {
|
||||
b := &bridgeNetworkConfigurator{
|
||||
ctx: ctx,
|
||||
bridgeName: bridgeName,
|
||||
allocSubnet: ipRange,
|
||||
rand: rand.New(rand.NewSource(time.Now().Unix())),
|
||||
|
@ -67,7 +65,13 @@ func newBridgeNetworkConfigurator(log hclog.Logger, ctx context.Context, bridgeN
|
|||
cniPath = defaultCNIPath
|
||||
}
|
||||
}
|
||||
b.cniConfig = libcni.NewCNIConfig(filepath.SplitList(cniPath), nil)
|
||||
|
||||
c, err := cni.New(cni.WithPluginDir(filepath.SplitList(cniPath)),
|
||||
cni.WithInterfacePrefix(bridgeNetworkAllocIfPrefix))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
b.cni = c
|
||||
|
||||
if b.bridgeName == "" {
|
||||
b.bridgeName = defaultNomadBridgeName
|
||||
|
@ -77,7 +81,7 @@ func newBridgeNetworkConfigurator(log hclog.Logger, ctx context.Context, bridgeN
|
|||
b.allocSubnet = defaultNomadAllocSubnet
|
||||
}
|
||||
|
||||
return b
|
||||
return b, nil
|
||||
}
|
||||
|
||||
// ensureForwardingRules ensures that a forwarding rule is added to iptables
|
||||
|
@ -141,13 +145,12 @@ func (b *bridgeNetworkConfigurator) generateAdminChainRule() []string {
|
|||
}
|
||||
|
||||
// Setup calls the CNI plugins with the add action
|
||||
func (b *bridgeNetworkConfigurator) Setup(alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
|
||||
func (b *bridgeNetworkConfigurator) Setup(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
|
||||
if err := b.ensureForwardingRules(); err != nil {
|
||||
return fmt.Errorf("failed to initialize table forwarding rules: %v", err)
|
||||
}
|
||||
|
||||
netconf, err := b.buildNomadNetConfig()
|
||||
if err != nil {
|
||||
if err := b.cni.Load(cni.WithConfListBytes(b.buildNomadNetConfig())); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
|
@ -156,18 +159,17 @@ func (b *bridgeNetworkConfigurator) Setup(alloc *structs.Allocation, spec *drive
|
|||
// in one of them to fail. This rety attempts to overcome any
|
||||
const retry = 3
|
||||
for attempt := 1; ; attempt++ {
|
||||
result, err := b.cniConfig.AddNetworkList(b.ctx, netconf, b.runtimeConf(alloc, spec))
|
||||
if err == nil {
|
||||
break
|
||||
//TODO eventually returning the IP from the result would be nice to have in the alloc
|
||||
if _, err := b.cni.Setup(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc))); err != nil {
|
||||
b.logger.Warn("failed to configure bridge network", "err", err, "attempt", attempt)
|
||||
if attempt == retry {
|
||||
return err
|
||||
}
|
||||
// Sleep for 1 second + jitter
|
||||
time.Sleep(time.Second + (time.Duration(b.rand.Int63n(1000)) * time.Millisecond))
|
||||
continue
|
||||
}
|
||||
|
||||
b.logger.Warn("failed to configure bridge network", "err", err, "result", result.String(), "attempt", attempt)
|
||||
if attempt == retry {
|
||||
return err
|
||||
}
|
||||
|
||||
// Sleep for 1 second + jitter
|
||||
time.Sleep(time.Second + (time.Duration(b.rand.Int63n(1000)) * time.Millisecond))
|
||||
break
|
||||
}
|
||||
|
||||
return nil
|
||||
|
@ -175,31 +177,24 @@ func (b *bridgeNetworkConfigurator) Setup(alloc *structs.Allocation, spec *drive
|
|||
}
|
||||
|
||||
// Teardown calls the CNI plugins with the delete action
|
||||
func (b *bridgeNetworkConfigurator) Teardown(alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
|
||||
netconf, err := b.buildNomadNetConfig()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
err = b.cniConfig.DelNetworkList(b.ctx, netconf, b.runtimeConf(alloc, spec))
|
||||
return err
|
||||
|
||||
func (b *bridgeNetworkConfigurator) Teardown(ctx context.Context, alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) error {
|
||||
return b.cni.Remove(ctx, alloc.ID, spec.Path, cni.WithCapabilityPortMap(getPortMapping(alloc)))
|
||||
}
|
||||
|
||||
// getPortMapping builds a list of portMapping structs that are used as the
|
||||
// portmapping capability arguments for the portmap CNI plugin
|
||||
func getPortMapping(alloc *structs.Allocation) []*portMapping {
|
||||
ports := []*portMapping{}
|
||||
func getPortMapping(alloc *structs.Allocation) []cni.PortMapping {
|
||||
ports := []cni.PortMapping{}
|
||||
for _, network := range alloc.AllocatedResources.Shared.Networks {
|
||||
for _, port := range append(network.DynamicPorts, network.ReservedPorts...) {
|
||||
if port.To < 1 {
|
||||
continue
|
||||
}
|
||||
for _, proto := range []string{"tcp", "udp"} {
|
||||
ports = append(ports, &portMapping{
|
||||
Host: port.Value,
|
||||
Container: port.To,
|
||||
Proto: proto,
|
||||
ports = append(ports, cni.PortMapping{
|
||||
HostPort: int32(port.Value),
|
||||
ContainerPort: int32(port.To),
|
||||
Protocol: proto,
|
||||
})
|
||||
}
|
||||
}
|
||||
|
@ -207,31 +202,8 @@ func getPortMapping(alloc *structs.Allocation) []*portMapping {
|
|||
return ports
|
||||
}
|
||||
|
||||
// portMapping is the json representation of the portmapping capability arguments
|
||||
// for the portmap CNI plugin
|
||||
type portMapping struct {
|
||||
Host int `json:"hostPort"`
|
||||
Container int `json:"containerPort"`
|
||||
Proto string `json:"protocol"`
|
||||
}
|
||||
|
||||
// runtimeConf builds the configuration needed by CNI to locate the target netns
|
||||
func (b *bridgeNetworkConfigurator) runtimeConf(alloc *structs.Allocation, spec *drivers.NetworkIsolationSpec) *libcni.RuntimeConf {
|
||||
return &libcni.RuntimeConf{
|
||||
ContainerID: fmt.Sprintf("nomad-%s", alloc.ID[:8]),
|
||||
NetNS: spec.Path,
|
||||
IfName: bridgeNetworkContainerIfName,
|
||||
CapabilityArgs: map[string]interface{}{
|
||||
"portMappings": getPortMapping(alloc),
|
||||
},
|
||||
}
|
||||
}
|
||||
|
||||
// buildNomadNetConfig generates the CNI network configuration for the bridge
|
||||
// networking mode
|
||||
func (b *bridgeNetworkConfigurator) buildNomadNetConfig() (*libcni.NetworkConfigList, error) {
|
||||
rendered := fmt.Sprintf(nomadCNIConfigTemplate, b.bridgeName, b.allocSubnet, cniAdminChainName)
|
||||
return libcni.ConfListFromBytes([]byte(rendered))
|
||||
func (b *bridgeNetworkConfigurator) buildNomadNetConfig() []byte {
|
||||
return []byte(fmt.Sprintf(nomadCNIConfigTemplate, b.bridgeName, b.allocSubnet, cniAdminChainName))
|
||||
}
|
||||
|
||||
const nomadCNIConfigTemplate = `{
|
||||
|
@ -243,6 +215,7 @@ const nomadCNIConfigTemplate = `{
|
|||
"bridge": "%s",
|
||||
"ipMasq": true,
|
||||
"isGateway": true,
|
||||
"forceAddress": true,
|
||||
"ipam": {
|
||||
"type": "host-local",
|
||||
"ranges": [
|
||||
|
|
|
@ -0,0 +1,201 @@
|
|||
Apache License
|
||||
Version 2.0, January 2004
|
||||
http://www.apache.org/licenses/
|
||||
|
||||
TERMS AND CONDITIONS FOR USE, REPRODUCTION, AND DISTRIBUTION
|
||||
|
||||
1. Definitions.
|
||||
|
||||
"License" shall mean the terms and conditions for use, reproduction,
|
||||
and distribution as defined by Sections 1 through 9 of this document.
|
||||
|
||||
"Licensor" shall mean the copyright owner or entity authorized by
|
||||
the copyright owner that is granting the License.
|
||||
|
||||
"Legal Entity" shall mean the union of the acting entity and all
|
||||
other entities that control, are controlled by, or are under common
|
||||
control with that entity. For the purposes of this definition,
|
||||
"control" means (i) the power, direct or indirect, to cause the
|
||||
direction or management of such entity, whether by contract or
|
||||
otherwise, or (ii) ownership of fifty percent (50%) or more of the
|
||||
outstanding shares, or (iii) beneficial ownership of such entity.
|
||||
|
||||
"You" (or "Your") shall mean an individual or Legal Entity
|
||||
exercising permissions granted by this License.
|
||||
|
||||
"Source" form shall mean the preferred form for making modifications,
|
||||
including but not limited to software source code, documentation
|
||||
source, and configuration files.
|
||||
|
||||
"Object" form shall mean any form resulting from mechanical
|
||||
transformation or translation of a Source form, including but
|
||||
not limited to compiled object code, generated documentation,
|
||||
and conversions to other media types.
|
||||
|
||||
"Work" shall mean the work of authorship, whether in Source or
|
||||
Object form, made available under the License, as indicated by a
|
||||
copyright notice that is included in or attached to the work
|
||||
(an example is provided in the Appendix below).
|
||||
|
||||
"Derivative Works" shall mean any work, whether in Source or Object
|
||||
form, that is based on (or derived from) the Work and for which the
|
||||
editorial revisions, annotations, elaborations, or other modifications
|
||||
represent, as a whole, an original work of authorship. For the purposes
|
||||
of this License, Derivative Works shall not include works that remain
|
||||
separable from, or merely link (or bind by name) to the interfaces of,
|
||||
the Work and Derivative Works thereof.
|
||||
|
||||
"Contribution" shall mean any work of authorship, including
|
||||
the original version of the Work and any modifications or additions
|
||||
to that Work or Derivative Works thereof, that is intentionally
|
||||
submitted to Licensor for inclusion in the Work by the copyright owner
|
||||
or by an individual or Legal Entity authorized to submit on behalf of
|
||||
the copyright owner. For the purposes of this definition, "submitted"
|
||||
means any form of electronic, verbal, or written communication sent
|
||||
to the Licensor or its representatives, including but not limited to
|
||||
communication on electronic mailing lists, source code control systems,
|
||||
and issue tracking systems that are managed by, or on behalf of, the
|
||||
Licensor for the purpose of discussing and improving the Work, but
|
||||
excluding communication that is conspicuously marked or otherwise
|
||||
designated in writing by the copyright owner as "Not a Contribution."
|
||||
|
||||
"Contributor" shall mean Licensor and any individual or Legal Entity
|
||||
on behalf of whom a Contribution has been received by Licensor and
|
||||
subsequently incorporated within the Work.
|
||||
|
||||
2. Grant of Copyright License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
copyright license to reproduce, prepare Derivative Works of,
|
||||
publicly display, publicly perform, sublicense, and distribute the
|
||||
Work and such Derivative Works in Source or Object form.
|
||||
|
||||
3. Grant of Patent License. Subject to the terms and conditions of
|
||||
this License, each Contributor hereby grants to You a perpetual,
|
||||
worldwide, non-exclusive, no-charge, royalty-free, irrevocable
|
||||
(except as stated in this section) patent license to make, have made,
|
||||
use, offer to sell, sell, import, and otherwise transfer the Work,
|
||||
where such license applies only to those patent claims licensable
|
||||
by such Contributor that are necessarily infringed by their
|
||||
Contribution(s) alone or by combination of their Contribution(s)
|
||||
with the Work to which such Contribution(s) was submitted. If You
|
||||
institute patent litigation against any entity (including a
|
||||
cross-claim or counterclaim in a lawsuit) alleging that the Work
|
||||
or a Contribution incorporated within the Work constitutes direct
|
||||
or contributory patent infringement, then any patent licenses
|
||||
granted to You under this License for that Work shall terminate
|
||||
as of the date such litigation is filed.
|
||||
|
||||
4. Redistribution. You may reproduce and distribute copies of the
|
||||
Work or Derivative Works thereof in any medium, with or without
|
||||
modifications, and in Source or Object form, provided that You
|
||||
meet the following conditions:
|
||||
|
||||
(a) You must give any other recipients of the Work or
|
||||
Derivative Works a copy of this License; and
|
||||
|
||||
(b) You must cause any modified files to carry prominent notices
|
||||
stating that You changed the files; and
|
||||
|
||||
(c) You must retain, in the Source form of any Derivative Works
|
||||
that You distribute, all copyright, patent, trademark, and
|
||||
attribution notices from the Source form of the Work,
|
||||
excluding those notices that do not pertain to any part of
|
||||
the Derivative Works; and
|
||||
|
||||
(d) If the Work includes a "NOTICE" text file as part of its
|
||||
distribution, then any Derivative Works that You distribute must
|
||||
include a readable copy of the attribution notices contained
|
||||
within such NOTICE file, excluding those notices that do not
|
||||
pertain to any part of the Derivative Works, in at least one
|
||||
of the following places: within a NOTICE text file distributed
|
||||
as part of the Derivative Works; within the Source form or
|
||||
documentation, if provided along with the Derivative Works; or,
|
||||
within a display generated by the Derivative Works, if and
|
||||
wherever such third-party notices normally appear. The contents
|
||||
of the NOTICE file are for informational purposes only and
|
||||
do not modify the License. You may add Your own attribution
|
||||
notices within Derivative Works that You distribute, alongside
|
||||
or as an addendum to the NOTICE text from the Work, provided
|
||||
that such additional attribution notices cannot be construed
|
||||
as modifying the License.
|
||||
|
||||
You may add Your own copyright statement to Your modifications and
|
||||
may provide additional or different license terms and conditions
|
||||
for use, reproduction, or distribution of Your modifications, or
|
||||
for any such Derivative Works as a whole, provided Your use,
|
||||
reproduction, and distribution of the Work otherwise complies with
|
||||
the conditions stated in this License.
|
||||
|
||||
5. Submission of Contributions. Unless You explicitly state otherwise,
|
||||
any Contribution intentionally submitted for inclusion in the Work
|
||||
by You to the Licensor shall be under the terms and conditions of
|
||||
this License, without any additional terms or conditions.
|
||||
Notwithstanding the above, nothing herein shall supersede or modify
|
||||
the terms of any separate license agreement you may have executed
|
||||
with Licensor regarding such Contributions.
|
||||
|
||||
6. Trademarks. This License does not grant permission to use the trade
|
||||
names, trademarks, service marks, or product names of the Licensor,
|
||||
except as required for reasonable and customary use in describing the
|
||||
origin of the Work and reproducing the content of the NOTICE file.
|
||||
|
||||
7. Disclaimer of Warranty. Unless required by applicable law or
|
||||
agreed to in writing, Licensor provides the Work (and each
|
||||
Contributor provides its Contributions) on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or
|
||||
implied, including, without limitation, any warranties or conditions
|
||||
of TITLE, NON-INFRINGEMENT, MERCHANTABILITY, or FITNESS FOR A
|
||||
PARTICULAR PURPOSE. You are solely responsible for determining the
|
||||
appropriateness of using or redistributing the Work and assume any
|
||||
risks associated with Your exercise of permissions under this License.
|
||||
|
||||
8. Limitation of Liability. In no event and under no legal theory,
|
||||
whether in tort (including negligence), contract, or otherwise,
|
||||
unless required by applicable law (such as deliberate and grossly
|
||||
negligent acts) or agreed to in writing, shall any Contributor be
|
||||
liable to You for damages, including any direct, indirect, special,
|
||||
incidental, or consequential damages of any character arising as a
|
||||
result of this License or out of the use or inability to use the
|
||||
Work (including but not limited to damages for loss of goodwill,
|
||||
work stoppage, computer failure or malfunction, or any and all
|
||||
other commercial damages or losses), even if such Contributor
|
||||
has been advised of the possibility of such damages.
|
||||
|
||||
9. Accepting Warranty or Additional Liability. While redistributing
|
||||
the Work or Derivative Works thereof, You may choose to offer,
|
||||
and charge a fee for, acceptance of support, warranty, indemnity,
|
||||
or other liability obligations and/or rights consistent with this
|
||||
License. However, in accepting such obligations, You may act only
|
||||
on Your own behalf and on Your sole responsibility, not on behalf
|
||||
of any other Contributor, and only if You agree to indemnify,
|
||||
defend, and hold each Contributor harmless for any liability
|
||||
incurred by, or claims asserted against, such Contributor by reason
|
||||
of your accepting any such warranty or additional liability.
|
||||
|
||||
END OF TERMS AND CONDITIONS
|
||||
|
||||
APPENDIX: How to apply the Apache License to your work.
|
||||
|
||||
To apply the Apache License to your work, attach the following
|
||||
boilerplate notice, with the fields enclosed by brackets "[]"
|
||||
replaced with your own identifying information. (Don't include
|
||||
the brackets!) The text should be enclosed in the appropriate
|
||||
comment syntax for the file format. We also recommend that a
|
||||
file or class name and description of purpose be included on the
|
||||
same "printed page" as the copyright notice for easier
|
||||
identification within third-party archives.
|
||||
|
||||
Copyright [yyyy] [name of copyright owner]
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
|
@ -0,0 +1,60 @@
|
|||
[![Build Status](https://travis-ci.org/containerd/go-cni.svg?branch=master)](https://travis-ci.org/containerd/go-cni)
|
||||
|
||||
# go-cni
|
||||
|
||||
A generic CNI library to provide APIs for CNI plugin interactions. The library provides APIs to:
|
||||
|
||||
- Load CNI network config from different sources
|
||||
- Setup networks for container namespace
|
||||
- Remove networks from container namespace
|
||||
- Query status of CNI network plugin initialization
|
||||
|
||||
go-cni aims to support plugins that implement [Container Network Interface](https://github.com/containernetworking/cni)
|
||||
|
||||
## Usage
|
||||
```go
|
||||
func main() {
|
||||
id := "123456"
|
||||
netns := "/proc/9999/ns/net"
|
||||
defaultIfName := "eth0"
|
||||
// Initialize library
|
||||
l = gocni.New(gocni.WithMinNetworkCount(2),
|
||||
gocni.WithPluginConfDir("/etc/mycni/net.d"),
|
||||
gocni.WithPluginDir([]string{"/opt/mycni/bin", "/opt/cni/bin"}),
|
||||
gocni.WithDefaultIfName(defaultIfName))
|
||||
|
||||
// Load the cni configuration
|
||||
err:= l.Load(gocni.WithLoNetwork, gocni.WithDefaultConf)
|
||||
if err != nil{
|
||||
log.Errorf("failed to load cni configuration: %v", err)
|
||||
return
|
||||
}
|
||||
|
||||
// Setup network for namespace.
|
||||
labels := map[string]string{
|
||||
"K8S_POD_NAMESPACE": "namespace1",
|
||||
"K8S_POD_NAME": "pod1",
|
||||
"K8S_POD_INFRA_CONTAINER_ID": id,
|
||||
}
|
||||
result, err := l.Setup(id, netns, gocni.WithLabels(labels))
|
||||
if err != nil {
|
||||
log.Errorf("failed to setup network for namespace %q: %v",id, err)
|
||||
return
|
||||
}
|
||||
|
||||
// Get IP of the default interface
|
||||
IP := result.Interfaces[defaultIfName].IPConfigs[0].IP.String()
|
||||
fmt.Printf("IP of the default interface %s:%s", defaultIfName, IP)
|
||||
}
|
||||
```
|
||||
|
||||
## Project details
|
||||
|
||||
The go-cni is a containerd sub-project, licensed under the [Apache 2.0 license](./LICENSE).
|
||||
As a containerd sub-project, you will find the:
|
||||
|
||||
* [Project governance](https://github.com/containerd/project/blob/master/GOVERNANCE.md),
|
||||
* [Maintainers](https://github.com/containerd/project/blob/master/MAINTAINERS),
|
||||
* and [Contributing guidelines](https://github.com/containerd/project/blob/master/CONTRIBUTING.md)
|
||||
|
||||
information in our [`containerd/project`](https://github.com/containerd/project) repository.
|
|
@ -0,0 +1,220 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"context"
|
||||
"fmt"
|
||||
"strings"
|
||||
"sync"
|
||||
|
||||
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CNI interface {
|
||||
// Setup setup the network for the namespace
|
||||
Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*CNIResult, error)
|
||||
// Remove tears down the network of the namespace.
|
||||
Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error
|
||||
// Load loads the cni network config
|
||||
Load(opts ...CNIOpt) error
|
||||
// Status checks the status of the cni initialization
|
||||
Status() error
|
||||
// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
|
||||
GetConfig() *ConfigResult
|
||||
}
|
||||
|
||||
type ConfigResult struct {
|
||||
PluginDirs []string
|
||||
PluginConfDir string
|
||||
PluginMaxConfNum int
|
||||
Prefix string
|
||||
Networks []*ConfNetwork
|
||||
}
|
||||
|
||||
type ConfNetwork struct {
|
||||
Config *NetworkConfList
|
||||
IFName string
|
||||
}
|
||||
|
||||
// NetworkConfList is a source bytes to string version of cnilibrary.NetworkConfigList
|
||||
type NetworkConfList struct {
|
||||
Name string
|
||||
CNIVersion string
|
||||
Plugins []*NetworkConf
|
||||
Source string
|
||||
}
|
||||
|
||||
// NetworkConf is a source bytes to string conversion of cnilibrary.NetworkConfig
|
||||
type NetworkConf struct {
|
||||
Network *types.NetConf
|
||||
Source string
|
||||
}
|
||||
|
||||
type libcni struct {
|
||||
config
|
||||
|
||||
cniConfig cnilibrary.CNI
|
||||
networkCount int // minimum network plugin configurations needed to initialize cni
|
||||
networks []*Network
|
||||
sync.RWMutex
|
||||
}
|
||||
|
||||
func defaultCNIConfig() *libcni {
|
||||
return &libcni{
|
||||
config: config{
|
||||
pluginDirs: []string{DefaultCNIDir},
|
||||
pluginConfDir: DefaultNetDir,
|
||||
pluginMaxConfNum: DefaultMaxConfNum,
|
||||
prefix: DefaultPrefix,
|
||||
},
|
||||
cniConfig: &cnilibrary.CNIConfig{
|
||||
Path: []string{DefaultCNIDir},
|
||||
},
|
||||
networkCount: 1,
|
||||
}
|
||||
}
|
||||
|
||||
// New creates a new libcni instance.
|
||||
func New(config ...CNIOpt) (CNI, error) {
|
||||
cni := defaultCNIConfig()
|
||||
var err error
|
||||
for _, c := range config {
|
||||
if err = c(cni); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return cni, nil
|
||||
}
|
||||
|
||||
// Load loads the latest config from cni config files.
|
||||
func (c *libcni) Load(opts ...CNIOpt) error {
|
||||
var err error
|
||||
c.Lock()
|
||||
defer c.Unlock()
|
||||
// Reset the networks on a load operation to ensure
|
||||
// config happens on a clean slate
|
||||
c.reset()
|
||||
|
||||
for _, o := range opts {
|
||||
if err = o(c); err != nil {
|
||||
return errors.Wrapf(ErrLoad, fmt.Sprintf("cni config load failed: %v", err))
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Status returns the status of CNI initialization.
|
||||
func (c *libcni) Status() error {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
if len(c.networks) < c.networkCount {
|
||||
return ErrCNINotInitialized
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// Networks returns all the configured networks.
|
||||
// NOTE: Caller MUST NOT modify anything in the returned array.
|
||||
func (c *libcni) Networks() []*Network {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
return append([]*Network{}, c.networks...)
|
||||
}
|
||||
|
||||
// Setup setups the network in the namespace
|
||||
func (c *libcni) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*CNIResult, error) {
|
||||
if err := c.Status(); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
ns, err := newNamespace(id, path, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
var results []*current.Result
|
||||
for _, network := range c.Networks() {
|
||||
r, err := network.Attach(ctx, ns)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
results = append(results, r)
|
||||
}
|
||||
return c.GetCNIResultFromResults(results)
|
||||
}
|
||||
|
||||
// Remove removes the network config from the namespace
|
||||
func (c *libcni) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
|
||||
if err := c.Status(); err != nil {
|
||||
return err
|
||||
}
|
||||
ns, err := newNamespace(id, path, opts...)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
for _, network := range c.Networks() {
|
||||
if err := network.Remove(ctx, ns); err != nil {
|
||||
// Based on CNI spec v0.7.0, empty network namespace is allowed to
|
||||
// do best effort cleanup. However, it is not handled consistently
|
||||
// right now:
|
||||
// https://github.com/containernetworking/plugins/issues/210
|
||||
// TODO(random-liu): Remove the error handling when the issue is
|
||||
// fixed and the CNI spec v0.6.0 support is deprecated.
|
||||
if path == "" && strings.Contains(err.Error(), "no such file or directory") {
|
||||
continue
|
||||
}
|
||||
return err
|
||||
}
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
// GetConfig returns a copy of the CNI plugin configurations as parsed by CNI
|
||||
func (c *libcni) GetConfig() *ConfigResult {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
r := &ConfigResult{
|
||||
PluginDirs: c.config.pluginDirs,
|
||||
PluginConfDir: c.config.pluginConfDir,
|
||||
PluginMaxConfNum: c.config.pluginMaxConfNum,
|
||||
Prefix: c.config.prefix,
|
||||
}
|
||||
for _, network := range c.networks {
|
||||
conf := &NetworkConfList{
|
||||
Name: network.config.Name,
|
||||
CNIVersion: network.config.CNIVersion,
|
||||
Source: string(network.config.Bytes),
|
||||
}
|
||||
for _, plugin := range network.config.Plugins {
|
||||
conf.Plugins = append(conf.Plugins, &NetworkConf{
|
||||
Network: plugin.Network,
|
||||
Source: string(plugin.Bytes),
|
||||
})
|
||||
}
|
||||
r.Networks = append(r.Networks, &ConfNetwork{
|
||||
Config: conf,
|
||||
IFName: network.ifName,
|
||||
})
|
||||
}
|
||||
return r
|
||||
}
|
||||
|
||||
func (c *libcni) reset() {
|
||||
c.networks = nil
|
||||
}
|
|
@ -0,0 +1,55 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrCNINotInitialized = errors.New("cni plugin not initialized")
|
||||
ErrInvalidConfig = errors.New("invalid cni config")
|
||||
ErrNotFound = errors.New("not found")
|
||||
ErrRead = errors.New("failed to read config file")
|
||||
ErrInvalidResult = errors.New("invalid result")
|
||||
ErrLoad = errors.New("failed to load cni config")
|
||||
)
|
||||
|
||||
// IsCNINotInitialized returns true if the error is due to cni config not being initialized
|
||||
func IsCNINotInitialized(err error) bool {
|
||||
return errors.Cause(err) == ErrCNINotInitialized
|
||||
}
|
||||
|
||||
// IsInvalidConfig returns true if the error is invalid cni config
|
||||
func IsInvalidConfig(err error) bool {
|
||||
return errors.Cause(err) == ErrInvalidConfig
|
||||
}
|
||||
|
||||
// IsNotFound returns true if the error is due to a missing config or result
|
||||
func IsNotFound(err error) bool {
|
||||
return errors.Cause(err) == ErrNotFound
|
||||
}
|
||||
|
||||
// IsReadFailure return true if the error is a config read failure
|
||||
func IsReadFailure(err error) bool {
|
||||
return errors.Cause(err) == ErrRead
|
||||
}
|
||||
|
||||
// IsInvalidResult return true if the error is due to invalid cni result
|
||||
func IsInvalidResult(err error) bool {
|
||||
return errors.Cause(err) == ErrInvalidResult
|
||||
}
|
|
@ -0,0 +1,41 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
)
|
||||
|
||||
func validateInterfaceConfig(ipConf *current.IPConfig, ifs int) error {
|
||||
if ipConf == nil {
|
||||
return fmt.Errorf("invalid IP configuration (nil)")
|
||||
}
|
||||
if ipConf.Interface != nil && *ipConf.Interface > ifs {
|
||||
return fmt.Errorf("invalid IP configuration (interface number %d is > number of interfaces %d)", *ipConf.Interface, ifs)
|
||||
}
|
||||
return nil
|
||||
}
|
||||
|
||||
func getIfName(prefix string, i int) string {
|
||||
return fmt.Sprintf("%s%d", prefix, i)
|
||||
}
|
||||
|
||||
func defaultInterface(prefix string) string {
|
||||
return getIfName(prefix, 0)
|
||||
}
|
|
@ -0,0 +1,77 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"context"
|
||||
|
||||
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
)
|
||||
|
||||
type Network struct {
|
||||
cni cnilibrary.CNI
|
||||
config *cnilibrary.NetworkConfigList
|
||||
ifName string
|
||||
}
|
||||
|
||||
func (n *Network) Attach(ctx context.Context, ns *Namespace) (*current.Result, error) {
|
||||
r, err := n.cni.AddNetworkList(ctx, n.config, ns.config(n.ifName))
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return current.NewResultFromResult(r)
|
||||
}
|
||||
|
||||
func (n *Network) Remove(ctx context.Context, ns *Namespace) error {
|
||||
return n.cni.DelNetworkList(ctx, n.config, ns.config(n.ifName))
|
||||
}
|
||||
|
||||
type Namespace struct {
|
||||
id string
|
||||
path string
|
||||
capabilityArgs map[string]interface{}
|
||||
args map[string]string
|
||||
}
|
||||
|
||||
func newNamespace(id, path string, opts ...NamespaceOpts) (*Namespace, error) {
|
||||
ns := &Namespace{
|
||||
id: id,
|
||||
path: path,
|
||||
capabilityArgs: make(map[string]interface{}),
|
||||
args: make(map[string]string),
|
||||
}
|
||||
for _, o := range opts {
|
||||
if err := o(ns); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
}
|
||||
return ns, nil
|
||||
}
|
||||
|
||||
func (ns *Namespace) config(ifName string) *cnilibrary.RuntimeConf {
|
||||
c := &cnilibrary.RuntimeConf{
|
||||
ContainerID: ns.id,
|
||||
NetNS: ns.path,
|
||||
IfName: ifName,
|
||||
}
|
||||
for k, v := range ns.args {
|
||||
c.Args = append(c.Args, [2]string{k, v})
|
||||
}
|
||||
c.CapabilityArgs = ns.capabilityArgs
|
||||
return c
|
||||
}
|
|
@ -0,0 +1,75 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
type NamespaceOpts func(s *Namespace) error
|
||||
|
||||
// Capabilities
|
||||
func WithCapabilityPortMap(portMapping []PortMapping) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.capabilityArgs["portMappings"] = portMapping
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCapabilityIPRanges(ipRanges []IPRanges) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.capabilityArgs["ipRanges"] = ipRanges
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCapabilityBandWitdh adds support for traffic shaping:
|
||||
// https://github.com/heptio/cni-plugins/tree/master/plugins/meta/bandwidth
|
||||
func WithCapabilityBandWidth(bandWidth BandWidth) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.capabilityArgs["bandwidth"] = bandWidth
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithCapabilityDNS adds support for dns
|
||||
func WithCapabilityDNS(dns DNS) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.capabilityArgs["dns"] = dns
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithCapability(name string, capability interface{}) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.capabilityArgs[name] = capability
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// Args
|
||||
func WithLabels(labels map[string]string) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
for k, v := range labels {
|
||||
c.args[k] = v
|
||||
}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
func WithArgs(k, v string) NamespaceOpts {
|
||||
return func(c *Namespace) error {
|
||||
c.args[k] = v
|
||||
return nil
|
||||
}
|
||||
}
|
|
@ -0,0 +1,263 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"sort"
|
||||
"strings"
|
||||
|
||||
cnilibrary "github.com/containernetworking/cni/libcni"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type CNIOpt func(c *libcni) error
|
||||
|
||||
// WithInterfacePrefix sets the prefix for network interfaces
|
||||
// e.g. eth or wlan
|
||||
func WithInterfacePrefix(prefix string) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
c.prefix = prefix
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPluginDir can be used to set the locations of
|
||||
// the cni plugin binaries
|
||||
func WithPluginDir(dirs []string) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
c.pluginDirs = dirs
|
||||
c.cniConfig = &cnilibrary.CNIConfig{Path: dirs}
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPluginConfDir can be used to configure the
|
||||
// cni configuration directory.
|
||||
func WithPluginConfDir(dir string) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
c.pluginConfDir = dir
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithPluginMaxConfNum can be used to configure the
|
||||
// max cni plugin config file num.
|
||||
func WithPluginMaxConfNum(max int) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
c.pluginMaxConfNum = max
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithMinNetworkCount can be used to configure the
|
||||
// minimum networks to be configured and initialized
|
||||
// for the status to report success. By default its 1.
|
||||
func WithMinNetworkCount(count int) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
c.networkCount = count
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithLoNetwork can be used to load the loopback
|
||||
// network config.
|
||||
func WithLoNetwork(c *libcni) error {
|
||||
loConfig, _ := cnilibrary.ConfListFromBytes([]byte(`{
|
||||
"cniVersion": "0.3.1",
|
||||
"name": "cni-loopback",
|
||||
"plugins": [{
|
||||
"type": "loopback"
|
||||
}]
|
||||
}`))
|
||||
|
||||
c.networks = append(c.networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: loConfig,
|
||||
ifName: "lo",
|
||||
})
|
||||
return nil
|
||||
}
|
||||
|
||||
// WithConf can be used to load config directly
|
||||
// from byte.
|
||||
func WithConf(bytes []byte) CNIOpt {
|
||||
return WithConfIndex(bytes, 0)
|
||||
}
|
||||
|
||||
// WithConfIndex can be used to load config directly
|
||||
// from byte and set the interface name's index.
|
||||
func WithConfIndex(bytes []byte, index int) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
conf, err := cnilibrary.ConfFromBytes(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
confList, err := cnilibrary.ConfListFromConf(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.networks = append(c.networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: confList,
|
||||
ifName: getIfName(c.prefix, index),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfFile can be used to load network config
|
||||
// from an .conf file. Supported with absolute fileName
|
||||
// with path only.
|
||||
func WithConfFile(fileName string) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
conf, err := cnilibrary.ConfFromFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
// upconvert to conf list
|
||||
confList, err := cnilibrary.ConfListFromConf(conf)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
c.networks = append(c.networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: confList,
|
||||
ifName: getIfName(c.prefix, 0),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfListBytes can be used to load network config list directly
|
||||
// from byte
|
||||
func WithConfListBytes(bytes []byte) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
confList, err := cnilibrary.ConfListFromBytes(bytes)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i := len(c.networks)
|
||||
c.networks = append(c.networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: confList,
|
||||
ifName: getIfName(c.prefix, i),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithConfListFile can be used to load network config
|
||||
// from an .conflist file. Supported with absolute fileName
|
||||
// with path only.
|
||||
func WithConfListFile(fileName string) CNIOpt {
|
||||
return func(c *libcni) error {
|
||||
confList, err := cnilibrary.ConfListFromFile(fileName)
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
i := len(c.networks)
|
||||
c.networks = append(c.networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: confList,
|
||||
ifName: getIfName(c.prefix, i),
|
||||
})
|
||||
return nil
|
||||
}
|
||||
}
|
||||
|
||||
// WithDefaultConf can be used to detect the default network
|
||||
// config file from the configured cni config directory and load
|
||||
// it.
|
||||
// Since the CNI spec does not specify a way to detect default networks,
|
||||
// the convention chosen is - the first network configuration in the sorted
|
||||
// list of network conf files as the default network.
|
||||
func WithDefaultConf(c *libcni) error {
|
||||
return loadFromConfDir(c, c.pluginMaxConfNum)
|
||||
}
|
||||
|
||||
// WithAllConf can be used to detect all network config
|
||||
// files from the configured cni config directory and load
|
||||
// them.
|
||||
func WithAllConf(c *libcni) error {
|
||||
return loadFromConfDir(c, 0)
|
||||
}
|
||||
|
||||
// loadFromConfDir detects network config files from the
|
||||
// configured cni config directory and load them. max is
|
||||
// the maximum network config to load (max i<= 0 means no limit).
|
||||
func loadFromConfDir(c *libcni, max int) error {
|
||||
files, err := cnilibrary.ConfFiles(c.pluginConfDir, []string{".conf", ".conflist", ".json"})
|
||||
switch {
|
||||
case err != nil:
|
||||
return errors.Wrapf(ErrRead, "failed to read config file: %v", err)
|
||||
case len(files) == 0:
|
||||
return errors.Wrapf(ErrCNINotInitialized, "no network config found in %s", c.pluginConfDir)
|
||||
}
|
||||
|
||||
// files contains the network config files associated with cni network.
|
||||
// Use lexicographical way as a defined order for network config files.
|
||||
sort.Strings(files)
|
||||
// Since the CNI spec does not specify a way to detect default networks,
|
||||
// the convention chosen is - the first network configuration in the sorted
|
||||
// list of network conf files as the default network and choose the default
|
||||
// interface provided during init as the network interface for this default
|
||||
// network. For every other network use a generated interface id.
|
||||
i := 0
|
||||
var networks []*Network
|
||||
for _, confFile := range files {
|
||||
var confList *cnilibrary.NetworkConfigList
|
||||
if strings.HasSuffix(confFile, ".conflist") {
|
||||
confList, err = cnilibrary.ConfListFromFile(confFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config list file %s: %v", confFile, err)
|
||||
}
|
||||
} else {
|
||||
conf, err := cnilibrary.ConfFromFile(confFile)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrInvalidConfig, "failed to load CNI config file %s: %v", confFile, err)
|
||||
}
|
||||
// Ensure the config has a "type" so we know what plugin to run.
|
||||
// Also catches the case where somebody put a conflist into a conf file.
|
||||
if conf.Network.Type == "" {
|
||||
return errors.Wrapf(ErrInvalidConfig, "network type not found in %s", confFile)
|
||||
}
|
||||
|
||||
confList, err = cnilibrary.ConfListFromConf(conf)
|
||||
if err != nil {
|
||||
return errors.Wrapf(ErrInvalidConfig, "failed to convert CNI config file %s to CNI config list: %v", confFile, err)
|
||||
}
|
||||
}
|
||||
if len(confList.Plugins) == 0 {
|
||||
return errors.Wrapf(ErrInvalidConfig, "CNI config list in config file %s has no networks, skipping", confFile)
|
||||
|
||||
}
|
||||
networks = append(networks, &Network{
|
||||
cni: c.cniConfig,
|
||||
config: confList,
|
||||
ifName: getIfName(c.prefix, i),
|
||||
})
|
||||
i++
|
||||
if i == max {
|
||||
break
|
||||
}
|
||||
}
|
||||
if len(networks) == 0 {
|
||||
return errors.Wrapf(ErrCNINotInitialized, "no valid networks found in %s", c.pluginDirs)
|
||||
}
|
||||
c.networks = append(c.networks, networks...)
|
||||
return nil
|
||||
}
|
|
@ -0,0 +1,106 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"net"
|
||||
|
||||
"github.com/containernetworking/cni/pkg/types"
|
||||
"github.com/containernetworking/cni/pkg/types/current"
|
||||
"github.com/pkg/errors"
|
||||
)
|
||||
|
||||
type IPConfig struct {
|
||||
IP net.IP
|
||||
Gateway net.IP
|
||||
}
|
||||
|
||||
type CNIResult struct {
|
||||
Interfaces map[string]*Config
|
||||
DNS []types.DNS
|
||||
Routes []*types.Route
|
||||
}
|
||||
|
||||
type Config struct {
|
||||
IPConfigs []*IPConfig
|
||||
Mac string
|
||||
Sandbox string
|
||||
}
|
||||
|
||||
// GetCNIResultFromResults returns a structured data containing the
|
||||
// interface configuration for each of the interfaces created in the namespace.
|
||||
// Conforms with
|
||||
// Result:
|
||||
// a) Interfaces list. Depending on the plugin, this can include the sandbox
|
||||
// (eg, container or hypervisor) interface name and/or the host interface
|
||||
// name, the hardware addresses of each interface, and details about the
|
||||
// sandbox (if any) the interface is in.
|
||||
// b) IP configuration assigned to each interface. The IPv4 and/or IPv6 addresses,
|
||||
// gateways, and routes assigned to sandbox and/or host interfaces.
|
||||
// c) DNS information. Dictionary that includes DNS information for nameservers,
|
||||
// domain, search domains and options.
|
||||
func (c *libcni) GetCNIResultFromResults(results []*current.Result) (*CNIResult, error) {
|
||||
c.RLock()
|
||||
defer c.RUnlock()
|
||||
|
||||
r := &CNIResult{
|
||||
Interfaces: make(map[string]*Config),
|
||||
}
|
||||
|
||||
// Plugins may not need to return Interfaces in result if
|
||||
// if there are no multiple interfaces created. In that case
|
||||
// all configs should be applied against default interface
|
||||
r.Interfaces[defaultInterface(c.prefix)] = &Config{}
|
||||
|
||||
// Walk through all the results
|
||||
for _, result := range results {
|
||||
// Walk through all the interface in each result
|
||||
for _, intf := range result.Interfaces {
|
||||
r.Interfaces[intf.Name] = &Config{
|
||||
Mac: intf.Mac,
|
||||
Sandbox: intf.Sandbox,
|
||||
}
|
||||
}
|
||||
// Walk through all the IPs in the result and attach it to corresponding
|
||||
// interfaces
|
||||
for _, ipConf := range result.IPs {
|
||||
if err := validateInterfaceConfig(ipConf, len(result.Interfaces)); err != nil {
|
||||
return nil, errors.Wrapf(ErrInvalidResult, "invalid interface config: %v", err)
|
||||
}
|
||||
name := c.getInterfaceName(result.Interfaces, ipConf)
|
||||
r.Interfaces[name].IPConfigs = append(r.Interfaces[name].IPConfigs,
|
||||
&IPConfig{IP: ipConf.Address.IP, Gateway: ipConf.Gateway})
|
||||
}
|
||||
r.DNS = append(r.DNS, result.DNS)
|
||||
r.Routes = append(r.Routes, result.Routes...)
|
||||
}
|
||||
if _, ok := r.Interfaces[defaultInterface(c.prefix)]; !ok {
|
||||
return nil, errors.Wrapf(ErrNotFound, "default network not found for: %s", defaultInterface(c.prefix))
|
||||
}
|
||||
return r, nil
|
||||
}
|
||||
|
||||
// getInterfaceName returns the interface name if the plugins
|
||||
// return the result with associated interfaces. If interface
|
||||
// is not present then default interface name is used
|
||||
func (c *libcni) getInterfaceName(interfaces []*current.Interface,
|
||||
ipConf *current.IPConfig) string {
|
||||
if ipConf.Interface != nil {
|
||||
return interfaces[*ipConf.Interface].Name
|
||||
}
|
||||
return defaultInterface(c.prefix)
|
||||
}
|
|
@ -0,0 +1,78 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
import (
|
||||
"fmt"
|
||||
"io/ioutil"
|
||||
"os"
|
||||
"path"
|
||||
"testing"
|
||||
)
|
||||
|
||||
func makeTmpDir(prefix string) (string, error) {
|
||||
tmpDir, err := ioutil.TempDir(os.TempDir(), prefix)
|
||||
if err != nil {
|
||||
return "", err
|
||||
}
|
||||
return tmpDir, nil
|
||||
}
|
||||
|
||||
func makeFakeCNIConfig(t *testing.T) (string, string) {
|
||||
cniDir, err := makeTmpDir("fakecni")
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create plugin config dir: %v", err)
|
||||
}
|
||||
|
||||
cniConfDir := path.Join(cniDir, "net.d")
|
||||
err = os.MkdirAll(cniConfDir, 0777)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create network config dir: %v", err)
|
||||
}
|
||||
|
||||
networkConfig1 := path.Join(cniConfDir, "mocknetwork1.conf")
|
||||
f1, err := os.Create(networkConfig1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create network config %v: %v", f1, err)
|
||||
}
|
||||
networkConfig2 := path.Join(cniConfDir, "mocknetwork2.conf")
|
||||
f2, err := os.Create(networkConfig2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to create network config %v: %v", f2, err)
|
||||
}
|
||||
|
||||
cfg1 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin1", "fakecni")
|
||||
_, err = f1.WriteString(cfg1)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write network config file %v: %v", f1, err)
|
||||
}
|
||||
f1.Close()
|
||||
cfg2 := fmt.Sprintf(`{ "name": "%s", "type": "%s", "capabilities": {"portMappings": true} }`, "plugin2", "fakecni")
|
||||
_, err = f2.WriteString(cfg2)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to write network config file %v: %v", f2, err)
|
||||
}
|
||||
f2.Close()
|
||||
return cniDir, cniConfDir
|
||||
}
|
||||
|
||||
func tearDownCNIConfig(t *testing.T, confDir string) {
|
||||
err := os.RemoveAll(confDir)
|
||||
if err != nil {
|
||||
t.Fatalf("Failed to cleanup CNI configs: %v", err)
|
||||
}
|
||||
}
|
|
@ -0,0 +1,65 @@
|
|||
/*
|
||||
Copyright The containerd Authors.
|
||||
|
||||
Licensed under the Apache License, Version 2.0 (the "License");
|
||||
you may not use this file except in compliance with the License.
|
||||
You may obtain a copy of the License at
|
||||
|
||||
http://www.apache.org/licenses/LICENSE-2.0
|
||||
|
||||
Unless required by applicable law or agreed to in writing, software
|
||||
distributed under the License is distributed on an "AS IS" BASIS,
|
||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
*/
|
||||
|
||||
package cni
|
||||
|
||||
const (
|
||||
CNIPluginName = "cni"
|
||||
DefaultNetDir = "/etc/cni/net.d"
|
||||
DefaultCNIDir = "/opt/cni/bin"
|
||||
DefaultMaxConfNum = 1
|
||||
VendorCNIDirTemplate = "%s/opt/%s/bin"
|
||||
DefaultPrefix = "eth"
|
||||
)
|
||||
|
||||
type config struct {
|
||||
pluginDirs []string
|
||||
pluginConfDir string
|
||||
pluginMaxConfNum int
|
||||
prefix string
|
||||
}
|
||||
|
||||
type PortMapping struct {
|
||||
HostPort int32
|
||||
ContainerPort int32
|
||||
Protocol string
|
||||
HostIP string
|
||||
}
|
||||
|
||||
type IPRanges struct {
|
||||
Subnet string
|
||||
RangeStart string
|
||||
RangeEnd string
|
||||
Gateway string
|
||||
}
|
||||
|
||||
// BandWidth defines the ingress/egress rate and burst limits
|
||||
type BandWidth struct {
|
||||
IngressRate uint64
|
||||
IngressBurst uint64
|
||||
EgressRate uint64
|
||||
EgressBurst uint64
|
||||
}
|
||||
|
||||
// DNS defines the dns config
|
||||
type DNS struct {
|
||||
// List of DNS servers of the cluster.
|
||||
Servers []string
|
||||
// List of DNS search domains of the cluster.
|
||||
Searches []string
|
||||
// List of DNS options.
|
||||
Options []string
|
||||
}
|
|
@ -0,0 +1,6 @@
|
|||
github.com/stretchr/testify b89eecf5ca5db6d3ba60b237ffe3df7bafb7662f
|
||||
github.com/davecgh/go-spew 8991bc29aa16c548c550c7ff78260e27b9ab7c73
|
||||
github.com/pmezard/go-difflib 792786c7400a136282c1664665ae0a8db921c6c2
|
||||
github.com/stretchr/objx 8a3f7159479fbc75b30357fbc48f380b7320f08e
|
||||
github.com/containernetworking/cni v0.7.1
|
||||
github.com/pkg/errors v0.8.0
|
|
@ -70,6 +70,7 @@
|
|||
{"path":"github.com/containerd/console","checksumSHA1":"IGtuR58l2zmYRcNf8sPDlCSgovE=","origin":"github.com/opencontainers/runc/vendor/github.com/containerd/console","revision":"459bfaec1fc6c17d8bfb12d0a0f69e7e7271ed2a","revisionTime":"2018-08-23T14:46:37Z"},
|
||||
{"path":"github.com/containerd/continuity/pathdriver","checksumSHA1":"GqIrOttKaO7k6HIaHQLPr3cY7rY=","origin":"github.com/docker/docker/vendor/github.com/containerd/continuity/pathdriver","revision":"320063a2ad06a1d8ada61c94c29dbe44e2d87473","revisionTime":"2018-08-16T08:14:46Z"},
|
||||
{"path":"github.com/containerd/fifo","checksumSHA1":"Ur3lVmFp+HTGUzQU+/ZBolKe8FU=","revision":"3d5202aec260678c48179c56f40e6f38a095738c","revisionTime":"2018-03-07T16:51:37Z"},
|
||||
{"path":"github.com/containerd/go-cni","checksumSHA1":"414xGcva33msbOXs7vUQ4ffeJek=","revision":"d20b7eebc7ee1339cb703c4c18be6fd3fa81ad8f","revisionTime":"2019-09-04T15:50:53Z"},
|
||||
{"path":"github.com/containernetworking/cni/libcni","checksumSHA1":"3CsFN6YsShG9EU2oB9vJIqYTxq4=","revision":"dc953e2fd91f9bc624b03cf9ea3706796bfee920","revisionTime":"2019-06-12T15:24:20Z"},
|
||||
{"path":"github.com/containernetworking/cni/pkg/invoke","checksumSHA1":"Xf2DxXUyjBO9u4LeyDzS38pdL+I=","revision":"dc953e2fd91f9bc624b03cf9ea3706796bfee920","revisionTime":"2019-06-12T15:24:20Z"},
|
||||
{"path":"github.com/containernetworking/cni/pkg/types","checksumSHA1":"Dhi4+8X7U2oVzVkgxPrmLaN8qFI=","revision":"dc953e2fd91f9bc624b03cf9ea3706796bfee920","revisionTime":"2019-06-12T15:24:20Z"},
|
||||
|
|
Loading…
Reference in New Issue