github.com/k8snetworkplumbingwg/sriov-network-operator@v1.2.1-0.20240408194816-2d2e5a45d453/pkg/host/internal/service/service.go (about)

     1  package service
     2  
     3  import (
     4  	"fmt"
     5  	"io"
     6  	"os"
     7  	"path"
     8  	"path/filepath"
     9  	"strings"
    10  
    11  	"github.com/coreos/go-systemd/v22/unit"
    12  	"gopkg.in/yaml.v3"
    13  	"sigs.k8s.io/controller-runtime/pkg/log"
    14  
    15  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/consts"
    16  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/host/types"
    17  	"github.com/k8snetworkplumbingwg/sriov-network-operator/pkg/utils"
    18  )
    19  
    20  // TODO: handle this to support unit-tests
    21  const systemdDir = "/usr/lib/systemd/system/"
    22  
    23  type service struct {
    24  	utilsHelper utils.CmdInterface
    25  }
    26  
    27  func New(utilsHelper utils.CmdInterface) types.ServiceInterface {
    28  	return &service{utilsHelper: utilsHelper}
    29  }
    30  
    31  // ServiceInjectionManifestFile service injection manifest file structure
    32  type ServiceInjectionManifestFile struct {
    33  	Name    string
    34  	Dropins []struct {
    35  		Contents string
    36  	}
    37  }
    38  
    39  // IsServiceExist check if service unit exist
    40  func (s *service) IsServiceExist(servicePath string) (bool, error) {
    41  	_, err := os.Stat(path.Join(consts.Chroot, servicePath))
    42  	if err != nil {
    43  		if os.IsNotExist(err) {
    44  			return false, nil
    45  		}
    46  		return false, err
    47  	}
    48  
    49  	return true, nil
    50  }
    51  
    52  // IsServiceEnabled check if service exist and enabled
    53  func (s *service) IsServiceEnabled(servicePath string) (bool, error) {
    54  	exist, err := s.IsServiceExist(servicePath)
    55  	if err != nil || !exist {
    56  		return false, err
    57  	}
    58  	serviceName := filepath.Base(servicePath)
    59  	// Change root dir
    60  	exit, err := s.utilsHelper.Chroot(consts.Chroot)
    61  	if err != nil {
    62  		return false, err
    63  	}
    64  	defer exit()
    65  
    66  	// TODO: add check for the output and logs
    67  	_, _, err = s.utilsHelper.RunCommand("systemctl", "is-enabled", serviceName)
    68  	return err == nil, nil
    69  }
    70  
    71  // ReadService read service from given path
    72  func (s *service) ReadService(servicePath string) (*types.Service, error) {
    73  	data, err := os.ReadFile(path.Join(consts.Chroot, servicePath))
    74  	if err != nil {
    75  		return nil, err
    76  	}
    77  
    78  	return &types.Service{
    79  		Name:    filepath.Base(servicePath),
    80  		Path:    servicePath,
    81  		Content: string(data),
    82  	}, nil
    83  }
    84  
    85  // EnableService creates service file and enables it with systemctl enable
    86  func (s *service) EnableService(service *types.Service) error {
    87  	// Write service file
    88  	err := os.WriteFile(path.Join(consts.Chroot, service.Path), []byte(service.Content), 0644)
    89  	if err != nil {
    90  		return err
    91  	}
    92  
    93  	// Change root dir
    94  	exit, err := s.utilsHelper.Chroot(consts.Chroot)
    95  	if err != nil {
    96  		return err
    97  	}
    98  	defer exit()
    99  
   100  	// Enable service
   101  	_, _, err = s.utilsHelper.RunCommand("systemctl", "enable", service.Name)
   102  	return err
   103  }
   104  
   105  // CompareServices returns true if serviceA needs update(doesn't contain all fields from service B)
   106  func (s *service) CompareServices(serviceA, serviceB *types.Service) (bool, error) {
   107  	optsA, err := unit.Deserialize(strings.NewReader(serviceA.Content))
   108  	if err != nil {
   109  		return false, err
   110  	}
   111  	optsB, err := unit.Deserialize(strings.NewReader(serviceB.Content))
   112  	if err != nil {
   113  		return false, err
   114  	}
   115  
   116  OUTER:
   117  	for _, optB := range optsB {
   118  		for _, optA := range optsA {
   119  			if optA.Match(optB) {
   120  				continue OUTER
   121  			}
   122  		}
   123  		log.Log.V(2).Info("CompareServices", "ServiceA", optsA, "ServiceB", *optB)
   124  		return true, nil
   125  	}
   126  
   127  	return false, nil
   128  }
   129  
   130  // ReadServiceInjectionManifestFile reads service injection file
   131  func (s *service) ReadServiceInjectionManifestFile(path string) (*types.Service, error) {
   132  	data, err := os.ReadFile(path)
   133  	if err != nil {
   134  		return nil, err
   135  	}
   136  
   137  	var serviceContent ServiceInjectionManifestFile
   138  	if err := yaml.Unmarshal(data, &serviceContent); err != nil {
   139  		return nil, err
   140  	}
   141  
   142  	return &types.Service{
   143  		Name:    serviceContent.Name,
   144  		Path:    systemdDir + serviceContent.Name,
   145  		Content: serviceContent.Dropins[0].Contents,
   146  	}, nil
   147  }
   148  
   149  // ReadServiceManifestFile reads service file
   150  func (s *service) ReadServiceManifestFile(path string) (*types.Service, error) {
   151  	data, err := os.ReadFile(path)
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  
   156  	var serviceFile *types.ServiceManifestFile
   157  	if err := yaml.Unmarshal(data, &serviceFile); err != nil {
   158  		return nil, err
   159  	}
   160  
   161  	return &types.Service{
   162  		Name:    serviceFile.Name,
   163  		Path:    "/etc/systemd/system/" + serviceFile.Name,
   164  		Content: serviceFile.Contents,
   165  	}, nil
   166  }
   167  
   168  func (s *service) UpdateSystemService(serviceObj *types.Service) error {
   169  	systemService, err := s.ReadService(serviceObj.Path)
   170  	if err != nil {
   171  		return err
   172  	}
   173  	if systemService == nil {
   174  		// Invalid case to reach here
   175  		return fmt.Errorf("can't update non-existing service %q", serviceObj.Name)
   176  	}
   177  	serviceOptions, err := unit.Deserialize(strings.NewReader(serviceObj.Content))
   178  	if err != nil {
   179  		return err
   180  	}
   181  	updatedService, err := appendToService(systemService, serviceOptions...)
   182  	if err != nil {
   183  		return err
   184  	}
   185  
   186  	return s.EnableService(updatedService)
   187  }
   188  
   189  // appendToService appends given fields to service
   190  func appendToService(service *types.Service, options ...*unit.UnitOption) (*types.Service, error) {
   191  	serviceOptions, err := unit.Deserialize(strings.NewReader(service.Content))
   192  	if err != nil {
   193  		return nil, err
   194  	}
   195  
   196  OUTER:
   197  	for _, appendOpt := range options {
   198  		for _, opt := range serviceOptions {
   199  			if opt.Match(appendOpt) {
   200  				continue OUTER
   201  			}
   202  		}
   203  		serviceOptions = append(serviceOptions, appendOpt)
   204  	}
   205  
   206  	data, err := io.ReadAll(unit.Serialize(serviceOptions))
   207  	if err != nil {
   208  		return nil, err
   209  	}
   210  
   211  	return &types.Service{
   212  		Name:    service.Name,
   213  		Path:    service.Path,
   214  		Content: string(data),
   215  	}, nil
   216  }