Skip to content

Commit d452216

Browse files
committed
add aws platform interface
Signed-off-by: Sebastian Sch <[email protected]>
1 parent 211ab3f commit d452216

File tree

7 files changed

+550
-2
lines changed

7 files changed

+550
-2
lines changed

pkg/consts/platforms.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -28,4 +28,6 @@ const (
2828
Baremetal PlatformTypes = "Baremetal"
2929
// VirtualOpenStack represents a virtual OpenStack platform
3030
VirtualOpenStack PlatformTypes = "Virtual/Openstack"
31+
// AWS represents an AWS platform
32+
AWS PlatformTypes = "AWS"
3133
)

pkg/platform/aws/aws.go

Lines changed: 194 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,194 @@
1+
package aws
2+
3+
import (
4+
"fmt"
5+
"strings"
6+
7+
"sigs.k8s.io/controller-runtime/pkg/log"
8+
9+
sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
10+
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
11+
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/helper"
12+
plugin "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/plugins"
13+
virtualplugin "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/plugins/virtual"
14+
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
15+
)
16+
17+
const (
18+
// metadataBaseURL is the base URL for the EC2 instance metadata service.
19+
metadataBaseURL = "http://169.254.169.254/latest/meta-data/"
20+
// macsPath is the path to list MAC addresses. Note the trailing slash,
21+
// which is standard for directory-like listings in the metadata service.
22+
macsPath = "network/interfaces/macs/"
23+
// subnetIDSuffix is appended to a specific MAC address path to get its subnet ID.
24+
subnetIDSuffix = "/subnet-id"
25+
)
26+
27+
type Aws struct {
28+
hostHelpers helper.HostHelpersInterface
29+
loadedDevicesInfo sriovnetworkv1.InterfaceExts
30+
}
31+
32+
func New(hostHelpers helper.HostHelpersInterface) (*Aws, error) {
33+
return &Aws{
34+
hostHelpers: hostHelpers,
35+
}, nil
36+
}
37+
38+
func (a *Aws) Init() error {
39+
ns, err := a.hostHelpers.GetCheckPointNodeState()
40+
if err != nil {
41+
return err
42+
}
43+
44+
if ns == nil {
45+
err = a.createDevicesInfo()
46+
return err
47+
}
48+
49+
a.createDevicesInfoFromNodeStatus(ns)
50+
return nil
51+
}
52+
53+
func (a *Aws) Name() string {
54+
return string(consts.AWS)
55+
}
56+
57+
func (a *Aws) GetVendorPlugins(_ *sriovnetworkv1.SriovNetworkNodeState) (plugin.VendorPlugin, []plugin.VendorPlugin, error) {
58+
virtual, err := virtualplugin.NewVirtualPlugin(a.hostHelpers)
59+
return virtual, []plugin.VendorPlugin{}, err
60+
}
61+
62+
// SystemdGetVendorPlugin is not supported on AWS platform.
63+
// Returns ErrOperationNotSupportedByPlatform as AWS does not support systemd.
64+
func (a *Aws) SystemdGetVendorPlugin(_ string) (plugin.VendorPlugin, error) {
65+
return nil, vars.ErrOperationNotSupportedByPlatform
66+
}
67+
68+
// DiscoverSriovDevices discovers VFs on a virtual platform
69+
func (a *Aws) DiscoverSriovDevices() ([]sriovnetworkv1.InterfaceExt, error) {
70+
funcLog := log.Log.WithName("DiscoverSriovDevices")
71+
log.Log.V(2).Info("discovering sriov devices")
72+
73+
// TODO: check if we want to support hot plug here in the future
74+
for idx, iface := range a.loadedDevicesInfo {
75+
hasDriver, driver := a.hostHelpers.HasDriver(iface.PciAddress)
76+
if !hasDriver {
77+
funcLog.Error(nil, "device doesn't have a driver, skipping",
78+
"iface", iface)
79+
continue
80+
}
81+
iface.Driver = driver
82+
83+
if mtu := a.hostHelpers.GetNetdevMTU(iface.PciAddress); mtu > 0 {
84+
iface.Mtu = mtu
85+
}
86+
87+
if name := a.hostHelpers.TryToGetVirtualInterfaceName(iface.PciAddress); name != "" {
88+
iface.Name = name
89+
if macAddr := a.hostHelpers.GetNetDevMac(name); macAddr != "" {
90+
iface.Mac = macAddr
91+
}
92+
iface.LinkSpeed = a.hostHelpers.GetNetDevLinkSpeed(name)
93+
iface.LinkType = a.hostHelpers.GetLinkType(name)
94+
}
95+
if len(iface.VFs) != 1 {
96+
log.Log.Error(nil, "only one vf should exist", "iface", iface)
97+
return nil, fmt.Errorf("unexpected number of vfs found for device %s", iface.Name)
98+
}
99+
iface.VFs[0] = sriovnetworkv1.VirtualFunction{
100+
PciAddress: iface.PciAddress,
101+
Driver: driver,
102+
VfID: 0,
103+
Vendor: iface.Vendor,
104+
DeviceID: iface.DeviceID,
105+
Mtu: iface.Mtu,
106+
Mac: iface.Mac,
107+
}
108+
a.loadedDevicesInfo[idx] = iface
109+
}
110+
return a.loadedDevicesInfo, nil
111+
}
112+
113+
// DiscoverBridges is not supported on AWS platform.
114+
// Returns ErrOperationNotSupportedByPlatform as AWS does not support software bridge management.
115+
func (a *Aws) DiscoverBridges() (sriovnetworkv1.Bridges, error) {
116+
if vars.ManageSoftwareBridges {
117+
return sriovnetworkv1.Bridges{}, vars.ErrOperationNotSupportedByPlatform
118+
}
119+
return sriovnetworkv1.Bridges{}, nil
120+
}
121+
122+
// createDevicesInfo creates the AWS device info map
123+
// This function is used to create the AWS device info map from the metadata server.
124+
func (a *Aws) createDevicesInfo() error {
125+
funcLog := log.Log.WithName("getDataFromMetadataService")
126+
a.loadedDevicesInfo = make(sriovnetworkv1.InterfaceExts, 0)
127+
funcLog.Info("getting aws network info from metadata server")
128+
metaData, err := a.hostHelpers.HTTPGetFetchData(metadataBaseURL + macsPath)
129+
if err != nil {
130+
return fmt.Errorf("error getting aws meta_data from %s: %v", metadataBaseURL+macsPath, err)
131+
}
132+
133+
// If the endpoint returns an empty body (e.g., no MACs available or an issue),
134+
// treat it as no MACs found rather than an error. The caller can then decide how to handle an empty list.
135+
if metaData == "" {
136+
return nil
137+
}
138+
139+
// MAC addresses are returned separated by newlines, each ending with a '/'.
140+
// Example raw response: "0e:1a:95:aa:12:a1/\n0e:92:d3:ee:52:1b/"
141+
macEntries := strings.Split(metaData, "\n")
142+
if len(macEntries) == 0 {
143+
return nil
144+
}
145+
146+
macAddressToSubNetID := map[string]string{}
147+
for _, macEntry := range macEntries {
148+
subnetIDURL := metadataBaseURL + macsPath + macEntry + subnetIDSuffix
149+
subnetIDData, err := a.hostHelpers.HTTPGetFetchData(subnetIDURL)
150+
if err != nil {
151+
return fmt.Errorf("error getting aws subnet_id from %s: %v", subnetIDURL, err)
152+
}
153+
154+
if subnetIDData == "" {
155+
return fmt.Errorf("empty subnet_id from %s: %v", subnetIDURL, err)
156+
}
157+
macAddressToSubNetID[strings.ReplaceAll(macEntry, "/", "")] = subnetIDData
158+
}
159+
160+
pfList, err := a.hostHelpers.DiscoverSriovVirtualDevices()
161+
if err != nil {
162+
return err
163+
}
164+
165+
for _, iface := range pfList {
166+
subnetID, exist := macAddressToSubNetID[iface.Mac]
167+
if !exist {
168+
continue
169+
}
170+
171+
subnetID = strings.TrimPrefix(subnetID, "subnet-")
172+
iface.NetFilter = fmt.Sprintf("aws/NetworkID:%s", subnetID)
173+
iface.TotalVfs = 1
174+
iface.NumVfs = 1
175+
176+
vf := sriovnetworkv1.VirtualFunction{
177+
PciAddress: iface.PciAddress,
178+
Driver: iface.Driver,
179+
VfID: 0,
180+
Vendor: iface.Vendor,
181+
DeviceID: iface.DeviceID,
182+
Mtu: iface.Mtu,
183+
Mac: iface.Mac,
184+
}
185+
iface.VFs = append(iface.VFs, vf)
186+
a.loadedDevicesInfo = append(a.loadedDevicesInfo, iface)
187+
}
188+
funcLog.V(2).Info("loaded devices info from metadata server", "devices", a.loadedDevicesInfo)
189+
return nil
190+
}
191+
192+
func (a *Aws) createDevicesInfoFromNodeStatus(networkState *sriovnetworkv1.SriovNetworkNodeState) {
193+
a.loadedDevicesInfo = networkState.Status.Interfaces
194+
}

pkg/platform/aws/aws_suite_test.go

Lines changed: 132 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,132 @@
1+
package aws_test
2+
3+
import (
4+
"context"
5+
"os"
6+
"path/filepath"
7+
"testing"
8+
"time"
9+
10+
. "github.com/onsi/ginkgo/v2"
11+
. "github.com/onsi/gomega"
12+
13+
netattdefv1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
14+
openshiftconfigv1 "github.com/openshift/api/config/v1"
15+
mcfgv1 "github.com/openshift/api/machineconfiguration/v1"
16+
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
17+
"go.uber.org/zap/zapcore"
18+
corev1 "k8s.io/api/core/v1"
19+
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
20+
"k8s.io/client-go/kubernetes/scheme"
21+
"k8s.io/client-go/rest"
22+
"sigs.k8s.io/controller-runtime/pkg/client"
23+
"sigs.k8s.io/controller-runtime/pkg/envtest"
24+
logf "sigs.k8s.io/controller-runtime/pkg/log"
25+
"sigs.k8s.io/controller-runtime/pkg/log/zap"
26+
27+
sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1"
28+
"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars"
29+
"github.com/k8snetworkplumbingwg/sriov-network-operator/test/util"
30+
)
31+
32+
var (
33+
k8sClient client.Client
34+
testEnv *envtest.Environment
35+
cfg *rest.Config
36+
)
37+
38+
// Define utility constants for object names and testing timeouts/durations and intervals.
39+
const testNamespace = "openshift-sriov-network-operator"
40+
41+
var _ = BeforeSuite(func() {
42+
var err error
43+
44+
logf.SetLogger(zap.New(
45+
zap.WriteTo(GinkgoWriter),
46+
zap.UseDevMode(true),
47+
func(o *zap.Options) {
48+
o.TimeEncoder = zapcore.RFC3339NanoTimeEncoder
49+
}))
50+
51+
// Go to project root directory
52+
err = os.Chdir("../../..")
53+
Expect(err).NotTo(HaveOccurred())
54+
55+
By("bootstrapping test environment")
56+
testEnv = &envtest.Environment{
57+
CRDDirectoryPaths: []string{filepath.Join("config", "crd", "bases"), filepath.Join("test", "util", "crds")},
58+
ErrorIfCRDPathMissing: true,
59+
}
60+
61+
testEnv.ControlPlane.GetAPIServer().Configure().Set("disable-admission-plugins", "MutatingAdmissionWebhook", "ValidatingAdmissionWebhook")
62+
63+
cfg, err = testEnv.Start()
64+
Expect(err).NotTo(HaveOccurred())
65+
Expect(cfg).NotTo(BeNil())
66+
67+
By("registering schemes")
68+
err = sriovnetworkv1.AddToScheme(scheme.Scheme)
69+
Expect(err).NotTo(HaveOccurred())
70+
err = netattdefv1.AddToScheme(scheme.Scheme)
71+
Expect(err).NotTo(HaveOccurred())
72+
err = mcfgv1.AddToScheme(scheme.Scheme)
73+
Expect(err).NotTo(HaveOccurred())
74+
err = openshiftconfigv1.AddToScheme(scheme.Scheme)
75+
Expect(err).NotTo(HaveOccurred())
76+
err = monitoringv1.AddToScheme(scheme.Scheme)
77+
Expect(err).NotTo(HaveOccurred())
78+
79+
vars.Config = cfg
80+
vars.Scheme = scheme.Scheme
81+
vars.Namespace = testNamespace
82+
83+
By("creating K8s client")
84+
k8sClient, err = client.New(cfg, client.Options{Scheme: scheme.Scheme})
85+
Expect(err).NotTo(HaveOccurred())
86+
Expect(k8sClient).NotTo(BeNil())
87+
88+
By("creating default/common k8s objects for tests")
89+
// Create test namespace
90+
ns := &corev1.Namespace{
91+
TypeMeta: metav1.TypeMeta{},
92+
ObjectMeta: metav1.ObjectMeta{
93+
Name: testNamespace,
94+
},
95+
Spec: corev1.NamespaceSpec{},
96+
Status: corev1.NamespaceStatus{},
97+
}
98+
Expect(k8sClient.Create(context.Background(), ns)).Should(Succeed())
99+
100+
sa := &corev1.ServiceAccount{TypeMeta: metav1.TypeMeta{},
101+
ObjectMeta: metav1.ObjectMeta{
102+
Name: "default",
103+
Namespace: testNamespace,
104+
}}
105+
Expect(k8sClient.Create(context.Background(), sa)).Should(Succeed())
106+
107+
// Create openshift Infrastructure
108+
infra := &openshiftconfigv1.Infrastructure{
109+
ObjectMeta: metav1.ObjectMeta{
110+
Name: "cluster",
111+
},
112+
Spec: openshiftconfigv1.InfrastructureSpec{},
113+
Status: openshiftconfigv1.InfrastructureStatus{
114+
ControlPlaneTopology: openshiftconfigv1.HighlyAvailableTopologyMode,
115+
},
116+
}
117+
Expect(k8sClient.Create(context.Background(), infra)).Should(Succeed())
118+
})
119+
120+
var _ = AfterSuite(func() {
121+
By("tearing down the test environment")
122+
if testEnv != nil {
123+
Eventually(func() error {
124+
return testEnv.Stop()
125+
}, util.APITimeout, time.Second).ShouldNot(HaveOccurred())
126+
}
127+
})
128+
129+
func TestBaremetal(t *testing.T) {
130+
RegisterFailHandler(Fail)
131+
RunSpecs(t, "Aws Suite")
132+
}

0 commit comments

Comments
 (0)