Skip to content

Commit c7d1ef9

Browse files
committed
Design proposal for the platform-orchestrator-abstraction
Signed-off-by: Sebastian Sch <[email protected]>
1 parent 4c5f5af commit c7d1ef9

File tree

1 file changed

+373
-0
lines changed

1 file changed

+373
-0
lines changed
Lines changed: 373 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,373 @@
1+
---
2+
title: Platform and Orchestrator Abstraction
3+
authors:
4+
- sriov-network-operator team
5+
reviewers:
6+
- TBD
7+
creation-date: 21-07-2025
8+
last-updated: 16-11-2025
9+
related-prs:
10+
- https://github.com/k8snetworkplumbingwg/sriov-network-operator/pull/902
11+
- https://github.com/k8snetworkplumbingwg/sriov-network-operator/pull/899
12+
---
13+
14+
# Platform and Orchestrator Abstraction
15+
16+
## Summary
17+
18+
This design document describes the introduction of platform and orchestrator abstraction layers in the SR-IOV Network Operator. These abstractions separate platform-specific (infrastructure provider) logic from orchestrator-specific (Kubernetes distribution) logic, making it easier to add support for new infrastructure platforms and Kubernetes distributions.
19+
20+
## Motivation
21+
22+
The SR-IOV Network Operator has historically been tightly coupled to specific infrastructure platforms and Kubernetes distributions, particularly OpenShift. As the operator expanded to support different virtualization platforms like OpenStack, AWS and various Kubernetes distributions, the need for a clean abstraction layer became apparent.
23+
24+
### Use Cases
25+
26+
1. **Multi-Platform Support**: Enable the operator to run efficiently on different infrastructure platforms (bare metal, OpenStack, AWS, Oracle, etc.) with platform-specific optimizations
27+
2. **Multi-Orchestrator Support**: Support different Kubernetes distributions (vanilla Kubernetes, OpenShift, etc.) with orchestrator-specific behaviors
28+
29+
### Goals
30+
31+
* Create a clean abstraction layer that separates platform-specific logic from orchestrator-specific logic
32+
* Re-implement existing support for bare metal and OpenStack platforms using the new abstraction layer
33+
* Re-implement existing support for Kubernetes and OpenShift orchestrators using the new abstraction layer
34+
* Provide a plugin architecture that makes it easy to add new platforms and orchestrators
35+
* Maintain backward compatibility with existing functionality
36+
* Enable better testability through interface-based design
37+
38+
### Non-Goals
39+
40+
* Support all possible infrastructure platforms in the initial implementation
41+
* Change existing SR-IOV CRD API structures or user-facing configuration interfaces
42+
43+
## Proposal
44+
45+
### Workflow Description
46+
47+
1. **Daemon Startup**: The SR-IOV daemon detects the platform type by examining the node's provider ID and environment variables
48+
2. **Platform Initialization**: The appropriate platform implementation is instantiated using the factory pattern and initialized
49+
3. **Orchestrator Detection**: The orchestrator type is detected based on cluster APIs and characteristics
50+
4. **Device Discovery**: The platform interface discovers available SR-IOV devices using platform-specific methods
51+
5. **Plugin Selection**: The platform selects appropriate vendor plugins based on discovered devices and platform constraints
52+
6. **Configuration Application**: When SR-IOV configurations change, the daemon uses the platform interface to apply changes through the selected plugins
53+
7. **Node Management**: During node operations, the orchestrator interface handles any distribution-specific logic like cordon/uncordon coordination
54+
55+
*NOTE:* The platform is detected at startup based on node metadata and environment variables, while the orchestrator is detected based on cluster characteristics and available APIs.
56+
57+
```mermaid
58+
flowchart TD
59+
A[Daemon Startup] --> B[Platform Detection]
60+
B --> C{Platform Type?}
61+
C -->|Bare Metal| D[Bare Metal Platform]
62+
C -->|OpenStack| E[OpenStack Platform]
63+
C -->|Other| F[Other Platform]
64+
65+
D --> G[Platform Initialization]
66+
E --> G
67+
F --> G
68+
69+
G --> H[Orchestrator Detection]
70+
H --> I{Orchestrator Type?}
71+
I -->|OpenShift| J[OpenShift Orchestrator]
72+
I -->|Kubernetes| K[Kubernetes Orchestrator]
73+
74+
J --> L[Device Discovery]
75+
K --> L
76+
77+
L --> M[Plugin Selection]
78+
M --> N[Ready for Configuration]
79+
80+
N --> O[SR-IOV Config Change?]
81+
O -->|Yes| P[Apply Configuration via Platform]
82+
O -->|No| Q[Node Operation?]
83+
84+
P --> R[Update Device State]
85+
R --> O
86+
87+
Q -->|Yes| S[Orchestrator Cordon/Uncordon Logic]
88+
Q -->|No| O
89+
S --> T[Platform Plugin Operations]
90+
T --> O
91+
92+
style A fill:#e1f5fe
93+
style N fill:#c8e6c9
94+
style P fill:#fff3e0
95+
style S fill:#fce4ec
96+
```
97+
98+
### API Extensions
99+
100+
#### Platform Interface
101+
102+
```golang
103+
type Interface interface {
104+
// Init initializes the platform-specific configuration.
105+
// This is called once during daemon startup after the platform is created.
106+
// Returns an error if initialization fails.
107+
Init() error
108+
109+
// Name returns the name of the platform implementation (e.g., "Baremetal", "OpenStack").
110+
// This is used for logging and identification purposes.
111+
Name() string
112+
113+
// DiscoverSriovDevices discovers all SR-IOV capable devices on the host.
114+
// This is called during status updates to get the current state of SR-IOV devices.
115+
// Returns a list of discovered interfaces with their SR-IOV capabilities, or an error if discovery fails.
116+
DiscoverSriovDevices() ([]sriovnetworkv1.InterfaceExt, error)
117+
118+
// DiscoverBridges discovers software bridges managed by the operator.
119+
// This is called during status updates when vars.ManageSoftwareBridges is enabled.
120+
// Returns a list of discovered bridges, or an error if discovery fails.
121+
// If the platform does not support bridge discovery, it should return an empty list and error of type ErrOperationNotSupportedByPlatform.
122+
DiscoverBridges() (sriovnetworkv1.Bridges, error)
123+
124+
// GetVendorPlugins returns the plugins to use for this platform.
125+
// The first return value is the main plugin that will be applied last (e.g., generic plugin).
126+
// The second return value is a list of additional plugins to run (e.g., vendor-specific plugins).
127+
// This is called during daemon initialization to load the appropriate plugins for the platform.
128+
// Returns the main plugin, additional plugins, and an error if plugin loading fails.
129+
GetVendorPlugins(ns *sriovnetworkv1.SriovNetworkNodeState) (plugin.VendorPlugin, []plugin.VendorPlugin, error)
130+
131+
// SystemdGetVendorPlugin returns the appropriate plugin for the given systemd configuration phase.
132+
// This is used when the daemon runs in systemd mode.
133+
// phase can be consts.PhasePre (before reboot) or consts.PhasePost (after reboot).
134+
// Returns the plugin to use for the specified phase, or an error if the phase is invalid.
135+
SystemdGetVendorPlugin(phase string) (plugin.VendorPlugin, error)
136+
}
137+
```
138+
139+
#### Orchestrator Interface
140+
141+
```golang
142+
type Interface interface {
143+
// Name returns the name of the orchestrator implementation (e.g., "Kubernetes", "OpenShift").
144+
// This is used for logging and identification purposes.
145+
Name() string
146+
147+
// ClusterType returns the type of cluster orchestrator (OpenShift or Kubernetes).
148+
// This is used to determine cluster-specific behavior throughout the operator.
149+
ClusterType() consts.ClusterType
150+
151+
// Flavor returns the specific flavor of the cluster orchestrator.
152+
// Most implementations return ClusterFlavorDefault for standard clusters.
153+
// For OpenShift, this can be ClusterFlavorDefault (standard) or ClusterFlavorHypershift.
154+
// If the implementation does not have a specific flavor, ClusterFlavorDefault should be returned.
155+
Flavor() consts.ClusterFlavor
156+
157+
// BeforeDrainNode performs orchestrator-specific logic before draining a node.
158+
// This is called by the drain controller before starting the node drain process.
159+
// For OpenShift, this may pause the MachineConfigPool to prevent automatic reboots.
160+
// Returns:
161+
// - bool: true if the drain can proceed, false if the orchestrator needs more time to prepare
162+
// - error: an error if the preparation failed
163+
BeforeDrainNode(context.Context, *corev1.Node) (bool, error)
164+
165+
// AfterCompleteDrainNode performs orchestrator-specific logic after node draining is completed.
166+
// This is called by the drain controller after the node has been successfully drained and uncordoned.
167+
// For OpenShift, this may unpause the MachineConfigPool if this was the last node being drained.
168+
// Returns:
169+
// - bool: true if the post-drain operations completed successfully, false if more time is needed
170+
// - error: an error if the post-drain operations failed
171+
AfterCompleteDrainNode(context.Context, *corev1.Node) (bool, error)
172+
}
173+
```
174+
175+
### Implementation Details/Notes/Constraints
176+
177+
#### Platform Implementations
178+
179+
1. **Bare Metal Platform (`pkg/platform/baremetal/`)**:
180+
- Uses standard SR-IOV device discovery via PCI scanning
181+
- Supports vendor-specific plugins (Intel, Mellanox/NVIDIA)
182+
- Handles bridge discovery and management
183+
- Supports both daemon and systemd configuration modes
184+
- Discovers actual physical SR-IOV devices with PF/VF relationships
185+
186+
2. **OpenStack Platform (`pkg/platform/openstack/`)**:
187+
- Uses virtual device discovery based on OpenStack metadata
188+
- Reads device information from config-drive or metadata service (`http://169.254.169.254/openstack/latest/`)
189+
- Uses virtual plugin for VF configuration
190+
- Does not support systemd mode or bridge management
191+
- Treats each VF as a standalone device with a single VF entry
192+
193+
3. **AWS Platform (`pkg/platform/aws/`)** ([PR #899](https://github.com/k8snetworkplumbingwg/sriov-network-operator/pull/899)):
194+
- Uses virtual device discovery based on AWS EC2 metadata service
195+
- Reads MAC addresses and subnet information from metadata service (`http://169.254.169.254/latest/meta-data/`)
196+
- Uses virtual plugin for VF configuration
197+
- Does not support systemd mode or bridge management
198+
- Treats each Elastic Network Adapter VF as a standalone device
199+
- Sets NetFilter field with AWS subnet ID for network isolation (format: `aws/NetworkID:<subnet-id>`)
200+
201+
#### Orchestrator Implementations
202+
203+
1. **Kubernetes Orchestrator (`pkg/orchestrator/kubernetes/`)**:
204+
- Simple implementation with minimal cluster-specific logic
205+
- No special drain/uncordon handling (returns true for all drain operations)
206+
- Always returns ClusterFlavorDefault
207+
- Suitable for vanilla Kubernetes clusters
208+
209+
2. **OpenShift Orchestrator (`pkg/orchestrator/openshift/`)**:
210+
- Complex drain handling with Machine Config Pool (MCP) management
211+
- Supports both regular OpenShift (ClusterFlavorDefault) and Hypershift (ClusterFlavorHypershift) flavors
212+
- Flavor detected by examining the Infrastructure resource (controlPlaneTopology field)
213+
- Manages MCP pausing during node drain operations to prevent automatic node reboots
214+
- Uses mutex to safely coordinate MCP pause/unpause operations across multiple nodes
215+
216+
#### Platform Detection
217+
218+
Platform detection occurs in the daemon startup code based on the node's provider ID. The daemon examines the `Spec.ProviderID` field of the node object and matches it against known platform identifiers.
219+
220+
The platform mapping is defined in `pkg/vars/vars.go`:
221+
222+
```golang
223+
// PlatformsMap contains supported platforms for virtual VF
224+
PlatformsMap = map[string]consts.PlatformTypes{
225+
"openstack": consts.VirtualOpenStack,
226+
"aws": consts.AWS, // Added in PR #899
227+
}
228+
```
229+
230+
Detection logic:
231+
```golang
232+
// Platform detection logic
233+
for key, pType := range vars.PlatformsMap {
234+
if strings.Contains(strings.ToLower(nodeInfo.Spec.ProviderID), strings.ToLower(key)) {
235+
vars.PlatformType = pType
236+
}
237+
}
238+
```
239+
240+
Examples of provider IDs:
241+
- AWS: `aws:///us-east-1a/i-0123456789abcdef0`
242+
- OpenStack: `openstack:///instance-uuid`
243+
- Bare Metal: No provider ID or unmatched provider ID (defaults to `consts.Baremetal`)
244+
245+
#### Factory Pattern
246+
247+
Both platform and orchestrator use factory patterns for instantiation, facilitating easy extensions for new implementations:
248+
249+
```golang
250+
// Platform factory (pkg/platform/platform.go)
251+
func New(platformType consts.PlatformTypes, hostHelpers helper.HostHelpersInterface) (Interface, error) {
252+
switch platformType {
253+
case consts.Baremetal:
254+
return baremetal.New(hostHelpers)
255+
case consts.VirtualOpenStack:
256+
return openstack.New(hostHelpers)
257+
case consts.AWS:
258+
return aws.New(hostHelpers) // Added in PR #899
259+
default:
260+
return nil, fmt.Errorf("unknown platform type %s", platformType)
261+
}
262+
}
263+
264+
// Orchestrator factory (pkg/orchestrator/orchestrator.go)
265+
func New(clusterType consts.ClusterType) (Interface, error) {
266+
switch clusterType {
267+
case consts.ClusterTypeOpenshift:
268+
return openshift.New()
269+
case consts.ClusterTypeKubernetes:
270+
return kubernetes.New()
271+
default:
272+
return nil, fmt.Errorf("unknown cluster type: %s", clusterType)
273+
}
274+
}
275+
```
276+
277+
### Upgrade & Downgrade considerations
278+
279+
Existing configurations and behaviors are preserved, with the abstraction layer providing the same functionality through the new interface structure.
280+
281+
No user-facing API changes are required, and existing SR-IOV configurations will continue to work without modification.
282+
283+
## Benefits for Adding New Platforms
284+
285+
### 1. Clear Separation of Concerns
286+
287+
The abstraction separates infrastructure-specific logic (platform) from Kubernetes distribution-specific logic (orchestrator), making it easier to reason about and implement support for new platforms.
288+
289+
### 2. Standardized Interface
290+
291+
New platforms only need to implement the well-defined `Platform Interface`, which includes:
292+
- Device discovery methods
293+
- Plugin selection logic
294+
- Platform-specific initialization
295+
296+
### 3. Minimal Core Changes
297+
298+
Adding a new platform requires:
299+
1. Creating a new package under `pkg/platform/<platform-name>/`
300+
2. Implementing the `Platform Interface`
301+
3. Adding the platform to the factory function
302+
4. Adding platform detection logic
303+
304+
No changes to core operator logic, existing platforms, or user-facing APIs are required.
305+
306+
### 4. Plugin Architecture
307+
308+
The platform interface includes plugin selection methods, allowing each platform to:
309+
- Choose appropriate vendor plugins
310+
- Use platform-specific plugins (like the virtual plugin for OpenStack)
311+
- Support different configuration modes (daemon vs systemd)
312+
313+
### 5. Independent Development and Testing
314+
315+
Each platform implementation is self-contained, enabling:
316+
- Independent development of platform support
317+
- Platform-specific unit tests
318+
- Mock-based testing of platform interactions
319+
- Easier debugging and maintenance
320+
321+
### Example: Adding a New Platform
322+
323+
Adding support for a new platform follows a standardized process demonstrated by the AWS implementation:
324+
325+
1. **Create Platform Package**: Create a new package under `pkg/platform/<platform-name>/` that implements the Platform Interface
326+
- Example: `pkg/platform/aws/aws.go`
327+
328+
2. **Platform Detection**: Add platform detection logic to `pkg/vars/vars.go` PlatformsMap
329+
- Example: `"aws": consts.AWS`
330+
331+
3. **Factory Registration**: Register the new platform in `pkg/platform/platform.go` factory function
332+
- Example: Add case statement for `consts.AWS`
333+
334+
4. **Implement Platform Interface**:
335+
- `Init()`: Load device info from metadata service or checkpoint
336+
- `Name()`: Return platform name
337+
- `DiscoverSriovDevices()`: Implement platform-specific device discovery
338+
- `DiscoverBridges()`: Return error if not supported
339+
- `GetVendorPlugins()`: Return appropriate plugins (typically virtual plugin for cloud)
340+
- `SystemdGetVendorPlugin()`: Return error if not supported
341+
342+
5. **Add Platform Constants**: Update `pkg/consts/platforms.go` with new platform type
343+
- Example: `AWS PlatformTypes = "AWS"`
344+
345+
6. **Testing**: Add comprehensive unit tests including:
346+
- Initialization tests (with and without checkpoint)
347+
- Device discovery tests
348+
- Metadata service interaction tests (using mocks)
349+
- Error handling tests
350+
- Plugin selection tests
351+
352+
7. **Helper Extensions**: Add any needed helper functions for platform-specific operations
353+
- Example: `DiscoverSriovVirtualDevices()` for virtual VF discovery
354+
355+
This standardized approach ensures that new platforms integrate seamlessly with the existing operator architecture without requiring changes to core logic or other platform implementations.
356+
357+
## Implementation Status
358+
359+
### Merged (PR #902)
360+
361+
The base platform and orchestrator abstraction has been implemented and merged:
362+
363+
- ✅ Platform Interface definition (`pkg/platform/platform.go`)
364+
- ✅ Orchestrator Interface definition (`pkg/orchestrator/orchestrator.go`)
365+
- ✅ Bare Metal Platform implementation (`pkg/platform/baremetal/`)
366+
- ✅ OpenStack Platform implementation (`pkg/platform/openstack/`)
367+
- ✅ Kubernetes Orchestrator implementation (`pkg/orchestrator/kubernetes/`)
368+
- ✅ OpenShift Orchestrator implementation (`pkg/orchestrator/openshift/`)
369+
- ✅ Factory patterns for both abstractions
370+
- ✅ Platform detection logic
371+
- ✅ Comprehensive unit tests
372+
- ✅ Mock interfaces for testing
373+
- ✅ Integration with daemon and operator controllers

0 commit comments

Comments
 (0)