github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/service/agentconf.go (about) 1 // Copyright 2018 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 // This file has routines which can be used for agent specific functionality 5 // related to service files: 6 // - finding all agents in the machine 7 // - create conf file using the machine details 8 // - write systemd service file and setting links 9 // - copy all tools and related to agents and setup the links 10 // - start all the agents 11 // These routines can be used by any tools/cmds trying to implement the above 12 // functionality as part of the process, eg. upgrade-series. 13 14 // TODO (manadart 2018-07-31) This module is specific to systemd and should 15 // reside in the service/systemd package. 16 17 package service 18 19 import ( 20 "fmt" 21 "os" 22 "path" 23 "path/filepath" 24 "strings" 25 26 "github.com/juju/errors" 27 "github.com/juju/utils/arch" 28 "github.com/juju/utils/fs" 29 "github.com/juju/utils/shell" 30 "github.com/juju/utils/symlink" 31 "github.com/juju/version" 32 "gopkg.in/juju/names.v2" 33 34 "github.com/juju/juju/agent/tools" 35 "github.com/juju/juju/juju/paths" 36 "github.com/juju/juju/service/common" 37 "github.com/juju/juju/service/systemd" 38 ) 39 40 type SystemdServiceManager interface { 41 // FindAgents finds all the agents available in the machine. 42 FindAgents(dataDir string) (string, []string, []string, error) 43 44 // WriteSystemdAgents creates systemd files and create symlinks for the 45 // list of machine and units passed in the standard filepath. 46 WriteSystemdAgents( 47 machineAgent string, unitAgents []string, dataDir, symLinkSystemdDir, symLinkSystemdMultiUserDir string, 48 ) ([]string, []string, []string, error) 49 50 //CreateAgentConf creates the configfile for specified agent running on a 51 // host with specified series. 52 CreateAgentConf(agentName string, dataDir string) (common.Conf, error) 53 54 // CopyAgentBinary copies all the tools into the path specified for each agent. 55 CopyAgentBinary( 56 machineAgent string, unitAgents []string, dataDir, toSeries, fromSeries string, jujuVersion version.Number) error 57 58 // StartAllAgents starts all the agents in the machine with specified series. 59 StartAllAgents(machineAgent string, unitAgents []string, dataDir string) (string, []string, error) 60 61 // WriteServiceFiles writes the service files for machine and unit agents 62 // in the '/lib/systemd/system' path. 63 WriteServiceFiles() error 64 } 65 66 type systemdServiceManager struct { 67 isRunning func() bool 68 newService func(string, common.Conf) (Service, error) 69 } 70 71 // NewServiceManagerWithDefaults returns a SystemdServiceManager created with 72 // sensible defaults. 73 func NewServiceManagerWithDefaults() SystemdServiceManager { 74 return NewServiceManager( 75 systemd.IsRunning, 76 func(name string, conf common.Conf) (Service, error) { 77 return systemd.NewServiceWithDefaults(name, conf) 78 }, 79 ) 80 } 81 82 // NewServiceManager allows creation of a new SystemdServiceManager from 83 // custom dependencies. 84 func NewServiceManager( 85 isRunning func() bool, 86 newService func(string, common.Conf) (Service, error), 87 ) SystemdServiceManager { 88 return &systemdServiceManager{ 89 isRunning: isRunning, 90 newService: newService, 91 } 92 } 93 94 // WriteServiceFiles writes service files to the standard 95 // "/lib/systemd/system" path. 96 func (s *systemdServiceManager) WriteServiceFiles() error { 97 machineAgent, unitAgents, _, err := s.FindAgents(paths.NixDataDir) 98 if err != nil { 99 return errors.Trace(err) 100 } 101 102 serviceNames, linkNames, failed, err := s.WriteSystemdAgents( 103 machineAgent, 104 unitAgents, 105 paths.NixDataDir, 106 systemd.EtcSystemdDir, 107 systemd.EtcSystemdMultiUserDir, 108 ) 109 if err != nil { 110 for _, agent := range failed { 111 logger.Errorf("failed to write service for %s: %s", agent, err) 112 } 113 logger.Errorf("%s", err) 114 return errors.Trace(err) 115 } 116 for _, s := range serviceNames { 117 logger.Infof("wrote %s agent, enabled and linked by systemd", s) 118 } 119 for _, s := range linkNames { 120 logger.Infof("wrote %s agent, enabled and linked by symlink", s) 121 } 122 123 return errors.Trace(systemd.SysdReload()) 124 } 125 126 // FindAgents finds all the agents available on the machine. 127 func (s *systemdServiceManager) FindAgents(dataDir string) (string, []string, []string, error) { 128 var ( 129 machineAgent string 130 unitAgents []string 131 errAgentNames []string 132 ) 133 134 agentDir := filepath.Join(dataDir, "agents") 135 dir, err := os.Open(agentDir) 136 if err != nil { 137 return "", nil, nil, errors.Annotate(err, "opening agents dir") 138 } 139 defer dir.Close() 140 141 entries, err := dir.Readdir(-1) 142 if err != nil { 143 return "", nil, nil, errors.Annotate(err, "reading agents dir") 144 } 145 for _, info := range entries { 146 name := info.Name() 147 tag, err := names.ParseTag(name) 148 if err != nil { 149 continue 150 } 151 switch tag.Kind() { 152 case names.MachineTagKind: 153 machineAgent = name 154 case names.UnitTagKind: 155 unitAgents = append(unitAgents, name) 156 default: 157 errAgentNames = append(errAgentNames, name) 158 logger.Infof("%s is not of type Machine nor Unit, ignoring", name) 159 } 160 } 161 return machineAgent, unitAgents, errAgentNames, nil 162 } 163 164 // WriteSystemdAgents creates systemd files and symlinks for the input machine 165 // and unit agents, in the standard filepath '/var/lib/juju'. 166 func (s *systemdServiceManager) WriteSystemdAgents( 167 machineAgent string, unitAgents []string, dataDir, symLinkSystemdDir, symLinkSystemdMultiUserDir string, 168 ) ([]string, []string, []string, error) { 169 var ( 170 startedSysServiceNames []string 171 startedSymServiceNames []string 172 errAgentNames []string 173 lastError error 174 ) 175 176 for _, agentName := range append(unitAgents, machineAgent) { 177 conf, err := s.CreateAgentConf(agentName, dataDir) 178 if err != nil { 179 logger.Infof("%s", err) 180 lastError = err 181 continue 182 } 183 184 svcName := serviceName(agentName) 185 svc, err := s.newService(svcName, conf) 186 if err != nil { 187 logger.Infof("Failed to create new service %s: ", err) 188 continue 189 } 190 191 uSvc, ok := svc.(UpgradableService) 192 if !ok { 193 return nil, nil, nil, errors.Errorf("%s service not of type UpgradableService", svcName) 194 } 195 196 dbusMethodFound := true 197 if err = uSvc.WriteService(); err != nil { 198 // Note that this error is already logged by the systemd package. 199 200 // This is not ideal, but it is possible on an Upstart-based OS 201 // (such as Trusty) for run/systemd/system to exist, which is used 202 // for detection of systemd as the running init system. 203 // If this happens, then D-Bus will error with the message below. 204 // We need to detect this condition and fall through to linking the 205 // service files manually. 206 if strings.Contains(strings.ToLower(err.Error()), "no such method") { 207 dbusMethodFound = false 208 logger.Infof("attempting to manually link service file for %s", agentName) 209 } else { 210 errAgentNames = append(errAgentNames, agentName) 211 lastError = err 212 continue 213 } 214 } else { 215 logger.Infof("successfully wrote service for %s:", agentName) 216 } 217 218 // If systemd is the running init system on this host, *and* if the 219 // call to DBusAPI.LinkUnitFiles in WriteService above returned no 220 // error, it will have resulted in updated sym-links for the file. 221 // We are done. 222 if s.isRunning() && dbusMethodFound { 223 startedSysServiceNames = append(startedSysServiceNames, svcName) 224 logger.Infof("wrote %s agent, enabled and linked by systemd", svcName) 225 continue 226 } 227 228 // Otherwise we need to manually ensure the service unit links. 229 svcFileName := svcName + ".service" 230 if err = os.Symlink(path.Join(systemd.LibSystemdDir, svcName, svcFileName), 231 path.Join(symLinkSystemdDir, svcFileName)); err != nil && !os.IsExist(err) { 232 return nil, nil, nil, errors.Errorf( 233 "failed to link service file (%s) in systemd dir: %s\n", svcFileName, err) 234 } 235 236 if err = os.Symlink(path.Join(systemd.LibSystemdDir, svcName, svcFileName), 237 path.Join(symLinkSystemdMultiUserDir, svcFileName)); err != nil && !os.IsExist(err) { 238 return nil, nil, nil, errors.Errorf( 239 "failed to link service file (%s) in multi-user.target.wants dir: %s\n", svcFileName, err) 240 } 241 242 startedSymServiceNames = append(startedSymServiceNames, svcName) 243 logger.Infof("wrote %s agent, enabled and linked by symlink", svcName) 244 } 245 return startedSysServiceNames, startedSymServiceNames, errAgentNames, lastError 246 } 247 248 // CreateAgentConf creates the configfile for specified agent running on a host with specified series. 249 func (s *systemdServiceManager) CreateAgentConf(name string, dataDir string) (_ common.Conf, err error) { 250 defer func() { 251 if err != nil { 252 logger.Infof("failed create agent conf for %s: %s", name, err) 253 } 254 }() 255 256 renderer, err := shell.NewRenderer("") 257 if err != nil { 258 return common.Conf{}, err 259 } 260 261 tag, err := names.ParseTag(name) 262 if err != nil { 263 return common.Conf{}, err 264 } 265 266 var kind AgentKind 267 switch tag.Kind() { 268 case names.MachineTagKind: 269 kind = AgentKindMachine 270 case names.UnitTagKind: 271 kind = AgentKindUnit 272 default: 273 return common.Conf{}, errors.NewNotValid(nil, fmt.Sprintf("agent %q is neither a machine nor a unit", name)) 274 } 275 276 srvPath := path.Join(paths.NixLogDir, "juju") 277 info := NewAgentInfo( 278 kind, 279 tag.Id(), 280 dataDir, 281 srvPath) 282 return AgentConf(info, renderer), nil 283 } 284 285 // CopyAgentBinary copies all the tools into the path specified for each agent. 286 func (s *systemdServiceManager) CopyAgentBinary( 287 machineAgent string, 288 unitAgents []string, 289 dataDir, toSeries, fromSeries string, 290 jujuVersion version.Number, 291 ) (err error) { 292 defer func() { 293 if err != nil { 294 err = errors.Annotate(err, "failed to copy tools") 295 } 296 }() 297 298 // Setup new and old version.Binary instances with different series. 299 fromVers := version.Binary{ 300 Number: jujuVersion, 301 Arch: arch.HostArch(), 302 Series: fromSeries, 303 } 304 toVers := version.Binary{ 305 Number: jujuVersion, 306 Arch: arch.HostArch(), 307 Series: toSeries, 308 } 309 310 // If tools with the new series don't already exist, copy 311 // current tools to new directory with correct series. 312 if _, err = os.Stat(tools.SharedToolsDir(dataDir, toVers)); err != nil { 313 // Copy tools to new directory with correct series. 314 if err = fs.Copy(tools.SharedToolsDir(dataDir, fromVers), tools.SharedToolsDir(dataDir, toVers)); err != nil { 315 return err 316 } 317 } 318 319 // Write tools metadata with new version, however don't change 320 // the URL, so we know where it came from. 321 jujuTools, err := tools.ReadTools(dataDir, toVers) 322 if err != nil { 323 return errors.Trace(err) 324 } 325 326 // Only write once 327 if jujuTools.Version != toVers { 328 jujuTools.Version = toVers 329 if err = tools.WriteToolsMetadataData(tools.ToolsDir(dataDir, toVers.String()), jujuTools); err != nil { 330 return err 331 } 332 } 333 334 // Update Agent Tool links 335 var lastError error 336 for _, agentName := range append(unitAgents, machineAgent) { 337 toolPath := tools.ToolsDir(dataDir, toVers.String()) 338 toolsDir := tools.ToolsDir(dataDir, agentName) 339 340 err = symlink.Replace(toolsDir, toolPath) 341 if err != nil { 342 lastError = err 343 } 344 } 345 346 return lastError 347 } 348 349 // StartAllAgents starts all of the input agents. 350 func (s *systemdServiceManager) StartAllAgents( 351 machineAgent string, unitAgents []string, dataDir string, 352 ) (string, []string, error) { 353 if !s.isRunning() { 354 return "", nil, errors.Errorf("cannot interact with systemd; reboot to start agents") 355 } 356 357 var startedUnits []string 358 for _, unit := range unitAgents { 359 if err := s.startAgent(unit, AgentKindUnit, dataDir); err != nil { 360 return "", startedUnits, errors.Annotatef(err, "failed to start %s service", serviceName(unit)) 361 } 362 startedUnits = append(startedUnits, serviceName(unit)) 363 logger.Infof("started %s service", serviceName(unit)) 364 } 365 366 machineService := serviceName(machineAgent) 367 err := s.startAgent(machineAgent, AgentKindMachine, dataDir) 368 if err == nil { 369 logger.Infof("started %s service", machineService) 370 return machineService, startedUnits, nil 371 } 372 373 return "", startedUnits, errors.Annotatef(err, "failed to start %s service", machineService) 374 } 375 376 func (s *systemdServiceManager) startAgent(name string, kind AgentKind, dataDir string) (err error) { 377 renderer, err := shell.NewRenderer("") 378 if err != nil { 379 return errors.Trace(err) 380 } 381 382 srvPath := path.Join(paths.NixLogDir, "juju") 383 info := NewAgentInfo(kind, name, dataDir, srvPath) 384 conf := AgentConf(info, renderer) 385 386 svc, err := s.newService(serviceName(name), conf) 387 if err != nil { 388 return errors.Trace(err) 389 } 390 391 return errors.Trace(svc.Start()) 392 } 393 394 func serviceName(agent string) string { 395 return "jujud-" + agent 396 }