Skip to content

Commit 3a70e9d

Browse files
Merge pull request #173 from lwan-wanglin/merge-upstream-v1x-to-ocp-v1x
Merge upstream v1x to ocp v1x
2 parents 78e7426 + 59eacf1 commit 3a70e9d

550 files changed

Lines changed: 51450 additions & 49928 deletions

File tree

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

CLAUDE.md

Lines changed: 4 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -160,8 +160,9 @@ Only one mode can be active at a time. Each mode has its own leader election ID.
160160
1. Webhook adds scheduling gate to pod, preventing scheduling
161161
2. PodReconciler watches gated pods
162162
3. Inspects container images to determine supported architectures
163-
4. Adds nodeAffinity requirement for kubernetes.io/arch
164-
5. Removes scheduling gate, allowing scheduler to place pod
163+
4. If image inspection fails and `fallbackArchitecture` is configured, uses the fallback architecture
164+
5. Adds nodeAffinity requirement for kubernetes.io/arch
165+
6. Removes scheduling gate, allowing scheduler to place pod
165166

166167
**Image Inspection** (pkg/image/):
167168
- Supports multi-arch manifests (OCI, Docker v2.2)
@@ -237,6 +238,7 @@ The operator binary runs in four mutually exclusive modes (controlled by flags i
237238
- `logVerbosity`: Normal, Debug, Trace, TraceAll
238239
- `namespaceSelector`: Label selector for processed namespaces
239240
- `plugins`: Optional plugins configuration (e.g., NodeAffinityScoring for preferred scheduling)
241+
- `fallbackArchitecture`: Architecture to use when image inspector cannot determine image architecture (arm64, amd64, ppc64le, s390x, or empty string)
240242
- Status conditions: Available, Progressing, Degraded, Deprovisioning, PodPlacementControllerNotRolledOut, PodPlacementWebhookNotRolledOut, MutatingWebhookConfigurationNotAvailable
241243

242244

Dockerfile

Lines changed: 9 additions & 7 deletions
Original file line numberDiff line numberDiff line change
@@ -9,8 +9,10 @@ ARG TARGETARCH
99
# This only affects the builder stage (used during compilation) and does not impact the
1010
# security of the final runtime image, which runs as USER 65532:65532 (non-root).
1111
USER 0
12-
RUN if which apt-get; then apt-get update && apt-get install -y libgpgme-dev && apt-get -y clean autoclean; \
13-
elif which dnf; then dnf install -y gpgme-devel && dnf clean all -y; fi;
12+
RUN if ! pkg-config --exists gpgme 2>/dev/null; then \
13+
if which apt-get; then apt-get update && apt-get install -y libgpgme-dev && apt-get -y clean autoclean; \
14+
elif which dnf; then dnf install -y gpgme-devel && dnf clean all -y; fi; \
15+
fi
1416

1517
WORKDIR /workspace
1618
# Copy the Go Modules manifests
@@ -31,8 +33,8 @@ COPY pkg/ pkg/
3133
# was called. For example, if we call make docker-build in a local env which has the Apple Silicon M1 SO
3234
# the docker BUILDPLATFORM arg will be linux/arm64 when for Apple x86 it will be linux/amd64. Therefore,
3335
# by leaving it empty we can ensure that the container and binary shipped on it will have the same platform.
34-
RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
35-
RUN CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o enoexec-daemon cmd/enoexec-daemon/main.go
36+
RUN GOGC=75 CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o manager cmd/main.go
37+
RUN GOGC=75 CGO_ENABLED=1 GOOS=${TARGETOS:-linux} GOARCH=${TARGETARCH} go build -a -o enoexec-daemon cmd/enoexec-daemon/main.go
3638

3739

3840
# Use UBI minimal as base image to package the manager binary
@@ -45,9 +47,9 @@ USER 65532:65532
4547
LABEL com.redhat.component="Multiarch Tuning Operator"
4648
LABEL distribution-scope="public"
4749
LABEL name="multiarch-tuning/multiarch-tuning-operator"
48-
LABEL release="1.2.2"
49-
LABEL version="1.2.2"
50-
LABEL cpe="cpe:/a:redhat:multiarch_tuning_operator:1.2::el9"
50+
LABEL release="1.3.0"
51+
LABEL version="1.3.0"
52+
LABEL cpe="cpe:/a:redhat:multiarch_tuning_operator:1.3::el9"
5153
LABEL url="https://github.com/openshift/multiarch-tuning-operator"
5254
LABEL vendor="Red Hat, Inc."
5355
LABEL description="The Multiarch Tuning Operator enhances the user experience for administrators of Openshift \

Makefile

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -9,7 +9,7 @@ ARTIFACT_DIR ?= ./_output
99
# To re-generate a bundle for another specific version without changing the standard setup, you can:
1010
# - use the VERSION as arg of the bundle target (e.g make bundle VERSION=0.0.2)
1111
# - use environment variables to overwrite this value (e.g export VERSION=0.0.2)
12-
VERSION ?= 1.2.2
12+
VERSION ?= 1.3.0
1313

1414
# CHANNELS define the bundle channels used in the bundle.
1515
# Add a new line here if you would like to change its default config. (E.g CHANNELS = "candidate,fast,stable")
@@ -71,15 +71,15 @@ IMG ?= registry.ci.openshift.org/origin/multiarch-tuning-operator:v1.x
7171
#### Tool Versions ####
7272
### TODO: NOTE: Update these values to match the versions of the K8S API when pivoting to a new version of K8S.
7373
# https://github.com/kubernetes-sigs/kustomize/releases
74-
KUSTOMIZE_VERSION ?= v5.8.0
74+
KUSTOMIZE_VERSION ?= v5.8.1
7575
# https://github.com/kubernetes-sigs/controller-tools/releases
7676
CONTROLLER_TOOLS_VERSION ?= v0.19.0
7777
# https://github.com/kubernetes-sigs/controller-runtime/branches
7878
SETUP_ENVTEST_VERSION ?= release-0.22
7979
# ENVTEST_K8S_VERSION refers to the version of kubebuilder assets to be downloaded by envtest binary.
8080
ENVTEST_K8S_VERSION = 1.34.1
8181
# https://github.com/golangci/golangci-lint/releases
82-
GOLINT_VERSION = v2.7.2
82+
GOLINT_VERSION = v2.10.1
8383

8484
# TODO: We'd need an upstream builder image that includes gpgme-devel (libgpgme-dev)
8585
BUILD_IMAGE ?= registry.ci.openshift.org/ocp/builder:rhel-9-golang-1.25-openshift-4.21

api/common/plugins/base_plugin.go

Lines changed: 24 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -20,7 +20,30 @@ import "github.com/openshift/multiarch-tuning-operator/api/common"
2020

2121
// +k8s:deepcopy-gen=package
2222

23-
// Plugins represents the plugins configuration.
23+
// LocalPlugins represents the plugins configuration for podplacementconfigs resource.
24+
// +kubebuilder:object:generate=true
25+
type LocalPlugins struct {
26+
NodeAffinityScoring *NodeAffinityScoring `json:"nodeAffinityScoring,omitempty"`
27+
}
28+
29+
// localPluginChecks is a map that associates a plugin name with a function that can
30+
// safely check if that specific plugin is enabled on a LocalPlugins struct.
31+
var localPluginChecks = map[common.Plugin]func(lp *LocalPlugins) bool{
32+
common.NodeAffinityScoringPluginName: func(lp *LocalPlugins) bool {
33+
return lp.NodeAffinityScoring != nil && lp.NodeAffinityScoring.IsEnabled()
34+
},
35+
}
36+
37+
// PluginEnabled provides a generic and safe way to check if a specific plugin is enabled.
38+
// It handles the case where the LocalPlugins struct itself is nil.
39+
func (lp *LocalPlugins) PluginEnabled(plugin common.Plugin) bool {
40+
if checkFunc, found := localPluginChecks[plugin]; found {
41+
return checkFunc(lp)
42+
}
43+
return false
44+
}
45+
46+
// Plugins represents the plugins configuration for cluster pod placement config.
2447
// +kubebuilder:object:generate=true
2548
type Plugins struct {
2649
NodeAffinityScoring *NodeAffinityScoring `json:"nodeAffinityScoring,omitempty"`

api/common/plugins/nodeaffinityscoring_plugin.go

Lines changed: 15 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,8 @@ limitations under the License.
1616

1717
package plugins
1818

19+
import "fmt"
20+
1921
const (
2022
// PluginName for NodeAffinityScoring.
2123
NodeAffinityScoringPluginName = "NodeAffinityScoring"
@@ -30,6 +32,19 @@ type NodeAffinityScoring struct {
3032
Platforms []NodeAffinityScoringPlatformTerm `json:"platforms" protobuf:"bytes,2,opt,name=platforms"`
3133
}
3234

35+
// ValidateArchitecturesSet checks whether duplicate architectures are set in NodeAffinityScoring
36+
func (n *NodeAffinityScoring) ValidateArchitecturesSet() (bool, error) {
37+
seen := make(map[string]struct{})
38+
for _, term := range n.Platforms {
39+
if _, exists := seen[term.Architecture]; exists {
40+
return false, fmt.Errorf("duplicate architecture %q found in nodeAffinityScoring.platforms", term.Architecture)
41+
}
42+
43+
seen[term.Architecture] = struct{}{}
44+
}
45+
return true, nil
46+
}
47+
3348
// NodeAffinityScoringPlatformTerm holds configuration for specific platforms, with required fields validated.
3449
type NodeAffinityScoringPlatformTerm struct {
3550
// Architecture must be a list of non-empty string of arch names.

api/common/plugins/zz_generated.deepcopy.go

Lines changed: 20 additions & 0 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

api/v1alpha1/clusterPodPlacementConfig_conversion.go

Lines changed: 18 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -22,6 +22,8 @@ import (
2222
multiarchv1beta1 "github.com/openshift/multiarch-tuning-operator/api/v1beta1"
2323
)
2424

25+
const FallbackArchAnnotation = "multiarch.openshift.io/fallback-architecture"
26+
2527
// ConvertTo converts this ClusterPodPlacementConfig to the Hub version v1beta1.
2628
func (src *ClusterPodPlacementConfig) ConvertTo(dstRaw conversion.Hub) error {
2729
dst := dstRaw.(*multiarchv1beta1.ClusterPodPlacementConfig)
@@ -32,6 +34,11 @@ func (src *ClusterPodPlacementConfig) ConvertTo(dstRaw conversion.Hub) error {
3234
// Spec
3335
dst.Spec.LogVerbosity = src.Spec.LogVerbosity
3436
dst.Spec.NamespaceSelector = src.Spec.NamespaceSelector
37+
// Restore FallbackArchitecture from the annotation, if present.
38+
// This ensures the value is preserved across API version conversions.
39+
if arch, ok := src.Annotations[FallbackArchAnnotation]; ok {
40+
dst.Spec.FallbackArchitecture = arch
41+
}
3542

3643
// Status
3744
dst.Status.Conditions = src.Status.Conditions
@@ -51,6 +58,17 @@ func (dst *ClusterPodPlacementConfig) ConvertFrom(srcRaw conversion.Hub) error {
5158

5259
// ObjectMeta
5360
dst.ObjectMeta = src.ObjectMeta
61+
// v1alpha1 does not have the FallbackArchitecture field.
62+
// Preserve the value in an annotation to avoid data loss during
63+
// v1beta1 -> v1alpha1 -> v1beta1 round-trip conversions.
64+
if dst.Annotations == nil {
65+
dst.Annotations = make(map[string]string)
66+
}
67+
if src.Spec.FallbackArchitecture != "" {
68+
dst.Annotations[FallbackArchAnnotation] = src.Spec.FallbackArchitecture
69+
} else {
70+
delete(dst.Annotations, FallbackArchAnnotation)
71+
}
5472

5573
// Spec
5674
dst.Spec.LogVerbosity = src.Spec.LogVerbosity

api/v1beta1/clusterpodplacementconfig_types.go

Lines changed: 8 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -48,6 +48,14 @@ type ClusterPodPlacementConfigSpec struct {
4848
// This field is optional and will be omitted from the output if not set.
4949
// +optional
5050
Plugins *plugins.Plugins `json:"plugins,omitempty"`
51+
52+
// FallbackArchitecture defines the architecture to use if the image inspector cannot determine the image's architecture.
53+
// If configured, the PodPlacementController will set the node affinity of the pod to the fallback architecture
54+
// if the image inspector cannot determine the image's architecture.
55+
// +optional
56+
// +kubebuilder:default=""
57+
// +kubebuilder:validation:Enum=arm64;amd64;ppc64le;s390x;""
58+
FallbackArchitecture string `json:"fallbackArchitecture,omitempty"`
5159
}
5260

5361
// ClusterPodPlacementConfigStatus defines the observed state of ClusterPodPlacementConfig

api/v1beta1/clusterpodplacementconfig_webhook.go

Lines changed: 22 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
/*
2-
Copyright 2023 Red Hat, Inc.
2+
Copyright 2025 Red Hat, Inc.
33
44
Licensed under the Apache License, Version 2.0 (the "License");
55
you may not use this file except in compliance with the License.
@@ -19,24 +19,34 @@ package v1beta1
1919
import (
2020
"context"
2121
"errors"
22+
"fmt"
2223

23-
runtime "k8s.io/apimachinery/pkg/runtime"
24+
"k8s.io/apimachinery/pkg/runtime"
2425
ctrl "sigs.k8s.io/controller-runtime"
26+
"sigs.k8s.io/controller-runtime/pkg/client"
27+
"sigs.k8s.io/controller-runtime/pkg/webhook"
2528
"sigs.k8s.io/controller-runtime/pkg/webhook/admission"
2629
)
2730

28-
// +kubebuilder:webhook:path=/validate-multiarch-openshift-io-v1beta1-clusterpodplacementconfig,mutating=false,failurePolicy=fail,sideEffects=None,groups=multiarch.openshift.io,resources=clusterpodplacementconfigs,verbs=create;update,versions=v1beta1,name=validate-clusterpodplacementconfig.multiarch.openshift.io,admissionReviewVersions=v1
31+
// +kubebuilder:webhook:path=/validate-multiarch-openshift-io-v1beta1-clusterpodplacementconfig,mutating=false,failurePolicy=fail,sideEffects=None,groups=multiarch.openshift.io,resources=clusterpodplacementconfigs,verbs=create;update;delete,versions=v1beta1,name=validate-clusterpodplacementconfig.multiarch.openshift.io,admissionReviewVersions=v1
2932

3033
func (c *ClusterPodPlacementConfig) SetupWebhookWithManager(mgr ctrl.Manager) error {
3134
return ctrl.NewWebhookManagedBy(mgr).
3235
For(c).
33-
WithValidator(&ClusterPodPlacementConfigValidator{}).
36+
WithValidator(&ClusterPodPlacementConfigValidator{
37+
Client: mgr.GetClient(),
38+
}).
3439
Complete()
3540
}
3641

42+
// ClusterPodPlacementConfigValidator validates ClusterPodPlacementConfig resources
43+
// +kubebuilder:object:generate=false
3744
type ClusterPodPlacementConfigValidator struct {
45+
Client client.Client
3846
}
3947

48+
var _ webhook.CustomValidator = &ClusterPodPlacementConfigValidator{}
49+
4050
func (v *ClusterPodPlacementConfigValidator) ValidateCreate(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
4151
return v.validate(obj)
4252
}
@@ -46,6 +56,14 @@ func (v *ClusterPodPlacementConfigValidator) ValidateUpdate(ctx context.Context,
4656
}
4757

4858
func (v *ClusterPodPlacementConfigValidator) ValidateDelete(ctx context.Context, obj runtime.Object) (warnings admission.Warnings, err error) {
59+
// Check if any local PodPlacementConfig exists. If exists, deny deletion of ClusterPodPlacementConfig.
60+
existingPPCs := &PodPlacementConfigList{}
61+
if err := v.Client.List(ctx, existingPPCs); err != nil {
62+
return nil, fmt.Errorf("failed to list existing PodPlacementConfigs: %w", err)
63+
}
64+
if len(existingPPCs.Items) != 0 {
65+
return nil, fmt.Errorf("cannot delete ClusterPodPlacementConfig while local PodPlacementConfigs still exist")
66+
}
4967
return nil, nil
5068
}
5169

api/v1beta1/groupversion_info.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -37,5 +37,7 @@ var (
3737

3838
const ClusterPodPlacementConfigResource = "clusterpodplacementconfigs"
3939
const ClusterPodPlacementConfigKind = "ClusterPodPlacementConfig"
40+
const PodPlacementConfigResource = "podplacementconfigs"
41+
const PodPlacementConfigKind = "PodPlacementConfig"
4042
const ENoExecEventKind = "ENoExecEvent"
4143
const ENoExecEventResource = "enoexecevents"

0 commit comments

Comments
 (0)