Skip to content

Commit

Permalink
Windows Support
Browse files Browse the repository at this point in the history
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
  • Loading branch information
thxCode authored and Peter White committed Oct 10, 2018
1 parent 1892882 commit a6288e6
Show file tree
Hide file tree
Showing 22 changed files with 1,214 additions and 75 deletions.
5 changes: 3 additions & 2 deletions .appveyor.yml
Original file line number Diff line number Diff line change
Expand Up @@ -6,15 +6,16 @@ environment:
install:
- echo %PATH%
- echo %GOPATH%
- set PATH=%GOPATH%\bin;c:\go\bin;%PATH%
- go version
- go env
- 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;
- set PATH=%GOPATH%\bin;c:\go\bin;c:\gcc\bin;%PATH%

build: off

test_script:
- ps: |
go list ./... | Select-String -Pattern (Get-Content "./plugins/linux_only.txt") -NotMatch > "to_test.txt"
go list ./... | Select-String -Pattern (Get-Content "./plugins/windows_only.txt") > "to_test.txt"
echo "Will test:"
Get-Content "to_test.txt"
foreach ($pkg in Get-Content "to_test.txt") {
Expand Down
1 change: 1 addition & 0 deletions .travis.yml
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,7 @@ matrix:
fast_finish: true

install:
- sudo apt-get install gcc-multilib gcc-mingw-w64 -y
- go get github.com/onsi/ginkgo/ginkgo
- go get github.com/containernetworking/cni/cnitool

Expand Down
4 changes: 3 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,9 @@ Read [CONTRIBUTING](CONTRIBUTING.md) for build and test instructions.
* `macvlan`: Creates a new MAC address, forwards all traffic to that to the container.
* `ptp`: Creates a veth pair.
* `vlan`: Allocates a vlan device.

#### Windows: windows specific
* `win-bridge`: Creates a bridge, adds the host and the container to it.
* `win-overlay`: Creates an overlay interface to the container.
### IPAM: IP address allocation
* `dhcp`: Runs a daemon on the host to make DHCP requests on behalf of the container
* `host-local`: Maintains a local database of allocated IPs
Expand Down
7 changes: 2 additions & 5 deletions Vagrantfile
Original file line number Diff line number Diff line change
Expand Up @@ -8,14 +8,11 @@ Vagrant.configure(2) do |config|

config.vm.provision "shell", inline: <<-SHELL
set -e -x -u
apt-get update -y || (sleep 40 && apt-get update -y)
apt-get install -y git
apt-get install -y git gcc-multilib gcc-mingw-w64
wget -qO- https://storage.googleapis.com/golang/go1.10.linux-amd64.tar.gz | tar -C /usr/local -xz
echo 'export GOPATH=/go' >> /root/.bashrc
echo 'export PATH=$PATH:/usr/local/go/bin:$GOPATH/bin' >> /root/.bashrc
cd /go/src/github.com/containernetworking/plugins
SHELL
end
end
14 changes: 11 additions & 3 deletions build.sh
Original file line number Diff line number Diff line change
Expand Up @@ -19,12 +19,20 @@ export GO="${GO:-go}"

mkdir -p "${PWD}/bin"

echo "Building plugins"
echo "Building plugins ${GOOS}"
PLUGINS="plugins/meta/* plugins/main/* plugins/ipam/* plugins/sample"
for d in $PLUGINS; do
if [ -d "$d" ]; then
plugin="$(basename "$d")"
echo " $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
if [ $plugin == "windows" ]
then
if [ "$GOARCH" == "amd64" ]
then
GOOS=windows . $d/build.sh
fi
else
echo " $plugin"
$GO build -o "${PWD}/bin/$plugin" "$@" "$REPO_PATH"/$d
fi
fi
done
152 changes: 152 additions & 0 deletions pkg/hns/endpoint_windows.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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 hns

import (
"fmt"
"net"
"strings"

"github.com/Microsoft/hcsshim"
"github.com/containernetworking/cni/pkg/types/current"
"github.com/juju/errors"
)

const (
pauseContainerNetNS = "none"
)

// GetSandboxContainerID returns the sandbox ID of this pod
func GetSandboxContainerID(containerID string, netNs string) string {
if len(netNs) != 0 && netNs != pauseContainerNetNS {
splits := strings.SplitN(netNs, ":", 2)
if len(splits) == 2 {
containerID = splits[1]
}
}

return containerID
}

// ConstructEndpointName constructs enpointId which is used to identify an endpoint from HNS
// There is a special consideration for netNs name here, which is required for Windows Server 1709
// containerID is the Id of the container on which the endpoint is worked on
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
}

// DeprovisionEndpoint removes an endpoint from the container by sending a Detach request to HNS
// For shared endpoint, ContainerDetach is used
// for removing the endpoint completely, HotDetachEndpoint is used
func DeprovisionEndpoint(epName string, netns string, containerID string) error {
if len(netns) == 0 {
return nil
}

hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
if err != nil {
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
}

if netns != pauseContainerNetNS {
// Shared endpoint removal. Do not remove the endpoint.
hnsEndpoint.ContainerDetach(containerID)
return nil
}

// Do not consider this as failure, else this would leak endpoints
hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)

// Do not return error
hnsEndpoint.Delete()

return nil
}

type EndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)

// ProvisionEndpoint provisions an endpoint to a container specified by containerID.
// If an endpoint already exists, the endpoint is reused.
// This call is idempotent
func ProvisionEndpoint(epName string, expectedNetworkId string, containerID string, makeEndpoint EndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
// check if endpoint already exists
createEndpoint := true
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
if hnsEndpoint != nil && hnsEndpoint.VirtualNetwork == expectedNetworkId {
createEndpoint = false
}

if createEndpoint {
if hnsEndpoint != nil {
if _, err = hnsEndpoint.Delete(); err != nil {
return nil, errors.Annotate(err, "failed to delete the stale HNSEndpoint")
}
}

if hnsEndpoint, err = makeEndpoint(); err != nil {
return nil, errors.Annotate(err, "failed to make a new HNSEndpoint")
}

if hnsEndpoint, err = hnsEndpoint.Create(); err != nil {
return nil, errors.Annotate(err, "failed to create the new HNSEndpoint")
}

}

// hot attach
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
if hcsshim.ErrComputeSystemDoesNotExist == err {
return hnsEndpoint, nil
}

return nil, err
}

return hnsEndpoint, nil
}

// ConstructResult constructs the CNI result for the endpoint
func ConstructResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
resultInterface := &current.Interface{
Name: hnsEndpoint.Name,
Mac: hnsEndpoint.MacAddress,
}
_, ipSubnet, err := net.ParseCIDR(hnsNetwork.Subnets[0].AddressPrefix)
if err != nil {
return nil, errors.Annotatef(err, "failed to parse CIDR from %s", hnsNetwork.Subnets[0].AddressPrefix)
}

var ipVersion string
if ipv4 := hnsEndpoint.IPAddress.To4(); ipv4 != nil {
ipVersion = "4"
} else if ipv6 := hnsEndpoint.IPAddress.To16(); ipv6 != nil {
ipVersion = "6"
} else {
return nil, fmt.Errorf("IPAddress of HNSEndpoint %s isn't a valid ipv4 or ipv6 Address", hnsEndpoint.Name)
}

resultIPConfig := &current.IPConfig{
Version: ipVersion,
Address: net.IPNet{
IP: hnsEndpoint.IPAddress,
Mask: ipSubnet.Mask},
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
}
result := &current.Result{}
result.Interfaces = []*current.Interface{resultInterface}
result.IPs = []*current.IPConfig{resultIPConfig}

return result, nil
}
152 changes: 152 additions & 0 deletions pkg/hns/netconf.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,152 @@
// 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 hns

import (
"encoding/json"
"strings"

"bytes"
"github.com/buger/jsonparser"
"github.com/containernetworking/cni/pkg/types"
)

// NetConf is the CNI spec
type NetConf struct {
types.NetConf

Policies []policy `json:"policies,omitempty"`
}

type policy struct {
Name string `json:"name"`
Value json.RawMessage `json:"value"`
}

// MarshalPolicies converts the Endpoint policies in Policies
// to HNS specific policies as Json raw bytes
func (n *NetConf) MarshalPolicies() []json.RawMessage {
if n.Policies == nil {
n.Policies = make([]policy, 0)
}

result := make([]json.RawMessage, 0, len(n.Policies))
for _, p := range n.Policies {
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}

result = append(result, p.Value)
}

return result
}

// ApplyOutboundNatPolicy applies NAT Policy in VFP using HNS
// Simultaneously an exception is added for the network that has to be Nat'd
func (n *NetConf) ApplyOutboundNatPolicy(nwToNat string) {
if n.Policies == nil {
n.Policies = make([]policy, 0)
}

nwToNatBytes := []byte(nwToNat)

for i, p := range n.Policies {
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}

typeValue, err := jsonparser.GetUnsafeString(p.Value, "Type")
if err != nil || len(typeValue) == 0 {
continue
}

if !strings.EqualFold(typeValue, "OutBoundNAT") {
continue
}

exceptionListValue, dt, _, _ := jsonparser.Get(p.Value, "ExceptionList")
// OutBoundNAT must with ExceptionList, so don't need to judge jsonparser.NotExist
if dt == jsonparser.Array {
buf := bytes.Buffer{}
buf.WriteString(`{"Type": "OutBoundNAT", "ExceptionList": [`)

jsonparser.ArrayEach(exceptionListValue, func(value []byte, dataType jsonparser.ValueType, offset int, err error) {
if dataType == jsonparser.String && len(value) != 0 {
if bytes.Compare(value, nwToNatBytes) != 0 {
buf.WriteByte('"')
buf.Write(value)
buf.WriteByte('"')
buf.WriteByte(',')
}
}
})

buf.WriteString(`"` + nwToNat + `"]}`)

n.Policies[i] = policy{
Name: "EndpointPolicy",
Value: buf.Bytes(),
}
} else {
n.Policies[i] = policy{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
}
}

return
}

// didn't find the policyArg, add it
n.Policies = append(n.Policies, policy{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "OutBoundNAT", "ExceptionList": ["` + nwToNat + `"]}`),
})
}

// ApplyDefaultPAPolicy is used to configure a endpoint PA policy in HNS
func (n *NetConf) ApplyDefaultPAPolicy(paAddress string) {
if n.Policies == nil {
n.Policies = make([]policy, 0)
}

// if its already present, leave untouched
for i, p := range n.Policies {
if !strings.EqualFold(p.Name, "EndpointPolicy") {
continue
}

paValue, dt, _, _ := jsonparser.Get(p.Value, "PA")
if dt == jsonparser.NotExist {
continue
} else if dt == jsonparser.String && len(paValue) != 0 {
// found it, don't override
return
}

n.Policies[i] = policy{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
}
return
}

// didn't find the policyArg, add it
n.Policies = append(n.Policies, policy{
Name: "EndpointPolicy",
Value: []byte(`{"Type": "PA", "PA": "` + paAddress + `"}`),
})
}
Loading

0 comments on commit a6288e6

Please sign in to comment.