Skip to content

Commit a6288e6

Browse files
thxCodePeter White
authored and
Peter White
committed
Windows Support
Patch for containernetworking#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: - containernetworking#85 - rakelkar@0049c64
1 parent 1892882 commit a6288e6

22 files changed

+1214
-75
lines changed

.appveyor.yml

+3-2
Original file line numberDiff line numberDiff line change
@@ -6,15 +6,16 @@ environment:
66
install:
77
- echo %PATH%
88
- echo %GOPATH%
9-
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
109
- go version
1110
- go env
11+
- ps: $webClient = New-Object System.Net.WebClient; $InstallPath="c:" ; $webClient.DownloadFile("https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/gcc.zip", "$InstallPath\gcc.zip"); Expand-Archive $InstallPath\gcc.zip -DestinationPath $InstallPath\gcc -Force; $webClient.DownloadFile("https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/runtime.zip", "$InstallPath\runtime.zip"); Expand-Archive $InstallPath\runtime.zip -DestinationPath $InstallPath\gcc -Force; $webClient.DownloadFile("https://raw.githubusercontent.com/jhowardmsft/docker-tdmgcc/master/binutils.zip","$InstallPath\binutils.zip"); Expand-Archive $InstallPath\binutils.zip -DestinationPath $InstallPath\gcc -Force;
12+
- set PATH=%GOPATH%\bin;c:\go\bin;c:\gcc\bin;%PATH%
1213

1314
build: off
1415

1516
test_script:
1617
- ps: |
17-
go list ./... | Select-String -Pattern (Get-Content "./plugins/linux_only.txt") -NotMatch > "to_test.txt"
18+
go list ./... | Select-String -Pattern (Get-Content "./plugins/windows_only.txt") > "to_test.txt"
1819
echo "Will test:"
1920
Get-Content "to_test.txt"
2021
foreach ($pkg in Get-Content "to_test.txt") {

.travis.yml

+1
Original file line numberDiff line numberDiff line change
@@ -20,6 +20,7 @@ matrix:
2020
fast_finish: true
2121

2222
install:
23+
- sudo apt-get install gcc-multilib gcc-mingw-w64 -y
2324
- go get github.com/onsi/ginkgo/ginkgo
2425
- go get github.com/containernetworking/cni/cnitool
2526

README.md

+3-1
Original file line numberDiff line numberDiff line change
@@ -14,7 +14,9 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
1414
* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container.
1515
* `ptp`: Creates a veth pair.
1616
* `vlan`: Allocates a vlan device.
17-
17+
#### Windows: windows specific
18+
* `win-bridge`: Creates a bridge, adds the host and the container to it.
19+
* `win-overlay`: Creates an overlay interface to the container.
1820
### IPAM: IP address allocation
1921
* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
2022
* `host-local`: Maintains a local database of allocated IPs

Vagrantfile

+2-5
Original file line numberDiff line numberDiff line change
@@ -8,14 +8,11 @@ Vagrant.configure(2) do |config|
88

99
config.vm.provision "shell", inline: <<-SHELL
1010
set -e -x -u
11-
1211
apt-get update -y || (sleep 40 && apt-get update -y)
13-
apt-get install -y git
14-
12+
apt-get install -y git gcc-multilib gcc-mingw-w64
1513
wget -qO- https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -C /usr/local -xz
16-
1714
echo 'export GOPATH=/go' >> /root/.bashrc
1815
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
1916
cd /go/src/github.com/containernetworking/plugins
2017
SHELL
21-
end
18+
end

build.sh

+11-3
Original file line numberDiff line numberDiff line change
@@ -19,12 +19,20 @@ export GO="${GO:-go}"
1919

2020
mkdir -p "${PWD}/bin"
2121

22-
echo "Building plugins"
22+
echo "Building plugins ${GOOS}"
2323
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample"
2424
for d in $PLUGINS; do
2525
if [ -d "$d" ]; then
2626
plugin="$(basename "$d")"
27-
echo " $plugin"
28-
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
27+
if [ $plugin == "windows" ]
28+
then
29+
if [ "$GOARCH" == "amd64" ]
30+
then
31+
GOOS=windows . $d/build.sh
32+
fi
33+
else
34+
echo " $plugin"
35+
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
36+
fi
2937
fi
3038
done

pkg/hns/endpoint_windows.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2017 CNI authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hns
16+
17+
import (
18+
"fmt"
19+
"net"
20+
"strings"
21+
22+
"github.com/Microsoft/hcsshim"
23+
"github.com/containernetworking/cni/pkg/types/current"
24+
"github.com/juju/errors"
25+
)
26+
27+
const (
28+
pauseContainerNetNS = "none"
29+
)
30+
31+
// GetSandboxContainerID returns the sandbox ID of this pod
32+
func GetSandboxContainerID(containerID string, netNs string) string {
33+
if len(netNs) != 0 && netNs != pauseContainerNetNS {
34+
splits := strings.SplitN(netNs, ":", 2)
35+
if len(splits) == 2 {
36+
containerID = splits[1]
37+
}
38+
}
39+
40+
return containerID
41+
}
42+
43+
// ConstructEndpointName constructs enpointId which is used to identify an endpoint from HNS
44+
// There is a special consideration for netNs name here, which is required for Windows Server 1709
45+
// containerID is the Id of the container on which the endpoint is worked on
46+
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
47+
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
48+
}
49+
50+
// DeprovisionEndpoint removes an endpoint from the container by sending a Detach request to HNS
51+
// For shared endpoint, ContainerDetach is used
52+
// for removing the endpoint completely, HotDetachEndpoint is used
53+
func DeprovisionEndpoint(epName string, netns string, containerID string) error {
54+
if len(netns) == 0 {
55+
return nil
56+
}
57+
58+
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
59+
if err != nil {
60+
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
61+
}
62+
63+
if netns != pauseContainerNetNS {
64+
// Shared endpoint removal. Do not remove the endpoint.
65+
hnsEndpoint.ContainerDetach(containerID)
66+
return nil
67+
}
68+
69+
// Do not consider this as failure, else this would leak endpoints
70+
hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
71+
72+
// Do not return error
73+
hnsEndpoint.Delete()
74+
75+
return nil
76+
}
77+
78+
type EndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
79+
80+
// ProvisionEndpoint provisions an endpoint to a container specified by containerID.
81+
// If an endpoint already exists, the endpoint is reused.
82+
// This call is idempotent
83+
func ProvisionEndpoint(epName string, expectedNetworkId string, containerID string, makeEndpoint EndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
84+
// check if endpoint already exists
85+
createEndpoint := true
86+
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
87+
if hnsEndpoint != nil && hnsEndpoint.VirtualNetwork == expectedNetworkId {
88+
createEndpoint = false
89+
}
90+
91+
if createEndpoint {
92+
if hnsEndpoint != nil {
93+
if _, err = hnsEndpoint.Delete(); err != nil {
94+
return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
95+
}
96+
}
97+
98+
if hnsEndpoint, err = makeEndpoint(); err != nil {
99+
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
100+
}
101+
102+
if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
103+
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
104+
}
105+
106+
}
107+
108+
// hot attach
109+
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
110+
if hcsshim.ErrComputeSystemDoesNotExist == err {
111+
return hnsEndpoint, nil
112+
}
113+
114+
return nil, err
115+
}
116+
117+
return hnsEndpoint, nil
118+
}
119+
120+
// ConstructResult constructs the CNI result for the endpoint
121+
func ConstructResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
122+
resultInterface := &current.Interface{
123+
Name: hnsEndpoint.Name,
124+
Mac: hnsEndpoint.MacAddress,
125+
}
126+
_, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix)
127+
if err != nil {
128+
return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
129+
}
130+
131+
var ipVersion string
132+
if ipv4 := hnsEndpoint.IPAddress.To4(); ipv4 != nil {
133+
ipVersion = "4"
134+
} else if ipv6 := hnsEndpoint.IPAddress.To16(); ipv6 != nil {
135+
ipVersion = "6"
136+
} else {
137+
return nil, fmt.Errorf("IPAddress of HNSEndpoint %s isn't a valid ipv4 or ipv6 Address", hnsEndpoint.Name)
138+
}
139+
140+
resultIPConfig := &current.IPConfig{
141+
Version: ipVersion,
142+
Address: net.IPNet{
143+
IP: hnsEndpoint.IPAddress,
144+
Mask: ipSubnet.Mask},
145+
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
146+
}
147+
result := &current.Result{}
148+
result.Interfaces = []*current.Interface{resultInterface}
149+
result.IPs = []*current.IPConfig{resultIPConfig}
150+
151+
return result, nil
152+
}

pkg/hns/netconf.go

+152
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,152 @@
1+
// Copyright 2017 CNI authors
2+
//
3+
// Licensed under the Apache License, Version 2.0 (the "License");
4+
// you may not use this file except in compliance with the License.
5+
// You may obtain a copy of the License at
6+
//
7+
// http://www.apache.org/licenses/LICENSE-2.0
8+
//
9+
// Unless required by applicable law or agreed to in writing, software
10+
// distributed under the License is distributed on an "AS IS" BASIS,
11+
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
12+
// See the License for the specific language governing permissions and
13+
// limitations under the License.
14+
15+
package hns
16+
17+
import (
18+
"encoding/json"
19+
"strings"
20+
21+
"bytes"
22+
"github.com/buger/jsonparser"
23+
"github.com/containernetworking/cni/pkg/types"
24+
)
25+
26+
// NetConf is the CNI spec
27+
type NetConf struct {
28+
types.NetConf
29+
30+
Policies []policy `json:"policies,omitempty"`
31+
}
32+
33+
type policy struct {
34+
Name string `json:"name"`
35+
Value json.RawMessage `json:"value"`
36+
}
37+
38+
// MarshalPolicies converts the Endpoint policies in Policies
39+
// to HNS specific policies as Json raw bytes
40+
func (n *NetConf) MarshalPolicies() []json.RawMessage {
41+
if n.Policies == nil {
42+
n.Policies = make([]policy, 0)
43+
}
44+
45+
result := make([]json.RawMessage, 0, len(n.Policies))
46+
for _, p := range n.Policies {
47+
if !strings.EqualFold(p.Name, "EndpointPolicy") {
48+
continue
49+
}
50+
51+
result = append(result, p.Value)
52+
}
53+
54+
return result
55+
}
56+
57+
// ApplyOutboundNatPolicy applies NAT Policy in VFP using HNS
58+
// Simultaneously an exception is added for the network that has to be Nat'd
59+
func (n *NetConf) ApplyOutboundNatPolicy(nwToNat string) {
60+
if n.Policies == nil {
61+
n.Policies = make([]policy, 0)
62+
}
63+
64+
nwToNatBytes := []byte(nwToNat)
65+
66+
for i, p := range n.Policies {
67+
if !strings.EqualFold(p.Name, "EndpointPolicy") {
68+
continue
69+
}
70+
71+
typeValue, err := jsonparser.GetUnsafeString(p.Value, "Type")
72+
if err != nil || len(typeValue) == 0 {
73+
continue
74+
}
75+
76+
if !strings.EqualFold(typeValue, "OutBoundNAT") {
77+
continue
78+
}
79+
80+
exceptionListValue, dt, _, _ := jsonparser.Get(p.Value, "ExceptionList")
81+
// OutBoundNAT must with ExceptionList, so don't need to judge jsonparser.NotExist
82+
if dt == jsonparser.Array {
83+
buf := bytes.Buffer{}
84+
buf.WriteString(`{"Type": "OutBoundNAT", "ExceptionList": [`)
85+
86+
jsonparser.ArrayEach(exceptionListValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
87+
if dataType == jsonparser.String && len(value) != 0 {
88+
if bytes.Compare(value, nwToNatBytes) != 0 {
89+
buf.WriteByte('"')
90+
buf.Write(value)
91+
buf.WriteByte('"')
92+
buf.WriteByte(',')
93+
}
94+
}
95+
})
96+
97+
buf.WriteString(`"` + nwToNat + `"]}`)
98+
99+
n.Policies[i] = policy{
100+
Name: "EndpointPolicy",
101+
Value: buf.Bytes(),
102+
}
103+
} else {
104+
n.Policies[i] = policy{
105+
Name: "EndpointPolicy",
106+
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
107+
}
108+
}
109+
110+
return
111+
}
112+
113+
// didn't find the policyArg, add it
114+
n.Policies = append(n.Policies, policy{
115+
Name: "EndpointPolicy",
116+
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
117+
})
118+
}
119+
120+
// ApplyDefaultPAPolicy is used to configure a endpoint PA policy in HNS
121+
func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
122+
if n.Policies == nil {
123+
n.Policies = make([]policy, 0)
124+
}
125+
126+
// if its already present, leave untouched
127+
for i, p := range n.Policies {
128+
if !strings.EqualFold(p.Name, "EndpointPolicy") {
129+
continue
130+
}
131+
132+
paValue, dt, _, _ := jsonparser.Get(p.Value, "PA")
133+
if dt == jsonparser.NotExist {
134+
continue
135+
} else if dt == jsonparser.String && len(paValue) != 0 {
136+
// found it, don't override
137+
return
138+
}
139+
140+
n.Policies[i] = policy{
141+
Name: "EndpointPolicy",
142+
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
143+
}
144+
return
145+
}
146+
147+
// didn't find the policyArg, add it
148+
n.Policies = append(n.Policies, policy{
149+
Name: "EndpointPolicy",
150+
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
151+
})
152+
}

0 commit comments

Comments
 (0)