* Copyright (c) 2026 Huawei Technologies Co., Ltd.
* openFuyao is licensed under Mulan PSL v2.
* You can use this software according to the terms and conditions of the Mulan PSL v2.
* You may obtain a copy of Mulan PSL v2 at:
* http://license.coscl.org.cn/MulanPSL2
* THIS SOFTWARE IS PROVIDED ON AN "AS IS" BASIS, WITHOUT WARRANTIES OF ANY KIND,
* EITHER EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO NON-INFRINGEMENT,
* MERCHANTABILITY OR FIT FOR A PARTICULAR PURPOSE.
* See the Mulan PSL v2 for more details.
*/
package main
import (
"encoding/json"
"fmt"
"sync"
"time"
"many-core-orchestrator/api/v1alpha1"
"many-core-orchestrator/pkg/constant"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/client-go/dynamic"
"k8s.io/client-go/dynamic/dynamicinformer"
"k8s.io/client-go/rest"
"k8s.io/client-go/tools/cache"
"k8s.io/klog/v2"
"volcano.sh/volcano/pkg/scheduler/api"
"volcano.sh/volcano/pkg/scheduler/framework"
)
const (
PluginName = "mco-plugin"
defaultCRStatusTTLSeconds = 30
neutralScore = 50.0
)
var (
cacheMu sync.RWMutex
cacheReports = make(map[string]*v1alpha1.NodeInterferenceNodeReport)
cacheLastRefresh time.Time
cacheLastMaxSeq int64
informerOnce sync.Once
informerErr error
)
type mcoPlugin struct {
args *pluginArgs
}
type pluginArgs struct {
CRName string `json:"crName"`
CRStatusTTLSeconds int `json:"crStatusTTLSeconds"`
HardConstraints hardConstraints `json:"hardConstraints"`
FallbackWhenCROutdated bool `json:"fallbackWhenCROutdated"`
}
type hardConstraints struct {
PsiMemoryFullAvg10 float64 `json:"psiMemoryFullAvg10"`
PsiIoSomeAvg10Threshold float64 `json:"psiIoSomeAvg10Threshold"`
}
func (p *mcoPlugin) Name() string {
return PluginName
}
func (p *mcoPlugin) OnSessionOpen(ssn *framework.Session) {
klog.V(4).Infof("[%s] session opened, registering filter/score", PluginName)
p.ensureInformerStarted()
ssn.AddPredicateFn(p.Name(), func(task *api.TaskInfo, node *api.NodeInfo) ([]*api.Status, error) {
return nil, p.filterNode(task, node)
})
ssn.AddNodeOrderFn(p.Name(), func(task *api.TaskInfo, node *api.NodeInfo) (float64, error) {
return p.scoreNode(task, node)
})
ssn.AddBatchNodeOrderFn(p.Name(), func(task *api.TaskInfo, nodes []*api.NodeInfo) (map[string]float64, error) {
scores := make(map[string]float64, len(nodes))
for _, node := range nodes {
s, err := p.scoreNode(task, node)
if err != nil {
klog.Warningf("[%s] batch score failed for node %s: %v", PluginName, node.Name, err)
scores[node.Name] = neutralScore
continue
}
scores[node.Name] = s
}
return scores, nil
})
}
func (p *mcoPlugin) OnSessionClose(ssn *framework.Session) {
klog.V(4).Infof("[%s] session closed", PluginName)
}
func (p *mcoPlugin) filterNode(task *api.TaskInfo, node *api.NodeInfo) error {
if needsKata(task) {
if node.Node.Labels[constant.KataRuntimeLabel] != "true" ||
node.Node.Labels[constant.KataReadyLabel] != "true" {
klog.V(5).Infof("[%s] filter: node %s lacks kata runtime or not kata-ready", PluginName, node.Name)
return fmt.Errorf("node %s does not have kata runtime ready", node.Name)
}
}
report, ok := p.getCachedReport(node.Name)
if !ok {
return nil
}
if !report.PSIAvailable {
klog.V(3).Infof("[%s] filter: PSI unavailable for node %s, skipping PSI constraints",
PluginName, node.Name)
return nil
}
psiMemFull := report.Metrics.PSIMemFullAvg10
if psiMemFull > p.args.HardConstraints.PsiMemoryFullAvg10 {
klog.V(3).Infof("[%s] filter: node %s excluded (psiMemoryFull=%.2f > %.2f)",
PluginName, node.Name, psiMemFull, p.args.HardConstraints.PsiMemoryFullAvg10)
return fmt.Errorf("node %s memory full pressure (psiMemoryFull=%.2f)", node.Name, psiMemFull)
}
psiIoSome := report.Metrics.PSIIOAvg10
if psiIoSome > p.args.HardConstraints.PsiIoSomeAvg10Threshold {
klog.V(3).Infof("[%s] filter: node %s excluded (psiIoSome=%.2f > %.2f)",
PluginName, node.Name, psiIoSome, p.args.HardConstraints.PsiIoSomeAvg10Threshold)
return fmt.Errorf("node %s I/O pressure (psiIoSome=%.2f)", node.Name, psiIoSome)
}
return nil
}
func (p *mcoPlugin) scoreNode(task *api.TaskInfo, node *api.NodeInfo) (float64, error) {
_ = task
report, ok := p.getCachedReport(node.Name)
if !ok {
klog.V(3).Infof("[%s] score: node %s not in CR or CR stale, returning neutral", PluginName, node.Name)
return neutralScore, nil
}
if report.Status != "ok" {
klog.V(3).Infof("[%s] score: node %s status=%q, using neutral score",
PluginName, node.Name, report.Status)
return neutralScore, nil
}
score := 100.0 * (1.0 - report.InterferenceLevel)
if score < 0 {
score = 0
}
if score > 100 {
score = 100
}
klog.V(3).Infof("[%s] score: node %s status=%s interferenceLevel=%.3f → score=%.1f",
PluginName, node.Name, report.Status, report.InterferenceLevel, score)
return score, nil
}
func (p *mcoPlugin) getCachedReport(nodeName string) (*v1alpha1.NodeInterferenceNodeReport, bool) {
cacheMu.RLock()
defer cacheMu.RUnlock()
ttl := time.Duration(p.args.CRStatusTTLSeconds) * time.Second
if ttl <= 0 {
ttl = defaultCRStatusTTLSeconds * time.Second
}
elapsed := time.Since(cacheLastRefresh)
if elapsed > ttl {
klog.Warningf("[%s] CR data stale (last update %v ago, TTL=%v), falling back to neutral score — check Analyzer health",
PluginName, elapsed.Round(time.Second), ttl)
return nil, false
}
report, ok := cacheReports[nodeName]
if !ok {
return nil, false
}
return report, true
}
func (p *mcoPlugin) updateCache(nir *v1alpha1.NodeInterferenceReport) {
cacheMu.Lock()
defer cacheMu.Unlock()
var maxSeq int64
newReports := make(map[string]*v1alpha1.NodeInterferenceNodeReport, len(nir.Status.Nodes))
for nodeName, report := range nir.Status.Nodes {
reportCopy := report
newReports[nodeName] = &reportCopy
if report.Sequence > maxSeq {
maxSeq = report.Sequence
}
}
if maxSeq != cacheLastMaxSeq {
cacheLastRefresh = time.Now()
klog.V(5).Infof("[%s] cache updated: %d nodes (maxSeq=%d)", PluginName, len(newReports), maxSeq)
cacheLastMaxSeq = maxSeq
}
cacheReports = newReports
}
func (p *mcoPlugin) clearCache() {
cacheMu.Lock()
defer cacheMu.Unlock()
cacheReports = make(map[string]*v1alpha1.NodeInterferenceNodeReport)
cacheLastRefresh = time.Time{}
cacheLastMaxSeq = 0
klog.V(4).Infof("[%s] cache cleared (CR deleted)", PluginName)
}
func (p *mcoPlugin) ensureInformerStarted() {
informerOnce.Do(func() {
stopCh := make(chan struct{})
informerErr = p.startInformer(stopCh)
if informerErr != nil {
klog.Errorf("[%s] CR informer failed to start: %v — scheduling will use fallback", PluginName, informerErr)
}
})
}
func (p *mcoPlugin) startInformer(stopCh <-chan struct{}) error {
config, err := rest.InClusterConfig()
if err != nil {
return fmt.Errorf("in-cluster config: %w", err)
}
dynClient, err := dynamic.NewForConfig(config)
if err != nil {
return fmt.Errorf("dynamic client: %w", err)
}
gvr := schema.GroupVersionResource{
Group: v1alpha1.GroupVersion.Group,
Version: v1alpha1.GroupVersion.Version,
Resource: "nodeinterferencereports",
}
factory := dynamicinformer.NewFilteredDynamicSharedInformerFactory(dynClient, 5*time.Second, "", nil)
informer := factory.ForResource(gvr).Informer()
informer.AddEventHandler(cache.ResourceEventHandlerFuncs{
AddFunc: func(obj interface{}) {
nir := p.unstructuredToReport(obj)
if nir == nil || nir.Name != "cluster" {
return
}
klog.V(4).Infof("[%s] CR 'cluster' created, updating cache", PluginName)
p.updateCache(nir)
},
UpdateFunc: func(_, obj interface{}) {
nir := p.unstructuredToReport(obj)
if nir == nil || nir.Name != "cluster" {
return
}
klog.V(6).Infof("[%s] CR 'cluster' updated, refreshing cache", PluginName)
p.updateCache(nir)
},
DeleteFunc: func(obj interface{}) {
if tombstone, ok := obj.(cache.DeletedFinalStateUnknown); ok {
obj = tombstone.Obj
}
nir := p.unstructuredToReport(obj)
if nir == nil || nir.Name != "cluster" {
return
}
klog.V(4).Infof("[%s] CR 'cluster' deleted, clearing cache", PluginName)
p.clearCache()
},
})
go informer.Run(stopCh)
klog.Infof("[%s] waiting for CR informer cache sync...", PluginName)
if !cache.WaitForCacheSync(stopCh, informer.HasSynced) {
return fmt.Errorf("timed out waiting for CR informer cache sync")
}
klog.Infof("[%s] CR informer synced successfully", PluginName)
return nil
}
func (p *mcoPlugin) unstructuredToReport(obj interface{}) *v1alpha1.NodeInterferenceReport {
u, ok := obj.(*unstructured.Unstructured)
if !ok {
return nil
}
nir := &v1alpha1.NodeInterferenceReport{}
if err := runtime.DefaultUnstructuredConverter.FromUnstructured(u.Object, nir); err != nil {
klog.Warningf("[%s] failed to convert CR: %v", PluginName, err)
return nil
}
return nir
}
func needsKata(task *api.TaskInfo) bool {
if task == nil || task.Pod == nil {
return false
}
rc := task.Pod.Spec.RuntimeClassName
if rc == nil {
return false
}
return len(*rc) >= 5 && (*rc)[:5] == "kata-"
}
func parseArgs(arguments framework.Arguments) (*pluginArgs, error) {
args := &pluginArgs{
CRName: "cluster",
CRStatusTTLSeconds: defaultCRStatusTTLSeconds,
FallbackWhenCROutdated: true,
HardConstraints: hardConstraints{
PsiMemoryFullAvg10: 0.0,
PsiIoSomeAvg10Threshold: 20.0,
},
}
raw, ok := arguments[PluginName]
if !ok || raw == nil {
klog.Infof("[%s] no plugin arguments provided, using defaults", PluginName)
return args, nil
}
normalized := normalizeMap(raw)
b, err := json.Marshal(normalized)
if err != nil {
return nil, fmt.Errorf("failed to marshal plugin arguments: %w", err)
}
if err := json.Unmarshal(b, args); err != nil {
return nil, fmt.Errorf("failed to parse plugin arguments: %w", err)
}
return args, nil
}
func normalizeMap(v interface{}) interface{} {
switch val := v.(type) {
case map[interface{}]interface{}:
out := make(map[string]interface{}, len(val))
for k, vv := range val {
out[fmt.Sprint(k)] = normalizeMap(vv)
}
return out
case map[string]interface{}:
out := make(map[string]interface{}, len(val))
for k, vv := range val {
out[k] = normalizeMap(vv)
}
return out
case []interface{}:
out := make([]interface{}, len(val))
for i, vv := range val {
out[i] = normalizeMap(vv)
}
return out
default:
return v
}
}
func New(arguments framework.Arguments) framework.Plugin {
klog.Infof("[%s] initializing MCO interference-aware scheduling plugin (dynamic .so)", PluginName)
args, err := parseArgs(arguments)
if err != nil {
klog.Errorf("[%s] failed to parse arguments: %v, using defaults", PluginName, err)
args = &pluginArgs{
CRName: "cluster",
CRStatusTTLSeconds: defaultCRStatusTTLSeconds,
FallbackWhenCROutdated: true,
HardConstraints: hardConstraints{
PsiMemoryFullAvg10: 0.0,
PsiIoSomeAvg10Threshold: 20.0,
},
}
}
return &mcoPlugin{
args: args,
}
}
func main() {
}