749 строки
27 KiB
Go
749 строки
27 KiB
Go
// Copyright 2017 Microsoft. All rights reserved.
|
|
// MIT License
|
|
|
|
package network
|
|
|
|
import (
|
|
"context"
|
|
"fmt"
|
|
"net"
|
|
"strings"
|
|
|
|
"github.com/Azure/azure-container-networking/cns"
|
|
"github.com/Azure/azure-container-networking/netio"
|
|
"github.com/Azure/azure-container-networking/netlink"
|
|
"github.com/Azure/azure-container-networking/network/policy"
|
|
"github.com/Azure/azure-container-networking/platform"
|
|
"github.com/Microsoft/hcsshim"
|
|
"github.com/Microsoft/hcsshim/hcn"
|
|
"github.com/pkg/errors"
|
|
"go.uber.org/zap"
|
|
)
|
|
|
|
const (
|
|
// hcnSchemaVersionMajor indicates major version number for hcn schema
|
|
hcnSchemaVersionMajor = 2
|
|
|
|
// hcnSchemaVersionMinor indicates minor version number for hcn schema
|
|
hcnSchemaVersionMinor = 0
|
|
|
|
// hcnIpamTypeStatic indicates the static type of ipam
|
|
hcnIpamTypeStatic = "Static"
|
|
|
|
// Default gateway Mac
|
|
defaultGwMac = "12-34-56-78-9a-bc"
|
|
|
|
// Container interface name prefix
|
|
containerIfNamePrefix = "vEthernet"
|
|
|
|
// hostNCApipaEndpointName indicates the prefix for the name of the apipa endpoint used for
|
|
// the host container connectivity
|
|
hostNCApipaEndpointNamePrefix = "HostNCApipaEndpoint"
|
|
|
|
// device without error flag 0
|
|
noError = "0"
|
|
// device disabled flag 22
|
|
deviceDisabled = "22"
|
|
)
|
|
|
|
// ConstructEndpointID constructs endpoint name from netNsPath.
|
|
func ConstructEndpointID(containerID string, netNsPath string, ifName string) (string, string) {
|
|
if len(containerID) > 8 {
|
|
containerID = containerID[:8]
|
|
}
|
|
|
|
infraEpName, workloadEpName := "", ""
|
|
|
|
splits := strings.Split(netNsPath, ":")
|
|
if len(splits) == 2 {
|
|
// For workload containers, we extract its linking infrastructure container ID.
|
|
if len(splits[1]) > 8 {
|
|
splits[1] = splits[1][:8]
|
|
}
|
|
infraEpName = splits[1] + "-" + ifName
|
|
workloadEpName = containerID + "-" + ifName
|
|
} else {
|
|
// For infrastructure containers, we use its container ID directly.
|
|
infraEpName = containerID + "-" + ifName
|
|
}
|
|
|
|
return infraEpName, workloadEpName
|
|
}
|
|
|
|
func (nw *network) getEndpointWithVFDevice(plc platform.ExecClient, epInfo *EndpointInfo) (*endpoint, error) {
|
|
logger.Info("disable and dismount VF device")
|
|
|
|
// check device state before disabling and dismounting vf device
|
|
devicePresence, problemCode, err := getPnpDeviceState(epInfo.PnPID, plc)
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get VF device state")
|
|
}
|
|
|
|
// state machine, use devicePresence and problemCode to determine actions
|
|
if devicePresence == "True" && problemCode == noError { //nolint
|
|
logger.Info("Device enabled and mounted")
|
|
|
|
if err := disableVFDevice(epInfo.PnPID, plc); err != nil { //nolint
|
|
return nil, errors.Wrap(err, "failed to disable VF device")
|
|
}
|
|
|
|
if err := dismountVFDevice(epInfo.PnPID, plc); err != nil { //nolint
|
|
return nil, errors.Wrap(err, "failed to dismount VF device")
|
|
}
|
|
|
|
// get new pnp id after VF dismount
|
|
pnpDeviceID, err := getPnPDeviceID(epInfo.PnPID, plc) //nolint
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get updated VF device ID")
|
|
}
|
|
|
|
// assign updated PciID back to containerd
|
|
epInfo.PnPID = pnpDeviceID
|
|
} else if devicePresence == "True" && problemCode == deviceDisabled {
|
|
logger.Info("Device disabled")
|
|
// device is disabled but not dismounted
|
|
if err := dismountVFDevice(epInfo.PnPID, plc); err != nil { //nolint
|
|
return nil, errors.Wrap(err, "failed to dismount VF device")
|
|
}
|
|
|
|
// get new pnp id after VF dismount
|
|
pnpDeviceID, err := getPnPDeviceID(epInfo.PnPID, plc) //nolint
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get updated VF device ID")
|
|
}
|
|
|
|
// assign updated PciID back to containerd
|
|
epInfo.PnPID = pnpDeviceID
|
|
} else if devicePresence == "False" {
|
|
logger.Info("Device dismounted")
|
|
// device is disabled and dismounted, just get the new PciID and assign back to containerd
|
|
pnpDeviceID, err := getPnPDeviceID(epInfo.PnPID, plc) //nolint
|
|
if err != nil {
|
|
return nil, errors.Wrap(err, "failed to get updated VF device ID")
|
|
}
|
|
// assign updated PciID back to containerd
|
|
epInfo.PnPID = pnpDeviceID
|
|
} else {
|
|
// return unexpected error and log devicePresence, problemCode
|
|
return nil, errors.Wrapf(err, "unexpected error with devicePresence %s and problemCode %s", devicePresence, problemCode)
|
|
}
|
|
|
|
// Create the endpoint object.
|
|
ep := &endpoint{
|
|
Id: epInfo.MasterIfName,
|
|
IfName: epInfo.MasterIfName,
|
|
ContainerID: epInfo.ContainerID,
|
|
MacAddress: epInfo.MacAddress,
|
|
NICType: cns.BackendNIC,
|
|
}
|
|
|
|
// do not create endpoint for IB NIC interface
|
|
return ep, nil
|
|
}
|
|
|
|
// newEndpointImpl creates a new endpoint in the network.
|
|
func (nw *network) newEndpointImpl(
|
|
cli apipaClient,
|
|
_ netlink.NetlinkInterface,
|
|
plc platform.ExecClient,
|
|
_ netio.NetIOInterface,
|
|
_ EndpointClient,
|
|
_ NamespaceClientInterface,
|
|
_ ipTablesClient,
|
|
_ dhcpClient,
|
|
epInfo *EndpointInfo,
|
|
) (*endpoint, error) {
|
|
if epInfo.NICType == cns.BackendNIC {
|
|
return nw.getEndpointWithVFDevice(plc, epInfo)
|
|
}
|
|
|
|
if useHnsV2, err := UseHnsV2(epInfo.NetNsPath); useHnsV2 {
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
return nw.newEndpointImplHnsV2(cli, epInfo)
|
|
}
|
|
|
|
return nw.newEndpointImplHnsV1(epInfo, plc)
|
|
}
|
|
|
|
// newEndpointImplHnsV1 creates a new endpoint in the network using HnsV1
|
|
func (nw *network) newEndpointImplHnsV1(epInfo *EndpointInfo, plc platform.ExecClient) (*endpoint, error) {
|
|
var vlanid int
|
|
|
|
if epInfo.Data != nil {
|
|
if _, ok := epInfo.Data[VlanIDKey]; ok {
|
|
vlanid = epInfo.Data[VlanIDKey].(int)
|
|
}
|
|
}
|
|
|
|
// Get Infrastructure containerID. Handle ADD calls for workload container.
|
|
var err error
|
|
infraEpName, _ := ConstructEndpointID(epInfo.ContainerID, epInfo.NetNsPath, epInfo.IfName)
|
|
hnsEndpoint := &hcsshim.HNSEndpoint{
|
|
Name: infraEpName,
|
|
VirtualNetwork: nw.HnsId,
|
|
DNSSuffix: epInfo.EndpointDNS.Suffix,
|
|
DNSServerList: strings.Join(epInfo.EndpointDNS.Servers, ","),
|
|
Policies: policy.SerializePolicies(policy.EndpointPolicy, epInfo.EndpointPolicies, epInfo.Data, epInfo.EnableSnatForDns, epInfo.EnableMultiTenancy),
|
|
}
|
|
|
|
// HNS currently supports one IP address and one IPv6 address per endpoint.
|
|
|
|
for _, ipAddr := range epInfo.IPAddresses {
|
|
if ipAddr.IP.To4() != nil {
|
|
hnsEndpoint.IPAddress = ipAddr.IP
|
|
pl, _ := ipAddr.Mask.Size()
|
|
hnsEndpoint.PrefixLength = uint8(pl)
|
|
} else {
|
|
hnsEndpoint.IPv6Address = ipAddr.IP
|
|
pl, _ := ipAddr.Mask.Size()
|
|
hnsEndpoint.IPv6PrefixLength = uint8(pl)
|
|
if len(nw.Subnets) > 1 {
|
|
hnsEndpoint.GatewayAddressV6 = nw.Subnets[1].Gateway.String()
|
|
}
|
|
}
|
|
}
|
|
|
|
hnsResponse, err := Hnsv1.CreateEndpoint(hnsEndpoint, "")
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
logger.Info("HNSEndpointRequest DELETE id", zap.String("id", hnsResponse.Id))
|
|
hnsResponse, err := Hnsv1.DeleteEndpoint(hnsResponse.Id)
|
|
logger.Error("HNSEndpointRequest DELETE response", zap.Any("hnsResponse", hnsResponse), zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
if epInfo.SkipHotAttachEp {
|
|
logger.Info("Skipping attaching the endpoint to container",
|
|
zap.String("id", hnsResponse.Id), zap.String("id", epInfo.ContainerID))
|
|
} else {
|
|
// Attach the endpoint.
|
|
logger.Info("Attaching endpoint to container", zap.String("id", hnsResponse.Id), zap.String("ContainerID", epInfo.ContainerID))
|
|
err = Hnsv1.HotAttachEndpoint(epInfo.ContainerID, hnsResponse.Id)
|
|
if err != nil {
|
|
logger.Error("Failed to attach endpoint", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
}
|
|
|
|
// add ipv6 neighbor entry for gateway IP to default mac in container
|
|
if err := nw.addIPv6NeighborEntryForGateway(epInfo, plc); err != nil {
|
|
return nil, err
|
|
}
|
|
|
|
// Create the endpoint object.
|
|
ep := &endpoint{
|
|
Id: infraEpName,
|
|
HnsId: hnsResponse.Id,
|
|
SandboxKey: epInfo.ContainerID,
|
|
IfName: epInfo.IfName,
|
|
IPAddresses: epInfo.IPAddresses,
|
|
Gateways: []net.IP{net.ParseIP(hnsResponse.GatewayAddress)},
|
|
DNS: epInfo.EndpointDNS,
|
|
VlanID: vlanid,
|
|
EnableSnatOnHost: epInfo.EnableSnatOnHost,
|
|
NetNs: epInfo.NetNsPath,
|
|
ContainerID: epInfo.ContainerID,
|
|
NICType: epInfo.NICType,
|
|
}
|
|
|
|
for _, route := range epInfo.Routes {
|
|
ep.Routes = append(ep.Routes, route)
|
|
}
|
|
|
|
ep.MacAddress, _ = net.ParseMAC(hnsResponse.MacAddress)
|
|
|
|
epInfo.HNSEndpointID = hnsResponse.Id // we use the ep info hns id later in stateless to clean up in ADD if there is an error
|
|
|
|
return ep, nil
|
|
}
|
|
|
|
func (nw *network) addIPv6NeighborEntryForGateway(epInfo *EndpointInfo, plc platform.ExecClient) error {
|
|
var (
|
|
err error
|
|
out string
|
|
)
|
|
|
|
if epInfo.IPV6Mode == IPV6Nat {
|
|
if len(nw.Subnets) < 2 {
|
|
return fmt.Errorf("Ipv6 subnet not found in network state")
|
|
}
|
|
|
|
// run powershell cmd to set neighbor entry for gw ip to 12-34-56-78-9a-bc
|
|
cmd := fmt.Sprintf("New-NetNeighbor -IPAddress %s -InterfaceAlias \"%s (%s)\" -LinkLayerAddress \"%s\"",
|
|
nw.Subnets[1].Gateway.String(), containerIfNamePrefix, epInfo.EndpointID, defaultGwMac)
|
|
|
|
if out, err = plc.ExecutePowershellCommand(cmd); err != nil {
|
|
logger.Error("Adding ipv6 gw neigh entry failed", zap.Any("out", out), zap.Error(err))
|
|
return err
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// configureHcnEndpoint configures hcn endpoint for creation
|
|
func (nw *network) configureHcnEndpoint(epInfo *EndpointInfo) (*hcn.HostComputeEndpoint, error) {
|
|
infraEpName, _ := ConstructEndpointID(epInfo.ContainerID, epInfo.NetNsPath, epInfo.IfName)
|
|
|
|
hcnEndpoint := &hcn.HostComputeEndpoint{
|
|
Name: infraEpName,
|
|
HostComputeNetwork: nw.HnsId,
|
|
Dns: hcn.Dns{
|
|
Search: strings.Split(epInfo.EndpointDNS.Suffix, ","),
|
|
ServerList: epInfo.EndpointDNS.Servers,
|
|
Options: epInfo.EndpointDNS.Options,
|
|
},
|
|
SchemaVersion: hcn.SchemaVersion{
|
|
Major: hcnSchemaVersionMajor,
|
|
Minor: hcnSchemaVersionMinor,
|
|
},
|
|
}
|
|
|
|
// macAddress type for InfraNIC is like "60:45:bd:12:45:65"
|
|
// if NICType is delegatedVMNIC or AccelnetNIC, convert the macaddress format
|
|
macAddress := epInfo.MacAddress.String()
|
|
if epInfo.NICType == cns.NodeNetworkInterfaceFrontendNIC {
|
|
// convert the format of macAddress that HNS can accept, i.e, "60-45-bd-12-45-65" if NIC type is delegated NIC
|
|
macAddress = strings.Join(strings.Split(macAddress, ":"), "-")
|
|
}
|
|
hcnEndpoint.MacAddress = macAddress
|
|
|
|
if epPolicies, err := policy.GetHcnEndpointPolicies(policy.EndpointPolicy, epInfo.EndpointPolicies, epInfo.Data, epInfo.EnableSnatForDns, epInfo.EnableMultiTenancy, epInfo.NATInfo); err == nil {
|
|
hcnEndpoint.Policies = append(hcnEndpoint.Policies, epPolicies...)
|
|
} else {
|
|
logger.Error("Failed to get endpoint policies due to", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// add hcnEndpoint policy for accelnet for frontendNIC
|
|
if epInfo.NICType == cns.NodeNetworkInterfaceFrontendNIC {
|
|
endpointPolicy, err := policy.AddAccelnetPolicySetting()
|
|
if err != nil {
|
|
logger.Error("Failed to set iov endpoint policy", zap.Error(err))
|
|
return nil, errors.Wrapf(err, "Failed to set iov endpoint policy for endpointId :%s", epInfo.EndpointID)
|
|
}
|
|
hcnEndpoint.Policies = append(hcnEndpoint.Policies, endpointPolicy)
|
|
}
|
|
|
|
for _, route := range epInfo.Routes {
|
|
hcnRoute := hcn.Route{
|
|
NextHop: route.Gw.String(),
|
|
DestinationPrefix: route.Dst.String(),
|
|
}
|
|
|
|
hcnEndpoint.Routes = append(hcnEndpoint.Routes, hcnRoute)
|
|
}
|
|
|
|
for _, ipAddress := range epInfo.IPAddresses {
|
|
prefixLength, _ := ipAddress.Mask.Size()
|
|
ipConfiguration := hcn.IpConfig{
|
|
IpAddress: ipAddress.IP.String(),
|
|
PrefixLength: uint8(prefixLength),
|
|
}
|
|
|
|
hcnEndpoint.IpConfigurations = append(hcnEndpoint.IpConfigurations, ipConfiguration)
|
|
}
|
|
|
|
return hcnEndpoint, nil
|
|
}
|
|
|
|
func (nw *network) deleteHostNCApipaEndpoint(networkContainerID string) error {
|
|
// TODO: this code is duplicated in cns/hnsclient, but that code has logging messages that require a CNSLogger,
|
|
// which makes is hard to use in this package. We should refactor this into a common package with no logging deps
|
|
// so it can be called in both places
|
|
|
|
// HostNCApipaEndpoint name is derived from NC ID
|
|
endpointName := fmt.Sprintf("%s-%s", hostNCApipaEndpointNamePrefix, networkContainerID)
|
|
logger.Info("Deleting HostNCApipaEndpoint for NC", zap.String("endpointName", endpointName), zap.String("networkContainerID", networkContainerID))
|
|
|
|
// Check if the endpoint exists
|
|
endpoint, err := Hnsv2.GetEndpointByName(endpointName)
|
|
if err != nil {
|
|
// If error is anything other than EndpointNotFoundError, return error.
|
|
// else log the error but don't return error because endpoint is already deleted.
|
|
if _, endpointNotFound := err.(hcn.EndpointNotFoundError); !endpointNotFound {
|
|
return fmt.Errorf("deleteEndpointByNameHnsV2 failed due to error with GetEndpointByName: %w", err)
|
|
}
|
|
|
|
logger.Error("Delete called on the Endpoint which doesn't exist. Error:", zap.String("endpointName", endpointName), zap.Error(err))
|
|
return nil
|
|
}
|
|
|
|
if err := Hnsv2.DeleteEndpoint(endpoint); err != nil {
|
|
return fmt.Errorf("failed to delete HostNCApipa endpoint: %+v: %w", endpoint, err)
|
|
}
|
|
|
|
logger.Info("Successfully deleted HostNCApipa endpoint", zap.Any("endpoint", endpoint))
|
|
|
|
return nil
|
|
}
|
|
|
|
// createHostNCApipaEndpoint creates a new endpoint in the HostNCApipaNetwork
|
|
// for host container connectivity
|
|
func (nw *network) createHostNCApipaEndpoint(cli apipaClient, epInfo *EndpointInfo) error {
|
|
var (
|
|
err error
|
|
hostNCApipaEndpointID string
|
|
namespace *hcn.HostComputeNamespace
|
|
)
|
|
|
|
if namespace, err = hcn.GetNamespaceByID(epInfo.NetNsPath); err != nil {
|
|
return fmt.Errorf("Failed to retrieve namespace with GetNamespaceByID for NetNsPath: %s"+
|
|
" due to error: %v", epInfo.NetNsPath, err)
|
|
}
|
|
|
|
logger.Info("Creating HostNCApipaEndpoint for host container connectivity for NC",
|
|
zap.String("NetworkContainerID", epInfo.NetworkContainerID))
|
|
|
|
if hostNCApipaEndpointID, err = cli.CreateHostNCApipaEndpoint(context.TODO(), epInfo.NetworkContainerID); err != nil {
|
|
return err
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
nw.deleteHostNCApipaEndpoint(epInfo.NetworkContainerID)
|
|
}
|
|
}()
|
|
|
|
if err = hcn.AddNamespaceEndpoint(namespace.Id, hostNCApipaEndpointID); err != nil {
|
|
return fmt.Errorf("Failed to add HostNCApipaEndpoint: %s to namespace: %s due to error: %v", hostNCApipaEndpointID, namespace.Id, err) //nolint
|
|
}
|
|
|
|
return nil
|
|
}
|
|
|
|
// newEndpointImplHnsV2 creates a new endpoint in the network using Hnsv2
|
|
func (nw *network) newEndpointImplHnsV2(cli apipaClient, epInfo *EndpointInfo) (*endpoint, error) {
|
|
hcnEndpoint, err := nw.configureHcnEndpoint(epInfo)
|
|
if err != nil {
|
|
logger.Error("Failed to configure hcn endpoint due to", zap.Error(err))
|
|
return nil, err
|
|
}
|
|
|
|
// Create the HCN endpoint.
|
|
logger.Info("Creating hcn endpoint", zap.Any("hcnEndpoint", hcnEndpoint), zap.String("computenetwork", hcnEndpoint.HostComputeNetwork))
|
|
hnsResponse, err := Hnsv2.CreateEndpoint(hcnEndpoint)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to create endpoint: %s due to error: %v", hcnEndpoint.Name, err)
|
|
}
|
|
|
|
logger.Info("Successfully created hcn endpoint with response", zap.Any("hnsResponse", hnsResponse))
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
logger.Info("Deleting hcn endpoint with id", zap.String("id", hnsResponse.Id))
|
|
err = Hnsv2.DeleteEndpoint(hnsResponse)
|
|
logger.Error("Completed hcn endpoint deletion for id with error", zap.String("id", hnsResponse.Id), zap.Error(err))
|
|
}
|
|
}()
|
|
|
|
var namespace *hcn.HostComputeNamespace
|
|
if namespace, err = Hnsv2.GetNamespaceByID(epInfo.NetNsPath); err != nil {
|
|
return nil, fmt.Errorf("Failed to get hcn namespace: %s due to error: %v", epInfo.NetNsPath, err)
|
|
}
|
|
|
|
if err = Hnsv2.AddNamespaceEndpoint(namespace.Id, hnsResponse.Id); err != nil {
|
|
return nil, fmt.Errorf("Failed to add endpoint: %s to hcn namespace: %s due to error: %v", hnsResponse.Id, namespace.Id, err) //nolint
|
|
}
|
|
|
|
defer func() {
|
|
if err != nil {
|
|
if errRemoveNsEp := Hnsv2.RemoveNamespaceEndpoint(namespace.Id, hnsResponse.Id); errRemoveNsEp != nil {
|
|
logger.Error("Failed to remove endpoint from namespace due to error",
|
|
zap.String("id", hnsResponse.Id), zap.String("id", hnsResponse.Id), zap.Error(errRemoveNsEp))
|
|
}
|
|
}
|
|
}()
|
|
|
|
// If the Host - container connectivity is requested, create endpoint in HostNCApipaNetwork
|
|
if epInfo.AllowInboundFromHostToNC || epInfo.AllowInboundFromNCToHost {
|
|
if err = nw.createHostNCApipaEndpoint(cli, epInfo); err != nil {
|
|
return nil, fmt.Errorf("Failed to create HostNCApipaEndpoint due to error: %v", err)
|
|
}
|
|
}
|
|
|
|
var vlanid int
|
|
if epInfo.Data != nil {
|
|
if vlanData, ok := epInfo.Data[VlanIDKey]; ok {
|
|
vlanid = vlanData.(int)
|
|
}
|
|
}
|
|
|
|
var gateway net.IP
|
|
if len(hnsResponse.Routes) > 0 {
|
|
gateway = net.ParseIP(hnsResponse.Routes[0].NextHop)
|
|
}
|
|
|
|
nicName := epInfo.IfName
|
|
// infra nic nicname will look like eth0, but delegated/secondary nics will look like "vEthernet x" where x is 1-7
|
|
if epInfo.NICType != cns.InfraNIC {
|
|
nicName = epInfo.MasterIfName
|
|
}
|
|
|
|
// Create the endpoint object.
|
|
ep := &endpoint{
|
|
Id: hcnEndpoint.Name,
|
|
HnsId: hnsResponse.Id,
|
|
SandboxKey: epInfo.ContainerID,
|
|
IfName: nicName,
|
|
IPAddresses: epInfo.IPAddresses,
|
|
Gateways: []net.IP{gateway},
|
|
DNS: epInfo.EndpointDNS,
|
|
VlanID: vlanid,
|
|
EnableSnatOnHost: epInfo.EnableSnatOnHost,
|
|
NetNs: epInfo.NetNsPath,
|
|
AllowInboundFromNCToHost: epInfo.AllowInboundFromNCToHost,
|
|
AllowInboundFromHostToNC: epInfo.AllowInboundFromHostToNC,
|
|
NetworkContainerID: epInfo.NetworkContainerID,
|
|
ContainerID: epInfo.ContainerID,
|
|
PODName: epInfo.PODName,
|
|
PODNameSpace: epInfo.PODNameSpace,
|
|
HNSNetworkID: epInfo.HNSNetworkID,
|
|
NICType: epInfo.NICType,
|
|
}
|
|
|
|
for _, route := range epInfo.Routes {
|
|
ep.Routes = append(ep.Routes, route)
|
|
}
|
|
|
|
ep.MacAddress, _ = net.ParseMAC(hnsResponse.MacAddress)
|
|
|
|
epInfo.HNSEndpointID = hnsResponse.Id // we use the ep info hns id later in stateless to clean up in ADD if there is an error
|
|
|
|
return ep, nil
|
|
}
|
|
|
|
// deleteEndpointImpl deletes an existing endpoint from the network.
|
|
func (nw *network) deleteEndpointImpl(_ netlink.NetlinkInterface, _ platform.ExecClient, _ EndpointClient, _ netio.NetIOInterface, _ NamespaceClientInterface,
|
|
_ ipTablesClient, _ dhcpClient, ep *endpoint,
|
|
) error {
|
|
// endpoint deletion is not required for IB
|
|
if ep.NICType == cns.BackendNIC {
|
|
return nil
|
|
}
|
|
|
|
if ep.HnsId == "" {
|
|
logger.Error("No HNS id found. Skip endpoint deletion", zap.Any("nicType", ep.NICType), zap.String("containerId", ep.ContainerID))
|
|
return fmt.Errorf("No HNS id found. Skip endpoint deletion for nicType %v, containerID %s", ep.NICType, ep.ContainerID) //nolint
|
|
}
|
|
|
|
if useHnsV2, err := UseHnsV2(ep.NetNs); useHnsV2 {
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
return nw.deleteEndpointImplHnsV2(ep)
|
|
}
|
|
|
|
return nw.deleteEndpointImplHnsV1(ep)
|
|
}
|
|
|
|
// deleteEndpointImplHnsV1 deletes an existing endpoint from the network using HNS v1.
|
|
func (nw *network) deleteEndpointImplHnsV1(ep *endpoint) error {
|
|
logger.Info("HNSEndpointRequest DELETE id", zap.String("id", ep.HnsId))
|
|
hnsResponse, err := Hnsv1.DeleteEndpoint(ep.HnsId)
|
|
logger.Info("HNSEndpointRequest DELETE response err", zap.Any("hnsResponse", hnsResponse), zap.Error(err))
|
|
|
|
// todo: may need to improve error handling if hns or hcsshim change their error bubbling.
|
|
// hcsshim bubbles up a generic error when delete fails with message "The endpoint was not found".
|
|
// the best we can do at the moment is string comparison, which is never great for error checking
|
|
if err != nil {
|
|
if strings.Contains(strings.ToLower(err.Error()), "not found") {
|
|
logger.Info("HNS endpoint id not found", zap.String("id", ep.HnsId))
|
|
return nil
|
|
}
|
|
}
|
|
|
|
return err
|
|
}
|
|
|
|
// deleteEndpointImplHnsV2 deletes an existing endpoint from the network using HNS v2.
|
|
func (nw *network) deleteEndpointImplHnsV2(ep *endpoint) error {
|
|
var (
|
|
hcnEndpoint *hcn.HostComputeEndpoint
|
|
err error
|
|
)
|
|
|
|
if ep.AllowInboundFromHostToNC || ep.AllowInboundFromNCToHost {
|
|
if err = nw.deleteHostNCApipaEndpoint(ep.NetworkContainerID); err != nil {
|
|
logger.Error("Failed to delete HostNCApipaEndpoint due to error", zap.Error(err))
|
|
return err
|
|
}
|
|
}
|
|
|
|
logger.Info("Deleting hcn endpoint with id", zap.String("HnsId", ep.HnsId))
|
|
|
|
hcnEndpoint, err = Hnsv2.GetEndpointByID(ep.HnsId)
|
|
if err != nil {
|
|
// If error is anything other than EndpointNotFoundError, return error.
|
|
// else log the error but don't return error because endpoint is already deleted.
|
|
if _, endpointNotFound := err.(hcn.EndpointNotFoundError); !endpointNotFound {
|
|
return fmt.Errorf("Failed to get hcn endpoint with id: %s due to err: %w", ep.HnsId, err)
|
|
}
|
|
|
|
logger.Error("Delete called on the Endpoint which doesn't exist. Error:", zap.String("HnsId", ep.HnsId), zap.Error(err))
|
|
return nil
|
|
}
|
|
|
|
// Remove this endpoint from the namespace
|
|
if err = Hnsv2.RemoveNamespaceEndpoint(hcnEndpoint.HostComputeNamespace, hcnEndpoint.Id); err != nil {
|
|
logger.Error("Failed to remove hcn endpoint from namespace due to error", zap.String("HnsId", ep.HnsId),
|
|
zap.String("HostComputeNamespace", hcnEndpoint.HostComputeNamespace), zap.Error(err))
|
|
}
|
|
|
|
if err = Hnsv2.DeleteEndpoint(hcnEndpoint); err != nil {
|
|
return fmt.Errorf("Failed to delete hcn endpoint: %s due to error: %v", ep.HnsId, err)
|
|
}
|
|
|
|
logger.Info("Successfully deleted hcn endpoint with id", zap.String("HnsId", ep.HnsId))
|
|
|
|
return nil
|
|
}
|
|
|
|
// getInfoImpl returns information about the endpoint.
|
|
func (ep *endpoint) getInfoImpl(epInfo *EndpointInfo) {
|
|
epInfo.Data["hnsid"] = ep.HnsId
|
|
}
|
|
|
|
// updateEndpointImpl in windows does nothing for now
|
|
func (nm *networkManager) updateEndpointImpl(nw *network, existingEpInfo *EndpointInfo, targetEpInfo *EndpointInfo) (*endpoint, error) {
|
|
return nil, nil
|
|
}
|
|
|
|
// GetEndpointInfoByIPImpl returns an endpointInfo with the corrsponding HNS Endpoint ID that matches an specific IP Address.
|
|
func (epInfo *EndpointInfo) GetEndpointInfoByIPImpl(ipAddresses []net.IPNet, networkID string) (*EndpointInfo, error) {
|
|
logger.Info("Fetching missing HNS endpoint id for endpoints in network with id", zap.String("id", networkID))
|
|
hnsResponse, err := Hnsv2.GetNetworkByName(networkID)
|
|
if err != nil || hnsResponse == nil {
|
|
return epInfo, errors.Wrapf(err, "HNS Network or endpoints not found")
|
|
}
|
|
hcnEndpoints, err := Hnsv2.ListEndpointsOfNetwork(hnsResponse.Id)
|
|
if err != nil {
|
|
return epInfo, errors.Wrapf(err, "failed to fetch HNS endpoints for the given network")
|
|
}
|
|
for i := range hcnEndpoints {
|
|
for _, ipConfiguration := range hcnEndpoints[i].IpConfigurations {
|
|
for _, ipAddress := range ipAddresses {
|
|
if ipConfiguration.IpAddress == ipAddress.IP.String() {
|
|
logger.Info("Successfully found hcn endpoint id for endpoint with ip", zap.String("id", hcnEndpoints[i].Id), zap.String("ip", ipAddress.IP.String()))
|
|
epInfo.HNSEndpointID = hcnEndpoints[i].Id
|
|
return epInfo, nil
|
|
}
|
|
}
|
|
}
|
|
}
|
|
return epInfo, errors.Wrapf(err, "No HNSEndpointID matches the IPAddress: "+ipAddresses[0].IP.String())
|
|
}
|
|
|
|
// Get PnP Device ID
|
|
func getPnPDeviceID(instanceID string, plc platform.ExecClient) (string, error) {
|
|
// get device locationPath
|
|
getLocationPath := fmt.Sprintf("(Get-PnpDeviceProperty -KeyName DEVPKEY_Device_LocationPaths –InstanceId \"%s\").Data[0]", instanceID) //nolint
|
|
locationPath, err := plc.ExecutePowershellCommand(getLocationPath)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to get VF locationPath due to error:%w", err)
|
|
}
|
|
|
|
// get device PnP id by locationPath
|
|
getPnPDeviceID := fmt.Sprintf("(Get-VMHostAssignableDevice | Where-Object LocationPath -eq \"%s\").InstanceID", locationPath) //nolint
|
|
pnpDeviceID, err := plc.ExecutePowershellCommand(getPnPDeviceID)
|
|
if err != nil {
|
|
logger.Error("Failed to get PnP device ID", zap.Error(err))
|
|
return "", fmt.Errorf("Failed to get PnP device ID due to error:%w", err)
|
|
}
|
|
|
|
logger.Info("Get PnP device ID succeeded", zap.String("new device pciID", pnpDeviceID))
|
|
return pnpDeviceID, nil
|
|
}
|
|
|
|
// Disable VF device
|
|
func disableVFDevice(instanceID string, plc platform.ExecClient) error {
|
|
// disable device
|
|
disableVFDevice := fmt.Sprintf("Disable-PnpDevice -InstanceId \"%s\" -confirm:$false", instanceID) //nolint
|
|
_, err := plc.ExecutePowershellCommand(disableVFDevice)
|
|
if err != nil {
|
|
logger.Error("Failed to disable VF device", zap.Error(err))
|
|
return fmt.Errorf("Failed to disable VF device due to error:%w", err)
|
|
}
|
|
|
|
logger.Info("pnp device disable succeeded", zap.String("VF device", instanceID))
|
|
return nil
|
|
}
|
|
|
|
// Dismount VF device
|
|
func dismountVFDevice(instanceID string, plc platform.ExecClient) error {
|
|
locationPath, err := getLocationPath(instanceID, plc)
|
|
if err != nil {
|
|
return err
|
|
}
|
|
|
|
// dismount device
|
|
dismountVFDevice := fmt.Sprintf("Dismount-VMHostAssignableDevice -Force -LocationPath \"%s\" -confirm:$false", locationPath) //nolint
|
|
_, err = plc.ExecutePowershellCommand(dismountVFDevice)
|
|
if err != nil {
|
|
logger.Error("Failed to dismount VF device", zap.Error(err))
|
|
return fmt.Errorf("Failed to disamount VF device due to error:%w", err)
|
|
}
|
|
|
|
logger.Info("PnP device dismount succeeded", zap.String("VF device", instanceID))
|
|
return nil
|
|
}
|
|
|
|
// Get LocationPath
|
|
func getLocationPath(instanceID string, plc platform.ExecClient) (string, error) {
|
|
// get device locationPath
|
|
getLocationPath := fmt.Sprintf("(Get-PnpDeviceProperty -KeyName DEVPKEY_Device_LocationPaths –InstanceId \"%s\").Data[0]", instanceID) //nolint
|
|
locationPath, err := plc.ExecutePowershellCommand(getLocationPath)
|
|
if err != nil {
|
|
logger.Error("Failed to get VF locationPath", zap.Error(err))
|
|
return "", fmt.Errorf("Failed to get VF locationPath due to error:%w", err)
|
|
}
|
|
|
|
logger.Info("Get pnp device locationPath succeeded", zap.String("locationPath", locationPath))
|
|
return locationPath, nil
|
|
}
|
|
|
|
// Get PnP device state; PnP device objects represent the mounted/dismounted IB VFs
|
|
// return devpkeyDeviceIsPresent and devpkeyDeviceProblemCode
|
|
func getPnpDeviceState(instanceID string, plc platform.ExecClient) (string, string, error) { //nolint
|
|
// get if device is present
|
|
getDeviceIsPresent := fmt.Sprintf("(Get-PnpDeviceProperty -InstanceId \"%s\" | Where-Object KeyName -eq DEVPKEY_Device_IsPresent).Data[0]", instanceID) //nolint
|
|
devpkeyDeviceIsPresent, err := plc.ExecutePowershellCommand(getDeviceIsPresent)
|
|
if err != nil {
|
|
logger.Error("Failed to get PnP device devpKeyIsPresent", zap.Error(err))
|
|
return "", "", fmt.Errorf("Failed to get PnP device devpKeyIsPresent due to error:%w", err)
|
|
}
|
|
logger.Info("Get pnp device property succeeded", zap.String("deviceKeyExists", devpkeyDeviceIsPresent))
|
|
|
|
// DEVPKEY_Device_ProblemCode is not there once device is disabled and dismounted, so need to check if DEVPKEY_Device_ProblemCode exists first
|
|
getDeviceProblemCodeExist := fmt.Sprintf("(Get-PnpDeviceProperty -InstanceId \"%s\" | Where-Object KeyName -eq DEVPKEY_Device_ProblemCode)", instanceID) //nolint
|
|
devpkeyDeviceProblemCodeExist, err := plc.ExecutePowershellCommand(getDeviceProblemCodeExist)
|
|
if err != nil {
|
|
logger.Error("problemCode is unknown", zap.Error(err))
|
|
return "", "", fmt.Errorf("problemCode is unknown due to error:%w", err)
|
|
}
|
|
|
|
// only return isPresent flag and empty string as problemCode
|
|
if devpkeyDeviceProblemCodeExist == "" {
|
|
return devpkeyDeviceIsPresent, "", nil
|
|
}
|
|
|
|
// get device problemCode
|
|
getDeviceProblemCode := fmt.Sprintf("(Get-PnpDeviceProperty -InstanceId \"%s\" | Where-Object KeyName -eq DEVPKEY_Device_ProblemCode).Data[0]", instanceID) //nolint
|
|
devpkeyDeviceProblemCode, err := plc.ExecutePowershellCommand(getDeviceProblemCode)
|
|
if err != nil {
|
|
logger.Error("Failed to get PnP device problemCode", zap.Error(err))
|
|
return "", "", fmt.Errorf("Failed to get if PnP device problemCode due to error:%w", err)
|
|
}
|
|
|
|
logger.Info("Retrieved device problem code", zap.String("code", devpkeyDeviceProblemCode))
|
|
return devpkeyDeviceIsPresent, devpkeyDeviceProblemCode, nil
|
|
}
|