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 }