github.com/juju/juju@v0.0.0-20240430160146-1752b71fcf00/worker/caasoperator/caasoperator.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package caasoperator 5 6 import ( 7 "fmt" 8 "os" 9 "path/filepath" 10 "strings" 11 "time" 12 13 "github.com/juju/clock" 14 "github.com/juju/errors" 15 "github.com/juju/loggo" 16 "github.com/juju/names/v5" 17 jujusymlink "github.com/juju/utils/v3/symlink" 18 "github.com/juju/version/v2" 19 "github.com/juju/worker/v3" 20 "github.com/juju/worker/v3/catacomb" 21 22 apiuniter "github.com/juju/juju/api/agent/uniter" 23 "github.com/juju/juju/caas" 24 caasconstants "github.com/juju/juju/caas/kubernetes/provider/constants" 25 "github.com/juju/juju/caas/kubernetes/provider/exec" 26 "github.com/juju/juju/core/arch" 27 "github.com/juju/juju/core/leadership" 28 "github.com/juju/juju/core/life" 29 "github.com/juju/juju/core/model" 30 coreos "github.com/juju/juju/core/os" 31 "github.com/juju/juju/core/paths" 32 "github.com/juju/juju/core/status" 33 "github.com/juju/juju/core/watcher" 34 jujunames "github.com/juju/juju/juju/names" 35 "github.com/juju/juju/juju/sockets" 36 jujuversion "github.com/juju/juju/version" 37 jworker "github.com/juju/juju/worker" 38 "github.com/juju/juju/worker/caasoperator/remotestate" 39 "github.com/juju/juju/worker/introspection" 40 "github.com/juju/juju/worker/uniter" 41 jujucharm "github.com/juju/juju/worker/uniter/charm" 42 uniterremotestate "github.com/juju/juju/worker/uniter/remotestate" 43 "github.com/juju/juju/wrench" 44 ) 45 46 // logger is here to stop the desire of creating a package level logger. 47 // Don't do this, instead pass one through as config to the worker. 48 type logger interface{} 49 50 var _ logger = struct{}{} 51 52 var ( 53 jujuExec = paths.JujuExec(paths.CurrentOS()) 54 jujuDumpLogs = paths.JujuDumpLogs(paths.CurrentOS()) 55 jujuIntrospect = paths.JujuIntrospect(paths.CurrentOS()) 56 57 jujudSymlinks = []string{ 58 jujuExec, 59 jujuDumpLogs, 60 jujuIntrospect, 61 } 62 ) 63 64 // caasOperator implements the capabilities of the caasoperator agent. It is not intended to 65 // implement the actual *behaviour* of the caasoperator agent; that responsibility is 66 // delegated to Mode values, which are expected to react to events and direct 67 // the caasoperator's responses to them. 68 type caasOperator struct { 69 catacomb catacomb.Catacomb 70 config Config 71 paths Paths 72 runner *worker.Runner 73 deployer jujucharm.Deployer 74 stateFile *StateFile 75 deploymentMode caas.DeploymentMode 76 } 77 78 // Config hold the configuration for a caasoperator worker. 79 type Config struct { 80 Logger Logger 81 82 // ModelUUID is the UUID of the model. 83 ModelUUID string 84 85 // ModelName is the name of the model. 86 ModelName string 87 88 // Application holds the name of the application that 89 // this CAAS operator manages. 90 Application string 91 92 // CharmGetter is an interface used for getting the 93 // application's charm URL and SHA256 hash. 94 CharmGetter CharmGetter 95 96 // Clock holds the clock to be used by the CAAS operator 97 // for time-related operations. 98 Clock clock.Clock 99 100 // DataDir holds the path to the Juju "data directory", 101 // i.e. "/var/lib/juju" (by default). The CAAS operator 102 // expects to find the jujud binary at <data-dir>/tools/jujud. 103 DataDir string 104 105 // ProfileDir is where the introspection scripts are written. 106 ProfileDir string 107 108 // Downloader is an interface used for downloading the 109 // application charm. 110 Downloader Downloader 111 112 // StatusSetter is an interface used for setting the 113 // application status. 114 StatusSetter StatusSetter 115 116 // UnitGetter is an interface for getting a unit. 117 UnitGetter UnitGetter 118 119 // UnitRemover is an interface for removing a unit. 120 UnitRemover UnitRemover 121 122 // ApplicationWatcher is an interface for getting info about an application's charm. 123 ApplicationWatcher ApplicationWatcher 124 125 // ContainerStartWatcher provides an interface for watching 126 // for unit container starts. 127 ContainerStartWatcher ContainerStartWatcher 128 129 // VersionSetter is an interface for setting the operator agent version. 130 VersionSetter VersionSetter 131 132 // LeadershipTrackerFunc is a function for getting a leadership tracker worker. 133 LeadershipTrackerFunc func(unitTag names.UnitTag) leadership.TrackerWorker 134 135 // UniterFacadeFunc is a function for making a uniter facade. 136 UniterFacadeFunc func(unitTag names.UnitTag) *apiuniter.State 137 138 // ResourcesFacadeFunc is a function for making a unit resources facade. 139 ResourcesFacadeFunc func(unitTag names.UnitTag) (*apiuniter.ResourcesFacadeClient, error) 140 141 // PayloadFacadeFunc is a function for making a unit payload facade. 142 PayloadFacadeFunc func() *apiuniter.PayloadFacadeClient 143 144 // UniterParams are parameters used to construct a uniter worker. 145 UniterParams *uniter.UniterParams 146 147 // StartUniterFunc starts a uniter worker using the given runner. 148 StartUniterFunc func(runner *worker.Runner, params *uniter.UniterParams) error 149 150 // RunListenerSocketFunc returns a socket used for the juju run listener. 151 RunListenerSocketFunc func(*uniter.SocketConfig) (*sockets.Socket, error) 152 153 // OperatorInfo contains serving information such as Certs and PrivateKeys. 154 OperatorInfo caas.OperatorInfo 155 156 // ExecClientGetter returns an exec client for initializing caas units. 157 ExecClientGetter func() (exec.Executor, error) 158 } 159 160 func (config Config) Validate() error { 161 if !names.IsValidApplication(config.Application) { 162 return errors.NotValidf("application name %q", config.Application) 163 } 164 if config.CharmGetter == nil { 165 return errors.NotValidf("missing CharmGetter") 166 } 167 if config.ApplicationWatcher == nil { 168 return errors.NotValidf("missing ApplicationWatcher") 169 } 170 if config.UnitGetter == nil { 171 return errors.NotValidf("missing UnitGetter") 172 } 173 if config.UnitRemover == nil { 174 return errors.NotValidf("missing UnitRemover") 175 } 176 if config.ContainerStartWatcher == nil { 177 return errors.NotValidf("missing ContainerStartWatcher") 178 } 179 if config.LeadershipTrackerFunc == nil { 180 return errors.NotValidf("missing LeadershipTrackerFunc") 181 } 182 if config.UniterFacadeFunc == nil { 183 return errors.NotValidf("missing UniterFacadeFunc") 184 } 185 if config.ResourcesFacadeFunc == nil { 186 return errors.NotValidf("missing ResourcesFacadeFunc") 187 } 188 if config.PayloadFacadeFunc == nil { 189 return errors.NotValidf("missing PayloadFacadeFunc") 190 } 191 if config.UniterParams == nil { 192 return errors.NotValidf("missing UniterParams") 193 } 194 if config.Clock == nil { 195 return errors.NotValidf("missing Clock") 196 } 197 if config.DataDir == "" { 198 return errors.NotValidf("missing DataDir") 199 } 200 if config.ProfileDir == "" { 201 return errors.NotValidf("missing ProfileDir") 202 } 203 if config.Downloader == nil { 204 return errors.NotValidf("missing Downloader") 205 } 206 if config.StatusSetter == nil { 207 return errors.NotValidf("missing StatusSetter") 208 } 209 if config.VersionSetter == nil { 210 return errors.NotValidf("missing VersionSetter") 211 } 212 if config.ExecClientGetter == nil { 213 return errors.NotValidf("missing ExecClientGetter") 214 } 215 216 if config.Logger == nil { 217 return errors.NotValidf("missing Logger") 218 } 219 return nil 220 } 221 222 func (config Config) getPaths() Paths { 223 return NewPaths(config.DataDir, names.NewApplicationTag(config.Application)) 224 } 225 226 // NewWorker creates a new worker which will install and operate a 227 // CaaS-based application, by executing hooks and operations in 228 // response to application state changes. 229 func NewWorker(config Config) (worker.Worker, error) { 230 if err := config.Validate(); err != nil { 231 return nil, errors.Trace(err) 232 } 233 paths := config.getPaths() 234 logger := config.Logger.Child("charm") 235 deployer, err := jujucharm.NewDeployer( 236 paths.State.CharmDir, 237 paths.State.DeployerDir, 238 jujucharm.NewBundlesDir( 239 paths.State.BundlesDir, 240 config.Downloader, 241 logger), 242 logger, 243 ) 244 if err != nil { 245 return nil, errors.Annotatef(err, "cannot create deployer") 246 } 247 248 op := &caasOperator{ 249 config: config, 250 paths: paths, 251 deployer: deployer, 252 runner: worker.NewRunner(worker.RunnerParams{ 253 Clock: config.Clock, 254 255 // One of the uniter workers failing should not 256 // prevent the others from running. 257 IsFatal: func(error) bool { return false }, 258 259 // For any failures, try again in 3 seconds. 260 RestartDelay: 3 * time.Second, 261 Logger: config.Logger.Child("runner"), 262 }), 263 } 264 if err := catacomb.Invoke(catacomb.Plan{ 265 Site: &op.catacomb, 266 Work: op.loop, 267 Init: []worker.Worker{op.runner}, 268 }); err != nil { 269 return nil, errors.Trace(err) 270 } 271 return op, nil 272 } 273 274 func (op *caasOperator) makeAgentSymlinks(unitTag names.UnitTag) error { 275 // All units share the same agent binary. 276 // Set up the required symlinks. 277 278 // First the agent binary. 279 agentBinaryDir := op.paths.GetToolsDir() 280 unitToolsDir := filepath.Join(agentBinaryDir, unitTag.String()) 281 err := os.Mkdir(unitToolsDir, 0600) 282 if err != nil && !os.IsExist(err) { 283 return errors.Trace(err) 284 } 285 jujudPath := filepath.Join(agentBinaryDir, jujunames.Jujud) 286 err = jujusymlink.New(jujudPath, filepath.Join(unitToolsDir, jujunames.Jujud)) 287 // Ignore permission denied as this won't happen in production 288 // but may happen in testing depending on setup of /tmp 289 if err != nil && !os.IsExist(err) && !os.IsPermission(err) { 290 return errors.Trace(err) 291 } 292 293 // TODO(caas) - remove this when upstream charmhelpers are fixed 294 // Charmhelpers expect to see a jujud in a machine-X directory. 295 legacyMachineDir := filepath.Join(agentBinaryDir, "machine-0") 296 err = os.Mkdir(legacyMachineDir, 0600) 297 if err != nil && !os.IsExist(err) { 298 return errors.Trace(err) 299 } 300 err = jujusymlink.New(jujudPath, filepath.Join(legacyMachineDir, jujunames.Jujud)) 301 if err != nil && !os.IsExist(err) && !os.IsPermission(err) { 302 return errors.Trace(err) 303 } 304 305 for _, slk := range jujudSymlinks { 306 err = jujusymlink.New(jujudPath, slk) 307 if err != nil && !os.IsExist(err) && !os.IsPermission(err) { 308 return errors.Trace(err) 309 } 310 // TODO(juju 4) - remove this legacy behaviour. 311 // Remove the obsolete "juju-run" symlink 312 if strings.Contains(slk, "/juju-exec") { 313 runLink := strings.Replace(slk, "/juju-exec", "/juju-run", 1) 314 _ = os.Remove(runLink) 315 } 316 } 317 318 // Ensure legacy charm symlinks created before 2.8 getting unlinked. 319 unitCharmDir := filepath.Join(op.config.DataDir, "agents", unitTag.String(), "charm") 320 isUnitCharmDirSymlink, err := jujusymlink.IsSymlink(unitCharmDir) 321 if os.IsNotExist(errors.Cause(err)) || os.IsPermission(errors.Cause(err)) { 322 // Ignore permission denied as this won't happen in production 323 // but may happen in testing depending on setup of /tmp. 324 return nil 325 } else if err != nil { 326 return errors.Trace(err) 327 } 328 if isUnitCharmDirSymlink { 329 op.config.Logger.Warningf("removing legacy charm symlink for %q", unitTag.String()) 330 if err := os.Remove(unitCharmDir); err != nil { 331 return errors.Trace(err) 332 } 333 } 334 return nil 335 } 336 337 func (op *caasOperator) removeUnitDir(unitTag names.UnitTag) error { 338 unitAgentDir := filepath.Join(op.config.DataDir, "agents", unitTag.String()) 339 return os.RemoveAll(unitAgentDir) 340 } 341 342 func toBinaryVersion(vers version.Number, osType string) version.Binary { 343 outVers := version.Binary{ 344 Number: vers, 345 Arch: arch.HostArch(), 346 Release: osType, 347 } 348 return outVers 349 } 350 351 func runListenerSocket(sc *uniter.SocketConfig) (*sockets.Socket, error) { 352 socket := sockets.Socket{ 353 Network: "tcp", 354 Address: fmt.Sprintf(":%d", caasconstants.JujuExecServerSocketPort), 355 TLSConfig: sc.TLSConfig, 356 } 357 return &socket, nil 358 } 359 360 func (op *caasOperator) init() (*LocalState, error) { 361 if err := introspection.WriteProfileFunctions(op.config.ProfileDir); err != nil { 362 // This isn't fatal, just annoying. 363 op.config.Logger.Errorf("failed to write profile funcs: %v", err) 364 } 365 366 if err := jujucharm.ClearDownloads(op.paths.State.BundlesDir); err != nil { 367 op.config.Logger.Warningf(err.Error()) 368 } 369 370 op.stateFile = NewStateFile(op.paths.State.OperationsFile) 371 localState, err := op.stateFile.Read() 372 if err == ErrNoStateFile { 373 localState = &LocalState{} 374 } 375 376 if err := op.ensureCharm(localState); err != nil { 377 if err == jworker.ErrTerminateAgent { 378 return nil, err 379 } 380 return nil, errors.Annotatef(err, 381 "failed to initialize caasoperator for %q", 382 op.config.Application, 383 ) 384 } 385 386 // Set up a single remote juju run listener to be used by all workload units. 387 if op.deploymentMode != caas.ModeOperator { 388 if op.config.RunListenerSocketFunc == nil { 389 return nil, errors.New("missing RunListenerSocketFunc") 390 } 391 if op.config.RunListenerSocketFunc != nil { 392 socket, err := op.config.RunListenerSocketFunc(op.config.UniterParams.SocketConfig) 393 if err != nil { 394 return nil, errors.Annotate(err, "creating juju run socket") 395 } 396 op.config.Logger.Debugf("starting caas operator juju-exec listener on %v", socket) 397 logger := loggo.GetLogger("juju.worker.uniter") 398 runListener, err := uniter.NewRunListener(*socket, logger) 399 if err != nil { 400 return nil, errors.Annotate(err, "creating juju run listener") 401 } 402 rlw := uniter.NewRunListenerWrapper(runListener, logger) 403 if err := op.catacomb.Add(rlw); err != nil { 404 return nil, errors.Trace(err) 405 } 406 op.config.UniterParams.RunListener = runListener 407 } 408 } 409 return localState, nil 410 } 411 412 func (op *caasOperator) loop() (err error) { 413 logger := op.config.Logger 414 415 defer func() { 416 if err == nil { 417 logger.Debugf("operator %q is peacefully shutting down", op.config.Application) 418 } else { 419 logger.Warningf("operator %q is shutting down, err: %s", op.config.Application, err.Error()) 420 } 421 if errors.IsNotFound(err) { 422 err = jworker.ErrTerminateAgent 423 } 424 }() 425 426 localState, err := op.init() 427 if err != nil { 428 return errors.Trace(err) 429 } 430 logger.Infof("operator %q started", op.config.Application) 431 432 // Start by reporting current tools (which includes arch/ostype). 433 hostOSType := coreos.HostOSTypeName() 434 if err := op.config.VersionSetter.SetVersion( 435 op.config.Application, toBinaryVersion(jujuversion.Current, hostOSType)); err != nil { 436 return errors.Annotate(err, "cannot set agent version") 437 } 438 439 var remoteWatcher remotestate.Watcher 440 441 restartWatcher := func() error { 442 if remoteWatcher != nil { 443 // watcher added to catacomb, will kill operator if there's an error. 444 _ = worker.Stop(remoteWatcher) 445 } 446 var err error 447 remoteWatcher, err = remotestate.NewWatcher( 448 remotestate.WatcherConfig{ 449 Logger: loggo.GetLogger("juju.worker.caasoperator.remotestate"), 450 CharmGetter: op.config.CharmGetter, 451 Application: op.config.Application, 452 ApplicationWatcher: op.config.ApplicationWatcher, 453 }) 454 if err != nil { 455 return errors.Trace(err) 456 } 457 if err := op.catacomb.Add(remoteWatcher); err != nil { 458 return errors.Trace(err) 459 } 460 return nil 461 } 462 463 jujuUnitsWatcher, err := op.config.UnitGetter.WatchUnits(op.config.Application) 464 if err != nil { 465 return errors.Trace(err) 466 } 467 if err := op.catacomb.Add(jujuUnitsWatcher); err != nil { 468 return errors.Trace(err) 469 } 470 471 var containerStartChan watcher.StringsChannel 472 if op.deploymentMode != caas.ModeOperator { 473 // Match the init container and the default container. 474 containerRegex := fmt.Sprintf("(?:%s|)", caas.InitContainerName) 475 containerStartWatcher, err := op.config.ContainerStartWatcher.WatchContainerStart( 476 op.config.Application, containerRegex) 477 if err != nil { 478 return errors.Trace(err) 479 } 480 if err := op.catacomb.Add(containerStartWatcher); err != nil { 481 return errors.Trace(err) 482 } 483 containerStartChan = containerStartWatcher.Changes() 484 } 485 486 if err := op.setStatus(status.Active, ""); err != nil { 487 return errors.Trace(err) 488 } 489 490 // Channels used to notify uniter worker that the workload container 491 // is running. 492 unitRunningChannels := make(map[string]chan struct{}) 493 494 if err = restartWatcher(); err != nil { 495 err = errors.Annotate(err, "(re)starting watcher") 496 return errors.Trace(err) 497 } 498 499 // We should not do anything until there has been a change 500 // to the remote state. The watcher will trigger at least 501 // once initially. 502 select { 503 case <-op.catacomb.Dying(): 504 return op.catacomb.ErrDying() 505 case <-remoteWatcher.RemoteStateChanged(): 506 } 507 508 for { 509 select { 510 case <-op.catacomb.Dying(): 511 return op.catacomb.ErrDying() 512 case <-remoteWatcher.RemoteStateChanged(): 513 snap := remoteWatcher.Snapshot() 514 if op.charmModified(localState, snap) { 515 // Charm changed so download and install the new version. 516 err := op.ensureCharm(localState) 517 if err != nil { 518 return errors.Annotatef(err, "error downloading updated charm %v", localState.CharmURL) 519 } 520 // Reset the application's "Downloading..." message. 521 if err := op.setStatus(status.Active, ""); err != nil { 522 return errors.Trace(err) 523 } 524 } 525 case units, ok := <-containerStartChan: 526 if !ok { 527 return errors.New("container start watcher closed channel") 528 } 529 for _, unitID := range units { 530 if runningChan, ok := unitRunningChannels[unitID]; ok { 531 logger.Debugf("trigger running status for caas unit %v", unitID) 532 select { 533 case <-op.catacomb.Dying(): 534 return op.catacomb.ErrDying() 535 case runningChan <- struct{}{}: 536 default: 537 // This will happen when the buffered channel already 538 // has an event. If this is the case it's ok to discard 539 // the event. 540 logger.Debugf("unit running chan[%q] discarding running event as one already exists", unitID) 541 } 542 } 543 } 544 case units, ok := <-jujuUnitsWatcher.Changes(): 545 if !ok { 546 return errors.New("watcher closed channel") 547 } 548 for _, v := range units { 549 unitID := v 550 unitLife, err := op.config.UnitGetter.Life(unitID) 551 if err != nil && !errors.IsNotFound(err) { 552 return errors.Trace(err) 553 } 554 logger.Debugf("got unit change %q (%s)", unitID, unitLife) 555 unitTag := names.NewUnitTag(unitID) 556 if errors.IsNotFound(err) || unitLife == life.Dead { 557 delete(unitRunningChannels, unitID) 558 logger.Debugf("stopping uniter for dead unit %q", unitID) 559 if err := op.runner.StopAndRemoveWorker(unitID, op.catacomb.Dying()); err != nil { 560 logger.Warningf("stopping uniter for dead unit %q: %v", unitID, err) 561 } 562 logger.Debugf("removing dead unit %q", unitID) 563 // Remove the unit from state. 564 if err := op.config.UnitRemover.RemoveUnit(unitID); err != nil { 565 return errors.Trace(err) 566 } 567 logger.Debugf("removing unit dir for dead unit %q", unitID) 568 // Remove the unit's directory 569 if err := op.removeUnitDir(unitTag); err != nil { 570 return errors.Trace(err) 571 } 572 // Nothing to do for a dead unit further. 573 continue 574 } else { 575 if _, ok := unitRunningChannels[unitID]; !ok && op.deploymentMode != caas.ModeOperator { 576 // We make a buffered channel here so that we don't 577 // block the operator while the uniter may not be ready 578 unitRunningChannels[unitID] = make(chan struct{}, 1) 579 } 580 } 581 // Start a worker to manage any new units. 582 if _, err := op.runner.Worker(unitID, op.catacomb.Dying()); err == nil || unitLife == life.Dead { 583 // Already watching the unit or we're 584 // not yet watching it and it's dead. 585 continue 586 } 587 588 // Make all the required symlinks. 589 if err := op.makeAgentSymlinks(unitTag); err != nil { 590 return errors.Trace(err) 591 } 592 params := *op.config.UniterParams 593 params.ModelType = model.CAAS 594 params.UnitTag = unitTag 595 params.Downloader = op.config.Downloader // TODO(caas): write a cache downloader 596 params.UniterFacade = op.config.UniterFacadeFunc(unitTag) 597 if params.ResourcesFacade, err = op.config.ResourcesFacadeFunc(unitTag); err != nil { 598 return errors.Trace(err) 599 } 600 params.PayloadFacade = op.config.PayloadFacadeFunc() 601 params.LeadershipTrackerFunc = op.config.LeadershipTrackerFunc 602 params.Logger = params.Logger.Child(unitID) 603 if op.deploymentMode != caas.ModeOperator { 604 params.IsRemoteUnit = true 605 params.ContainerRunningStatusChannel = unitRunningChannels[unitID] 606 607 execClient, err := op.config.ExecClientGetter() 608 if err != nil { 609 return errors.Trace(err) 610 } 611 params.ContainerRunningStatusFunc = func(providerID string) (*uniterremotestate.ContainerRunningStatus, error) { 612 if wrench.IsActive("remote-init", "fatal-error") { 613 return nil, errors.New("fake remote-init fatal-error") 614 } 615 return op.runningStatus(execClient, unitTag, providerID) 616 } 617 params.RemoteInitFunc = func(runningStatus uniterremotestate.ContainerRunningStatus, cancel <-chan struct{}) error { 618 // TODO(caas): Remove the cached status uniterremotestate.ContainerRunningStatus from uniter watcher snapshot. 619 return op.remoteInitForUniter(execClient, unitTag, runningStatus, cancel) 620 } 621 params.NewRemoteRunnerExecutor = getNewRunnerExecutor(logger, execClient) 622 } 623 if err := op.config.StartUniterFunc(op.runner, ¶ms); err != nil { 624 return errors.Trace(err) 625 } 626 } 627 } 628 } 629 } 630 631 func (op *caasOperator) runningStatus(client exec.Executor, unit names.UnitTag, providerID string) (*uniterremotestate.ContainerRunningStatus, error) { 632 op.config.Logger.Debugf("request running status for %q %s", unit.String(), providerID) 633 params := exec.StatusParams{ 634 PodName: providerID, 635 } 636 status, err := client.Status(params) 637 if err != nil { 638 op.config.Logger.Errorf("could not get pod %q %q %v", unit.String(), providerID, err) 639 return nil, errors.Annotatef(err, "getting pod status for unit %q, container %q", unit, providerID) 640 } 641 result := &uniterremotestate.ContainerRunningStatus{ 642 PodName: status.PodName, 643 } 644 once := true 645 for _, cs := range status.ContainerStatus { 646 if cs.InitContainer && cs.Name == caas.InitContainerName { 647 result.Initialising = cs.Running 648 result.InitialisingTime = cs.StartedAt 649 } 650 // Only check the default container. 651 if !cs.InitContainer && !cs.EphemeralContainer && once { 652 result.Running = cs.Running 653 once = false 654 } 655 } 656 return result, nil 657 } 658 func (op *caasOperator) remoteInitForUniter(client exec.Executor, unit names.UnitTag, runningStatus uniterremotestate.ContainerRunningStatus, cancel <-chan struct{}) error { 659 return runnerWithRetry( 660 func() error { 661 status, err := op.runningStatus(client, unit, runningStatus.PodName) 662 // get the current status rather than using the status cached in remote state. 663 if err != nil { 664 return errors.Trace(err) 665 } 666 return op.remoteInit(client, unit, *status, cancel) 667 }, 668 func(err error) bool { 669 // We need to re-fetch the running status then retry remoteInit if the container is not running. 670 return err != nil && !exec.IsContainerNotRunningError(err) && !errors.IsNotFound(err) 671 }, op.config.Logger, op.config.Clock, cancel, 672 ) 673 } 674 675 func (op *caasOperator) remoteInit(client exec.Executor, unit names.UnitTag, runningStatus uniterremotestate.ContainerRunningStatus, cancel <-chan struct{}) error { 676 op.config.Logger.Debugf("remote init for %q %+v", unit.String(), runningStatus) 677 switch { 678 case runningStatus.Initialising: 679 // all good, continue to do remote-init. 680 return errors.Trace(initializeUnit(initializeUnitParams{ 681 ExecClient: client, 682 Logger: op.config.Logger, 683 OperatorInfo: op.config.OperatorInfo, 684 Paths: op.paths, 685 UnitTag: unit, 686 ProviderID: runningStatus.PodName, 687 WriteFile: os.WriteFile, 688 TempDir: os.MkdirTemp, 689 Clock: op.config.Clock, 690 ReTrier: runnerWithRetry, 691 }, cancel)) 692 case runningStatus.Running: 693 op.config.Logger.Debugf("no need to do remote-init for a running container") 694 return nil 695 default: 696 return errors.NotFoundf("container not running") 697 } 698 } 699 700 func (op *caasOperator) charmModified(local *LocalState, remote remotestate.Snapshot) bool { 701 // CAAS models may not yet have read the charm url from state. 702 if remote.CharmURL == nil { 703 return false 704 } 705 if local == nil || local.CharmURL == nil { 706 op.config.Logger.Warningf("unexpected nil local charm URL") 707 return true 708 } 709 if *local.CharmURL != *remote.CharmURL { 710 op.config.Logger.Debugf("upgrade from %v to %v", local.CharmURL, remote.CharmURL) 711 return true 712 } 713 714 if local.CharmModifiedVersion != remote.CharmModifiedVersion { 715 op.config.Logger.Debugf("upgrade from CharmModifiedVersion %v to %v", local.CharmModifiedVersion, remote.CharmModifiedVersion) 716 return true 717 } 718 if remote.ForceCharmUpgrade { 719 op.config.Logger.Debugf("force charm upgrade to %v", remote.CharmURL) 720 return true 721 } 722 return false 723 } 724 725 func (op *caasOperator) setStatus(status status.Status, message string, args ...interface{}) error { 726 err := op.config.StatusSetter.SetStatus( 727 op.config.Application, 728 status, 729 fmt.Sprintf(message, args...), 730 nil, 731 ) 732 return errors.Annotate(err, "setting status") 733 } 734 735 // Kill is part of the worker.Worker interface. 736 func (op *caasOperator) Kill() { 737 op.catacomb.Kill(nil) 738 } 739 740 // Wait is part of the worker.Worker interface. 741 func (op *caasOperator) Wait() error { 742 return op.catacomb.Wait() 743 }