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

View File

@ -86,3 +86,50 @@ flannel plugin will set the following fields in the delegated plugin configurati
* `mtu`: `$FLANNEL_MTU`
Additionally, for the bridge plugin, `isGateway` will be set to `true`, if not present.
## Windows Support (Experimental)
This plugin supports delegating to the windows CNI plugins (overlay.exe, l2bridge.exe) to work in conjunction with [Flannel on Windows](https://github.com/coreos/flannel/issues/833).
Flannel sets up an [HNS Network](https://docs.microsoft.com/en-us/virtualization/windowscontainers/manage-containers/container-networking) in L2Bridge mode for host-gw and in Overlay mode for vxlan.
The following fields must be set in the delegated plugin configuration:
* `name` (string, required): the name of the network (must match the name in Flannel config / name of the HNS network)
* `type` (string, optional): set to `win-l2bridge` by default. Can be set to `win-overlay` or other custom windows CNI
* `ipMasq`: the inverse of `$FLANNEL_IPMASQ`
* `endpointMacPrefix` (string, optional): required for `win-overlay` mode, set to the MAC prefix configured for Flannel
* `clusterNetworkPrefix` (string, optional): required for `win-l2bridge` mode, setup NAT if `ipMasq` is set to true
For `win-l2bridge`, the Flannel CNI plugin will set:
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .2 address in `$FLANNEL_NETWORK`
For `win-overlay`, the Flannel CNI plugin will set:
* `ipam`: "host-local" type will be used with "subnet" set to `$FLANNEL_SUBNET` and gateway as the .1 address in `$FLANNEL_NETWORK`
If IPMASQ is true, the Flannel CNI plugin will setup an OutBoundNAT policy and add FLANNEL_SUBNET to any existing exclusions.
All other delegate config e.g. other HNS endpoint policies in AdditionalArgs will be passed to WINCNI as-is.
Example VXLAN Flannel CNI config
```
{
"name": "mynet",
"type": "flannel",
"delegate": {
"type": "win-overlay",
"endpointMacPrefix": "0E-2A"
}
}
```
For this example, Flannel CNI would generate the following config to delegate to the windows CNI when FLANNEL_NETWORK=10.244.0.0/16, FLANNEL_SUBNET=10.244.1.0/24 and IPMASQ=true
```
{
"name": "mynet",
"type": "win-overlay",
"endpointMacPrefix": "0E-2A",
"ipMasq": true,
"ipam": {
"subnet": "10.244.1.0/24",
"type": "host-local"
}
}
```

View File

@ -42,6 +42,7 @@ const (
type NetConf struct {
types.NetConf
SubnetFile string `json:"subnetFile"`
DataDir string `json:"dataDir"`
Delegate map[string]interface{} `json:"delegate"`
@ -202,43 +203,7 @@ func cmdAdd(args *skel.CmdArgs) error {
}
}
n.Delegate["name"] = n.Name
if !hasKey(n.Delegate, "type") {
n.Delegate["type"] = "bridge"
}
if !hasKey(n.Delegate, "ipMasq") {
// if flannel is not doing ipmasq, we should
ipmasq := !*fenv.ipmasq
n.Delegate["ipMasq"] = ipmasq
}
if !hasKey(n.Delegate, "mtu") {
mtu := fenv.mtu
n.Delegate["mtu"] = mtu
}
if n.Delegate["type"].(string) == "bridge" {
if !hasKey(n.Delegate, "isGateway") {
n.Delegate["isGateway"] = true
}
}
if n.CNIVersion != "" {
n.Delegate["cniVersion"] = n.CNIVersion
}
n.Delegate["ipam"] = map[string]interface{}{
"type": "host-local",
"subnet": fenv.sn.String(),
"routes": []types.Route{
types.Route{
Dst: *fenv.nw,
},
},
}
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
return doCmdAdd(args, n, fenv)
}
func cmdDel(args *skel.CmdArgs) error {
@ -247,25 +212,10 @@ func cmdDel(args *skel.CmdArgs) error {
return err
}
netconfBytes, err := consumeScratchNetConf(args.ContainerID, nc.DataDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
return nil
}
return err
}
n := &types.NetConf{}
if err = json.Unmarshal(netconfBytes, n); err != nil {
return fmt.Errorf("failed to parse netconf: %v", err)
}
return invoke.DelegateDel(n.Type, netconfBytes, nil)
return doCmdDel(args, nc)
}
func main() {
// TODO: implement plugin version
skel.PluginMain(cmdAdd, cmdGet, cmdDel, version.All, "TODO")
}

View File

@ -0,0 +1,86 @@
// Copyright 2018 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.
// This is a "meta-plugin". It reads in its own netconf, combines it with
// the data from flannel generated subnet file and then invokes a plugin
// like bridge or ipvlan to do the real work.
package main
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"os"
)
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
n.Delegate["name"] = n.Name
if !hasKey(n.Delegate, "type") {
n.Delegate["type"] = "bridge"
}
if !hasKey(n.Delegate, "ipMasq") {
// if flannel is not doing ipmasq, we should
ipmasq := !*fenv.ipmasq
n.Delegate["ipMasq"] = ipmasq
}
if !hasKey(n.Delegate, "mtu") {
mtu := fenv.mtu
n.Delegate["mtu"] = mtu
}
if n.Delegate["type"].(string) == "bridge" {
if !hasKey(n.Delegate, "isGateway") {
n.Delegate["isGateway"] = true
}
}
if n.CNIVersion != "" {
n.Delegate["cniVersion"] = n.CNIVersion
}
n.Delegate["ipam"] = map[string]interface{}{
"type": "host-local",
"subnet": fenv.sn.String(),
"routes": []types.Route{
{
Dst: *fenv.nw,
},
},
}
return delegateAdd(args.ContainerID, n.DataDir, n.Delegate)
}
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
netconfBytes, err := consumeScratchNetConf(args.ContainerID, n.DataDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
return nil
}
return err
}
nc := &types.NetConf{}
if err = json.Unmarshal(netconfBytes, nc); err != nil {
return fmt.Errorf("failed to parse netconf: %v", err)
}
return invoke.DelegateDel(nc.Type, netconfBytes, nil)
}

View File

@ -0,0 +1,73 @@
// Copyright 2018 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.
// This is a "meta-plugin". It reads in its own netconf, combines it with
// the data from flannel generated subnet file and then invokes a plugin
// like bridge or ipvlan to do the real work.
package main
import (
"encoding/json"
"fmt"
"github.com/containernetworking/cni/pkg/invoke"
"github.com/containernetworking/cni/pkg/skel"
"github.com/containernetworking/cni/pkg/types"
"github.com/containernetworking/cni/pkg/types/020"
"github.com/containernetworking/plugins/pkg/hns"
"os"
)
func doCmdAdd(args *skel.CmdArgs, n *NetConf, fenv *subnetEnv) error {
n.Delegate["name"] = n.Name
if !hasKey(n.Delegate, "type") {
n.Delegate["type"] = "win-bridge"
}
// if flannel needs ipmasq - get the plugin to configure it
// (this is the opposite of how linux works - on linux the flannel daemon configure ipmasq)
n.Delegate["ipMasq"] = *fenv.ipmasq
n.Delegate["ipMasqNetwork"] = fenv.nw.String()
n.Delegate["cniVersion"] = types020.ImplementedSpecVersion
if len(n.CNIVersion) != 0 {
n.Delegate["cniVersion"] = n.CNIVersion
}
n.Delegate["ipam"] = map[string]interface{}{
"type": "host-local",
"subnet": fenv.sn.String(),
}
return delegateAdd(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir, n.Delegate)
}
func doCmdDel(args *skel.CmdArgs, n *NetConf) error {
netconfBytes, err := consumeScratchNetConf(hns.GetSandboxContainerID(args.ContainerID, args.Netns), n.DataDir)
if err != nil {
if os.IsNotExist(err) {
// Per spec should ignore error if resources are missing / already removed
return nil
}
return err
}
nc := &types.NetConf{}
if err = json.Unmarshal(netconfBytes, nc); err != nil {
return fmt.Errorf("failed to parse netconf: %v", err)
}
return invoke.DelegateDel(nc.Type, netconfBytes, nil)
}