package controller
import (
"context"
"encoding/hex"
"errors"
"fmt"
"hash/fnv"
"maps"
"os"
"reflect"
"strings"
"github.com/davecgh/go-spew/spew"
monitoringv1 "github.com/prometheus-operator/prometheus-operator/pkg/apis/monitoring/v1"
appsv1 "k8s.io/api/apps/v1"
coordinationv1 "k8s.io/api/coordination/v1"
corev1 "k8s.io/api/core/v1"
nodev1 "k8s.io/api/node/v1"
rbacv1 "k8s.io/api/rbac/v1"
apiextensionsv1 "k8s.io/apiextensions-apiserver/pkg/apis/apiextensions/v1"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"sigs.k8s.io/controller-runtime/pkg/client"
"sigs.k8s.io/controller-runtime/pkg/controller/controllerutil"
"sigs.k8s.io/controller-runtime/pkg/log"
"volcano.sh/apis/pkg/apis/batch/v1alpha1"
busv1alpha1 "volcano.sh/apis/pkg/apis/bus/v1alpha1"
flowv1alpha1 "volcano.sh/apis/pkg/apis/flow/v1alpha1"
nodeinfov1alpha1 "volcano.sh/apis/pkg/apis/nodeinfo/v1alpha1"
"volcano.sh/apis/pkg/apis/scheduling"
v1 "openfuyao.com/npu-operator/api/v1"
)
const (
hashAnnotationKey = "openfuyao.com/last-applied-hash"
placeholderValue = "FILLED_BY_OPERATOR"
operatorName = "npu-operator"
runtimeDaemonSetNamePrefix = "ascend-runtime-"
driverDaemonSetName = "npu-driver"
devicePluginDaemonSetName = "ascend-device-plugin"
trainerDeploymentName = "ascend-operator-manager"
clusterdDeploymentName = "clusterd"
nodedDaemonsetName = "noded"
exporterDaemonsetName = "npu-exporter"
rsControllerDeploymentName = "resilience-controller"
vcControllerDeploymentName = "volcano-controllers"
vcSchedulerDeploymentName = "volcano-scheduler"
mindIOACPDaemonsetName = "mindio-acp"
mindIOTFTDaemonsetName = "mindio-tft"
volcanoSchedulerConfigMapName = "volcano-scheduler-configmap"
logRotateLogLevelEnvName = "NPU_OPERATOR_LOG_LEVEL"
logRotateLogFileEnvName = "NPU_OPERATOR_LOG_FILE"
logRotateRotateEnvName = "NPU_OPERATOR_ROTATE"
logRotateMaxAgeEnvName = "NPU_OPERATOR_MAX_AGE"
logRotateCompressEnvName = "NPU_OPERATOR_COMPRESS"
operatorNodeEnvName = "OPERATOR_NODE_NAME"
)
var (
gvkNamespace = schema.GroupVersionKind{
Version: "v1",
Kind: "Namespace",
}
gvkRuntimeClass = schema.GroupVersionKind{
Group: "node.k8s.io",
Version: "v1",
Kind: "RuntimeClass",
}
gvkDaemonSet = schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "DaemonSet",
}
gvkDeployment = schema.GroupVersionKind{
Group: "apps",
Version: "v1",
Kind: "Deployment",
}
gvkServiceAccount = schema.GroupVersionKind{
Version: "v1",
Kind: "ServiceAccount",
}
gvkClusterRole = schema.GroupVersionKind{
Group: "rbac.authorization.k8s.io",
Version: "v1",
Kind: "ClusterRole",
}
gvkClusterRoleBinding = schema.GroupVersionKind{
Group: "rbac.authorization.k8s.io",
Version: "v1",
Kind: "ClusterRoleBinding",
}
gvkServiceMonitor = schema.GroupVersionKind{
Group: "monitoring.coreos.com",
Version: "v1",
Kind: "ServiceMonitor",
}
gvkService = schema.GroupVersionKind{
Version: "v1",
Kind: "Service",
}
gvkLease = schema.GroupVersionKind{
Group: "coordination.k8s.io",
Version: "v1",
Kind: "Lease",
}
gvkVolcanoBatchJob = schema.GroupVersionKind{
Group: "batch.volcano.sh",
Version: "v1alpha1",
Kind: "Job",
}
gvkVolcanoBusCommand = schema.GroupVersionKind{
Group: "bus.volcano.sh",
Version: "v1alpha1",
Kind: "Command",
}
gvkSchedulingPodgroup = schema.GroupVersionKind{
Group: "scheduling.volcano.sh",
Version: "v1beta1",
Kind: "PodGroup",
}
gvkSchedulerQueue = schema.GroupVersionKind{
Group: "scheduling.volcano.sh",
Version: "v1beta1",
Kind: "Queue",
}
gvkNodeInfo = schema.GroupVersionKind{
Group: "nodeinfo.volcano.sh",
Version: "v1alpha1",
Kind: "Numatopology",
}
gvkJobTemplate = schema.GroupVersionKind{
Group: "flow.volcano.sh",
Version: "v1alpha1",
Kind: "JobTemplate",
}
gvkJobFlow = schema.GroupVersionKind{
Group: "flow.volcano.sh",
Version: "v1alpha1",
Kind: "JobFlow",
}
gvkConfigMap = schema.GroupVersionKind{
Version: "v1",
Kind: "ConfigMap",
}
gvkCRD = schema.GroupVersionKind{
Group: "apiextensions.k8s.io",
Version: "v1",
Kind: "CustomResourceDefinition",
}
resourceCreators = map[schema.GroupVersionKind]func(client.Object) componentResource{
gvkCRD: func(obj client.Object) componentResource {
return newComponentResource(obj.(*apiextensionsv1.CustomResourceDefinition), CRDReconcileHooks{})
},
gvkNamespace: func(obj client.Object) componentResource {
return newComponentResource(obj.(*corev1.Namespace), namespaceReconcileHooks{})
},
gvkRuntimeClass: func(obj client.Object) componentResource {
return newComponentResource(obj.(*nodev1.RuntimeClass), runtimeClassReconcileHooks{})
},
gvkDaemonSet: func(obj client.Object) componentResource {
return newComponentResource(obj.(*appsv1.DaemonSet), newDaemonSetReconcileHooks(obj))
},
gvkDeployment: func(obj client.Object) componentResource {
return newComponentResource(obj.(*appsv1.Deployment), newDeploymentReconcileHooks(obj))
},
gvkServiceAccount: func(obj client.Object) componentResource {
return newComponentResource(obj.(*corev1.ServiceAccount), serviceAccountReconcileHooks{})
},
gvkClusterRole: func(obj client.Object) componentResource {
return newComponentResource(obj.(*rbacv1.ClusterRole), clusterRoleReconcileHooks{})
},
gvkClusterRoleBinding: func(obj client.Object) componentResource {
return newComponentResource(obj.(*rbacv1.ClusterRoleBinding), clusterRoleBindingReconcileHooks{})
},
gvkServiceMonitor: func(obj client.Object) componentResource {
return newComponentResource(obj.(*monitoringv1.ServiceMonitor), serviceMoniterReconcileHooks{})
},
gvkService: func(obj client.Object) componentResource {
return newComponentResource(obj.(*corev1.Service), serviceReconcileHooks{})
},
gvkLease: func(obj client.Object) componentResource {
return newComponentResource(obj.(*coordinationv1.Lease), leaseReconcileHooks{})
},
gvkConfigMap: func(obj client.Object) componentResource {
return newComponentResource(obj.(*corev1.ConfigMap), configMapReconcileHooks{})
},
gvkVolcanoBatchJob: func(obj client.Object) componentResource {
return newComponentResource(obj.(*v1alpha1.Job), jobReconcileHooks{})
},
gvkVolcanoBusCommand: func(obj client.Object) componentResource {
return newComponentResource(obj.(*busv1alpha1.Command), commandReconcileHooks{})
},
gvkSchedulingPodgroup: func(obj client.Object) componentResource {
return newComponentResource(obj.(*scheduling.PodGroup), podGroupReconcileHooks{})
},
gvkSchedulerQueue: func(obj client.Object) componentResource {
return newComponentResource(obj.(*scheduling.Queue), queueReconcileHooks{})
},
gvkNodeInfo: func(obj client.Object) componentResource {
return newComponentResource(obj.(*nodeinfov1alpha1.Numatopology), numatopologyReconcileHooks{})
},
gvkJobTemplate: func(obj client.Object) componentResource {
return newComponentResource(obj.(*flowv1alpha1.JobTemplate), jobTemplateReconcileHooks{})
},
gvkJobFlow: func(obj client.Object) componentResource {
return newComponentResource(obj.(*flowv1alpha1.JobFlow), jobFlowReconcileHooks{})
},
}
errSkipCreation = errors.New("skip resource creation")
errSkipReconciliation = errors.New("skip resource reconciliation")
)
type componentResource interface {
clone() componentResource
gvk() string
reconcile(ctx context.Context, r *NPUClusterPolicyReconciler, isManaged bool) (*v1.ComponentState, error)
}
type componentResourceReconcileHooks[T any] interface {
canDelete(context.Context, *NPUClusterPolicyReconciler, *T) (client.Object, bool)
transform(context.Context, *NPUClusterPolicyReconciler, *T) error
inspect(context.Context, *NPUClusterPolicyReconciler, *T) (*v1.ComponentState, error)
}
type (
componentResourceTransformer[T any] func(context.Context, *NPUClusterPolicyReconciler, *T) error
componentResourceInspector[T any] func(context.Context, *NPUClusterPolicyReconciler, *T) error
)
type clientObject[T any] interface {
*T
client.Object
}
type typedComponentResource[T any, PT clientObject[T]] struct {
componentResourceReconcileHooks[T]
obj PT
}
func (res typedComponentResource[T, PT]) clone() componentResource {
return typedComponentResource[T, PT]{
componentResourceReconcileHooks: res.componentResourceReconcileHooks,
obj: res.obj.DeepCopyObject().(PT),
}
}
func (res typedComponentResource[T, PT]) gvk() string {
gvk := res.obj.GetObjectKind().GroupVersionKind()
return fmt.Sprintf("%s/%s", gvk.GroupKind(), gvk.Version)
}
func (res typedComponentResource[T, PT]) reconcile(
ctx context.Context,
r *NPUClusterPolicyReconciler,
isManaged bool,
) (*v1.ComponentState, error) {
gvk := res.gvk()
logger := log.FromContext(ctx, gvk, res.obj.GetName())
ctx = log.IntoContext(ctx, logger)
mutated := true
if !isManaged {
realObj, ok := res.canDelete(ctx, r, res.obj)
if ok {
if err := r.Delete(ctx, realObj); client.IgnoreNotFound(err) != nil {
return nil, fmt.Errorf("delete %s resource: %w", gvk, err)
}
}
return nil, nil
}
if err := res.transform(ctx, r, res.obj); err != nil {
if errors.Is(err, errSkipReconciliation) {
return nil, nil
}
if !errors.Is(err, errSkipCreation) {
return nil, fmt.Errorf("transform %s resource: %w", gvk, err)
}
mutated = false
}
if mutated {
o, err := r.Scheme.New(res.obj.GetObjectKind().GroupVersionKind())
if err != nil {
return nil, fmt.Errorf("new %s object: %w", gvk, err)
}
obj := o.(client.Object)
obj.SetNamespace(res.obj.GetNamespace())
obj.SetName(res.obj.GetName())
op, err := controllerutil.CreateOrUpdate(ctx, r.Client, obj, func() error {
res.into(obj)
return nil
})
if err != nil && !errors.Is(err, errSkipCreation) {
return nil, fmt.Errorf("create or update %s resource: %w", gvk, err)
}
logger.V(1).Info(fmt.Sprintf("Resource %s", op))
}
state, err := res.inspect(ctx, r, res.obj)
if err != nil && state == nil {
state = &v1.ComponentState{
Phase: v1.ComponentTerminated,
Reason: reconcileFailedReason,
Message: err.Error(),
}
}
return state, err
}
func (res typedComponentResource[T, PT]) into(receiver client.Object) {
currentLabels := receiver.GetLabels()
if currentLabels == nil {
currentLabels = make(map[string]string)
}
for k, v := range res.obj.GetLabels() {
currentLabels[k] = v
}
receiver.SetLabels(currentLabels)
currentAnno := receiver.GetAnnotations()
if currentAnno == nil {
currentAnno = make(map[string]string)
}
for k, v := range res.obj.GetAnnotations() {
currentAnno[k] = v
}
receiver.SetAnnotations(currentAnno)
receiver.SetOwnerReferences(res.obj.GetOwnerReferences())
receiver.SetFinalizers(res.obj.GetFinalizers())
if res.copyWorkloads(receiver) {
return
}
if res.copyCoreResources(receiver) {
return
}
if res.copyVolcanoResources(receiver) {
return
}
if res.copyExtendedResources(receiver) {
return
}
log.Log.WithName("npu-operator").Error(nil, "Unhandled type in into()", "type", fmt.Sprintf("%T", receiver))
}
func (res typedComponentResource[T, PT]) copyWorkloads(receiver client.Object) bool {
switch target := receiver.(type) {
case *appsv1.DaemonSet:
target.Spec = any(res.obj).(*appsv1.DaemonSet).Spec
return true
case *appsv1.Deployment:
target.Spec = any(res.obj).(*appsv1.Deployment).Spec
return true
case *v1alpha1.Job:
target.Spec = any(res.obj).(*v1alpha1.Job).Spec
return true
}
return false
}
func (res typedComponentResource[T, PT]) copyCoreResources(receiver client.Object) bool {
switch target := receiver.(type) {
case *corev1.Service:
s := any(res.obj).(*corev1.Service)
oldIP := target.Spec.ClusterIP
target.Spec = s.Spec
if oldIP != "" {
target.Spec.ClusterIP = oldIP
}
return true
case *corev1.ConfigMap:
s := any(res.obj).(*corev1.ConfigMap)
target.Data = s.Data
target.BinaryData = s.BinaryData
return true
case *corev1.ServiceAccount:
s := any(res.obj).(*corev1.ServiceAccount)
target.Secrets = s.Secrets
target.ImagePullSecrets = s.ImagePullSecrets
target.AutomountServiceAccountToken = s.AutomountServiceAccountToken
return true
case *corev1.Namespace:
target.Spec = any(res.obj).(*corev1.Namespace).Spec
return true
}
return false
}
func (res typedComponentResource[T, PT]) copyVolcanoResources(receiver client.Object) bool {
switch target := receiver.(type) {
case *scheduling.PodGroup:
target.Spec = any(res.obj).(*scheduling.PodGroup).Spec
return true
case *scheduling.Queue:
target.Spec = any(res.obj).(*scheduling.Queue).Spec
return true
case *nodeinfov1alpha1.Numatopology:
target.Spec = any(res.obj).(*nodeinfov1alpha1.Numatopology).Spec
return true
case *busv1alpha1.Command:
target.Action = any(res.obj).(*busv1alpha1.Command).Action
target.TargetObject = any(res.obj).(*busv1alpha1.Command).TargetObject
target.Message = any(res.obj).(*busv1alpha1.Command).Message
target.Reason = any(res.obj).(*busv1alpha1.Command).Reason
return true
case *flowv1alpha1.JobTemplate:
target.Spec = any(res.obj).(*flowv1alpha1.JobTemplate).Spec
return true
case *flowv1alpha1.JobFlow:
target.Spec = any(res.obj).(*flowv1alpha1.JobFlow).Spec
return true
}
return false
}
func (res typedComponentResource[T, PT]) copyExtendedResources(receiver client.Object) bool {
switch target := receiver.(type) {
case *nodev1.RuntimeClass:
s := any(res.obj).(*nodev1.RuntimeClass)
target.Handler = s.Handler
target.Overhead = s.Overhead
target.Scheduling = s.Scheduling
return true
case *rbacv1.ClusterRole:
s := any(res.obj).(*rbacv1.ClusterRole)
target.Rules = s.Rules
target.AggregationRule = s.AggregationRule
return true
case *rbacv1.ClusterRoleBinding:
s := any(res.obj).(*rbacv1.ClusterRoleBinding)
target.Subjects = s.Subjects
target.RoleRef = s.RoleRef
return true
case *monitoringv1.ServiceMonitor:
target.Spec = any(res.obj).(*monitoringv1.ServiceMonitor).Spec
return true
case *apiextensionsv1.CustomResourceDefinition:
target.Spec = any(res.obj).(*apiextensionsv1.CustomResourceDefinition).Spec
return true
case *coordinationv1.Lease:
target.Spec = any(res.obj).(*coordinationv1.Lease).Spec
return true
}
return false
}
func newComponentResource[T any, PT clientObject[T]](
obj *T,
hooks componentResourceReconcileHooks[T],
) componentResource {
return typedComponentResource[T, PT]{hooks, obj}
}
type defaultResourceReconcileHooks[T client.Object] struct{}
func (defaultResourceReconcileHooks[T]) canDelete(ctx context.Context, r *NPUClusterPolicyReconciler, obj T) (client.Object, bool) {
logger := log.FromContext(ctx)
if obj.GetNamespace() == placeholderValue {
obj.SetNamespace(r.namespace)
}
key := client.ObjectKeyFromObject(obj)
currentResource := reflect.New(reflect.TypeOf(obj).Elem()).Interface().(T)
if err := r.Get(ctx, key, currentResource); err != nil {
if !apierrors.IsNotFound(err) {
logger.Error(err, "Failed to get resource")
}
return nil, false
}
references := currentResource.GetOwnerReferences()
logger.Info("Reference", "reference", references)
if references == nil {
return nil, false
}
for _, ref := range references {
if ref.Kind == r.instance.Kind && ref.Name == r.instance.Name && ref.UID == r.instance.UID {
return currentResource, true
}
}
return nil, false
}
func (defaultResourceReconcileHooks[T]) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj T,
) error {
if err := controllerutil.SetControllerReference(r.instance, obj, r.Scheme); err != nil {
return fmt.Errorf("set owner as controller: %w", err)
}
return nil
}
func (defaultResourceReconcileHooks[T]) inspect(
context.Context,
*NPUClusterPolicyReconciler,
T,
) (*v1.ComponentState, error) {
return nil, nil
}
type namespaceReconcileHooks struct {
defaultResourceReconcileHooks[*corev1.Namespace]
}
func (h namespaceReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *corev1.Namespace,
) error {
found := &corev1.Namespace{}
if err := r.Get(ctx, client.ObjectKeyFromObject(obj), found); err != nil {
if apierrors.IsNotFound(err) {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
return err
}
return nil
}
type runtimeClassReconcileHooks struct {
defaultResourceReconcileHooks[*nodev1.RuntimeClass]
}
func (h runtimeClassReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *nodev1.RuntimeClass,
) error {
if obj.Name == placeholderValue {
obj.Name = r.instance.Spec.Operator.RuntimeClass
}
obj.Handler = r.instance.Spec.Operator.RuntimeClass
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type daemonSetReconcileHooks struct {
defaultResourceReconcileHooks[*appsv1.DaemonSet]
transformers []componentResourceTransformer[appsv1.DaemonSet]
inspectors []componentResourceInspector[appsv1.DaemonSet]
}
func (h daemonSetReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.DaemonSet,
) error {
if !r.hasNPUNodes {
logger := log.FromContext(ctx)
logger.V(1).Info("No supported NPU node in cluster, skip DaemonSet creation")
return errSkipReconciliation
}
if obj.Namespace == placeholderValue {
obj.Namespace = r.namespace
}
dsSpec := &r.instance.Spec.Daemonsets
podTmpl := &obj.Spec.Template
setCommonDaemonSetMetadata(&obj.ObjectMeta, dsSpec)
setCommonDaemonSetMetadata(&podTmpl.ObjectMeta, dsSpec)
if podTmpl.Spec.Tolerations == nil {
podTmpl.Spec.Tolerations = make([]corev1.Toleration, 0)
}
if len(dsSpec.Tolerations) > 0 {
podTmpl.Spec.Tolerations = append(podTmpl.Spec.Tolerations, dsSpec.Tolerations...)
}
for _, tf := range h.transformers {
if err := tf(ctx, r, obj); err != nil {
return err
}
}
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
func (h daemonSetReconcileHooks) inspect(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.DaemonSet,
) (*v1.ComponentState, error) {
logger := log.FromContext(ctx)
if err := r.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
return nil, fmt.Errorf("get DaemonSet: %w", err)
}
for _, inspect := range h.inspectors {
if err := inspect(ctx, r, obj); err != nil {
return nil, err
}
}
if obj.Status.DesiredNumberScheduled == 0 {
logger.V(1).Info("DaemonSet selects 0 node")
return nil, nil
}
if obj.Status.NumberUnavailable != 0 {
logger.V(1).Info(
fmt.Sprintf("DaemonSet %s/%s not ready", obj.Namespace, obj.Name),
"NumberUnavailable",
obj.Status.NumberUnavailable,
)
return nil, fmt.Errorf("DaemonSet %s/%s not ready", obj.Namespace, obj.Name)
}
podList := &corev1.PodList{}
if err := r.Client.List(ctx, podList, client.MatchingLabels(obj.Spec.Selector.MatchLabels)); err != nil {
return nil, fmt.Errorf("Get pod list from daemonset failed: %w", err)
}
for _, pod := range podList.Items {
if pod.Status.Phase != corev1.PodRunning {
return &v1.ComponentState{
Phase: v1.ComponentPending,
Reason: "PodNotRunning",
Message: fmt.Sprintf("Daemonset Pod %s ,state %s", pod.Name, pod.Status.Phase),
}, nil
}
}
return nil, nil
}
func setCommonDaemonSetMetadata(metadata *metav1.ObjectMeta, spec *v1.DaemonsetsSpec) {
if metadata.Labels == nil {
metadata.Labels = make(map[string]string)
}
if len(spec.Labels) > 0 {
maps.Copy(metadata.Labels, spec.Labels)
}
if metadata.Annotations == nil {
metadata.Annotations = make(map[string]string)
}
if len(spec.Annotations) > 0 {
maps.Copy(metadata.Annotations, spec.Annotations)
}
}
func newDaemonSetReconcileHooks(obj client.Object) daemonSetReconcileHooks {
hooks := daemonSetReconcileHooks{}
name := obj.GetName()
tfs := &hooks.transformers
ins := &hooks.inspectors
if strings.HasPrefix(name, runtimeDaemonSetNamePrefix) {
*tfs = append(
*tfs,
runtimeDaemonSetTransformer,
newWorkloadImageTransformer[appsv1.DaemonSet](runtimeImageConfigGetter),
)
*ins = append(*ins, runtimeDaemonSetInspector)
}
switch name {
case driverDaemonSetName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.DaemonSet](driverLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.DaemonSet](driverImageConfigGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](driverEnvConfigGetter),
)
case devicePluginDaemonSetName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.DaemonSet](devicePluginLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.DaemonSet](devicePluginImageConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.DaemonSet](devicePluginCommandParametersGetter),
newWorkloadResourceRequirementsTransformer[appsv1.DaemonSet](devicePluginResourceRequirementsConfigGetter),
newWorkloadVolumesTransformer[appsv1.DaemonSet](devicePluginVolumesGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](devicePluginEnvConfigGetter),
)
case nodedDaemonsetName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.DaemonSet](nodeDLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.DaemonSet](nodeDImageConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.DaemonSet](nodeDCommandParametersGetter),
newWorkloadResourceRequirementsTransformer[appsv1.DaemonSet](nodeDResourceConfigGetter),
newWorkloadVolumesTransformer[appsv1.DaemonSet](nodeDVolumesGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](nodeDEnvConfigGetter),
)
case exporterDaemonsetName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.DaemonSet](exporterLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.DaemonSet](exporterImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.DaemonSet](exporterResourceRequirementsConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.DaemonSet](exporterCommandParametersGetter),
newWorkloadVolumesTransformer[appsv1.DaemonSet](exporterVolumesGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](exporterEnvConfigGetter),
)
case mindIOTFTDaemonsetName:
*tfs = append(
*tfs,
newWorkloadImageTransformer[appsv1.DaemonSet](mindioTFTImageConfigGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](tftEnvConfigGetter),
)
case mindIOACPDaemonsetName:
*tfs = append(
*tfs,
newWorkloadImageTransformer[appsv1.DaemonSet](mindioACPImageConfigGetter),
newWorkloadEnvTransformer[appsv1.DaemonSet](acpEnvConfigGetter),
)
default:
}
*tfs = append(*tfs, workloadHashTransformer[appsv1.DaemonSet])
return hooks
}
func runtimeDaemonSetTransformer(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.DaemonSet,
) error {
logger := log.FromContext(ctx)
runtime := strings.TrimPrefix(obj.Name, runtimeDaemonSetNamePrefix)
if _, ok := r.runtimes[runtime]; !ok {
logger.V(1).Info(fmt.Sprintf("No node with CRI runtime %s found in clustre", runtime))
return errSkipReconciliation
}
selector := obj.Spec.Template.Spec.NodeSelector
if selector == nil {
selector = map[string]string{}
}
selector[runtimeLabelKey] = runtime
obj.Spec.Template.Spec.NodeSelector = selector
anno := obj.Spec.Template.Annotations
if anno == nil {
anno = map[string]string{}
}
anno[runtimeEndpointAnnotationKey] = ""
obj.Spec.Template.Annotations = anno
return nil
}
func runtimeDaemonSetInspector(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.DaemonSet,
) error {
logger := log.FromContext(ctx)
podList := &corev1.PodList{}
if err := r.List(ctx, podList, client.InNamespace(r.namespace), client.MatchingLabels(obj.Spec.Selector.MatchLabels)); err != nil {
return fmt.Errorf("list pods: %w", err)
}
for _, pod := range podList.Items {
logger := logger.WithValues("pod", pod.Name)
anno := pod.Annotations
if anno == nil {
anno = map[string]string{}
}
update := false
node := pod.Spec.NodeName
if endpoint, ok := r.nodeRuntimeEndpoints[node]; ok {
logger.Info(fmt.Sprintf("CRI container runtime endpoint: %s", endpoint))
update = addAnnotation(anno, runtimeEndpointAnnotationKey, endpoint)
} else {
logger.Info(fmt.Sprintf("Pod running on unrecognized node %s", node))
}
pod.Annotations = anno
if update {
if err := r.Update(ctx, &pod); err != nil {
return fmt.Errorf("inject endpoint to pod: %w", err)
}
}
}
return nil
}
type deploymentReconcileHooks struct {
defaultResourceReconcileHooks[*appsv1.Deployment]
transformers []componentResourceTransformer[appsv1.Deployment]
}
func (h deploymentReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.Deployment,
) error {
if obj.Namespace == placeholderValue {
obj.Namespace = r.namespace
}
for _, tf := range h.transformers {
if err := tf(ctx, r, obj); err != nil {
return err
}
}
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
func (deploymentReconcileHooks) inspect(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *appsv1.Deployment,
) (*v1.ComponentState, error) {
logger := log.FromContext(ctx)
if err := r.Get(ctx, client.ObjectKeyFromObject(obj), obj); err != nil {
return nil, fmt.Errorf("get Deployment: %w", err)
}
if obj.Status.UnavailableReplicas != 0 {
logger.V(1).Info(
fmt.Sprintf("Deployment %s/%s not ready", obj.Namespace, obj.Name),
"NumberUnavailable",
obj.Status.UnavailableReplicas,
)
return nil, fmt.Errorf("Deployment %s/%s not ready", obj.Namespace, obj.Name)
}
podList := &corev1.PodList{}
if err := r.Client.List(ctx, podList, client.MatchingLabels(obj.Spec.Selector.MatchLabels)); err != nil {
return nil, fmt.Errorf("Get pod list from deployment failed: %w", err)
}
for _, pod := range podList.Items {
if pod.Status.Phase != corev1.PodRunning {
return &v1.ComponentState{
Phase: v1.ComponentPending,
Reason: "PodNotRunning",
Message: fmt.Sprintf("Deployment Pod %s ,state %s", pod.Name, pod.Status.Phase),
}, nil
}
}
return nil, nil
}
func newDeploymentReconcileHooks(obj client.Object) deploymentReconcileHooks {
hooks := deploymentReconcileHooks{}
tfs := &hooks.transformers
name := obj.GetName()
switch name {
case trainerDeploymentName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.Deployment](trainerLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.Deployment](trainerImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.Deployment](trainerResourceRequirementsConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.Deployment](trainerCommandParametersGetter),
newWorkloadVolumesTransformer[appsv1.Deployment](trainerVolumesGetter),
newWorkloadEnvTransformer[appsv1.Deployment](trainerEnvConfigGetter),
)
case clusterdDeploymentName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.Deployment](clusterdLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.Deployment](clusterdImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.Deployment](clusterdResourceConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.Deployment](clusterDCommandParametersGetter),
newWorkloadVolumesTransformer[appsv1.Deployment](clusterDVolumesGetter),
newWorkloadEnvTransformer[appsv1.Deployment](clusterDEnvConfigGetter),
)
case rsControllerDeploymentName:
*tfs = append(
*tfs,
newWorkloadLogRotateTransformer[appsv1.Deployment](rsControllerLogRotateConfigGetter),
newWorkloadImageTransformer[appsv1.Deployment](rscontrollerImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.Deployment](rsControllerResourceConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.Deployment](rsControllerCommandParametersGetter),
)
case vcControllerDeploymentName:
*tfs = append(
*tfs,
newWorkloadImageTransformer[appsv1.Deployment](vcControllerImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.Deployment](vcControllerResourceRequirementsConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.Deployment](vcControllerCommandParametersGetter),
newWorkloadVolumesTransformer[appsv1.Deployment](vcControllerVolumesGetter),
newWorkloadEnvTransformer[appsv1.Deployment](vcControllerEnvConfigGetter),
)
case vcSchedulerDeploymentName:
*tfs = append(
*tfs,
newWorkloadImageTransformer[appsv1.Deployment](vcSchedulerImageConfigGetter),
newWorkloadResourceRequirementsTransformer[appsv1.Deployment](vcSchedulerResourceRequirementsConfigGetter),
newWorkloadCommandParameterTransformer[appsv1.Deployment](schedulerCommandParametersGetter),
newWorkloadVolumesTransformer[appsv1.Deployment](vcSchedulerVolumesGetter),
newWorkloadEnvTransformer[appsv1.Deployment](vcSchedulerEnvConfigGetter),
)
default:
}
*tfs = append(*tfs, workloadHashTransformer[appsv1.Deployment])
return hooks
}
func newWorkloadPodTemplateSpecGetter[T appsv1.DaemonSet | appsv1.Deployment]() func(*T) *corev1.PodTemplateSpec {
var o T
switch any(o).(type) {
case appsv1.DaemonSet:
return func(obj *T) *corev1.PodTemplateSpec { return &any(obj).(*appsv1.DaemonSet).Spec.Template }
case appsv1.Deployment:
return func(obj *T) *corev1.PodTemplateSpec { return &any(obj).(*appsv1.Deployment).Spec.Template }
default:
return nil
}
}
func workloadHashTransformer[T appsv1.DaemonSet | appsv1.Deployment, PT clientObject[T]](
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj PT,
) error {
hasher := fnv.New32a()
spewer := spew.ConfigState{
DisableMethods: true,
DisablePointerAddresses: true,
SortKeys: true,
SpewKeys: true,
}
spewer.Fprintf(hasher, "%#v", obj)
anno := obj.GetAnnotations()
if anno == nil {
anno = make(map[string]string)
obj.SetAnnotations(anno)
}
obj.GetAnnotations()[hashAnnotationKey] = hex.EncodeToString(hasher.Sum(nil))
return nil
}
func newWorkloadEnvTransformer[T appsv1.DaemonSet | appsv1.Deployment](
obtainEnv func(*v1.NPUClusterPolicySpec) []corev1.EnvVar,
) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
specEnv := obtainEnv(&r.instance.Spec)
podTmpl := podTemplateSpec(obj)
injectEnv := func(containers []corev1.Container) []corev1.Container {
for i := range containers {
env := containers[i].Env
if env == nil {
env = make([]corev1.EnvVar, 0)
}
for j := range specEnv {
replaced := false
for k := range env {
if env[k].Name != specEnv[j].Name {
continue
}
env[k] = specEnv[j]
replaced = true
break
}
if !replaced {
env = append(env, specEnv[j])
}
}
containers[i].Env = env
}
return containers
}
podTmpl.Spec.Containers = injectEnv(podTmpl.Spec.Containers)
podTmpl.Spec.InitContainers = injectEnv(podTmpl.Spec.InitContainers)
return nil
}
}
func newWorkloadLogRotateTransformer[T appsv1.DaemonSet | appsv1.Deployment]( // 通过环境变量的方式修改log的相关参数
logRotate func(*v1.NPUClusterPolicySpec) *v1.LogRotate,
) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
conf := logRotate(&r.instance.Spec)
podTmpl := podTemplateSpec(obj)
containers := podTmpl.Spec.Containers
for i := range containers {
env := containers[i].Env
for j := range env {
switch env[j].Name {
case logRotateLogLevelEnvName:
env[j].Value = fmt.Sprintf("%d", conf.LogLevel.Value())
case logRotateLogFileEnvName:
env[j].Value = conf.LogFile
case logRotateRotateEnvName:
env[j].Value = fmt.Sprintf("%d", conf.Rotate)
case logRotateMaxAgeEnvName:
env[j].Value = fmt.Sprintf("%d", conf.MaxAge)
case logRotateCompressEnvName:
env[j].Value = fmt.Sprintf("%t", conf.Compress)
}
}
}
return nil
}
}
func trainerLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.Trainer.LogRotate
}
func devicePluginLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.DevicePlugin.LogRotate
}
func nodeDLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.NodeD.LogRotate
}
func exporterLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.Exporter.LogRotate
}
func driverLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.Driver.LogRotate
}
func clusterdLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.ClusterD.LogRotate
}
func rsControllerLogRotateConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.LogRotate {
return &spec.RSController.LogRotate
}
func trainerResourceRequirementsConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.Trainer.Resources
}
func vcSchedulerResourceRequirementsConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.VCScheduler.Resources
}
func vcControllerResourceRequirementsConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.VCController.Resources
}
func devicePluginResourceRequirementsConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.DevicePlugin.Resources
}
func nodeDResourceConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.NodeD.Resources
}
func exporterResourceRequirementsConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.Exporter.Resources
}
func clusterdResourceConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.ClusterD.Resources
}
func rsControllerResourceConfigGetter(spec *v1.NPUClusterPolicySpec) *v1.ResourceRequirements {
return &spec.RSController.Resources
}
func schedulerCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.VCScheduler.Command
}
func vcControllerCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.VCController.Command
}
func trainerCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.Trainer.Command
}
func devicePluginCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.DevicePlugin.Command
}
func nodeDCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.NodeD.Command
}
func exporterCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.Exporter.Command
}
func clusterDCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.ClusterD.Command
}
func rsControllerCommandParametersGetter(spec *v1.NPUClusterPolicySpec) *v1.CommandSpec {
return &spec.RSController.Command
}
func devicePluginVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.DevicePlugin.VolumeSpec
}
func nodeDVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.NodeD.VolumeSpec
}
func vcSchedulerVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.VCScheduler.VolumeSpec
}
func vcControllerVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.VCController.VolumeSpec
}
func trainerVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.Trainer.VolumeSpec
}
func exporterVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.Exporter.VolumeSpec
}
func clusterDVolumesGetter(spec *v1.NPUClusterPolicySpec) *v1.VolumesSpec {
return &spec.ClusterD.VolumeSpec
}
func driverEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.Driver.Env
}
func tftEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.MindIOTFT.Env
}
func acpEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.MindIOACP.Env
}
func vcSchedulerEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.VCScheduler.Env
}
func vcControllerEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.VCController.Env
}
func trainerEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.Trainer.Env
}
func nodeDEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.NodeD.Env
}
func clusterDEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.ClusterD.Env
}
func exporterEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.Exporter.Env
}
func devicePluginEnvConfigGetter(spec *v1.NPUClusterPolicySpec) []corev1.EnvVar {
return spec.DevicePlugin.Env
}
func newWorkloadVolumesTransformer[T appsv1.DaemonSet | appsv1.Deployment](
volConfigGetter func(*v1.NPUClusterPolicySpec) *v1.VolumesSpec,
) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
volConfig := volConfigGetter(&r.instance.Spec)
if volConfig == nil || (len(volConfig.Volumes) == 0 && len(volConfig.VolumeMounts) == 0) {
return nil
}
podTmpl := podTemplateSpec(obj)
if len(volConfig.Volumes) > 0 {
if podTmpl.Spec.Volumes == nil {
podTmpl.Spec.Volumes = make([]corev1.Volume, 0)
}
existingVolMap := make(map[string]int, len(podTmpl.Spec.Volumes))
for idx, vol := range podTmpl.Spec.Volumes {
existingVolMap[vol.Name] = idx
}
for _, newVol := range volConfig.Volumes {
if newVol.Name == "" {
continue
}
if idx, exists := existingVolMap[newVol.Name]; exists {
podTmpl.Spec.Volumes[idx] = newVol
} else {
podTmpl.Spec.Volumes = append(podTmpl.Spec.Volumes, newVol)
}
}
}
if len(volConfig.VolumeMounts) > 0 {
for i := range podTmpl.Spec.Containers {
container := &podTmpl.Spec.Containers[i]
if container.VolumeMounts == nil {
container.VolumeMounts = make([]corev1.VolumeMount, 0)
}
existingMountMap := make(map[string]int, len(container.VolumeMounts))
for idx, mount := range container.VolumeMounts {
existingMountMap[mount.Name] = idx
}
for _, newMount := range volConfig.VolumeMounts {
if newMount.Name == "" {
continue
}
if idx, exists := existingMountMap[newMount.Name]; exists {
container.VolumeMounts[idx] = newMount
} else {
container.VolumeMounts = append(container.VolumeMounts, newMount)
}
}
}
}
return nil
}
}
func newWorkloadCommandParameterTransformer[T appsv1.DaemonSet | appsv1.Deployment](
command func(*v1.NPUClusterPolicySpec) *v1.CommandSpec) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
conf := command(&r.instance.Spec)
podTmpl := podTemplateSpec(obj)
containers := podTmpl.Spec.Containers
for i := range containers {
c := &containers[i]
if len(c.Command) == 1 && c.Command[0] == placeholderValue {
c.Command = conf.Command
}
if len(c.Args) == 1 && c.Args[0] == placeholderValue {
c.Args = conf.Args
}
}
return nil
}
}
func newWorkloadResourceRequirementsTransformer[T appsv1.DaemonSet | appsv1.Deployment](
resources func(*v1.NPUClusterPolicySpec) *v1.ResourceRequirements) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
conf := resources(&r.instance.Spec)
if conf == nil {
return nil
}
podTmpl := podTemplateSpec(obj)
containers := podTmpl.Spec.Containers
for i := range containers {
c := &containers[i]
if c.Resources.Limits == nil {
c.Resources.Limits = corev1.ResourceList{}
}
if c.Resources.Requests == nil {
c.Resources.Requests = corev1.ResourceList{}
}
if len(conf.Limits) > 0 {
for key, value := range conf.Limits {
c.Resources.Limits[key] = value
}
}
if len(conf.Requests) > 0 {
for key, value := range conf.Requests {
c.Resources.Requests[key] = value
}
}
}
return nil
}
}
func newWorkloadImageTransformer[T appsv1.DaemonSet | appsv1.Deployment](
image func(*v1.NPUClusterPolicySpec) []*v1.ImageSpec,
) componentResourceTransformer[T] {
podTemplateSpec := newWorkloadPodTemplateSpecGetter[T]()
return func(ctx context.Context, r *NPUClusterPolicyReconciler, obj *T) error {
conf := image(&r.instance.Spec)
podTmpl := podTemplateSpec(obj)
mainImage := conf[0]
imagePullSecrets := podTmpl.Spec.ImagePullSecrets
containers := podTmpl.Spec.Containers
for _, c := range conf {
for _, secret := range c.ImagePullSecrets {
imagePullSecrets = append(imagePullSecrets, corev1.LocalObjectReference{Name: secret})
}
}
podTmpl.Spec.ImagePullSecrets = imagePullSecrets
mainImagename := mainImage.Name()
for i := range containers {
c := &containers[i]
if c.Image == placeholderValue {
if len(mainImagename) == 0 {
return fmt.Errorf("invalid image name: %v", *mainImage)
}
c.Image = mainImagename
c.ImagePullPolicy = corev1.PullPolicy(mainImage.ImagePullPolicy)
}
}
initImages := conf[1:]
if len(initImages) > 0 {
for i, initImage := range initImages {
initContainer := &podTmpl.Spec.InitContainers[i]
if initContainer.Image == placeholderValue {
name := initImage.Name()
if len(name) == 0 {
return fmt.Errorf("invalid init image name at index %d: %v", i, *initImage)
}
initContainer.Image = name
initContainer.ImagePullPolicy = corev1.PullPolicy(initImage.ImagePullPolicy)
}
}
}
return nil
}
}
func runtimeImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{
&spec.OCIRuntime.Image, &spec.OCIRuntime.InitRuntimeImage, &spec.OCIRuntime.InitConfigImage}
}
func vcSchedulerImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.VCScheduler.Image, &spec.VCScheduler.InitImage}
}
func vcControllerImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.VCController.Image, &spec.VCController.InitImage}
}
func trainerImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.Trainer.Image, &spec.Trainer.InitImage}
}
func devicePluginImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{
&spec.DevicePlugin.Image, &spec.DevicePlugin.InitImage}
}
func nodeDImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.NodeD.Image}
}
func exporterImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.Exporter.Image}
}
func driverImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.Driver.Image, &spec.Driver.InitImage}
}
func clusterdImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.ClusterD.Image}
}
func rscontrollerImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.RSController.Image}
}
func mindioTFTImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.MindIOTFT.Image}
}
func mindioACPImageConfigGetter(spec *v1.NPUClusterPolicySpec) []*v1.ImageSpec {
return []*v1.ImageSpec{&spec.MindIOACP.Image}
}
func schedulerConfigMapGetter(spec *v1.NPUClusterPolicySpec) string {
return spec.VCScheduler.SchedulerConfigMap
}
type serviceAccountReconcileHooks struct {
defaultResourceReconcileHooks[*corev1.ServiceAccount]
}
func (h serviceAccountReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *corev1.ServiceAccount) error {
if obj.Namespace == placeholderValue {
obj.Namespace = r.namespace
}
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type clusterRoleReconcileHooks struct {
defaultResourceReconcileHooks[*rbacv1.ClusterRole]
}
func (h clusterRoleReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *rbacv1.ClusterRole,
) error {
if obj.Namespace == placeholderValue {
obj.Namespace = r.namespace
}
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type clusterRoleBindingReconcileHooks struct {
defaultResourceReconcileHooks[*rbacv1.ClusterRoleBinding]
}
func (h clusterRoleBindingReconcileHooks) transform(
ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *rbacv1.ClusterRoleBinding,
) error {
for i := range obj.Subjects {
if obj.Subjects[i].Namespace == placeholderValue {
obj.Subjects[i].Namespace = r.namespace
}
}
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type serviceMoniterReconcileHooks struct {
defaultResourceReconcileHooks[*monitoringv1.ServiceMonitor]
}
func (h serviceMoniterReconcileHooks) transform(ctx context.Context,
r *NPUClusterPolicyReconciler,
obj *monitoringv1.ServiceMonitor,
) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type serviceReconcileHooks struct {
defaultResourceReconcileHooks[*corev1.Service]
}
func (h serviceReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *corev1.Service) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type leaseReconcileHooks struct {
defaultResourceReconcileHooks[*coordinationv1.Lease]
}
func (h leaseReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *coordinationv1.Lease) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type configMapReconcileHooks struct {
defaultResourceReconcileHooks[*corev1.ConfigMap]
}
func ObtainVolcanoVersion(ctx context.Context, r *NPUClusterPolicyReconciler) (version string, err error) {
logger := log.FromContext(ctx)
instance := &v1.NPUClusterPolicy{}
if err := r.Get(ctx, client.ObjectKey{Name: r.instance.Name, Namespace: r.instance.Namespace}, instance); err != nil {
return "", fmt.Errorf("get CR instance: %w", err)
}
tag := instance.Spec.VCScheduler.Image.Tag
part := strings.Split(tag, "-")
if len(part) > 0 {
version = part[len(part)-1]
}
logger.Info("volcano version", "version", version)
return version, err
}
func (h configMapReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *corev1.ConfigMap) error {
logger := log.FromContext(ctx)
if obj.Name != volcanoSchedulerConfigMapName {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
updateConfig(r, obj, ctx)
nodeName := os.Getenv("OPERATOR_NODE_NAME")
if nodeName == "" {
logger.Error(nil, "OPERATOR_NODE_NAME environment variable is not set")
return fmt.Errorf("OPERATOR_NODE_NAME environment variable is not set")
}
node := &corev1.Node{}
if err := r.Get(ctx, client.ObjectKey{Name: nodeName}, node); err != nil {
logger.Error(err, "failed to get node", "node", nodeName)
return fmt.Errorf("get node %s: %w", nodeName, err)
}
version, err := ObtainVolcanoVersion(ctx, r)
if err != nil || version == "" {
logger.Error(err, "failed to get volcano version")
return fmt.Errorf("obtain volcano version: %w", err)
}
arch := node.Status.NodeInfo.Architecture
var name string
switch arch {
case "amd64", "x86_64":
name = fmt.Sprintf("volcano-npu_%s_linux-x86_64", version)
case "arm64", "aarch64":
name = fmt.Sprintf("volcano-npu_%s_linux-aarch64", version)
default:
logger.Error(nil, "unsupported architecture", "architecture", arch)
return fmt.Errorf("unsupported architecture: %s", arch)
}
logger.Info("updating volcano scheduler config", "pluginName", name)
data := obj.Data["volcano-scheduler.conf"]
lines := []string{}
for _, line := range strings.Split(data, "\n") {
if strings.Contains(line, "volcano-npu") {
line = fmt.Sprintf(" - name: %s", name)
}
lines = append(lines, line)
}
obj.Data["volcano-scheduler.conf"] = strings.Join(lines, "\n")
logger.Info("successfully updated volcano scheduler configmap")
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
func updateConfig(r *NPUClusterPolicyReconciler, obj *corev1.ConfigMap, ctx context.Context) {
logger := log.FromContext(ctx)
conf := schedulerConfigMapGetter(&r.instance.Spec)
obj.Data["volcano-scheduler.conf"] = conf
logger.Info("updating volcano scheduler configmap", "volcano-scheduler.conf", conf)
}
type jobReconcileHooks struct {
defaultResourceReconcileHooks[*v1alpha1.Job]
}
func (h jobReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *v1alpha1.Job) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type commandReconcileHooks struct {
defaultResourceReconcileHooks[*busv1alpha1.Command]
}
func (h commandReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *busv1alpha1.Command) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type podGroupReconcileHooks struct {
defaultResourceReconcileHooks[*scheduling.PodGroup]
}
func (h podGroupReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *scheduling.PodGroup) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type queueReconcileHooks struct {
defaultResourceReconcileHooks[*scheduling.Queue]
}
func (h queueReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *scheduling.Queue) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type numatopologyReconcileHooks struct {
defaultResourceReconcileHooks[*nodeinfov1alpha1.Numatopology]
}
func (h numatopologyReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *nodeinfov1alpha1.Numatopology) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type jobTemplateReconcileHooks struct {
defaultResourceReconcileHooks[*flowv1alpha1.JobTemplate]
}
func (h jobTemplateReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *flowv1alpha1.JobTemplate) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type jobFlowReconcileHooks struct {
defaultResourceReconcileHooks[*flowv1alpha1.JobFlow]
}
func (h jobFlowReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler,
obj *flowv1alpha1.JobFlow) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}
type CRDReconcileHooks struct {
defaultResourceReconcileHooks[*apiextensionsv1.CustomResourceDefinition]
}
func (h CRDReconcileHooks) transform(ctx context.Context, r *NPUClusterPolicyReconciler, obj *apiextensionsv1.CustomResourceDefinition) error {
return h.defaultResourceReconcileHooks.transform(ctx, r, obj)
}