• Home
  • Features
  • Pricing
  • Docs
  • Announcements
  • Sign In

kubevirt / kubevirt / 66091390-c356-4ea4-b0ca-2727d96e4ed3

24 Jun 2026 07:30PM UTC coverage: 71.957% (-0.01%) from 71.969%
66091390-c356-4ea4-b0ca-2727d96e4ed3

push

prow

web-flow
Merge pull request #18001 from iholder101/worktree-vep190-pr5-node-hooks

VEP 190: Plugin node hooks

93 of 153 new or added lines in 6 files covered. (60.78%)

4 existing lines in 1 file now uncovered.

81275 of 112950 relevant lines covered (71.96%)

486.58 hits per line

Source File
Press 'n' to go to next uncovered line, 'b' for previous

78.13
/pkg/virt-handler/plugins/manager.go
1
/*
2
 * This file is part of the KubeVirt project
3
 *
4
 * Licensed under the Apache License, Version 2.0 (the "License");
5
 * you may not use this file except in compliance with the License.
6
 * You may obtain a copy of the License at
7
 *
8
 *     http://www.apache.org/licenses/LICENSE-2.0
9
 *
10
 * Unless required by applicable law or agreed to in writing, software
11
 * distributed under the License is distributed on an "AS IS" BASIS,
12
 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13
 * See the License for the specific language governing permissions and
14
 * limitations under the License.
15
 *
16
 * Copyright The KubeVirt Authors.
17
 *
18
 */
19

20
package plugins
21

22
import (
23
        "fmt"
24
        "slices"
25
        "sort"
26
        "time"
27

28
        "k8s.io/client-go/tools/cache"
29

30
        pluginv1alpha1 "kubevirt.io/api/plugin/v1alpha1"
31

32
        virtv1 "kubevirt.io/api/core/v1"
33
        "kubevirt.io/client-go/log"
34

35
        hooksclient "kubevirt.io/kubevirt/pkg/hooks/plugins/v1alpha1"
36
        plugincel "kubevirt.io/kubevirt/pkg/plugins/cel"
37
        virtconfig "kubevirt.io/kubevirt/pkg/virt-config"
38
)
39

40
//go:generate mockgen -source $GOFILE -package=$GOPACKAGE -destination=generated_mock_manager.go
41

42
type NodeHookExecutor interface {
43
        CallNodeHooks(hookPoint pluginv1alpha1.NodeHookPoint, vmi *virtv1.VirtualMachineInstance, nodeName string) error
44
}
45

46
type nodeHookManager struct {
47
        pluginStore   cache.Store
48
        clusterConfig *virtconfig.ClusterConfig
49
        celEvaluator  *plugincel.Evaluator
50
}
51

52
func NewNodeHookManager(pluginStore cache.Store, clusterConfig *virtconfig.ClusterConfig) (NodeHookExecutor, error) {
7✔
53
        celEvaluator, err := plugincel.NewEvaluator()
7✔
54
        if err != nil {
7✔
NEW
55
                return nil, fmt.Errorf("failed to create CEL evaluator: %w", err)
×
NEW
56
        }
×
57
        return &nodeHookManager{
7✔
58
                pluginStore:   pluginStore,
7✔
59
                clusterConfig: clusterConfig,
7✔
60
                celEvaluator:  celEvaluator,
7✔
61
        }, nil
7✔
62
}
63

64
type matchedHook struct {
65
        pluginName string
66
        nodeHook   pluginv1alpha1.NodeHook
67
}
68

69
func (m *nodeHookManager) CallNodeHooks(hookPoint pluginv1alpha1.NodeHookPoint, vmi *virtv1.VirtualMachineInstance, nodeName string) error {
7✔
70
        if !m.clusterConfig.PluginsEnabled() {
8✔
71
                return nil
1✔
72
        }
1✔
73

74
        items := m.pluginStore.List()
6✔
75
        if len(items) == 0 {
7✔
76
                return nil
1✔
77
        }
1✔
78

79
        var matches []matchedHook
5✔
80
        for _, obj := range items {
12✔
81
                plugin, ok := obj.(*pluginv1alpha1.Plugin)
7✔
82
                if !ok {
7✔
NEW
83
                        log.Log.Warningf("Unexpected object type in plugin store: %T", obj)
×
NEW
84
                        continue
×
85
                }
86

87
                for _, nh := range plugin.Spec.NodeHooks {
14✔
88
                        if !slices.Contains(nh.PermittedHooks, hookPoint) {
8✔
89
                                log.Log.Object(vmi).V(4).Infof("Hook point %s not permitted for plugin %s, skipping", hookPoint, plugin.Name)
1✔
90
                                continue
1✔
91
                        }
92

93
                        match, err := m.celEvaluator.EvaluateCondition(nh.Condition, map[string]any{"vmi": vmi})
6✔
94
                        if err != nil {
6✔
NEW
95
                                return fmt.Errorf("CEL evaluation failed for plugin %s: %w", plugin.Name, err)
×
NEW
96
                        }
×
97
                        if !match {
6✔
NEW
98
                                log.Log.Object(vmi).V(3).Infof("CEL condition for plugin %s did not match, skipping hook", plugin.Name)
×
NEW
99
                                continue
×
100
                        }
101

102
                        matches = append(matches, matchedHook{
6✔
103
                                pluginName: plugin.Name,
6✔
104
                                nodeHook:   nh,
6✔
105
                        })
6✔
106
                }
107
        }
108

109
        sort.SliceStable(matches, func(i, j int) bool {
8✔
110
                return matches[i].pluginName < matches[j].pluginName
3✔
111
        })
3✔
112

113
        for _, match := range matches {
9✔
114
                timeout := hooksclient.DefaultTimeout
4✔
115
                if match.nodeHook.Timeout != nil {
4✔
NEW
116
                        timeout = match.nodeHook.Timeout.Duration
×
NEW
117
                }
×
118

119
                if err := executeHook(match.nodeHook.Socket, string(hookPoint), vmi, match.pluginName, nodeName, timeout); err != nil {
8✔
120
                        switch match.nodeHook.FailureStrategy {
4✔
121
                        case pluginv1alpha1.FailureStrategyIgnore:
1✔
122
                                log.Log.Object(vmi).Warningf("Node hook %s from plugin %s failed (ignored): %v", hookPoint, match.pluginName, err)
1✔
123
                                continue
1✔
124
                        default: // FailureStrategyFail
3✔
125
                                return fmt.Errorf("node hook %s from plugin %s failed: %w", hookPoint, match.pluginName, err)
3✔
126
                        }
127
                }
128
        }
129

130
        return nil
2✔
131
}
132

133
func executeHook(socketPath, hookPoint string, vmi *virtv1.VirtualMachineInstance, pluginName string, nodeName string, timeout time.Duration) error {
4✔
134
        conn, err := hooksclient.DialSocket(socketPath)
4✔
135
        if err != nil {
8✔
136
                return fmt.Errorf("failed to dial plugin %s at %s: %w", pluginName, socketPath, err)
4✔
137
        }
4✔
NEW
138
        defer conn.Close()
×
NEW
139

×
NEW
140
        client := hooksclient.NewNodeHookServiceClient(conn)
×
NEW
141
        return hooksclient.ExecuteNodeHook(client, hookPoint, vmi, nodeName, timeout)
×
142
}
STATUS · Troubleshooting · Open an Issue · Sales · Support · CAREERS · ENTERPRISE · START FREE · SCHEDULE DEMO
ANNOUNCEMENTS · TWITTER · TOS & SLA · Supported CI Services · What's a CI service? · Automated Testing

© 2026 Coveralls, Inc