github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/pkg/plugins/k8s/k8s_plugin.go (about) 1 package k8s 2 3 import ( 4 "strings" 5 6 "sigs.k8s.io/controller-runtime/pkg/log" 7 8 sriovnetworkv1 "github.com/k8snetworkplumbingwg/sriov-network-operator/api/v1" 9 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts" 10 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/helper" 11 hostTypes "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types" 12 plugins "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/plugins" 13 "github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/vars" 14 ) 15 16 var PluginName = "k8s" 17 18 type K8sPlugin struct { 19 PluginName string 20 SpecVersion string 21 hostHelper helper.HostHelpersInterface 22 23 openVSwitchService *hostTypes.Service 24 sriovService *hostTypes.Service 25 sriovPostNetworkService *hostTypes.Service 26 27 updateTarget *k8sUpdateTarget 28 } 29 type updateTargetReq struct { 30 update bool 31 reboot bool 32 } 33 34 // set need update flag for updateTargetReq 35 func (u *updateTargetReq) SetNeedUpdate() { 36 u.update = true 37 } 38 39 // set need update and reboot flags for updateTargetReq 40 func (u *updateTargetReq) SetNeedReboot() { 41 u.update = true 42 u.reboot = true 43 } 44 45 // returns state of the update flag 46 func (u *updateTargetReq) NeedUpdate() bool { 47 return u.update 48 } 49 50 // returns state of the reboot flag 51 func (u *updateTargetReq) NeedReboot() bool { 52 return u.reboot 53 } 54 55 type k8sUpdateTarget struct { 56 sriovScript updateTargetReq 57 sriovPostNetworkScript updateTargetReq 58 openVSwitch updateTargetReq 59 } 60 61 func (u *k8sUpdateTarget) String() string { 62 var updateList []string 63 if u.sriovScript.NeedReboot() { 64 updateList = append(updateList, "sriov-config.service") 65 } 66 if u.sriovPostNetworkScript.NeedReboot() { 67 updateList = append(updateList, "sriov-config-post-network.service") 68 } 69 if u.openVSwitch.NeedReboot() { 70 updateList = append(updateList, "ovs-vswitchd.service") 71 } 72 return strings.Join(updateList, ",") 73 } 74 75 func (u *k8sUpdateTarget) needReboot() bool { 76 return u.sriovScript.NeedReboot() || u.sriovPostNetworkScript.NeedReboot() || u.openVSwitch.NeedReboot() 77 } 78 79 func (u *k8sUpdateTarget) reset() { 80 u.sriovScript = updateTargetReq{} 81 u.sriovPostNetworkScript = updateTargetReq{} 82 u.openVSwitch = updateTargetReq{} 83 } 84 85 const ( 86 bindataManifestPath = "bindata/manifests/" 87 switchdevManifestPath = bindataManifestPath + "switchdev-config/" 88 switchdevUnits = switchdevManifestPath + "switchdev-units/" 89 sriovUnits = bindataManifestPath + "sriov-config-service/kubernetes/" 90 sriovUnitFile = sriovUnits + "sriov-config-service.yaml" 91 sriovPostNetworkUnitFile = sriovUnits + "sriov-config-post-network-service.yaml" 92 ovsUnitFile = switchdevManifestPath + "ovs-units/ovs-vswitchd.service.yaml" 93 ) 94 95 // Initialize our plugin and set up initial values 96 func NewK8sPlugin(helper helper.HostHelpersInterface) (plugins.VendorPlugin, error) { 97 k8sPluging := &K8sPlugin{ 98 PluginName: PluginName, 99 SpecVersion: "1.0", 100 hostHelper: helper, 101 updateTarget: &k8sUpdateTarget{}, 102 } 103 104 return k8sPluging, k8sPluging.readManifestFiles() 105 } 106 107 // Name returns the name of the plugin 108 func (p *K8sPlugin) Name() string { 109 return p.PluginName 110 } 111 112 // Spec returns the version of the spec expected by the plugin 113 func (p *K8sPlugin) Spec() string { 114 return p.SpecVersion 115 } 116 117 // OnNodeStateChange Invoked when SriovNetworkNodeState CR is created or updated, return if need dain and/or reboot node 118 func (p *K8sPlugin) OnNodeStateChange(new *sriovnetworkv1.SriovNetworkNodeState) (needDrain bool, needReboot bool, err error) { 119 log.Log.Info("k8s plugin OnNodeStateChange()") 120 needDrain = false 121 needReboot = false 122 123 p.updateTarget.reset() 124 // TODO add check for enableOvsOffload in OperatorConfig later 125 // Update services if switchdev required 126 if !vars.UsingSystemdMode && !sriovnetworkv1.IsSwitchdevModeSpec(new.Spec) { 127 return 128 } 129 130 if sriovnetworkv1.IsSwitchdevModeSpec(new.Spec) { 131 // Check services 132 err = p.ovsServiceStateUpdate() 133 if err != nil { 134 log.Log.Error(err, "k8s plugin OnNodeStateChange(): failed") 135 return 136 } 137 } 138 139 if vars.UsingSystemdMode { 140 // Check sriov service 141 err = p.sriovServicesStateUpdate() 142 if err != nil { 143 log.Log.Error(err, "k8s plugin OnNodeStateChange(): failed") 144 return 145 } 146 } 147 148 if p.updateTarget.needReboot() { 149 needDrain = true 150 needReboot = true 151 log.Log.Info("k8s plugin OnNodeStateChange(): needReboot to update", "target", p.updateTarget) 152 } 153 154 return 155 } 156 157 // Apply config change 158 func (p *K8sPlugin) Apply() error { 159 log.Log.Info("k8s plugin Apply()") 160 if vars.UsingSystemdMode { 161 if err := p.updateSriovServices(); err != nil { 162 return err 163 } 164 } 165 return p.updateOVSService() 166 } 167 168 func (p *K8sPlugin) readOpenVSwitchdManifest() error { 169 openVSwitchService, err := p.hostHelper.ReadServiceInjectionManifestFile(ovsUnitFile) 170 if err != nil { 171 return err 172 } 173 p.openVSwitchService = openVSwitchService 174 return nil 175 } 176 177 func (p *K8sPlugin) readSriovServiceManifest() error { 178 sriovService, err := p.hostHelper.ReadServiceManifestFile(sriovUnitFile) 179 if err != nil { 180 return err 181 } 182 p.sriovService = sriovService 183 return nil 184 } 185 186 func (p *K8sPlugin) readSriovPostNetworkServiceManifest() error { 187 sriovService, err := p.hostHelper.ReadServiceManifestFile(sriovPostNetworkUnitFile) 188 if err != nil { 189 return err 190 } 191 p.sriovPostNetworkService = sriovService 192 return nil 193 } 194 195 func (p *K8sPlugin) readManifestFiles() error { 196 if err := p.readOpenVSwitchdManifest(); err != nil { 197 return err 198 } 199 if err := p.readSriovServiceManifest(); err != nil { 200 return err 201 } 202 if err := p.readSriovPostNetworkServiceManifest(); err != nil { 203 return err 204 } 205 return nil 206 } 207 208 func (p *K8sPlugin) sriovServicesStateUpdate() error { 209 for _, s := range []struct { 210 srv *hostTypes.Service 211 update *updateTargetReq 212 }{ 213 {srv: p.sriovService, update: &p.updateTarget.sriovScript}, 214 {srv: p.sriovPostNetworkService, update: &p.updateTarget.sriovPostNetworkScript}, 215 } { 216 isServiceEnabled, err := p.hostHelper.IsServiceEnabled(s.srv.Path) 217 if err != nil { 218 return err 219 } 220 // create and enable the service if it doesn't exist or is not enabled 221 if !isServiceEnabled { 222 s.update.SetNeedReboot() 223 } else { 224 if p.isSystemDServiceNeedUpdate(s.srv) { 225 s.update.SetNeedReboot() 226 } 227 } 228 } 229 return nil 230 } 231 232 func (p *K8sPlugin) updateSriovServices() error { 233 for _, s := range []struct { 234 srv *hostTypes.Service 235 update *updateTargetReq 236 }{ 237 {srv: p.sriovService, update: &p.updateTarget.sriovScript}, 238 {srv: p.sriovPostNetworkService, update: &p.updateTarget.sriovPostNetworkScript}, 239 } { 240 if s.update.NeedUpdate() { 241 err := p.hostHelper.EnableService(s.srv) 242 if err != nil { 243 return err 244 } 245 } 246 } 247 return nil 248 } 249 250 func (p *K8sPlugin) ovsServiceStateUpdate() error { 251 exist, err := p.hostHelper.IsServiceExist(p.openVSwitchService.Path) 252 if err != nil { 253 return err 254 } 255 if !exist { 256 log.Log.Info("k8s plugin systemServicesStateUpdate(): WARNING! openvswitch system service not found, skip update", 257 "service", p.openVSwitchService.Name) 258 return nil 259 } 260 if !p.isSystemDServiceNeedUpdate(p.openVSwitchService) { 261 // service is up to date 262 return nil 263 } 264 if p.isOVSHwOffloadingEnabled() { 265 p.updateTarget.openVSwitch.SetNeedUpdate() 266 } else { 267 p.updateTarget.openVSwitch.SetNeedReboot() 268 } 269 return nil 270 } 271 272 func (p *K8sPlugin) updateOVSService() error { 273 if p.updateTarget.openVSwitch.NeedUpdate() { 274 return p.hostHelper.UpdateSystemService(p.openVSwitchService) 275 } 276 return nil 277 } 278 279 func (p *K8sPlugin) isSystemDServiceNeedUpdate(serviceObj *hostTypes.Service) bool { 280 systemService, err := p.hostHelper.ReadService(serviceObj.Path) 281 if err != nil { 282 log.Log.Error(err, "k8s plugin isSystemDServiceNeedUpdate(): failed to read service file, ignoring", 283 "path", serviceObj.Path) 284 return false 285 } 286 if systemService != nil { 287 needChange, err := p.hostHelper.CompareServices(systemService, serviceObj) 288 if err != nil { 289 log.Log.Error(err, "k8s plugin isSystemDServiceNeedUpdate(): failed to compare service, ignoring") 290 return false 291 } 292 return needChange 293 } 294 return false 295 } 296 297 // try to check if OVS HW offloading is already enabled 298 // required to avoid unneeded reboots in case if HW offloading is already enabled by different entity 299 // TODO move to the right package and avoid ovs-vsctl binary call 300 // the function should be revisited when support for software bridge configuration 301 // is implemented 302 func (p *K8sPlugin) isOVSHwOffloadingEnabled() bool { 303 log.Log.V(2).Info("isOVSHwOffloadingEnabled()") 304 exit, err := p.hostHelper.Chroot(consts.Chroot) 305 if err != nil { 306 return false 307 } 308 defer exit() 309 out, _, err := p.hostHelper.RunCommand("ovs-vsctl", "get", "Open_vSwitch", ".", "other_config:hw-offload") 310 if err != nil { 311 log.Log.V(2).Info("isOVSHwOffloadingEnabled() check failed, assume offloading is disabled", "error", err.Error()) 312 return false 313 } 314 if strings.Trim(out, "\n") == `"true"` { 315 log.Log.V(2).Info("isOVSHwOffloadingEnabled() offloading is already enabled") 316 return true 317 } 318 return false 319 }