
This PR bring V2 API support into win-overlay CNI. With the current V1 API, only docker runtime works for win-overlay. By bringing new changes, we should be able to use containerd as the runtime.Below are the key points regarding this implementation. 1. Clear seperation for V1 & V2 API support 2. New cni.conf sample that works for win-overlay Signed-off-by: selansen <esiva@redhat.com> Signed-off-by: mansikulkarni96 <mankulka@redhat.com>
381 lines
12 KiB
Go
381 lines
12 KiB
Go
// 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/Microsoft/hcsshim/hcn"
|
|
|
|
"github.com/containernetworking/cni/pkg/types"
|
|
current "github.com/containernetworking/cni/pkg/types/100"
|
|
|
|
"github.com/containernetworking/plugins/pkg/errors"
|
|
)
|
|
|
|
const (
|
|
pauseContainerNetNS = "none"
|
|
)
|
|
|
|
type EndpointInfo struct {
|
|
EndpointName string
|
|
DNS types.DNS
|
|
NetworkName string
|
|
NetworkId string
|
|
Gateway net.IP
|
|
IpAddress net.IP
|
|
MacAddress string
|
|
}
|
|
|
|
// 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
|
|
}
|
|
|
|
// GetIpString returns the given IP as a string.
|
|
func GetIpString(ip *net.IP) string {
|
|
if len(*ip) == 0 {
|
|
return ""
|
|
} else {
|
|
return ip.String()
|
|
}
|
|
}
|
|
|
|
// GetDefaultDestinationPrefix returns the default destination prefix according to the given IP type.
|
|
func GetDefaultDestinationPrefix(ip *net.IP) string {
|
|
destinationPrefix := "0.0.0.0/0"
|
|
if ip.To4() == nil {
|
|
destinationPrefix = "::/0"
|
|
}
|
|
return destinationPrefix
|
|
}
|
|
|
|
// ConstructEndpointName constructs endpoint id which is used to identify an endpoint from HNS/HCN.
|
|
func ConstructEndpointName(containerID string, netNs string, networkName string) string {
|
|
return GetSandboxContainerID(containerID, netNs) + "_" + networkName
|
|
}
|
|
|
|
// GenerateHnsEndpoint generates an HNSEndpoint with given info and config.
|
|
func GenerateHnsEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcsshim.HNSEndpoint, error) {
|
|
// run the IPAM plugin and get back the config to apply
|
|
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epInfo.EndpointName)
|
|
if err != nil && !hcsshim.IsNotExist(err) {
|
|
return nil, errors.Annotatef(err, "failed to get HNSEndpoint %s", epInfo.EndpointName)
|
|
}
|
|
|
|
if hnsEndpoint != nil {
|
|
if strings.EqualFold(hnsEndpoint.VirtualNetwork, epInfo.NetworkId) {
|
|
return nil, fmt.Errorf("HNSEndpoint %s is already existed", epInfo.EndpointName)
|
|
}
|
|
// remove endpoint if corrupted
|
|
if _, err = hnsEndpoint.Delete(); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epInfo.EndpointName)
|
|
}
|
|
}
|
|
|
|
if n.LoopbackDSR {
|
|
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
|
|
}
|
|
hnsEndpoint = &hcsshim.HNSEndpoint{
|
|
Name: epInfo.EndpointName,
|
|
VirtualNetwork: epInfo.NetworkId,
|
|
DNSServerList: strings.Join(epInfo.DNS.Nameservers, ","),
|
|
DNSSuffix: strings.Join(epInfo.DNS.Search, ","),
|
|
GatewayAddress: GetIpString(&epInfo.Gateway),
|
|
IPAddress: epInfo.IpAddress,
|
|
Policies: n.GetHNSEndpointPolicies(),
|
|
}
|
|
return hnsEndpoint, nil
|
|
}
|
|
|
|
// RemoveHnsEndpoint detaches the given name endpoint from container specified by containerID,
|
|
// or removes the given name endpoint completely.
|
|
func RemoveHnsEndpoint(epName string, netns string, containerID string) error {
|
|
if len(netns) == 0 {
|
|
return nil
|
|
}
|
|
|
|
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
|
if err != nil {
|
|
if hcsshim.IsNotExist(err) {
|
|
return nil
|
|
}
|
|
return errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
|
}
|
|
|
|
// for shared endpoint, detach it from the container
|
|
if netns != pauseContainerNetNS {
|
|
_ = hnsEndpoint.ContainerDetach(containerID)
|
|
return nil
|
|
}
|
|
|
|
// for removing the endpoint completely, hot detach is used at first
|
|
_ = hcsshim.HotDetachEndpoint(containerID, hnsEndpoint.Id)
|
|
_, _ = hnsEndpoint.Delete()
|
|
return nil
|
|
}
|
|
|
|
type HnsEndpointMakerFunc func() (*hcsshim.HNSEndpoint, error)
|
|
|
|
// AddHnsEndpoint attaches an HNSEndpoint to a container specified by containerID.
|
|
func AddHnsEndpoint(epName string, expectedNetworkId string, containerID string, netns string, makeEndpoint HnsEndpointMakerFunc) (*hcsshim.HNSEndpoint, error) {
|
|
hnsEndpoint, err := hcsshim.GetHNSEndpointByName(epName)
|
|
if err != nil {
|
|
if !hcsshim.IsNotExist(err) {
|
|
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
|
}
|
|
}
|
|
|
|
// for shared endpoint, we expect that the endpoint already exists
|
|
if netns != pauseContainerNetNS {
|
|
if hnsEndpoint == nil {
|
|
return nil, errors.Annotatef(err, "failed to find HNSEndpoint %s", epName)
|
|
}
|
|
}
|
|
|
|
// verify the existing endpoint is corrupted or not
|
|
if hnsEndpoint != nil {
|
|
if !strings.EqualFold(hnsEndpoint.VirtualNetwork, expectedNetworkId) {
|
|
if _, err := hnsEndpoint.Delete(); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete corrupted HNSEndpoint %s", epName)
|
|
}
|
|
hnsEndpoint = nil
|
|
}
|
|
}
|
|
|
|
// create endpoint if not found
|
|
var isNewEndpoint bool
|
|
if hnsEndpoint == nil {
|
|
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")
|
|
}
|
|
isNewEndpoint = true
|
|
}
|
|
|
|
// attach to container
|
|
if err := hcsshim.HotAttachEndpoint(containerID, hnsEndpoint.Id); err != nil {
|
|
if isNewEndpoint {
|
|
if err := RemoveHnsEndpoint(epName, netns, containerID); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to remove the new HNSEndpoint %s after attaching container %s failure", hnsEndpoint.Id, containerID)
|
|
}
|
|
} else if hcsshim.ErrComputeSystemDoesNotExist == err {
|
|
return hnsEndpoint, nil
|
|
}
|
|
return nil, errors.Annotatef(err, "failed to attach container %s to HNSEndpoint %s", containerID, hnsEndpoint.Id)
|
|
}
|
|
return hnsEndpoint, nil
|
|
}
|
|
|
|
// ConstructHnsResult constructs the CNI result for the HNSEndpoint.
|
|
func ConstructHnsResult(hnsNetwork *hcsshim.HNSNetwork, hnsEndpoint *hcsshim.HNSEndpoint) (*current.Result, error) {
|
|
resultInterface := ¤t.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)
|
|
}
|
|
|
|
resultIPConfig := ¤t.IPConfig{
|
|
Address: net.IPNet{
|
|
IP: hnsEndpoint.IPAddress,
|
|
Mask: ipSubnet.Mask},
|
|
Gateway: net.ParseIP(hnsEndpoint.GatewayAddress),
|
|
}
|
|
result := ¤t.Result{
|
|
CNIVersion: current.ImplementedSpecVersion,
|
|
Interfaces: []*current.Interface{resultInterface},
|
|
IPs: []*current.IPConfig{resultIPConfig},
|
|
DNS: types.DNS{
|
|
Search: strings.Split(hnsEndpoint.DNSSuffix, ","),
|
|
Nameservers: strings.Split(hnsEndpoint.DNSServerList, ","),
|
|
},
|
|
}
|
|
|
|
return result, nil
|
|
}
|
|
|
|
// GenerateHcnEndpoint generates a HostComputeEndpoint with given info and config.
|
|
func GenerateHcnEndpoint(epInfo *EndpointInfo, n *NetConf) (*hcn.HostComputeEndpoint, error) {
|
|
// run the IPAM plugin and get back the config to apply
|
|
hcnEndpoint, err := hcn.GetEndpointByName(epInfo.EndpointName)
|
|
if err != nil && !hcn.IsNotFoundError(err) {
|
|
return nil, errors.Annotatef(err, "failed to get HostComputeEndpoint %s", epInfo.EndpointName)
|
|
}
|
|
|
|
// verify the existing endpoint is corrupted or not
|
|
if hcnEndpoint != nil {
|
|
if strings.EqualFold(hcnEndpoint.HostComputeNetwork, epInfo.NetworkId) {
|
|
return nil, fmt.Errorf("HostComputeNetwork %s is already existed", epInfo.EndpointName)
|
|
}
|
|
// remove endpoint if corrupted
|
|
if err := hcnEndpoint.Delete(); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epInfo.EndpointName)
|
|
}
|
|
}
|
|
|
|
if n.LoopbackDSR {
|
|
n.ApplyLoopbackDSRPolicy(&epInfo.IpAddress)
|
|
}
|
|
hcnEndpoint = &hcn.HostComputeEndpoint{
|
|
SchemaVersion: hcn.SchemaVersion{
|
|
Major: 2,
|
|
Minor: 0,
|
|
},
|
|
Name: epInfo.EndpointName,
|
|
MacAddress: epInfo.MacAddress,
|
|
HostComputeNetwork: epInfo.NetworkId,
|
|
Dns: hcn.Dns{
|
|
Domain: epInfo.DNS.Domain,
|
|
Search: epInfo.DNS.Search,
|
|
ServerList: epInfo.DNS.Nameservers,
|
|
Options: epInfo.DNS.Options,
|
|
},
|
|
Routes: []hcn.Route{
|
|
{
|
|
NextHop: GetIpString(&epInfo.Gateway),
|
|
DestinationPrefix: GetDefaultDestinationPrefix(&epInfo.Gateway),
|
|
},
|
|
},
|
|
IpConfigurations: []hcn.IpConfig{
|
|
{
|
|
IpAddress: GetIpString(&epInfo.IpAddress),
|
|
},
|
|
},
|
|
Policies: n.GetHostComputeEndpointPolicies(),
|
|
}
|
|
return hcnEndpoint, nil
|
|
}
|
|
|
|
// RemoveHcnEndpoint removes the given name endpoint from namespace.
|
|
func RemoveHcnEndpoint(epName string) error {
|
|
hcnEndpoint, err := hcn.GetEndpointByName(epName)
|
|
if err != nil {
|
|
if hcn.IsNotFoundError(err) {
|
|
return nil
|
|
}
|
|
return errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
|
|
}
|
|
epNamespace, err := hcn.GetNamespaceByID(hcnEndpoint.HostComputeNamespace)
|
|
if err != nil && !hcn.IsNotFoundError(err) {
|
|
return errors.Annotatef(err, "failed to get HostComputeNamespace %s", epName)
|
|
}
|
|
if epNamespace != nil {
|
|
err = hcn.RemoveNamespaceEndpoint(hcnEndpoint.HostComputeNamespace, hcnEndpoint.Id)
|
|
if err != nil && !hcn.IsNotFoundError(err) {
|
|
return errors.Annotatef(err,"error removing endpoint: %s from namespace", epName)
|
|
}
|
|
}
|
|
|
|
err = hcnEndpoint.Delete()
|
|
if err != nil {
|
|
return errors.Annotatef(err, "failed to remove HostComputeEndpoint %s", epName)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
type HcnEndpointMakerFunc func() (*hcn.HostComputeEndpoint, error)
|
|
|
|
// AddHcnEndpoint attaches a HostComputeEndpoint to the given namespace.
|
|
func AddHcnEndpoint(epName string, expectedNetworkId string, namespace string, makeEndpoint HcnEndpointMakerFunc) (*hcn.HostComputeEndpoint, error) {
|
|
hcnEndpoint, err := hcn.GetEndpointByName(epName)
|
|
if err != nil {
|
|
if !hcn.IsNotFoundError(err) {
|
|
return nil, errors.Annotatef(err, "failed to find HostComputeEndpoint %s", epName)
|
|
}
|
|
}
|
|
|
|
// verify the existing endpoint is corrupted or not
|
|
if hcnEndpoint != nil {
|
|
if !strings.EqualFold(hcnEndpoint.HostComputeNetwork, expectedNetworkId) {
|
|
if err := hcnEndpoint.Delete(); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to delete corrupted HostComputeEndpoint %s", epName)
|
|
}
|
|
hcnEndpoint = nil
|
|
}
|
|
}
|
|
|
|
// create endpoint if not found
|
|
var isNewEndpoint bool
|
|
if hcnEndpoint == nil {
|
|
if hcnEndpoint, err = makeEndpoint(); err != nil {
|
|
return nil, errors.Annotate(err, "failed to make a new HostComputeEndpoint")
|
|
}
|
|
if hcnEndpoint, err = hcnEndpoint.Create(); err != nil {
|
|
return nil, errors.Annotate(err, "failed to create the new HostComputeEndpoint")
|
|
}
|
|
isNewEndpoint = true
|
|
}
|
|
|
|
// add to namespace
|
|
err = hcn.AddNamespaceEndpoint(namespace, hcnEndpoint.Id)
|
|
if err != nil {
|
|
if isNewEndpoint {
|
|
if err := RemoveHcnEndpoint(epName); err != nil {
|
|
return nil, errors.Annotatef(err, "failed to remove the new HostComputeEndpoint %s after adding HostComputeNamespace %s failure", epName, namespace)
|
|
}
|
|
}
|
|
return nil, errors.Annotatef(err, "failed to add HostComputeEndpoint %s to HostComputeNamespace %s", epName, namespace)
|
|
}
|
|
return hcnEndpoint, nil
|
|
}
|
|
|
|
// ConstructHcnResult constructs the CNI result for the HostComputeEndpoint.
|
|
func ConstructHcnResult(hcnNetwork *hcn.HostComputeNetwork, hcnEndpoint *hcn.HostComputeEndpoint) (*current.Result, error) {
|
|
resultInterface := ¤t.Interface{
|
|
Name: hcnEndpoint.Name,
|
|
Mac: hcnEndpoint.MacAddress,
|
|
}
|
|
_, ipSubnet, err := net.ParseCIDR(hcnNetwork.Ipams[0].Subnets[0].IpAddressPrefix)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
ipAddress := net.ParseIP(hcnEndpoint.IpConfigurations[0].IpAddress)
|
|
resultIPConfig := ¤t.IPConfig{
|
|
Address: net.IPNet{
|
|
IP: ipAddress,
|
|
Mask: ipSubnet.Mask},
|
|
Gateway: net.ParseIP(hcnEndpoint.Routes[0].NextHop),
|
|
}
|
|
result := ¤t.Result{
|
|
CNIVersion: current.ImplementedSpecVersion,
|
|
Interfaces: []*current.Interface{resultInterface},
|
|
IPs: []*current.IPConfig{resultIPConfig},
|
|
DNS: types.DNS{
|
|
Search: hcnEndpoint.Dns.Search,
|
|
Nameservers: hcnEndpoint.Dns.ServerList,
|
|
Options: hcnEndpoint.Dns.Options,
|
|
Domain: hcnEndpoint.Dns.Domain,
|
|
},
|
|
}
|
|
|
|
return result, nil
|
|
}
|