Windows Support

Patch for https://github.com/containernetworking/plugins/pull/85

+ Windows cni plugins are added
   (*) win-bridge (hostgw)
   (*) win-overlay (vxlan)
+ Windows netconf unit test
+ Fix appveyor config to run the test
+ Build release support for windows plugins

Address comments

From:
    - https://github.com/containernetworking/plugins/pull/85
    - 0049c64e3f
This commit is contained in:
MaiWJ
2018-08-23 16:52:41 +08:00
parent e1d29e9fe4
commit b56ca2fe45
22 changed files with 1214 additions and 75 deletions

13
plugins/main/windows/build.sh Executable file
View File

@ -0,0 +1,13 @@
#!/usr/bin/env bash
set -e
PLUGINS=$(cat plugins/windows_only.txt)
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d").exe"
echo " $plugin"
CXX=x86_64-w64-mingw32-g++ CC=x86_64-w64-mingw32-gcc CGO_ENABLED=1 \
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
fi
done

View File

@ -0,0 +1,25 @@
# win-bridge plugin
## Overview
With win-bridge plugin, all containers (on the same host) are plugged into an L2Bridge network that has one endpoint in the host namespace.
## Example configuration
```
{
"name": "mynet",
"type": "win-bridge",
"ipMasqNetwork": "10.244.0.0/16",
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "win-bridge".
* `ipMasqNetwork` (string, optional): setup NAT if not empty.
* `ipam` (dictionary, required): IPAM configuration to be used for this network.

View File

@ -0,0 +1,44 @@
{
"name":"cbr0",
"type":"flannel",
"delegate":{
"type":"win-bridge",
"dns":{
"nameservers":[
"11.0.0.10"
],
"search":[
"svc.cluster.local"
]
},
"policies":[
{
"name":"EndpointPolicy",
"value":{
"Type":"OutBoundNAT",
"ExceptionList":[
"192.168.0.0/16",
"11.0.0.0/8",
"10.137.196.0/23"
]
}
},
{
"name":"EndpointPolicy",
"value":{
"Type":"ROUTE",
"DestinationPrefix":"11.0.0.0/8",
"NeedEncap":true
}
},
{
"name":"EndpointPolicy",
"value":{
"Type":"ROUTE",
"DestinationPrefix":"10.137.198.27/32",
"NeedEncap":true
}
}
]
}
}

View File

@ -0,0 +1,151 @@
// Copyright 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 main
import (
"encoding/json"
"fmt"
"runtime"
"strings"
"github.com/juju/errors"
"github.com/Microsoft/hcsshim"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
)
type NetConf struct {
hns.NetConf
IPMasqNetwork string `json:"ipMasqNetwork,omitempty"`
}
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) (*NetConf, string, error) {
n := &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 cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadNetConf(args.StdinData)
if err != nil {
return errors.Annotate(err, "error while loadNetConf")
}
networkName := n.Name
hnsNetwork, err := hcsshim.GetHNSNetworkByName(networkName)
if err != nil {
return errors.Annotatef(err, "error while GETHNSNewtorkByName(%s)", networkName)
}
if hnsNetwork == nil {
return fmt.Errorf("network %v not found", networkName)
}
if !strings.EqualFold(hnsNetwork.Type, "L2Bridge") {
return fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
}
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, func() (*hcsshim.HNSEndpoint, error) {
// run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil {
return nil, errors.Annotatef(err, "error while ipam.ExecAdd")
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r)
if err != nil {
return nil, errors.Annotatef(err, "error while NewResultFromResult")
}
if len(result.IPs) == 0 {
return nil, errors.New("IPAM plugin return is missing IP config")
}
// Calculate gateway for bridge network (needs to be x.2)
gw := result.IPs[0].Address.IP.Mask(result.IPs[0].Address.Mask)
gw[len(gw)-1] += 2
// NAT based on the the configured cluster network
if len(n.IPMasqNetwork) != 0 {
n.ApplyOutboundNatPolicy(n.IPMasqNetwork)
}
result.DNS = n.DNS
hnsEndpoint := &hcsshim.HNSEndpoint{
Name: epName,
VirtualNetwork: hnsNetwork.Id,
DNSServerList: strings.Join(result.DNS.Nameservers, ","),
DNSSuffix: strings.Join(result.DNS.Search, ","),
GatewayAddress: gw.String(),
IPAddress: result.IPs[0].Address.IP,
Policies: n.MarshalPolicies(),
}
return hnsEndpoint, nil
})
if err != nil {
return errors.Annotatef(err, "error while ProvisionEndpoint(%v,%v,%v)", epName, hnsNetwork.Id, args.ContainerID)
}
result, err := hns.ConstructResult(hnsNetwork, hnsEndpoint)
if err != nil {
return errors.Annotatef(err, "error while constructResult")
}
return types.PrintResult(result, cniVersion)
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
return err
}
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
return hns.DeprovisionEndpoint(epName, args.Netns, args.ContainerID)
}
func cmdGet(_ *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
}

View File

@ -0,0 +1,27 @@
# win-overlay plugin
## Overview
With win-overlay plugin, all containers (on the same host) are plugged into an Overlay network based on VXLAN encapsulation.
## Example configuration
```
{
"name": "mynet",
"type": "win-overlay",
"ipMasq": true,
"endpointMacPrefix": "0E-2A",
"ipam": {
"type": "host-local",
"subnet": "10.10.0.0/16"
}
}
```
## Network configuration reference
* `name` (string, required): the name of the network.
* `type` (string, required): "win-overlay".
* `ipMasq` (bool, optional): the inverse of `$FLANNEL_IPMASQ`, setup NAT for the hnsNetwork subnet.
* `endpointMacPrefix` (string, optional): set to the MAC prefix configured for Flannel
* `ipam` (dictionary, required): IPAM configuration to be used for this network.

View File

@ -0,0 +1,36 @@
{
"cniVersion":"0.2.0",
"name":"vxlan0",
"type":"flannel",
"delegate":{
"type":"win-overlay",
"dns":{
"nameservers":[
"11.0.0.10"
],
"search":[
"svc.cluster.local"
]
},
"policies":[
{
"name":"EndpointPolicy",
"value":{
"Type":"OutBoundNAT",
"ExceptionList":[
"192.168.0.0/16",
"11.0.0.0/8"
]
}
},
{
"name":"EndpointPolicy",
"value":{
"Type":"ROUTE",
"DestinationPrefix":"11.0.0.0/8",
"NeedEncap":true
}
}
]
}
}

View File

@ -0,0 +1,166 @@
// Copyright 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 main
import (
"encoding/json"
"fmt"
"runtime"
"strings"
"github.com/juju/errors"
"github.com/Microsoft/hcsshim"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/cni/pkg/version"
"github.com/containernetworking/plugins/pkg/hns"
"github.com/containernetworking/plugins/pkg/ipam"
)
type NetConf struct {
hns.NetConf
IPMasq bool `json:"ipMasq"`
EndpointMacPrefix string `json:"endpointMacPrefix,omitempty"`
}
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) (*NetConf, string, error) {
n := &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 cmdAdd(args *skel.CmdArgs) error {
n, cniVersion, err := loadNetConf(args.StdinData)
if err != nil {
return errors.Annotate(err, "error while loadNetConf")
}
if len(n.EndpointMacPrefix) != 0 {
if len(n.EndpointMacPrefix) != 5 || n.EndpointMacPrefix[2] != '-' {
return fmt.Errorf("endpointMacPrefix [%v] is invalid, value must be of the format xx-xx", n.EndpointMacPrefix)
}
} else {
n.EndpointMacPrefix = "0E-2A"
}
networkName := n.Name
hnsNetwork, err := hcsshim.GetHNSNetworkByName(networkName)
if err != nil {
return errors.Annotatef(err, "error while GETHNSNewtorkByName(%s)", networkName)
}
if hnsNetwork == nil {
return fmt.Errorf("network %v not found", networkName)
}
if !strings.EqualFold(hnsNetwork.Type, "Overlay") {
return fmt.Errorf("network %v is of an unexpected type: %v", networkName, hnsNetwork.Type)
}
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
hnsEndpoint, err := hns.ProvisionEndpoint(epName, hnsNetwork.Id, args.ContainerID, func() (*hcsshim.HNSEndpoint, error) {
// run the IPAM plugin and get back the config to apply
r, err := ipam.ExecAdd(n.IPAM.Type, args.StdinData)
if err != nil {
return nil, errors.Annotatef(err, "error while ipam.ExecAdd")
}
// Convert whatever the IPAM result was into the current Result type
result, err := current.NewResultFromResult(r)
if err != nil {
return nil, errors.Annotatef(err, "error while NewResultFromResult")
}
if len(result.IPs) == 0 {
return nil, errors.New("IPAM plugin return is missing IP config")
}
ipAddr := result.IPs[0].Address.IP.To4()
if ipAddr == nil {
return nil, errors.New("win-overlay doesn't support IPv6 now")
}
// conjure a MAC based on the IP for Overlay
macAddr := fmt.Sprintf("%v-%02x-%02x-%02x-%02x", n.EndpointMacPrefix, ipAddr[0], ipAddr[1], ipAddr[2], ipAddr[3])
// use the HNS network gateway
gw := hnsNetwork.Subnets[0].GatewayAddress
n.ApplyDefaultPAPolicy(hnsNetwork.ManagementIP)
if n.IPMasq {
n.ApplyOutboundNatPolicy(hnsNetwork.Subnets[0].AddressPrefix)
}
result.DNS = n.DNS
hnsEndpoint := &hcsshim.HNSEndpoint{
Name: epName,
VirtualNetwork: hnsNetwork.Id,
DNSServerList: strings.Join(result.DNS.Nameservers, ","),
DNSSuffix: strings.Join(result.DNS.Search, ","),
GatewayAddress: gw,
IPAddress: ipAddr,
MacAddress: macAddr,
Policies: n.MarshalPolicies(),
}
return hnsEndpoint, nil
})
if err != nil {
return errors.Annotatef(err, "error while ProvisionEndpoint(%v,%v,%v)", epName, hnsNetwork.Id, args.ContainerID)
}
result, err := hns.ConstructResult(hnsNetwork, hnsEndpoint)
if err != nil {
return errors.Annotatef(err, "error while constructResult")
}
return types.PrintResult(result, cniVersion)
}
func cmdDel(args *skel.CmdArgs) error {
n, _, err := loadNetConf(args.StdinData)
if err != nil {
return err
}
if err := ipam.ExecDel(n.IPAM.Type, args.StdinData); err != nil {
return err
}
epName := hns.ConstructEndpointName(args.ContainerID, args.Netns, n.Name)
return hns.DeprovisionEndpoint(epName, args.Netns, args.ContainerID)
}
func cmdGet(_ *skel.CmdArgs) error {
// TODO: implement
return fmt.Errorf("not implemented")
}
func main() {
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
}