Add CNI network plugin

This PR adds CNI network plugin to stackube.

Usage:

1. CNI network config

{
    "cniVersion": "0.3.1",
    "name": "net",
    "type": "kubestack",
    "kubestack-config": "/etc/kubestack.conf"
}

2. /etc/kubestack.conf

[Global]
auth-url=https://192.168.0.3/identity_admin/v2.0
username=admin
password=password
tenant-name=admin
region=RegionOne
ext-net-id=550370a3-4fc2-4494-919d-cae33f5b3de8
[Plugin]
plugin-name=ovs
integration-bridge=br-int

3. Put stackube command to /opt/cni/bin

Change-Id: I3a666aa2b0b90ef5d8e2f065ddda0deb6d0fcd94
Implements: blueprint cni-plugin
Signed-off-by: Pengfei Ni <feiskyer@gmail.com>
Signed-off-by: mozhuli <21621232@zju.edu.cn>
This commit is contained in:
Pengfei Ni 2017-07-10 16:07:46 +08:00
parent 7bb760620f
commit 69dc3b3a6b
35 changed files with 3880 additions and 20 deletions

46
Godeps/Godeps.json generated
View File

@ -25,6 +25,35 @@
"ImportPath": "github.com/PuerkitoBio/urlesc",
"Rev": "5bd2802263f21d8788851d5305584c82a5c75d7e"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/skel",
"Comment": "v0.5.2-64-g6a6f9d7",
"Rev": "6a6f9d7f5c148ba7134c9cf31687329bdf1c7eeb"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types",
"Comment": "v0.5.2-64-g6a6f9d7",
"Rev": "6a6f9d7f5c148ba7134c9cf31687329bdf1c7eeb"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/020",
"Comment": "v0.5.2-64-g6a6f9d7",
"Rev": "6a6f9d7f5c148ba7134c9cf31687329bdf1c7eeb"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/types/current",
"Comment": "v0.5.2-64-g6a6f9d7",
"Rev": "6a6f9d7f5c148ba7134c9cf31687329bdf1c7eeb"
},
{
"ImportPath": "github.com/containernetworking/cni/pkg/version",
"Comment": "v0.5.2-64-g6a6f9d7",
"Rev": "6a6f9d7f5c148ba7134c9cf31687329bdf1c7eeb"
},
{
"ImportPath": "github.com/containernetworking/plugins/pkg/ns",
"Rev": "56d8984bbc73211051aaaa07ab32b2c194e39340"
},
{
"ImportPath": "github.com/davecgh/go-spew/spew",
"Comment": "v1.1.0",
@ -40,6 +69,11 @@
"Comment": "v2.6.1-2-g42ea75c",
"Rev": "42ea75ca2d11ff2f34b8b4fffae7e3dc45ccf906"
},
{
"ImportPath": "github.com/docker/distribution/uuid",
"Comment": "v2.6.1-2-g42ea75c",
"Rev": "42ea75ca2d11ff2f34b8b4fffae7e3dc45ccf906"
},
{
"ImportPath": "github.com/emicklei/go-restful",
"Comment": "2.2.0-4-gff4f55a",
@ -162,6 +196,18 @@
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers",
"Rev": "5102b608e3e070dadf65b060362fe4052f0c5967"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding",
"Rev": "5102b608e3e070dadf65b060362fe4052f0c5967"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups",
"Rev": "5102b608e3e070dadf65b060362fe4052f0c5967"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules",
"Rev": "5102b608e3e070dadf65b060362fe4052f0c5967"
},
{
"ImportPath": "github.com/gophercloud/gophercloud/openstack/networking/v2/networks",
"Rev": "5102b608e3e070dadf65b060362fe4052f0c5967"

View File

@ -37,11 +37,15 @@ depend-update: work
.PHONY: build
build: depend
cd $(DEST) && go build $(GOFLAGS) -a -o $(OUTPUT)/stackube-controller ./cmd/stackube-controller
cd $(DEST)
go build $(GOFLAGS) -a -o $(OUTPUT)/stackube-controller ./cmd/stackube-controller
go build $(GOFLAGS) -a -o $(OUTPUT)/kubestack ./cmd/kubestack
.PHONY: install
install: depend
cd $(DEST) && go install $(GOFLAGS) -tags '$(TAGS)' -ldflags '$(LDFLAGS)' ./...
cd $(DEST)
install -D -m 755 $(OUTPUT)/stackube-controller /usr/local/bin/stackube-controller
install -D -m 755 $(OUTPUT)/kubestack /opt/cni/bin/kubestack
.PHONY: test
test: test-unit
@ -59,7 +63,7 @@ test-flags:
# to detect if any files are listed as having format problems.
.PHONY: fmt
fmt: work
files=$$(cd $(DEST) && find . -not \( \( -wholename '*/vendor/*' \) -prune \) -name '*.go' | xargs gofmt -s -l | tee >(cat - >&2)); [ -z "$$files" ]
files=$$(cd $(DEST) && find . -not \( \( -wholename '*/vendor/*' \) -prune \) -name '*.go' | xargs gofmt -s -l | tee >(cat - >&2)); [ -z "$$files" ]
.PHONY: fmtfix
fmtfix: work

322
cmd/kubestack/kubestack.go Normal file
View File

@ -0,0 +1,322 @@
package main
import (
"encoding/json"
"flag"
"fmt"
"net"
"os"
"path"
"runtime"
"git.openstack.org/openstack/stackube/pkg/kubestack/plugins"
kubestacktypes "git.openstack.org/openstack/stackube/pkg/kubestack/types"
"git.openstack.org/openstack/stackube/pkg/openstack"
"git.openstack.org/openstack/stackube/pkg/util"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
cniSpecVersion "github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
// import plugins
_ "git.openstack.org/openstack/stackube/pkg/kubestack/plugins/openvswitch"
)
var (
// VERSION is filled out during the build process (using git describe output)
VERSION = "0.1"
)
// OpenStack describes openstack client and its plugins.
type OpenStack struct {
Client openstack.Client
Plugin plugins.PluginInterface
}
func init() {
// this ensures that main runs only on main thread (thread group leader).
// since namespace ops (unshare, setns) are done for a single thread, we
// must ensure that the goroutine does not jump from OS thread to thread
runtime.LockOSThread()
}
func loadNetConf(bytes []byte) (*kubestacktypes.NetConf, string, error) {
n := &kubestacktypes.NetConf{}
if err := json.Unmarshal(bytes, n); err != nil {
return nil, "", fmt.Errorf("failed to load netconf: %v", err)
}
return n, n.CNIVersion, nil
}
func (os *OpenStack) getNetworkIDByNamespace(namespace string) (string, error) {
// Only support one network and network's name is same with namespace.
// TODO: make it general after multi-network is supported.
networkName := util.BuildNetworkName(namespace, namespace)
network, err := os.Client.GetNetwork(networkName)
if err != nil {
glog.Errorf("Get network by name %q failed: %v", networkName, err)
return "", err
}
return network.Uid, nil
}
func getHostName() string {
host, err := os.Hostname()
if err != nil {
return ""
}
return host
}
func getK8sArgs(args string) (string, string, error) {
k8sArgs := kubestacktypes.K8sArgs{}
if err := types.LoadArgs(args, &k8sArgs); err != nil {
return "", "", err
}
return string(k8sArgs.K8S_POD_NAME), string(k8sArgs.K8S_POD_NAMESPACE), nil
}
func initOpenstack(stdinData []byte) (OpenStack, string, error) {
// Load cni net config
n, cniVersion, err := loadNetConf(stdinData)
if err != nil {
return OpenStack{}, "", err
}
//Init openstack client
if n.KubestackConfig == "" {
return OpenStack{}, "", fmt.Errorf("kubestack-config not specified")
}
openStackClient, err := openstack.NewClient(n.KubestackConfig)
if err != nil {
return OpenStack{}, "", err
}
os := OpenStack{
Client: *openStackClient,
}
// Init plugin
pluginName := os.Client.PluginName
if pluginName == "" {
pluginName = "ovs"
}
integrationBridge := os.Client.IntegrationBridge
if integrationBridge == "" {
integrationBridge = "br-int"
}
plugin, _ := plugins.GetNetworkPlugin(pluginName)
if plugin == nil {
return OpenStack{}, "", fmt.Errorf("plugin %q not found", pluginName)
}
plugin.Init(integrationBridge)
os.Plugin = plugin
return os, cniVersion, nil
}
func cmdAdd(args *skel.CmdArgs) error {
os, cniVersion, err := initOpenstack(args.StdinData)
if err != nil {
glog.Errorf("Init OpenStack failed: %v", err)
return err
}
// Get k8s args
podName, podNamespace, err := getK8sArgs(args.Args)
if err != nil {
glog.Errorf("GetK8sArgs failed: %v", err)
return err
}
// Get tenantID
tenantID, err := os.Client.GetTenantIDFromName(podNamespace)
if err != nil {
glog.Errorf("Get tenantID failed: %v", err)
return err
}
// Get networkID
networkID, err := os.getNetworkIDByNamespace(podNamespace)
if err != nil {
glog.Errorf("Get networkID failed: %v", err)
return err
}
// Build port name
portName := util.BuildPortName(podNamespace, podName)
// Get port from openstack.
port, err := os.Client.GetPort(portName)
if err == util.ErrNotFound || port == nil {
// Port not found, create a new one.
portWithBinding, err := os.Client.CreatePort(networkID, tenantID, portName)
if err != nil {
glog.Errorf("CreatePort failed: %v", err)
return err
}
port = &portWithBinding.Port
} else if err != nil {
glog.Errorf("GetPort failed: %v", err)
return err
}
deviceOwner := fmt.Sprintf("compute:%s", getHostName())
if port.DeviceOwner != deviceOwner {
// Update hostname in order to make sure it is correct
updateOpts := portsbinding.UpdateOpts{
HostID: getHostName(),
UpdateOptsBuilder: ports.UpdateOpts{
DeviceOwner: deviceOwner,
},
}
_, err = portsbinding.Update(os.Client.Network, port.ID, updateOpts).Extract()
if err != nil {
ports.Delete(os.Client.Network, port.ID)
glog.Errorf("Update port %s failed: %v", portName, err)
return err
}
}
glog.V(4).Infof("Pod %s's port is %v", podName, port)
// Get subnet and gateway
subnet, err := os.Client.GetProviderSubnet(port.FixedIPs[0].SubnetID)
if err != nil {
glog.Errorf("Get info of subnet %s failed: %v", port.FixedIPs[0].SubnetID, err)
if nil != ports.Delete(os.Client.Network, port.ID).ExtractErr() {
glog.Warningf("Delete port %s failed", port.ID)
}
return err
}
// Get network namespace.
netns, err := ns.GetNS(args.Netns)
if err != nil {
return fmt.Errorf("failed to open netns %q: %v", args.Netns, err)
}
defer netns.Close()
// Setup interface for pod
_, cidr, _ := net.ParseCIDR(subnet.Cidr)
prefixSize, _ := cidr.Mask.Size()
netnsName := path.Base(netns.Path())
brInterface, conInterface, err := os.Plugin.SetupInterface(portName, args.ContainerID, port,
fmt.Sprintf("%s/%d", port.FixedIPs[0].IPAddress, prefixSize),
subnet.Gateway, args.IfName, netnsName)
if err != nil {
glog.Errorf("SetupInterface failed: %v", err)
if nil != ports.Delete(os.Client.Network, port.ID).ExtractErr() {
glog.Warningf("Delete port %s failed", port.ID)
}
return err
}
// Collect the result in this variable - this is ultimately what gets "returned"
// by this function by printing it to stdout.
result := &current.Result{}
// Populate container interface sandbox path
conInterface.Sandbox = netns.Path()
// Populate result.Interfaces
result.Interfaces = []*current.Interface{brInterface, conInterface}
// Populate result.IPs
ip := net.ParseIP(port.FixedIPs[0].IPAddress)
ipCidr := net.IPNet{
IP: ip,
Mask: cidr.Mask,
}
gateway := net.ParseIP(subnet.Gateway)
containerIPConfig := &current.IPConfig{
Version: "4",
Address: ipCidr,
Gateway: gateway,
}
result.IPs = []*current.IPConfig{containerIPConfig}
// Print result to stdout, in the format defined by the requested cniVersion.
return types.PrintResult(result, cniVersion)
}
func cmdDel(args *skel.CmdArgs) error {
os, _, err := initOpenstack(args.StdinData)
if err != nil {
glog.Errorf("Init OpenStack failed: %v", err)
return err
}
// Get k8s args
podName, podNamespace, err := getK8sArgs(args.Args)
if err != nil {
glog.Errorf("GetK8sArgs failed: %v", err)
return err
}
// Build port name
portName := util.BuildPortName(podNamespace, podName)
// Get port from openstack
port, err := os.Client.GetPort(portName)
if err != nil {
glog.Errorf("GetPort %s failed: %v", portName, err)
return err
}
if port == nil {
glog.Warningf("Port %s already deleted", portName)
return nil
}
glog.V(4).Infof("Pod %s's port is %v", podName, port)
// Delete interface
err = os.Plugin.DestroyInterface(portName, args.ContainerID, port)
if err != nil {
glog.Errorf("DestroyInterface for pod %s failed: %v", podName, err)
return err
}
// Delete port from openstack
err = os.Client.DeletePort(portName)
if err != nil {
glog.Errorf("DeletePort %s failed: %v", portName, err)
return err
}
return nil
}
// AddIgnoreUnknownArgs appends the 'IgnoreUnknown=1' option to CNI_ARGS before
// calling the IPAM plugin. Otherwise, it will complain about the Kubernetes
// arguments. See https://github.com/kubernetes/kubernetes/pull/24983
func AddIgnoreUnknownArgs() error {
cniArgs := "IgnoreUnknown=1"
if os.Getenv("CNI_ARGS") != "" {
cniArgs = fmt.Sprintf("%s;%s", cniArgs, os.Getenv("CNI_ARGS"))
}
return os.Setenv("CNI_ARGS", cniArgs)
}
func main() {
// Display the version on "-v", otherwise just delegate to the skel code.
// Use a new flag set so as not to conflict with existing libraries which use "flag"
flagSet := flag.NewFlagSet("kubestack", flag.ExitOnError)
version := flagSet.Bool("v", false, "Display version")
err := flagSet.Parse(os.Args[1:])
if err != nil {
fmt.Println(err)
os.Exit(1)
}
if *version {
fmt.Println(VERSION)
os.Exit(0)
}
if err := AddIgnoreUnknownArgs(); err != nil {
os.Exit(1)
}
skel.PluginMain(cmdAdd, cmdDel, cniSpecVersion.All)
}

View File

@ -0,0 +1,276 @@
package openvswitch
import (
"fmt"
"strings"
"git.openstack.org/openstack/stackube/pkg/kubestack/plugins"
"git.openstack.org/openstack/stackube/pkg/util"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
const (
pluginName = "ovs"
)
type OVSPlugin struct {
IntegrationBridge string
}
func init() {
plugins.RegisterNetworkPlugin(pluginName, func() (plugins.PluginInterface, error) {
return NewOVSPlugin(), nil
})
}
func NewOVSPlugin() *OVSPlugin {
return &OVSPlugin{}
}
func (p *OVSPlugin) Name() string {
return pluginName
}
func (p *OVSPlugin) Init(integrationBridge string) error {
p.IntegrationBridge = integrationBridge
return nil
}
func (p *OVSPlugin) buildBridgeName(portID string) string {
return ("qbr" + portID)[:14]
}
func (p *OVSPlugin) buildTapName(portID string) string {
return ("tap" + portID)[:14]
}
func (p *OVSPlugin) buildSandboxInterfaceName(portID string) (string, string) {
return ("vib" + portID)[:14], ("vif" + portID)[:14]
}
func (p *OVSPlugin) buildVethName(portID string) (string, string) {
return ("qvb" + portID)[:14], ("qvo" + portID)[:14]
}
func (p *OVSPlugin) SetupSandboxInterface(podName, podInfraContainerID string, port *ports.Port, ipcidr, gateway, ifName, netns string) (*current.Interface, error) {
vibName, vifName := p.buildSandboxInterfaceName(port.ID)
ret, err := util.RunCommand("ip", "link", "add", vibName, "type", "veth", "peer", "name", vifName)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
bridge := p.buildBridgeName(port.ID)
ret, err = util.RunCommand("brctl", "addif", bridge, vibName)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", "dev", vifName, "address", port.MACAddress)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", vifName, "netns", netns)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "netns", "exec", netns, "ip", "link", "set", vifName, "down")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "netns", "exec", netns, "ip", "link", "set", vifName, "name", ifName)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "netns", "exec", netns, "ip", "link", "set", ifName, "up")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "netns", "exec", netns, "ip", "addr", "add", "dev", ifName, ipcidr)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "netns", "exec", netns, "ip", "route", "add", "default", "via", gateway)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", "dev", vibName, "up")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
_, err = util.RunCommand("rm", "-f", fmt.Sprintf("/var/run/netns/%s", netns))
if err != nil {
glog.V(5).Infof("Warning: remove netns symlink failed: %v", err)
}
return &current.Interface{
Name: p.buildTapName(port.ID),
Mac: port.MACAddress,
}, nil
}
func (p *OVSPlugin) SetupOVSInterface(podName, podInfraContainerID string, port *ports.Port) (*current.Interface, error) {
qvb, qvo := p.buildVethName(port.ID)
ret, err := util.RunCommand("ip", "link", "add", qvb, "type", "veth", "peer", "name", qvo)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
bridge := p.buildBridgeName(port.ID)
ret, err = util.RunCommand("brctl", "addbr", bridge)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", qvb, "up")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", qvo, "up")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ip", "link", "set", bridge, "up")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("brctl", "addif", bridge, qvb)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
ret, err = util.RunCommand("ovs-vsctl", "-vconsole:off", "--", "--if-exists", "del-port",
qvo, "--", "add-port", p.IntegrationBridge, qvo, "--", "set", "Interface", qvo,
fmt.Sprintf("external_ids:attached-mac=%s", port.MACAddress),
fmt.Sprintf("external_ids:iface-id=%s", port.ID),
fmt.Sprintf("external_ids:vm-id=%s", podName),
"external_ids:iface-status=active")
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
// Get bridge mac
ret, err = util.RunCommand("ip", "link", "show", bridge)
if err != nil {
glog.Warningf("SetupInterface failed, ret:%s, error:%v", strings.Join(ret, "\n"), err)
p.DestroyInterface(podName, podInfraContainerID, port)
return nil, err
}
mac := ret[1][15:32]
return &current.Interface{
Name: bridge,
Mac: mac,
}, nil
}
func (p *OVSPlugin) SetupInterface(podName, podInfraContainerID string, port *ports.Port, ipcidr, gateway, ifName, netns string) (*current.Interface, *current.Interface, error) {
brInterface, err := p.SetupOVSInterface(podName, podInfraContainerID, port)
if err != nil {
glog.Errorf("SetupOVSInterface failed: %v", err)
return nil, nil, err
}
conInterface, err := p.SetupSandboxInterface(podName, podInfraContainerID, port, ipcidr, gateway, ifName, netns)
if err != nil {
glog.Errorf("SetupSandboxInterface failed: %v", err)
return nil, nil, err
}
glog.V(4).Infof("SetupInterface for %s done", podName)
return brInterface, conInterface, nil
}
func (p *OVSPlugin) destroyOVSInterface(podName, portID string) error {
_, qvo := p.buildVethName(portID)
bridge := p.buildBridgeName(portID)
output, err := util.RunCommand("ovs-vsctl", "-vconsole:off", "--if-exists", "del-port", qvo)
if err != nil {
glog.Warningf("Warning: ovs del-port %s failed: %v, %v", qvo, output, err)
}
output, err = util.RunCommand("ip", "link", "set", "dev", qvo, "down")
if err != nil {
glog.Warningf("Warning: set dev %s down failed: %v, %v", qvo, output, err)
}
output, err = util.RunCommand("ip", "link", "delete", "dev", qvo)
if err != nil {
glog.Warningf("Warning: delete dev %s failed: %v, %v", qvo, output, err)
}
output, err = util.RunCommand("ip", "link", "set", "dev", bridge, "down")
if err != nil {
glog.Warningf("Warning: set bridge %s down failed: %v, %v", bridge, output, err)
}
output, err = util.RunCommand("brctl", "delbr", bridge)
if err != nil {
glog.Warningf("Warning: delete bridge %s failed: %v, %v", bridge, output, err)
}
return nil
}
func (p *OVSPlugin) destroySandboxInterface(podName, podInfraContainerID, portID string) error {
vibName, _ := p.buildSandboxInterfaceName(portID)
_, err := util.RunCommand("ip", "link", "delete", vibName)
if err != nil {
glog.V(5).Infof("Warning: DestroyInterface failed: %v", err)
}
return nil
}
func (p *OVSPlugin) DestroyInterface(podName, podInfraContainerID string, port *ports.Port) error {
p.destroyOVSInterface(podName, port.ID)
p.destroySandboxInterface(podName, podInfraContainerID, port.ID)
glog.V(4).Infof("DestroyInterface for %s done", podName)
return nil
}

View File

@ -0,0 +1,73 @@
package plugins
import (
"fmt"
"sync"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
type PluginInterface interface {
SetupInterface(podName, podInfraContainerID string, port *ports.Port, ipcidr, gateway, ifName, netns string) (*current.Interface, *current.Interface, error)
DestroyInterface(podName, podInfraContainerID string, port *ports.Port) error
Init(integrationBridge string) error
}
// Factory is a function that returns a networkplugin.Interface.
// The config parameter provides an io.Reader handler to the factory in
// order to load specific configurations. If no configuration is provided
// the parameter is nil.
type Factory func() (PluginInterface, error)
// All registered network plugins.
var pluginsMutex sync.Mutex
var plugins = make(map[string]Factory)
// RegisterNetworkPlugin registers a networkplugin.Factory by name. This
// is expected to happen during app startup.
func RegisterNetworkPlugin(name string, networkPlugin Factory) {
pluginsMutex.Lock()
defer pluginsMutex.Unlock()
if _, found := plugins[name]; found {
glog.Fatalf("Network plugin %q was registered twice", name)
}
glog.V(1).Infof("Registered network plugin %q", name)
plugins[name] = networkPlugin
}
// GetNetworkPlugin creates an instance of the named network plugin, or nil if
// the name is not known. The error return is only used if the named plugin
// was known but failed to initialize.
func GetNetworkPlugin(name string) (PluginInterface, error) {
pluginsMutex.Lock()
defer pluginsMutex.Unlock()
f, found := plugins[name]
if !found {
return nil, nil
}
return f()
}
// InitNetworkPlugin creates an instance of the named networkPlugin plugin.
func InitNetworkPlugin(name string) (PluginInterface, error) {
var networkPlugin PluginInterface
if name == "" {
glog.Info("No network plugin specified.")
return nil, nil
}
var err error
networkPlugin, err = GetNetworkPlugin(name)
if err != nil {
return nil, fmt.Errorf("could not init networkPlugin plugin %q: %v", name, err)
}
if networkPlugin == nil {
return nil, fmt.Errorf("unknown networkPlugin plugin %q", name)
}
return networkPlugin, nil
}

View File

@ -0,0 +1,21 @@
package types
import (
"net"
"github.com/containernetworking/cni/pkg/types"
)
type NetConf struct {
types.NetConf
KubestackConfig string `json:"kubestack-config"`
}
// K8sArgs is the valid CNI_ARGS used for Kubernetes
type K8sArgs struct {
types.CommonArgs
IP net.IP
K8S_POD_NAME types.UnmarshallableString
K8S_POD_NAMESPACE types.UnmarshallableString
K8S_POD_INFRA_CONTAINER_ID types.UnmarshallableString
}

View File

@ -2,22 +2,25 @@ package openstack
import (
"errors"
"fmt"
"os"
drivertypes "git.openstack.org/openstack/stackube/pkg/openstack/types"
"git.openstack.org/openstack/stackube/pkg/util"
"github.com/docker/distribution/uuid"
"github.com/golang/glog"
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack"
"github.com/gophercloud/gophercloud/openstack/identity/v2/tenants"
"github.com/gophercloud/gophercloud/openstack/identity/v2/users"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/layer3/routers"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/portsbinding"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/groups"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
"github.com/gophercloud/gophercloud/openstack/networking/v2/networks"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/openstack/networking/v2/subnets"
"github.com/gophercloud/gophercloud/pagination"
"fmt"
drivertypes "git.openstack.org/openstack/stackube/pkg/openstack/types"
gcfg "gopkg.in/gcfg.v1"
)
@ -26,7 +29,7 @@ const (
podNamePrefix = "kube"
securitygroupName = "kube-securitygroup-default"
hostnameMaxLen = 63
HostnameMaxLen = 63
// Service affinities
ServiceAffinityNone = "None"
@ -41,11 +44,18 @@ var (
)
type Client struct {
Identity *gophercloud.ServiceClient
Provider *gophercloud.ProviderClient
Network *gophercloud.ServiceClient
Region string
ExtNetID string
Identity *gophercloud.ServiceClient
Provider *gophercloud.ProviderClient
Network *gophercloud.ServiceClient
Region string
ExtNetID string
PluginName string
IntegrationBridge string
}
type PluginOpts struct {
PluginName string `gcfg:"plugin-name"`
IntegrationBridge string `gcfg:"integration-bridge"`
}
type Config struct {
@ -57,6 +67,7 @@ type Config struct {
Region string `gcfg:"region"`
ExtNetID string `gcfg:"ext-net-id"`
}
Plugin PluginOpts
}
func toAuthOptions(cfg Config) gophercloud.AuthOptions {
@ -108,11 +119,13 @@ func NewClient(config string) (*Client, error) {
}
client := &Client{
Identity: identity,
Provider: provider,
Network: network,
Region: cfg.Global.Region,
ExtNetID: cfg.Global.ExtNetID,
Identity: identity,
Provider: provider,
Network: network,
Region: cfg.Global.Region,
ExtNetID: cfg.Global.ExtNetID,
PluginName: cfg.Plugin.PluginName,
IntegrationBridge: cfg.Plugin.IntegrationBridge,
}
return client, nil
}
@ -243,6 +256,12 @@ func reasonForError(err error) int {
return 0
}
// Get tenant's network by tenantID(tenant and network are one to one mapping in stackube)
func (os *Client) GetOpenStackNetworkByTenantID(tenantID string) (*networks.Network, error) {
opts := networks.ListOpts{TenantID: tenantID}
return os.getOpenStackNetwork(&opts)
}
// Get openstack network by id
func (os *Client) getOpenStackNetworkByID(id string) (*networks.Network, error) {
opts := networks.ListOpts{ID: id}
@ -280,7 +299,7 @@ func (os *Client) getOpenStackNetwork(opts *networks.ListOpts) (*networks.Networ
}
// Get provider subnet by id
func (os *Client) getProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error) {
func (os *Client) GetProviderSubnet(osSubnetID string) (*drivertypes.Subnet, error) {
s, err := subnets.Get(os.Network, osSubnetID).Extract()
if err != nil {
glog.Errorf("Get openstack subnet failed: %v", err)
@ -339,7 +358,7 @@ func (os *Client) OSNetworktoProviderNetwork(osNetwork *networks.Network) (*driv
providerNetwork.TenantID = osNetwork.TenantID
for _, subnetID := range osNetwork.Subnets {
s, err := os.getProviderSubnet(subnetID)
s, err := os.GetProviderSubnet(subnetID)
if err != nil {
return nil, err
}
@ -580,3 +599,204 @@ func (os *Client) CheckTenantID(tenantID string) (bool, error) {
return found, err
}
func (os *Client) GetPort(name string) (*ports.Port, error) {
opts := ports.ListOpts{Name: name}
pager := ports.List(os.Network, opts)
var port *ports.Port
err := pager.EachPage(func(page pagination.Page) (bool, error) {
portList, err := ports.ExtractPorts(page)
if err != nil {
glog.Errorf("Get openstack ports error: %v", err)
return false, err
}
if len(portList) > 1 {
return false, ErrMultipleResults
}
if len(portList) == 0 {
return false, ErrNotFound
}
port = &portList[0]
return true, err
})
return port, err
}
func getHostName() string {
host, err := os.Hostname()
if err != nil {
return ""
}
return host
}
func (os *Client) ensureSecurityGroup(tenantID string) (string, error) {
var securitygroup *groups.SecGroup
opts := groups.ListOpts{
TenantID: tenantID,
Name: securitygroupName,
}
pager := groups.List(os.Network, opts)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
sg, err := groups.ExtractGroups(page)
if err != nil {
glog.Errorf("Get openstack securitygroups error: %v", err)
return false, err
}
if len(sg) > 0 {
securitygroup = &sg[0]
}
return true, err
})
if err != nil {
return "", err
}
// If securitygroup doesn't exist, create a new one
if securitygroup == nil {
securitygroup, err = groups.Create(os.Network, groups.CreateOpts{
Name: securitygroupName,
TenantID: tenantID,
}).Extract()
if err != nil {
return "", err
}
}
var secGroupsRules int
listopts := rules.ListOpts{
TenantID: tenantID,
Direction: string(rules.DirIngress),
SecGroupID: securitygroup.ID,
}
rulesPager := rules.List(os.Network, listopts)
err = rulesPager.EachPage(func(page pagination.Page) (bool, error) {
r, err := rules.ExtractRules(page)
if err != nil {
glog.Errorf("Get openstack securitygroup rules error: %v", err)
return false, err
}
secGroupsRules = len(r)
return true, err
})
if err != nil {
return "", err
}
// create new rules
if secGroupsRules == 0 {
// create egress rule
_, err = rules.Create(os.Network, rules.CreateOpts{
TenantID: tenantID,
SecGroupID: securitygroup.ID,
Direction: rules.DirEgress,
EtherType: rules.EtherType4,
}).Extract()
// create ingress rule
_, err := rules.Create(os.Network, rules.CreateOpts{
TenantID: tenantID,
SecGroupID: securitygroup.ID,
Direction: rules.DirIngress,
EtherType: rules.EtherType4,
}).Extract()
if err != nil {
return "", err
}
}
return securitygroup.ID, nil
}
// Create an port
func (os *Client) CreatePort(networkID, tenantID, portName string) (*portsbinding.Port, error) {
securitygroup, err := os.ensureSecurityGroup(tenantID)
if err != nil {
glog.Errorf("EnsureSecurityGroup failed: %v", err)
return nil, err
}
opts := portsbinding.CreateOpts{
HostID: getHostName(),
CreateOptsBuilder: ports.CreateOpts{
NetworkID: networkID,
Name: portName,
AdminStateUp: &adminStateUp,
TenantID: tenantID,
DeviceID: uuid.Generate().String(),
DeviceOwner: fmt.Sprintf("compute:%s", getHostName()),
SecurityGroups: []string{securitygroup},
},
}
port, err := portsbinding.Create(os.Network, opts).Extract()
if err != nil {
glog.Errorf("Create port %s failed: %v", portName, err)
return nil, err
}
return port, nil
}
// List all ports in the network
func (os *Client) ListPorts(networkID, deviceOwner string) ([]ports.Port, error) {
var results []ports.Port
opts := ports.ListOpts{
NetworkID: networkID,
DeviceOwner: deviceOwner,
}
pager := ports.List(os.Network, opts)
err := pager.EachPage(func(page pagination.Page) (bool, error) {
portList, err := ports.ExtractPorts(page)
if err != nil {
glog.Errorf("Get openstack ports error: %v", err)
return false, err
}
for _, port := range portList {
results = append(results, port)
}
return true, err
})
if err != nil {
return nil, err
}
return results, nil
}
// Delete port by portName
func (os *Client) DeletePort(portName string) error {
port, err := os.GetPort(portName)
if err == util.ErrNotFound {
glog.V(4).Infof("Port %s already deleted", portName)
return nil
} else if err != nil {
glog.Errorf("Get openstack port %s failed: %v", portName, err)
return err
}
if port != nil {
err := ports.Delete(os.Network, port.ID).ExtractErr()
if err != nil {
glog.Errorf("Delete openstack port %s failed: %v", portName, err)
return err
}
}
return nil
}

39
pkg/util/exec.go Normal file
View File

@ -0,0 +1,39 @@
package util
import (
"os/exec"
"strings"
)
func absPath(cmd string) (string, error) {
cmdAbsPath, err := exec.LookPath(cmd)
if err != nil {
return "", err
}
return cmdAbsPath, nil
}
func buildCommand(cmd string, args ...string) (*exec.Cmd, error) {
cmdAbsPath, err := absPath(cmd)
if err != nil {
return nil, err
}
command := exec.Command(cmdAbsPath)
command.Args = append(command.Args, args...)
return command, nil
}
func RunCommand(cmd string, args ...string) ([]string, error) {
command, err := buildCommand(cmd, args...)
if err != nil {
return nil, err
}
output, err := command.CombinedOutput()
if err != nil {
return []string{string(output)}, err
}
return strings.Split(strings.TrimSpace(string(output)), "\n"), nil
}

View File

@ -18,3 +18,7 @@ func BuildNetworkName(namespace, name string) string {
func BuildLoadBalancerName(namespace, name string) string {
return namePrefix + "_" + namespace + "_" + name
}
func BuildPortName(namespace, podName string) string {
return namePrefix + "_" + namespace + "_" + podName
}

202
vendor/github.com/containernetworking/cni/LICENSE generated vendored Normal file
View File

@ -0,0 +1,202 @@
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.

View File

@ -0,0 +1,228 @@
// Copyright 2014-2016 CNI 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 skel provides skeleton code for a CNI plugin.
// In particular, it implements argument parsing and validation.
package skel
import (
"fmt"
"io"
"io/ioutil"
"log"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/version"
)
// CmdArgs captures all the arguments passed in to the plugin
// via both env vars and stdin
type CmdArgs struct {
ContainerID string
Netns string
IfName string
Args string
Path string
StdinData []byte
}
type dispatcher struct {
Getenv func(string) string
Stdin io.Reader
Stdout io.Writer
Stderr io.Writer
ConfVersionDecoder version.ConfigDecoder
VersionReconciler version.Reconciler
}
type reqForCmdEntry map[string]bool
func (t *dispatcher) getCmdArgsFromEnv() (string, *CmdArgs, error) {
var cmd, contID, netns, ifName, args, path string
vars := []struct {
name string
val *string
reqForCmd reqForCmdEntry
}{
{
"CNI_COMMAND",
&cmd,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
{
"CNI_CONTAINERID",
&contID,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_NETNS",
&netns,
reqForCmdEntry{
"ADD": true,
"DEL": false,
},
},
{
"CNI_IFNAME",
&ifName,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
{
"CNI_ARGS",
&args,
reqForCmdEntry{
"ADD": false,
"DEL": false,
},
},
{
"CNI_PATH",
&path,
reqForCmdEntry{
"ADD": true,
"DEL": true,
},
},
}
argsMissing := false
for _, v := range vars {
*v.val = t.Getenv(v.name)
if *v.val == "" {
if v.reqForCmd[cmd] || v.name == "CNI_COMMAND" {
fmt.Fprintf(t.Stderr, "%v env variable missing\n", v.name)
argsMissing = true
}
}
}
if argsMissing {
return "", nil, fmt.Errorf("required env variables missing")
}
stdinData, err := ioutil.ReadAll(t.Stdin)
if err != nil {
return "", nil, fmt.Errorf("error reading from stdin: %v", err)
}
cmdArgs := &CmdArgs{
ContainerID: contID,
Netns: netns,
IfName: ifName,
Args: args,
Path: path,
StdinData: stdinData,
}
return cmd, cmdArgs, nil
}
func createTypedError(f string, args ...interface{}) *types.Error {
return &types.Error{
Code: 100,
Msg: fmt.Sprintf(f, args...),
}
}
func (t *dispatcher) checkVersionAndCall(cmdArgs *CmdArgs, pluginVersionInfo version.PluginInfo, toCall func(*CmdArgs) error) error {
configVersion, err := t.ConfVersionDecoder.Decode(cmdArgs.StdinData)
if err != nil {
return err
}
verErr := t.VersionReconciler.Check(configVersion, pluginVersionInfo)
if verErr != nil {
return &types.Error{
Code: types.ErrIncompatibleCNIVersion,
Msg: "incompatible CNI versions",
Details: verErr.Details(),
}
}
return toCall(cmdArgs)
}
func (t *dispatcher) pluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
cmd, cmdArgs, err := t.getCmdArgsFromEnv()
if err != nil {
return createTypedError(err.Error())
}
switch cmd {
case "ADD":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdAdd)
case "DEL":
err = t.checkVersionAndCall(cmdArgs, versionInfo, cmdDel)
case "VERSION":
err = versionInfo.Encode(t.Stdout)
default:
return createTypedError("unknown CNI_COMMAND: %v", cmd)
}
if err != nil {
if e, ok := err.(*types.Error); ok {
// don't wrap Error in Error
return e
}
return createTypedError(err.Error())
}
return nil
}
// PluginMainWithError is the core "main" for a plugin. It accepts
// callback functions for add and del CNI commands and returns an error.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// It is the responsibility of the caller to check for non-nil error return.
//
// For a plugin to comply with the CNI spec, it must print any error to stdout
// as JSON and then exit with nonzero status code.
//
// To let this package automatically handle errors and call os.Exit(1) for you,
// use PluginMain() instead.
func PluginMainWithError(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) *types.Error {
return (&dispatcher{
Getenv: os.Getenv,
Stdin: os.Stdin,
Stdout: os.Stdout,
Stderr: os.Stderr,
}).pluginMain(cmdAdd, cmdDel, versionInfo)
}
// PluginMain is the core "main" for a plugin which includes automatic error handling.
//
// The caller must also specify what CNI spec versions the plugin supports.
//
// When an error occurs in either cmdAdd or cmdDel, PluginMain will print the error
// as JSON to stdout and call os.Exit(1).
//
// To have more control over error handling, use PluginMainWithError() instead.
func PluginMain(cmdAdd, cmdDel func(_ *CmdArgs) error, versionInfo version.PluginInfo) {
if e := PluginMainWithError(cmdAdd, cmdDel, versionInfo); e != nil {
if err := e.Print(); err != nil {
log.Print("Error writing error JSON to stdout: ", err)
}
os.Exit(1)
}
}

View File

@ -0,0 +1,135 @@
// Copyright 2016 CNI 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 types020
import (
"encoding/json"
"fmt"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
)
const ImplementedSpecVersion string = "0.2.0"
var SupportedVersions = []string{"", "0.1.0", ImplementedSpecVersion}
// Compatibility types for CNI version 0.1.0 and 0.2.0
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}
func GetResult(r types.Result) (*Result, error) {
// We expect version 0.1.0/0.2.0 results
result020, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := result020.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
IP4 *IPConfig `json:"ip4,omitempty"`
IP6 *IPConfig `json:"ip6,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
func (r *Result) Version() string {
return ImplementedSpecVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
for _, supportedVersion := range SupportedVersions {
if version == supportedVersion {
r.CNIVersion = version
return r, nil
}
}
return nil, fmt.Errorf("cannot convert version %q to %s", SupportedVersions, version)
}
func (r *Result) Print() error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// String returns a formatted string in the form of "[IP4: $1,][ IP6: $2,] DNS: $3" where
// $1 represents the receiver's IPv4, $2 represents the receiver's IPv6 and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if r.IP4 != nil {
str = fmt.Sprintf("IP4:%+v, ", *r.IP4)
}
if r.IP6 != nil {
str += fmt.Sprintf("IP6:%+v, ", *r.IP6)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// IPConfig contains values necessary to configure an interface
type IPConfig struct {
IP net.IPNet
Gateway net.IP
Routes []types.Route
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type
// JSON (un)marshallable types
type ipConfig struct {
IP types.IPNet `json:"ip"`
Gateway net.IP `json:"gateway,omitempty"`
Routes []types.Route `json:"routes,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
IP: types.IPNet(c.IP),
Gateway: c.Gateway,
Routes: c.Routes,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.IP = net.IPNet(ipc.IP)
c.Gateway = ipc.Gateway
c.Routes = ipc.Routes
return nil
}

View File

@ -0,0 +1,101 @@
// Copyright 2015 CNI 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 types
import (
"encoding"
"fmt"
"reflect"
"strings"
)
// UnmarshallableBool typedef for builtin bool
// because builtin type's methods can't be declared
type UnmarshallableBool bool
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns boolean true if the string is "1" or "[Tt]rue"
// Returns boolean false if the string is "0" or "[Ff]alse"
func (b *UnmarshallableBool) UnmarshalText(data []byte) error {
s := strings.ToLower(string(data))
switch s {
case "1", "true":
*b = true
case "0", "false":
*b = false
default:
return fmt.Errorf("Boolean unmarshal error: invalid input %s", s)
}
return nil
}
// UnmarshallableString typedef for builtin string
type UnmarshallableString string
// UnmarshalText implements the encoding.TextUnmarshaler interface.
// Returns the string
func (s *UnmarshallableString) UnmarshalText(data []byte) error {
*s = UnmarshallableString(data)
return nil
}
// CommonArgs contains the IgnoreUnknown argument
// and must be embedded by all Arg structs
type CommonArgs struct {
IgnoreUnknown UnmarshallableBool `json:"ignoreunknown,omitempty"`
}
// GetKeyField is a helper function to receive Values
// Values that represent a pointer to a struct
func GetKeyField(keyString string, v reflect.Value) reflect.Value {
return v.Elem().FieldByName(keyString)
}
// LoadArgs parses args from a string in the form "K=V;K2=V2;..."
func LoadArgs(args string, container interface{}) error {
if args == "" {
return nil
}
containerValue := reflect.ValueOf(container)
pairs := strings.Split(args, ";")
unknownArgs := []string{}
for _, pair := range pairs {
kv := strings.Split(pair, "=")
if len(kv) != 2 {
return fmt.Errorf("ARGS: invalid pair %q", pair)
}
keyString := kv[0]
valueString := kv[1]
keyField := GetKeyField(keyString, containerValue)
if !keyField.IsValid() {
unknownArgs = append(unknownArgs, pair)
continue
}
u := keyField.Addr().Interface().(encoding.TextUnmarshaler)
err := u.UnmarshalText([]byte(valueString))
if err != nil {
return fmt.Errorf("ARGS: error parsing value of pair %q: %v)", pair, err)
}
}
isIgnoreUnknown := GetKeyField("IgnoreUnknown", containerValue).Bool()
if len(unknownArgs) > 0 && !isIgnoreUnknown {
return fmt.Errorf("ARGS: unknown args %q", unknownArgs)
}
return nil
}

View File

@ -0,0 +1,300 @@
// Copyright 2016 CNI 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 current
import (
"encoding/json"
"fmt"
"net"
"os"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
)
const ImplementedSpecVersion string = "0.3.1"
var SupportedVersions = []string{"0.3.0", ImplementedSpecVersion}
func NewResult(data []byte) (types.Result, error) {
result := &Result{}
if err := json.Unmarshal(data, result); err != nil {
return nil, err
}
return result, nil
}
func GetResult(r types.Result) (*Result, error) {
resultCurrent, err := r.GetAsVersion(ImplementedSpecVersion)
if err != nil {
return nil, err
}
result, ok := resultCurrent.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
return result, nil
}
var resultConverters = []struct {
versions []string
convert func(types.Result) (*Result, error)
}{
{types020.SupportedVersions, convertFrom020},
{SupportedVersions, convertFrom030},
}
func convertFrom020(result types.Result) (*Result, error) {
oldResult, err := types020.GetResult(result)
if err != nil {
return nil, err
}
newResult := &Result{
CNIVersion: ImplementedSpecVersion,
DNS: oldResult.DNS,
Routes: []*types.Route{},
}
if oldResult.IP4 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "4",
Address: oldResult.IP4.IP,
Gateway: oldResult.IP4.Gateway,
})
for _, route := range oldResult.IP4.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP4.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
})
}
}
if oldResult.IP6 != nil {
newResult.IPs = append(newResult.IPs, &IPConfig{
Version: "6",
Address: oldResult.IP6.IP,
Gateway: oldResult.IP6.Gateway,
})
for _, route := range oldResult.IP6.Routes {
gw := route.GW
if gw == nil {
gw = oldResult.IP6.Gateway
}
newResult.Routes = append(newResult.Routes, &types.Route{
Dst: route.Dst,
GW: gw,
})
}
}
if len(newResult.IPs) == 0 {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return newResult, nil
}
func convertFrom030(result types.Result) (*Result, error) {
newResult, ok := result.(*Result)
if !ok {
return nil, fmt.Errorf("failed to convert result")
}
newResult.CNIVersion = ImplementedSpecVersion
return newResult, nil
}
func NewResultFromResult(result types.Result) (*Result, error) {
version := result.Version()
for _, converter := range resultConverters {
for _, supportedVersion := range converter.versions {
if version == supportedVersion {
return converter.convert(result)
}
}
}
return nil, fmt.Errorf("unsupported CNI result22 version %q", version)
}
// Result is what gets returned from the plugin (via stdout) to the caller
type Result struct {
CNIVersion string `json:"cniVersion,omitempty"`
Interfaces []*Interface `json:"interfaces,omitempty"`
IPs []*IPConfig `json:"ips,omitempty"`
Routes []*types.Route `json:"routes,omitempty"`
DNS types.DNS `json:"dns,omitempty"`
}
// Convert to the older 0.2.0 CNI spec Result type
func (r *Result) convertTo020() (*types020.Result, error) {
oldResult := &types020.Result{
CNIVersion: types020.ImplementedSpecVersion,
DNS: r.DNS,
}
for _, ip := range r.IPs {
// Only convert the first IP address of each version as 0.2.0
// and earlier cannot handle multiple IP addresses
if ip.Version == "4" && oldResult.IP4 == nil {
oldResult.IP4 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
} else if ip.Version == "6" && oldResult.IP6 == nil {
oldResult.IP6 = &types020.IPConfig{
IP: ip.Address,
Gateway: ip.Gateway,
}
}
if oldResult.IP4 != nil && oldResult.IP6 != nil {
break
}
}
for _, route := range r.Routes {
is4 := route.Dst.IP.To4() != nil
if is4 && oldResult.IP4 != nil {
oldResult.IP4.Routes = append(oldResult.IP4.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
} else if !is4 && oldResult.IP6 != nil {
oldResult.IP6.Routes = append(oldResult.IP6.Routes, types.Route{
Dst: route.Dst,
GW: route.GW,
})
}
}
if oldResult.IP4 == nil && oldResult.IP6 == nil {
return nil, fmt.Errorf("cannot convert: no valid IP addresses")
}
return oldResult, nil
}
func (r *Result) Version() string {
return ImplementedSpecVersion
}
func (r *Result) GetAsVersion(version string) (types.Result, error) {
switch version {
case "0.3.0", ImplementedSpecVersion:
r.CNIVersion = version
return r, nil
case types020.SupportedVersions[0], types020.SupportedVersions[1], types020.SupportedVersions[2]:
return r.convertTo020()
}
return nil, fmt.Errorf("cannot convert version 0.3.x to %q", version)
}
func (r *Result) Print() error {
data, err := json.MarshalIndent(r, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// String returns a formatted string in the form of "[Interfaces: $1,][ IP: $2,] DNS: $3" where
// $1 represents the receiver's Interfaces, $2 represents the receiver's IP addresses and $3 the
// receiver's DNS. If $1 or $2 are nil, they won't be present in the returned string.
func (r *Result) String() string {
var str string
if len(r.Interfaces) > 0 {
str += fmt.Sprintf("Interfaces:%+v, ", r.Interfaces)
}
if len(r.IPs) > 0 {
str += fmt.Sprintf("IP:%+v, ", r.IPs)
}
if len(r.Routes) > 0 {
str += fmt.Sprintf("Routes:%+v, ", r.Routes)
}
return fmt.Sprintf("%sDNS:%+v", str, r.DNS)
}
// Convert this old version result to the current CNI version result
func (r *Result) Convert() (*Result, error) {
return r, nil
}
// Interface contains values about the created interfaces
type Interface struct {
Name string `json:"name"`
Mac string `json:"mac,omitempty"`
Sandbox string `json:"sandbox,omitempty"`
}
func (i *Interface) String() string {
return fmt.Sprintf("%+v", *i)
}
// Int returns a pointer to the int value passed in. Used to
// set the IPConfig.Interface field.
func Int(v int) *int {
return &v
}
// IPConfig contains values necessary to configure an IP address on an interface
type IPConfig struct {
// IP version, either "4" or "6"
Version string
// Index into Result structs Interfaces list
Interface *int
Address net.IPNet
Gateway net.IP
}
func (i *IPConfig) String() string {
return fmt.Sprintf("%+v", *i)
}
// JSON (un)marshallable types
type ipConfig struct {
Version string `json:"version"`
Interface *int `json:"interface,omitempty"`
Address types.IPNet `json:"address"`
Gateway net.IP `json:"gateway,omitempty"`
}
func (c *IPConfig) MarshalJSON() ([]byte, error) {
ipc := ipConfig{
Version: c.Version,
Interface: c.Interface,
Address: types.IPNet(c.Address),
Gateway: c.Gateway,
}
return json.Marshal(ipc)
}
func (c *IPConfig) UnmarshalJSON(data []byte) error {
ipc := ipConfig{}
if err := json.Unmarshal(data, &ipc); err != nil {
return err
}
c.Version = ipc.Version
c.Interface = ipc.Interface
c.Address = net.IPNet(ipc.Address)
c.Gateway = ipc.Gateway
return nil
}

View File

@ -0,0 +1,189 @@
// Copyright 2015 CNI 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 types
import (
"encoding/json"
"errors"
"fmt"
"net"
"os"
)
// like net.IPNet but adds JSON marshalling and unmarshalling
type IPNet net.IPNet
// ParseCIDR takes a string like "10.2.3.1/24" and
// return IPNet with "10.2.3.1" and /24 mask
func ParseCIDR(s string) (*net.IPNet, error) {
ip, ipn, err := net.ParseCIDR(s)
if err != nil {
return nil, err
}
ipn.IP = ip
return ipn, nil
}
func (n IPNet) MarshalJSON() ([]byte, error) {
return json.Marshal((*net.IPNet)(&n).String())
}
func (n *IPNet) UnmarshalJSON(data []byte) error {
var s string
if err := json.Unmarshal(data, &s); err != nil {
return err
}
tmp, err := ParseCIDR(s)
if err != nil {
return err
}
*n = IPNet(*tmp)
return nil
}
// NetConf describes a network.
type NetConf struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Type string `json:"type,omitempty"`
Capabilities map[string]bool `json:"capabilities,omitempty"`
IPAM struct {
Type string `json:"type,omitempty"`
} `json:"ipam,omitempty"`
DNS DNS `json:"dns"`
}
// NetConfList describes an ordered list of networks.
type NetConfList struct {
CNIVersion string `json:"cniVersion,omitempty"`
Name string `json:"name,omitempty"`
Plugins []*NetConf `json:"plugins,omitempty"`
}
type ResultFactoryFunc func([]byte) (Result, error)
// Result is an interface that provides the result of plugin execution
type Result interface {
// The highest CNI specification result verison the result supports
// without having to convert
Version() string
// Returns the result converted into the requested CNI specification
// result version, or an error if conversion failed
GetAsVersion(version string) (Result, error)
// Prints the result in JSON format to stdout
Print() error
// Returns a JSON string representation of the result
String() string
}
func PrintResult(result Result, version string) error {
newResult, err := result.GetAsVersion(version)
if err != nil {
return err
}
return newResult.Print()
}
// DNS contains values interesting for DNS resolvers
type DNS struct {
Nameservers []string `json:"nameservers,omitempty"`
Domain string `json:"domain,omitempty"`
Search []string `json:"search,omitempty"`
Options []string `json:"options,omitempty"`
}
type Route struct {
Dst net.IPNet
GW net.IP
}
func (r *Route) String() string {
return fmt.Sprintf("%+v", *r)
}
// Well known error codes
// see https://github.com/containernetworking/cni/blob/master/SPEC.md#well-known-error-codes
const (
ErrUnknown uint = iota // 0
ErrIncompatibleCNIVersion // 1
ErrUnsupportedField // 2
)
type Error struct {
Code uint `json:"code"`
Msg string `json:"msg"`
Details string `json:"details,omitempty"`
}
func (e *Error) Error() string {
details := ""
if e.Details != "" {
details = fmt.Sprintf("; %v", e.Details)
}
return fmt.Sprintf("%v%v", e.Msg, details)
}
func (e *Error) Print() error {
return prettyPrint(e)
}
// net.IPNet is not JSON (un)marshallable so this duality is needed
// for our custom IPNet type
// JSON (un)marshallable types
type route struct {
Dst IPNet `json:"dst"`
GW net.IP `json:"gw,omitempty"`
}
func (r *Route) UnmarshalJSON(data []byte) error {
rt := route{}
if err := json.Unmarshal(data, &rt); err != nil {
return err
}
r.Dst = net.IPNet(rt.Dst)
r.GW = rt.GW
return nil
}
func (r *Route) MarshalJSON() ([]byte, error) {
rt := route{
Dst: IPNet(r.Dst),
GW: r.GW,
}
return json.Marshal(rt)
}
func prettyPrint(obj interface{}) error {
data, err := json.MarshalIndent(obj, "", " ")
if err != nil {
return err
}
_, err = os.Stdout.Write(data)
return err
}
// NotImplementedError is used to indicate that a method is not implemented for the given platform
var NotImplementedError = errors.New("Not Implemented")

View File

@ -0,0 +1,37 @@
// Copyright 2016 CNI 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 version
import (
"encoding/json"
"fmt"
)
// ConfigDecoder can decode the CNI version available in network config data
type ConfigDecoder struct{}
func (*ConfigDecoder) Decode(jsonBytes []byte) (string, error) {
var conf struct {
CNIVersion string `json:"cniVersion"`
}
err := json.Unmarshal(jsonBytes, &conf)
if err != nil {
return "", fmt.Errorf("decoding version from network config: %s", err)
}
if conf.CNIVersion == "" {
return "0.1.0", nil
}
return conf.CNIVersion, nil
}

View File

@ -0,0 +1,81 @@
// Copyright 2016 CNI 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 version
import (
"encoding/json"
"fmt"
"io"
)
// PluginInfo reports information about CNI versioning
type PluginInfo interface {
// SupportedVersions returns one or more CNI spec versions that the plugin
// supports. If input is provided in one of these versions, then the plugin
// promises to use the same CNI version in its response
SupportedVersions() []string
// Encode writes this CNI version information as JSON to the given Writer
Encode(io.Writer) error
}
type pluginInfo struct {
CNIVersion_ string `json:"cniVersion"`
SupportedVersions_ []string `json:"supportedVersions,omitempty"`
}
// pluginInfo implements the PluginInfo interface
var _ PluginInfo = &pluginInfo{}
func (p *pluginInfo) Encode(w io.Writer) error {
return json.NewEncoder(w).Encode(p)
}
func (p *pluginInfo) SupportedVersions() []string {
return p.SupportedVersions_
}
// PluginSupports returns a new PluginInfo that will report the given versions
// as supported
func PluginSupports(supportedVersions ...string) PluginInfo {
if len(supportedVersions) < 1 {
panic("programmer error: you must support at least one version")
}
return &pluginInfo{
CNIVersion_: Current(),
SupportedVersions_: supportedVersions,
}
}
// PluginDecoder can decode the response returned by a plugin's VERSION command
type PluginDecoder struct{}
func (*PluginDecoder) Decode(jsonBytes []byte) (PluginInfo, error) {
var info pluginInfo
err := json.Unmarshal(jsonBytes, &info)
if err != nil {
return nil, fmt.Errorf("decoding version info: %s", err)
}
if info.CNIVersion_ == "" {
return nil, fmt.Errorf("decoding version info: missing field cniVersion")
}
if len(info.SupportedVersions_) == 0 {
if info.CNIVersion_ == "0.2.0" {
return PluginSupports("0.1.0", "0.2.0"), nil
}
return nil, fmt.Errorf("decoding version info: missing field supportedVersions")
}
return &info, nil
}

View File

@ -0,0 +1,49 @@
// Copyright 2016 CNI 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 version
import "fmt"
type ErrorIncompatible struct {
Config string
Supported []string
}
func (e *ErrorIncompatible) Details() string {
return fmt.Sprintf("config is %q, plugin supports %q", e.Config, e.Supported)
}
func (e *ErrorIncompatible) Error() string {
return fmt.Sprintf("incompatible CNI versions: %s", e.Details())
}
type Reconciler struct{}
func (r *Reconciler) Check(configVersion string, pluginInfo PluginInfo) *ErrorIncompatible {
return r.CheckRaw(configVersion, pluginInfo.SupportedVersions())
}
func (*Reconciler) CheckRaw(configVersion string, supportedVersions []string) *ErrorIncompatible {
for _, supportedVersion := range supportedVersions {
if configVersion == supportedVersion {
return nil
}
}
return &ErrorIncompatible{
Config: configVersion,
Supported: supportedVersions,
}
}

View File

@ -0,0 +1,61 @@
// Copyright 2016 CNI 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 version
import (
"fmt"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/cni/pkg/types/current"
)
// Current reports the version of the CNI spec implemented by this library
func Current() string {
return "0.3.1"
}
// Legacy PluginInfo describes a plugin that is backwards compatible with the
// CNI spec version 0.1.0. In particular, a runtime compiled against the 0.1.0
// library ought to work correctly with a plugin that reports support for
// Legacy versions.
//
// Any future CNI spec versions which meet this definition should be added to
// this list.
var Legacy = PluginSupports("0.1.0", "0.2.0")
var All = PluginSupports("0.1.0", "0.2.0", "0.3.0", "0.3.1")
var resultFactories = []struct {
supportedVersions []string
newResult types.ResultFactoryFunc
}{
{current.SupportedVersions, current.NewResult},
{types020.SupportedVersions, types020.NewResult},
}
// Finds a Result object matching the requested version (if any) and asks
// that object to parse the plugin result, returning an error if parsing failed.
func NewResult(version string, resultBytes []byte) (types.Result, error) {
reconciler := &Reconciler{}
for _, resultFactory := range resultFactories {
err := reconciler.CheckRaw(version, resultFactory.supportedVersions)
if err == nil {
// Result supports this version
return resultFactory.newResult(resultBytes)
}
}
return nil, fmt.Errorf("unsupported CNI result version %q", version)
}

201
vendor/github.com/containernetworking/plugins/LICENSE generated vendored Normal file
View File

@ -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.

View File

@ -0,0 +1,40 @@
### Namespaces, Threads, and Go
On Linux each OS thread can have a different network namespace. Go's thread scheduling model switches goroutines between OS threads based on OS thread load and whether the goroutine would block other goroutines. This can result in a goroutine switching network namespaces without notice and lead to errors in your code.
### Namespace Switching
Switching namespaces with the `ns.Set()` method is not recommended without additional strategies to prevent unexpected namespace changes when your goroutines switch OS threads.
Go provides the `runtime.LockOSThread()` function to ensure a specific goroutine executes on its current OS thread and prevents any other goroutine from running in that thread until the locked one exits. Careful usage of `LockOSThread()` and goroutines can provide good control over which network namespace a given goroutine executes in.
For example, you cannot rely on the `ns.Set()` namespace being the current namespace after the `Set()` call unless you do two things. First, the goroutine calling `Set()` must have previously called `LockOSThread()`. Second, you must ensure `runtime.UnlockOSThread()` is not called somewhere in-between. You also cannot rely on the initial network namespace remaining the current network namespace if any other code in your program switches namespaces, unless you have already called `LockOSThread()` in that goroutine. Note that `LockOSThread()` prevents the Go scheduler from optimally scheduling goroutines for best performance, so `LockOSThread()` should only be used in small, isolated goroutines that release the lock quickly.
### Do() The Recommended Thing
The `ns.Do()` method provides **partial** control over network namespaces for you by implementing these strategies. All code dependent on a particular network namespace (including the root namespace) should be wrapped in the `ns.Do()` method to ensure the correct namespace is selected for the duration of your code. For example:
```go
targetNs, err := ns.NewNS()
if err != nil {
return err
}
err = targetNs.Do(func(hostNs ns.NetNS) error {
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{
Name: "dummy0",
},
}
return netlink.LinkAdd(dummy)
})
```
Note this requirement to wrap every network call is very onerous - any libraries you call might call out to network services such as DNS, and all such calls need to be protected after you call `ns.Do()`. The CNI plugins all exit very soon after calling `ns.Do()` which helps to minimize the problem.
Also: If the runtime spawns a new OS thread, it will inherit the network namespace of the parent thread, which may have been temporarily switched, and thus the new OS thread will be permanently "stuck in the wrong namespace".
In short, **there is no safe way to change network namespaces from within a long-lived, multithreaded Go process**. If your daemon process needs to be namespace aware, consider spawning a separate process (like a CNI plugin) for each namespace.
### Further Reading
- https://github.com/golang/go/wiki/LockOSThread
- http://morsmachine.dk/go-scheduler
- https://github.com/containernetworking/cni/issues/262
- https://golang.org/pkg/runtime/
- https://www.weave.works/blog/linux-namespaces-and-go-don-t-mix

View File

@ -0,0 +1,178 @@
// Copyright 2015 CNI 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 ns
import (
"fmt"
"os"
"runtime"
"sync"
"syscall"
)
type NetNS interface {
// Executes the passed closure in this object's network namespace,
// attempting to restore the original namespace before returning.
// However, since each OS thread can have a different network namespace,
// and Go's thread scheduling is highly variable, callers cannot
// guarantee any specific namespace is set unless operations that
// require that namespace are wrapped with Do(). Also, no code called
// from Do() should call runtime.UnlockOSThread(), or the risk
// of executing code in an incorrect namespace will be greater. See
// https://github.com/golang/go/wiki/LockOSThread for further details.
Do(toRun func(NetNS) error) error
// Sets the current network namespace to this object's network namespace.
// Note that since Go's thread scheduling is highly variable, callers
// cannot guarantee the requested namespace will be the current namespace
// after this function is called; to ensure this wrap operations that
// require the namespace with Do() instead.
Set() error
// Returns the filesystem path representing this object's network namespace
Path() string
// Returns a file descriptor representing this object's network namespace
Fd() uintptr
// Cleans up this instance of the network namespace; if this instance
// is the last user the namespace will be destroyed
Close() error
}
type netNS struct {
file *os.File
mounted bool
closed bool
}
// netNS implements the NetNS interface
var _ NetNS = &netNS{}
const (
// https://github.com/torvalds/linux/blob/master/include/uapi/linux/magic.h
NSFS_MAGIC = 0x6e736673
PROCFS_MAGIC = 0x9fa0
)
type NSPathNotExistErr struct{ msg string }
func (e NSPathNotExistErr) Error() string { return e.msg }
type NSPathNotNSErr struct{ msg string }
func (e NSPathNotNSErr) Error() string { return e.msg }
func IsNSorErr(nspath string) error {
stat := syscall.Statfs_t{}
if err := syscall.Statfs(nspath, &stat); err != nil {
if os.IsNotExist(err) {
err = NSPathNotExistErr{msg: fmt.Sprintf("failed to Statfs %q: %v", nspath, err)}
} else {
err = fmt.Errorf("failed to Statfs %q: %v", nspath, err)
}
return err
}
switch stat.Type {
case PROCFS_MAGIC, NSFS_MAGIC:
return nil
default:
return NSPathNotNSErr{msg: fmt.Sprintf("unknown FS magic on %q: %x", nspath, stat.Type)}
}
}
// Returns an object representing the namespace referred to by @path
func GetNS(nspath string) (NetNS, error) {
err := IsNSorErr(nspath)
if err != nil {
return nil, err
}
fd, err := os.Open(nspath)
if err != nil {
return nil, err
}
return &netNS{file: fd}, nil
}
func (ns *netNS) Path() string {
return ns.file.Name()
}
func (ns *netNS) Fd() uintptr {
return ns.file.Fd()
}
func (ns *netNS) errorIfClosed() error {
if ns.closed {
return fmt.Errorf("%q has already been closed", ns.file.Name())
}
return nil
}
func (ns *netNS) Do(toRun func(NetNS) error) error {
if err := ns.errorIfClosed(); err != nil {
return err
}
containedCall := func(hostNS NetNS) error {
threadNS, err := GetCurrentNS()
if err != nil {
return fmt.Errorf("failed to open current netns: %v", err)
}
defer threadNS.Close()
// switch to target namespace
if err = ns.Set(); err != nil {
return fmt.Errorf("error switching to ns %v: %v", ns.file.Name(), err)
}
defer threadNS.Set() // switch back
return toRun(hostNS)
}
// save a handle to current network namespace
hostNS, err := GetCurrentNS()
if err != nil {
return fmt.Errorf("Failed to open current namespace: %v", err)
}
defer hostNS.Close()
var wg sync.WaitGroup
wg.Add(1)
var innerError error
go func() {
defer wg.Done()
runtime.LockOSThread()
innerError = containedCall(hostNS)
}()
wg.Wait()
return innerError
}
// WithNetNSPath executes the passed closure under the given network
// namespace, restoring the original namespace afterwards.
func WithNetNSPath(nspath string, toRun func(NetNS) error) error {
ns, err := GetNS(nspath)
if err != nil {
return err
}
defer ns.Close()
return ns.Do(toRun)
}

View File

@ -0,0 +1,149 @@
// Copyright 2015-2017 CNI 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 ns
import (
"crypto/rand"
"fmt"
"os"
"path"
"runtime"
"sync"
"golang.org/x/sys/unix"
)
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
return GetNS(getCurrentThreadNetNSPath())
}
func getCurrentThreadNetNSPath() string {
// /proc/self/ns/net returns the namespace of the main thread, not
// of whatever thread this goroutine is running on. Make sure we
// use the thread's net namespace since the thread is switching around
return fmt.Sprintf("/proc/%d/task/%d/ns/net", os.Getpid(), unix.Gettid())
}
// Creates a new persistent network namespace and returns an object
// representing that namespace, without switching to it
func NewNS() (NetNS, error) {
const nsRunDir = "/var/run/netns"
b := make([]byte, 16)
_, err := rand.Reader.Read(b)
if err != nil {
return nil, fmt.Errorf("failed to generate random netns name: %v", err)
}
err = os.MkdirAll(nsRunDir, 0755)
if err != nil {
return nil, err
}
// create an empty file at the mount point
nsName := fmt.Sprintf("cni-%x-%x-%x-%x-%x", b[0:4], b[4:6], b[6:8], b[8:10], b[10:])
nsPath := path.Join(nsRunDir, nsName)
mountPointFd, err := os.Create(nsPath)
if err != nil {
return nil, err
}
mountPointFd.Close()
// Ensure the mount point is cleaned up on errors; if the namespace
// was successfully mounted this will have no effect because the file
// is in-use
defer os.RemoveAll(nsPath)
var wg sync.WaitGroup
wg.Add(1)
// do namespace work in a dedicated goroutine, so that we can safely
// Lock/Unlock OSThread without upsetting the lock/unlock state of
// the caller of this function
var fd *os.File
go (func() {
defer wg.Done()
runtime.LockOSThread()
var origNS NetNS
origNS, err = GetNS(getCurrentThreadNetNSPath())
if err != nil {
return
}
defer origNS.Close()
// create a new netns on the current thread
err = unix.Unshare(unix.CLONE_NEWNET)
if err != nil {
return
}
defer origNS.Set()
// bind mount the new netns from the current thread onto the mount point
err = unix.Mount(getCurrentThreadNetNSPath(), nsPath, "none", unix.MS_BIND, "")
if err != nil {
return
}
fd, err = os.Open(nsPath)
if err != nil {
return
}
})()
wg.Wait()
if err != nil {
unix.Unmount(nsPath, unix.MNT_DETACH)
return nil, fmt.Errorf("failed to create namespace: %v", err)
}
return &netNS{file: fd, mounted: true}, nil
}
func (ns *netNS) Close() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := ns.file.Close(); err != nil {
return fmt.Errorf("Failed to close %q: %v", ns.file.Name(), err)
}
ns.closed = true
if ns.mounted {
if err := unix.Unmount(ns.file.Name(), unix.MNT_DETACH); err != nil {
return fmt.Errorf("Failed to unmount namespace %s: %v", ns.file.Name(), err)
}
if err := os.RemoveAll(ns.file.Name()); err != nil {
return fmt.Errorf("Failed to clean up namespace %s: %v", ns.file.Name(), err)
}
ns.mounted = false
}
return nil
}
func (ns *netNS) Set() error {
if err := ns.errorIfClosed(); err != nil {
return err
}
if err := unix.Setns(int(ns.Fd()), unix.CLONE_NEWNET); err != nil {
return fmt.Errorf("Error switching to ns %v: %v", ns.file.Name(), err)
}
return nil
}

View File

@ -0,0 +1,36 @@
// Copyright 2015-2017 CNI 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.
// +build !linux
package ns
import "github.com/containernetworking/cni/pkg/types"
// Returns an object representing the current OS thread's network namespace
func GetCurrentNS() (NetNS, error) {
return nil, types.NotImplementedError
}
func NewNS() (NetNS, error) {
return nil, types.NotImplementedError
}
func (ns *netNS) Close() error {
return types.NotImplementedError
}
func (ns *netNS) Set() error {
return types.NotImplementedError
}

126
vendor/github.com/docker/distribution/uuid/uuid.go generated vendored Normal file
View File

@ -0,0 +1,126 @@
// Package uuid provides simple UUID generation. Only version 4 style UUIDs
// can be generated.
//
// Please see http://tools.ietf.org/html/rfc4122 for details on UUIDs.
package uuid
import (
"crypto/rand"
"fmt"
"io"
"os"
"syscall"
"time"
)
const (
// Bits is the number of bits in a UUID
Bits = 128
// Size is the number of bytes in a UUID
Size = Bits / 8
format = "%08x-%04x-%04x-%04x-%012x"
)
var (
// ErrUUIDInvalid indicates a parsed string is not a valid uuid.
ErrUUIDInvalid = fmt.Errorf("invalid uuid")
// Loggerf can be used to override the default logging destination. Such
// log messages in this library should be logged at warning or higher.
Loggerf = func(format string, args ...interface{}) {}
)
// UUID represents a UUID value. UUIDs can be compared and set to other values
// and accessed by byte.
type UUID [Size]byte
// Generate creates a new, version 4 uuid.
func Generate() (u UUID) {
const (
// ensures we backoff for less than 450ms total. Use the following to
// select new value, in units of 10ms:
// n*(n+1)/2 = d -> n^2 + n - 2d -> n = (sqrt(8d + 1) - 1)/2
maxretries = 9
backoff = time.Millisecond * 10
)
var (
totalBackoff time.Duration
count int
retries int
)
for {
// This should never block but the read may fail. Because of this,
// we just try to read the random number generator until we get
// something. This is a very rare condition but may happen.
b := time.Duration(retries) * backoff
time.Sleep(b)
totalBackoff += b
n, err := io.ReadFull(rand.Reader, u[count:])
if err != nil {
if retryOnError(err) && retries < maxretries {
count += n
retries++
Loggerf("error generating version 4 uuid, retrying: %v", err)
continue
}
// Any other errors represent a system problem. What did someone
// do to /dev/urandom?
panic(fmt.Errorf("error reading random number generator, retried for %v: %v", totalBackoff.String(), err))
}
break
}
u[6] = (u[6] & 0x0f) | 0x40 // set version byte
u[8] = (u[8] & 0x3f) | 0x80 // set high order byte 0b10{8,9,a,b}
return u
}
// Parse attempts to extract a uuid from the string or returns an error.
func Parse(s string) (u UUID, err error) {
if len(s) != 36 {
return UUID{}, ErrUUIDInvalid
}
// create stack addresses for each section of the uuid.
p := make([][]byte, 5)
if _, err := fmt.Sscanf(s, format, &p[0], &p[1], &p[2], &p[3], &p[4]); err != nil {
return u, err
}
copy(u[0:4], p[0])
copy(u[4:6], p[1])
copy(u[6:8], p[2])
copy(u[8:10], p[3])
copy(u[10:16], p[4])
return
}
func (u UUID) String() string {
return fmt.Sprintf(format, u[:4], u[4:6], u[6:8], u[8:10], u[10:])
}
// retryOnError tries to detect whether or not retrying would be fruitful.
func retryOnError(err error) bool {
switch err := err.(type) {
case *os.PathError:
return retryOnError(err.Err) // unpack the target error
case syscall.Errno:
if err == syscall.EPERM {
// EPERM represents an entropy pool exhaustion, a condition under
// which we backoff and retry.
return true
}
}
return false
}

View File

@ -0,0 +1,3 @@
// Package portsbinding provides information and interaction with the port
// binding extension for the OpenStack Networking service.
package portsbinding

View File

@ -0,0 +1,117 @@
package portsbinding
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
)
// Get retrieves a specific port based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(getURL(c, id), &r.Body, nil)
return
}
// CreateOpts represents the attributes used when creating a new
// port with extended attributes.
type CreateOpts struct {
// CreateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Create operation in this package.
ports.CreateOptsBuilder `json:"-"`
// The ID of the host where the port is allocated
HostID string `json:"binding:host_id,omitempty"`
// The dns name
DNSName string `json:"dns_name,omitempty"`
// The virtual network interface card (vNIC) type that is bound to the
// neutron port
VNICType string `json:"binding:vnic_type,omitempty"`
// A dictionary that enables the application running on the specified
// host to pass and receive virtual network interface (VIF) port-specific
// information to the plug-in
Profile map[string]string `json:"binding:profile,omitempty"`
}
// ToPortCreateMap casts a CreateOpts struct to a map.
func (opts CreateOpts) ToPortCreateMap() (map[string]interface{}, error) {
b1, err := opts.CreateOptsBuilder.ToPortCreateMap()
if err != nil {
return nil, err
}
b2, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
port := b1["port"].(map[string]interface{})
for k, v := range b2 {
port[k] = v
}
return map[string]interface{}{"port": port}, nil
}
// Create accepts a CreateOpts struct and creates a new port with extended attributes.
// You must remember to provide a NetworkID value.
func Create(c *gophercloud.ServiceClient, opts ports.CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToPortCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(createURL(c), b, &r.Body, nil)
return
}
// UpdateOpts represents the attributes used when updating an existing port.
type UpdateOpts struct {
// UpdateOptsBuilder is the interface options structs have to satisfy in order
// to be used in the main Update operation in this package.
ports.UpdateOptsBuilder `json:"-"`
// The ID of the host where the port is allocated
HostID string `json:"binding:host_id,omitempty"`
// The dns name
DNSName string `json:"dns_name,omitempty"`
// The virtual network interface card (vNIC) type that is bound to the
// neutron port
VNICType string `json:"binding:vnic_type,omitempty"`
// A dictionary that enables the application running on the specified
// host to pass and receive virtual network interface (VIF) port-specific
// information to the plug-in
Profile map[string]string `json:"binding:profile,omitempty"`
}
// ToPortUpdateMap casts an UpdateOpts struct to a map.
func (opts UpdateOpts) ToPortUpdateMap() (map[string]interface{}, error) {
b1, err := opts.UpdateOptsBuilder.ToPortUpdateMap()
if err != nil {
return nil, err
}
b2, err := gophercloud.BuildRequestBody(opts, "")
if err != nil {
return nil, err
}
port := b1["port"].(map[string]interface{})
for k, v := range b2 {
port[k] = v
}
return map[string]interface{}{"port": port}, nil
}
// Update accepts a UpdateOpts struct and updates an existing port using the
// values provided.
func Update(c *gophercloud.ServiceClient, id string, opts ports.UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToPortUpdateMap()
if err != nil {
r.Err = err
return r
}
_, r.Err = c.Put(updateURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200, 201},
})
return
}

View File

@ -0,0 +1,73 @@
package portsbinding
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/ports"
"github.com/gophercloud/gophercloud/pagination"
)
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a port resource.
func (r commonResult) Extract() (*Port, error) {
var s struct {
Port *Port `json:"port"`
}
err := r.ExtractInto(&s)
return s.Port, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
commonResult
}
// IP is a sub-struct that represents an individual IP.
type IP struct {
SubnetID string `json:"subnet_id"`
IPAddress string `json:"ip_address"`
}
// Port represents a Neutron port. See package documentation for a top-level
// description of what this is.
type Port struct {
ports.Port
// The ID of the host where the port is allocated
HostID string `json:"binding:host_id"`
// A dictionary that enables the application to pass information about
// functions that the Networking API provides.
VIFDetails map[string]interface{} `json:"binding:vif_details"`
// The VIF type for the port.
VIFType string `json:"binding:vif_type"`
// The virtual network interface card (vNIC) type that is bound to the
// neutron port
VNICType string `json:"binding:vnic_type"`
// A dictionary that enables the application running on the specified
// host to pass and receive virtual network interface (VIF) port-specific
// information to the plug-in
Profile map[string]string `json:"binding:profile"`
}
// ExtractPorts accepts a Page struct, specifically a PortPage struct,
// and extracts the elements into a slice of Port structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractPorts(r pagination.Page) ([]Port, error) {
var s struct {
Ports []Port `json:"ports"`
}
err := (r.(ports.PortPage)).ExtractInto(&s)
return s.Ports, err
}

View File

@ -0,0 +1,23 @@
package portsbinding
import "github.com/gophercloud/gophercloud"
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL("ports", id)
}
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL("ports")
}
func getURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}
func createURL(c *gophercloud.ServiceClient) string {
return rootURL(c)
}
func updateURL(c *gophercloud.ServiceClient, id string) string {
return resourceURL(c, id)
}

View File

@ -0,0 +1,138 @@
package groups
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the floating IP attributes you want to see returned. SortKey allows you to
// sort by a particular network attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
ID string `q:"id"`
Name string `q:"name"`
TenantID string `q:"tenant_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// List returns a Pager which allows you to iterate over a collection of
// security groups. It accepts a ListOpts struct, which allows you to filter
// and sort the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
q, err := gophercloud.BuildQueryString(&opts)
if err != nil {
return pagination.Pager{Err: err}
}
u := rootURL(c) + q.String()
return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
return SecGroupPage{pagination.LinkedPageBase{PageResult: r}}
})
}
type CreateOptsBuilder interface {
ToSecGroupCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new security group.
type CreateOpts struct {
// Required. Human-readable name for the Security Group. Does not have to be unique.
Name string `json:"name" required:"true"`
// Required for admins. Indicates the owner of the Security Group.
TenantID string `json:"tenant_id,omitempty"`
// Optional. Describes the security group.
Description string `json:"description,omitempty"`
}
func (opts CreateOpts) ToSecGroupCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
// Create is an operation which provisions a new security group with default
// security group rules for the IPv4 and IPv6 ether types.
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSecGroupCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
type UpdateOptsBuilder interface {
ToSecGroupUpdateMap() (map[string]interface{}, error)
}
// UpdateOpts contains all the values needed to update an existing security group.
type UpdateOpts struct {
// Human-readable name for the Security Group. Does not have to be unique.
Name string `json:"name,omitempty"`
// Optional. Describes the security group.
Description string `json:"description,omitempty"`
}
func (opts UpdateOpts) ToSecGroupUpdateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group")
}
// Update is an operation which updates an existing security group.
func Update(c *gophercloud.ServiceClient, id string, opts UpdateOptsBuilder) (r UpdateResult) {
b, err := opts.ToSecGroupUpdateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Put(resourceURL(c, id), b, &r.Body, &gophercloud.RequestOpts{
OkCodes: []int{200},
})
return
}
// Get retrieves a particular security group based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// Delete will permanently delete a particular security group based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}
// IDFromName is a convenience function that returns a security group's ID given its name.
func IDFromName(client *gophercloud.ServiceClient, name string) (string, error) {
count := 0
id := ""
pages, err := List(client, ListOpts{}).AllPages()
if err != nil {
return "", err
}
all, err := ExtractGroups(pages)
if err != nil {
return "", err
}
for _, s := range all {
if s.Name == name {
count++
id = s.ID
}
}
switch count {
case 0:
return "", gophercloud.ErrResourceNotFound{Name: name, ResourceType: "security group"}
case 1:
return id, nil
default:
return "", gophercloud.ErrMultipleResourcesFound{Name: name, Count: count, ResourceType: "security group"}
}
}

View File

@ -0,0 +1,99 @@
package groups
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/openstack/networking/v2/extensions/security/rules"
"github.com/gophercloud/gophercloud/pagination"
)
// SecGroup represents a container for security group rules.
type SecGroup struct {
// The UUID for the security group.
ID string
// Human-readable name for the security group. Might not be unique. Cannot be
// named "default" as that is automatically created for a tenant.
Name string
// The security group description.
Description string
// A slice of security group rules that dictate the permitted behaviour for
// traffic entering and leaving the group.
Rules []rules.SecGroupRule `json:"security_group_rules"`
// Owner of the security group. Only admin users can specify a TenantID
// other than their own.
TenantID string `json:"tenant_id"`
}
// SecGroupPage is the page returned by a pager when traversing over a
// collection of security groups.
type SecGroupPage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of security groups has
// reached the end of a page and the pager seeks to traverse over a new one. In
// order to do this, it needs to construct the next page's URL.
func (r SecGroupPage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"security_groups_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a SecGroupPage struct is empty.
func (r SecGroupPage) IsEmpty() (bool, error) {
is, err := ExtractGroups(r)
return len(is) == 0, err
}
// ExtractGroups accepts a Page struct, specifically a SecGroupPage struct,
// and extracts the elements into a slice of SecGroup structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractGroups(r pagination.Page) ([]SecGroup, error) {
var s struct {
SecGroups []SecGroup `json:"security_groups"`
}
err := (r.(SecGroupPage)).ExtractInto(&s)
return s.SecGroups, err
}
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a security group.
func (r commonResult) Extract() (*SecGroup, error) {
var s struct {
SecGroup *SecGroup `json:"security_group"`
}
err := r.ExtractInto(&s)
return s.SecGroup, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// UpdateResult represents the result of an update operation.
type UpdateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,13 @@
package groups
import "github.com/gophercloud/gophercloud"
const rootPath = "security-groups"
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(rootPath)
}
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, id)
}

View File

@ -0,0 +1,145 @@
package rules
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// ListOpts allows the filtering and sorting of paginated collections through
// the API. Filtering is achieved by passing in struct field values that map to
// the security group attributes you want to see returned. SortKey allows you to
// sort by a particular network attribute. SortDir sets the direction, and is
// either `asc' or `desc'. Marker and Limit are used for pagination.
type ListOpts struct {
Direction string `q:"direction"`
EtherType string `q:"ethertype"`
ID string `q:"id"`
PortRangeMax int `q:"port_range_max"`
PortRangeMin int `q:"port_range_min"`
Protocol string `q:"protocol"`
RemoteGroupID string `q:"remote_group_id"`
RemoteIPPrefix string `q:"remote_ip_prefix"`
SecGroupID string `q:"security_group_id"`
TenantID string `q:"tenant_id"`
Limit int `q:"limit"`
Marker string `q:"marker"`
SortKey string `q:"sort_key"`
SortDir string `q:"sort_dir"`
}
// List returns a Pager which allows you to iterate over a collection of
// security group rules. It accepts a ListOpts struct, which allows you to filter
// and sort the returned collection for greater efficiency.
func List(c *gophercloud.ServiceClient, opts ListOpts) pagination.Pager {
q, err := gophercloud.BuildQueryString(&opts)
if err != nil {
return pagination.Pager{Err: err}
}
u := rootURL(c) + q.String()
return pagination.NewPager(c, u, func(r pagination.PageResult) pagination.Page {
return SecGroupRulePage{pagination.LinkedPageBase{PageResult: r}}
})
}
type RuleDirection string
type RuleProtocol string
type RuleEtherType string
// Constants useful for CreateOpts
const (
DirIngress RuleDirection = "ingress"
DirEgress RuleDirection = "egress"
EtherType4 RuleEtherType = "IPv4"
EtherType6 RuleEtherType = "IPv6"
ProtocolAH RuleProtocol = "ah"
ProtocolDCCP RuleProtocol = "dccp"
ProtocolEGP RuleProtocol = "egp"
ProtocolESP RuleProtocol = "esp"
ProtocolGRE RuleProtocol = "gre"
ProtocolICMP RuleProtocol = "icmp"
ProtocolIGMP RuleProtocol = "igmp"
ProtocolIPv6Encap RuleProtocol = "ipv6-encap"
ProtocolIPv6Frag RuleProtocol = "ipv6-frag"
ProtocolIPv6ICMP RuleProtocol = "ipv6-icmp"
ProtocolIPv6NoNxt RuleProtocol = "ipv6-nonxt"
ProtocolIPv6Opts RuleProtocol = "ipv6-opts"
ProtocolIPv6Route RuleProtocol = "ipv6-route"
ProtocolOSPF RuleProtocol = "ospf"
ProtocolPGM RuleProtocol = "pgm"
ProtocolRSVP RuleProtocol = "rsvp"
ProtocolSCTP RuleProtocol = "sctp"
ProtocolTCP RuleProtocol = "tcp"
ProtocolUDP RuleProtocol = "udp"
ProtocolUDPLite RuleProtocol = "udplite"
ProtocolVRRP RuleProtocol = "vrrp"
)
// CreateOptsBuilder is what types must satisfy to be used as Create
// options.
type CreateOptsBuilder interface {
ToSecGroupRuleCreateMap() (map[string]interface{}, error)
}
// CreateOpts contains all the values needed to create a new security group rule.
type CreateOpts struct {
// Required. Must be either "ingress" or "egress": the direction in which the
// security group rule is applied.
Direction RuleDirection `json:"direction" required:"true"`
// Required. Must be "IPv4" or "IPv6", and addresses represented in CIDR must
// match the ingress or egress rules.
EtherType RuleEtherType `json:"ethertype" required:"true"`
// Required. The security group ID to associate with this security group rule.
SecGroupID string `json:"security_group_id" required:"true"`
// Optional. The maximum port number in the range that is matched by the
// security group rule. The PortRangeMin attribute constrains the PortRangeMax
// attribute. If the protocol is ICMP, this value must be an ICMP type.
PortRangeMax int `json:"port_range_max,omitempty"`
// Optional. The minimum port number in the range that is matched by the
// security group rule. If the protocol is TCP or UDP, this value must be
// less than or equal to the value of the PortRangeMax attribute. If the
// protocol is ICMP, this value must be an ICMP type.
PortRangeMin int `json:"port_range_min,omitempty"`
// Optional. The protocol that is matched by the security group rule. Valid
// values are "tcp", "udp", "icmp" or an empty string.
Protocol RuleProtocol `json:"protocol,omitempty"`
// Optional. The remote group ID to be associated with this security group
// rule. You can specify either RemoteGroupID or RemoteIPPrefix.
RemoteGroupID string `json:"remote_group_id,omitempty"`
// Optional. The remote IP prefix to be associated with this security group
// rule. You can specify either RemoteGroupID or RemoteIPPrefix. This
// attribute matches the specified IP prefix as the source IP address of the
// IP packet.
RemoteIPPrefix string `json:"remote_ip_prefix,omitempty"`
// Required for admins. Indicates the owner of the VIP.
TenantID string `json:"tenant_id,omitempty"`
}
// ToSecGroupRuleCreateMap allows CreateOpts to satisfy the CreateOptsBuilder
// interface
func (opts CreateOpts) ToSecGroupRuleCreateMap() (map[string]interface{}, error) {
return gophercloud.BuildRequestBody(opts, "security_group_rule")
}
// Create is an operation which adds a new security group rule and associates it
// with an existing security group (whose ID is specified in CreateOpts).
func Create(c *gophercloud.ServiceClient, opts CreateOptsBuilder) (r CreateResult) {
b, err := opts.ToSecGroupRuleCreateMap()
if err != nil {
r.Err = err
return
}
_, r.Err = c.Post(rootURL(c), b, &r.Body, nil)
return
}
// Get retrieves a particular security group rule based on its unique ID.
func Get(c *gophercloud.ServiceClient, id string) (r GetResult) {
_, r.Err = c.Get(resourceURL(c, id), &r.Body, nil)
return
}
// Delete will permanently delete a particular security group rule based on its unique ID.
func Delete(c *gophercloud.ServiceClient, id string) (r DeleteResult) {
_, r.Err = c.Delete(resourceURL(c, id), nil)
return
}

View File

@ -0,0 +1,118 @@
package rules
import (
"github.com/gophercloud/gophercloud"
"github.com/gophercloud/gophercloud/pagination"
)
// SecGroupRule represents a rule to dictate the behaviour of incoming or
// outgoing traffic for a particular security group.
type SecGroupRule struct {
// The UUID for this security group rule.
ID string
// The direction in which the security group rule is applied. The only values
// allowed are "ingress" or "egress". For a compute instance, an ingress
// security group rule is applied to incoming (ingress) traffic for that
// instance. An egress rule is applied to traffic leaving the instance.
Direction string
// Must be IPv4 or IPv6, and addresses represented in CIDR must match the
// ingress or egress rules.
EtherType string `json:"ethertype"`
// The security group ID to associate with this security group rule.
SecGroupID string `json:"security_group_id"`
// The minimum port number in the range that is matched by the security group
// rule. If the protocol is TCP or UDP, this value must be less than or equal
// to the value of the PortRangeMax attribute. If the protocol is ICMP, this
// value must be an ICMP type.
PortRangeMin int `json:"port_range_min"`
// The maximum port number in the range that is matched by the security group
// rule. The PortRangeMin attribute constrains the PortRangeMax attribute. If
// the protocol is ICMP, this value must be an ICMP type.
PortRangeMax int `json:"port_range_max"`
// The protocol that is matched by the security group rule. Valid values are
// "tcp", "udp", "icmp" or an empty string.
Protocol string
// The remote group ID to be associated with this security group rule. You
// can specify either RemoteGroupID or RemoteIPPrefix.
RemoteGroupID string `json:"remote_group_id"`
// The remote IP prefix to be associated with this security group rule. You
// can specify either RemoteGroupID or RemoteIPPrefix . This attribute
// matches the specified IP prefix as the source IP address of the IP packet.
RemoteIPPrefix string `json:"remote_ip_prefix"`
// The owner of this security group rule.
TenantID string `json:"tenant_id"`
}
// SecGroupRulePage is the page returned by a pager when traversing over a
// collection of security group rules.
type SecGroupRulePage struct {
pagination.LinkedPageBase
}
// NextPageURL is invoked when a paginated collection of security group rules has
// reached the end of a page and the pager seeks to traverse over a new one. In
// order to do this, it needs to construct the next page's URL.
func (r SecGroupRulePage) NextPageURL() (string, error) {
var s struct {
Links []gophercloud.Link `json:"security_group_rules_links"`
}
err := r.ExtractInto(&s)
if err != nil {
return "", err
}
return gophercloud.ExtractNextURL(s.Links)
}
// IsEmpty checks whether a SecGroupRulePage struct is empty.
func (r SecGroupRulePage) IsEmpty() (bool, error) {
is, err := ExtractRules(r)
return len(is) == 0, err
}
// ExtractRules accepts a Page struct, specifically a SecGroupRulePage struct,
// and extracts the elements into a slice of SecGroupRule structs. In other words,
// a generic collection is mapped into a relevant slice.
func ExtractRules(r pagination.Page) ([]SecGroupRule, error) {
var s struct {
SecGroupRules []SecGroupRule `json:"security_group_rules"`
}
err := (r.(SecGroupRulePage)).ExtractInto(&s)
return s.SecGroupRules, err
}
type commonResult struct {
gophercloud.Result
}
// Extract is a function that accepts a result and extracts a security rule.
func (r commonResult) Extract() (*SecGroupRule, error) {
var s struct {
SecGroupRule *SecGroupRule `json:"security_group_rule"`
}
err := r.ExtractInto(&s)
return s.SecGroupRule, err
}
// CreateResult represents the result of a create operation.
type CreateResult struct {
commonResult
}
// GetResult represents the result of a get operation.
type GetResult struct {
commonResult
}
// DeleteResult represents the result of a delete operation.
type DeleteResult struct {
gophercloud.ErrResult
}

View File

@ -0,0 +1,13 @@
package rules
import "github.com/gophercloud/gophercloud"
const rootPath = "security-group-rules"
func rootURL(c *gophercloud.ServiceClient) string {
return c.ServiceURL(rootPath)
}
func resourceURL(c *gophercloud.ServiceClient, id string) string {
return c.ServiceURL(rootPath, id)
}