mirror of
https://github.com/containers/podman.git
synced 2025-05-21 00:56:36 +08:00

While manually playing with --service-container, I encountered a number of too verbose logs. For instance, there's no need to error-log when the service-container has already been stopped. For testing, add a new kube test with a multi-pod YAML which will implicitly show that #17024 is now working. Fixes: #17024 Signed-off-by: Valentin Rothberg <vrothberg@redhat.com>
251 lines
6.9 KiB
Go
251 lines
6.9 KiB
Go
package libpod
|
|
|
|
import (
|
|
"context"
|
|
"errors"
|
|
"fmt"
|
|
|
|
"github.com/containers/podman/v4/libpod/define"
|
|
"github.com/sirupsen/logrus"
|
|
)
|
|
|
|
// A service consists of one or more pods. The service container is started
|
|
// before all pods and is stopped when the last pod stops. The service
|
|
// container allows for tracking and managing the entire life cycle of service
|
|
// which may be started via `podman-play-kube`.
|
|
type Service struct {
|
|
// Pods running as part of the service.
|
|
Pods []string `json:"servicePods"`
|
|
}
|
|
|
|
// Indicates whether the pod is associated with a service container.
|
|
// The pod is expected to be updated and locked.
|
|
func (p *Pod) hasServiceContainer() bool {
|
|
return p.config.ServiceContainerID != ""
|
|
}
|
|
|
|
// Returns the pod's service container.
|
|
// The pod is expected to be updated and locked.
|
|
func (p *Pod) serviceContainer() (*Container, error) {
|
|
id := p.config.ServiceContainerID
|
|
if id == "" {
|
|
return nil, fmt.Errorf("pod has no service container: %w", define.ErrNoSuchCtr)
|
|
}
|
|
return p.runtime.state.Container(id)
|
|
}
|
|
|
|
// ServiceContainer returns the service container.
|
|
func (p *Pod) ServiceContainer() (*Container, error) {
|
|
p.lock.Lock()
|
|
defer p.lock.Unlock()
|
|
if err := p.updatePod(); err != nil {
|
|
return nil, err
|
|
}
|
|
return p.serviceContainer()
|
|
}
|
|
|
|
func (c *Container) addServicePodLocked(id string) error {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
if err := c.syncContainer(); err != nil {
|
|
return err
|
|
}
|
|
c.state.Service.Pods = append(c.state.Service.Pods, id)
|
|
return c.save()
|
|
}
|
|
|
|
// IsService returns true when the container is a "service container".
|
|
func (c *Container) IsService() bool {
|
|
return c.config.IsService
|
|
}
|
|
|
|
// canStopServiceContainerLocked returns true if all pods of the service are stopped.
|
|
// Note that the method acquires the container lock.
|
|
func (c *Container) canStopServiceContainerLocked() (bool, error) {
|
|
c.lock.Lock()
|
|
defer c.lock.Unlock()
|
|
if err := c.syncContainer(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
if !c.IsService() {
|
|
return false, fmt.Errorf("internal error: checking service: container %s is not a service container", c.ID())
|
|
}
|
|
|
|
return c.canStopServiceContainer()
|
|
}
|
|
|
|
// canStopServiceContainer returns true if all pods of the service are stopped.
|
|
// Note that the method expects the container to be locked.
|
|
func (c *Container) canStopServiceContainer() (bool, error) {
|
|
for _, id := range c.state.Service.Pods {
|
|
pod, err := c.runtime.LookupPod(id)
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchPod) {
|
|
continue
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
status, err := pod.GetPodStatus()
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchPod) {
|
|
continue
|
|
}
|
|
return false, err
|
|
}
|
|
|
|
// We can only stop the service if all pods are done.
|
|
switch status {
|
|
case define.PodStateStopped, define.PodStateExited, define.PodStateErrored:
|
|
continue
|
|
default:
|
|
return false, nil
|
|
}
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Checks whether the service container can be stopped and does so.
|
|
func (p *Pod) maybeStopServiceContainer() error {
|
|
if !p.hasServiceContainer() {
|
|
return nil
|
|
}
|
|
|
|
serviceCtr, err := p.serviceContainer()
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("getting pod's service container: %w", err)
|
|
}
|
|
// Checking whether the service can be stopped must be done in
|
|
// the runtime's work queue to resolve ABBA dead locks in the
|
|
// pod->container->servicePods hierarchy.
|
|
p.runtime.queueWork(func() {
|
|
logrus.Debugf("Pod %s has a service %s: checking if it can be stopped", p.ID(), serviceCtr.ID())
|
|
canStop, err := serviceCtr.canStopServiceContainerLocked()
|
|
if err != nil {
|
|
logrus.Errorf("Checking whether service of container %s can be stopped: %v", serviceCtr.ID(), err)
|
|
return
|
|
}
|
|
if !canStop {
|
|
return
|
|
}
|
|
logrus.Debugf("Stopping service container %s", serviceCtr.ID())
|
|
if err := serviceCtr.Stop(); err != nil {
|
|
if !errors.Is(err, define.ErrCtrStopped) {
|
|
logrus.Errorf("Stopping service container %s: %v", serviceCtr.ID(), err)
|
|
}
|
|
}
|
|
})
|
|
return nil
|
|
}
|
|
|
|
// Starts the pod's service container if it's not already running.
|
|
func (p *Pod) maybeStartServiceContainer(ctx context.Context) error {
|
|
if !p.hasServiceContainer() {
|
|
return nil
|
|
}
|
|
|
|
serviceCtr, err := p.serviceContainer()
|
|
if err != nil {
|
|
return fmt.Errorf("getting pod's service container: %w", err)
|
|
}
|
|
|
|
serviceCtr.lock.Lock()
|
|
defer serviceCtr.lock.Unlock()
|
|
|
|
if err := serviceCtr.syncContainer(); err != nil {
|
|
return err
|
|
}
|
|
|
|
if serviceCtr.state.State == define.ContainerStateRunning {
|
|
return nil
|
|
}
|
|
|
|
// Restart will reinit among other things.
|
|
return serviceCtr.restartWithTimeout(ctx, 0)
|
|
}
|
|
|
|
// canRemoveServiceContainer returns true if all pods of the service are removed.
|
|
// Note that the method acquires the container lock.
|
|
func (c *Container) canRemoveServiceContainer() (bool, error) {
|
|
if !c.IsService() {
|
|
return false, fmt.Errorf("internal error: checking service: container %s is not a service container", c.ID())
|
|
}
|
|
|
|
for _, id := range c.state.Service.Pods {
|
|
if _, err := c.runtime.LookupPod(id); err != nil {
|
|
if errors.Is(err, define.ErrNoSuchPod) {
|
|
continue
|
|
}
|
|
return false, err
|
|
}
|
|
return false, nil
|
|
}
|
|
|
|
return true, nil
|
|
}
|
|
|
|
// Checks whether the service container can be removed and does so.
|
|
// It also unlinks the pod from the service container.
|
|
func (p *Pod) maybeRemoveServiceContainer() error {
|
|
if !p.hasServiceContainer() {
|
|
return nil
|
|
}
|
|
|
|
serviceCtr, err := p.serviceContainer()
|
|
if err != nil {
|
|
if errors.Is(err, define.ErrNoSuchCtr) {
|
|
return nil
|
|
}
|
|
return fmt.Errorf("getting pod's service container: %w", err)
|
|
}
|
|
// Checking whether the service can be stopped must be done in
|
|
// the runtime's work queue to resolve ABBA dead locks in the
|
|
// pod->container->servicePods hierarchy.
|
|
p.runtime.queueWork(func() {
|
|
logrus.Debugf("Pod %s has a service %s: checking if it can be removed", p.ID(), serviceCtr.ID())
|
|
canRemove, err := func() (bool, error) { // Anonymous func for easy locking
|
|
serviceCtr.lock.Lock()
|
|
defer serviceCtr.lock.Unlock()
|
|
if err := serviceCtr.syncContainer(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
// Unlink the pod from the service container.
|
|
servicePods := make([]string, 0, len(serviceCtr.state.Service.Pods)-1)
|
|
for _, id := range serviceCtr.state.Service.Pods {
|
|
if id != p.ID() {
|
|
servicePods = append(servicePods, id)
|
|
}
|
|
}
|
|
serviceCtr.state.Service.Pods = servicePods
|
|
if err := serviceCtr.save(); err != nil {
|
|
return false, err
|
|
}
|
|
|
|
return serviceCtr.canRemoveServiceContainer()
|
|
}()
|
|
if err != nil {
|
|
if !errors.Is(err, define.ErrNoSuchCtr) {
|
|
logrus.Errorf("Checking whether service container %s can be removed: %v", serviceCtr.ID(), err)
|
|
}
|
|
return
|
|
}
|
|
if !canRemove {
|
|
return
|
|
}
|
|
timeout := uint(0)
|
|
logrus.Debugf("Removing service container %s", serviceCtr.ID())
|
|
if err := p.runtime.RemoveContainer(context.Background(), serviceCtr, true, false, &timeout); err != nil {
|
|
if !errors.Is(err, define.ErrNoSuchCtr) {
|
|
logrus.Errorf("Removing service container %s: %v", serviceCtr.ID(), err)
|
|
}
|
|
}
|
|
})
|
|
return nil
|
|
}
|