azure-container-networking/network/endpoint_windows.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
}