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  }