github.com/demonoid81/containerd@v1.3.4/runtime/v2/manager.go (about)

     1  /*
     2     Copyright The containerd Authors.
     3  
     4     Licensed under the Apache License, Version 2.0 (the "License");
     5     you may not use this file except in compliance with the License.
     6     You may obtain a copy of the License at
     7  
     8         http://www.apache.org/licenses/LICENSE-2.0
     9  
    10     Unless required by applicable law or agreed to in writing, software
    11     distributed under the License is distributed on an "AS IS" BASIS,
    12     WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13     See the License for the specific language governing permissions and
    14     limitations under the License.
    15  */
    16  
    17  package v2
    18  
    19  import (
    20  	"context"
    21  	"fmt"
    22  	"io/ioutil"
    23  	"os"
    24  	"path/filepath"
    25  
    26  	"github.com/containerd/containerd/containers"
    27  	"github.com/containerd/containerd/events/exchange"
    28  	"github.com/containerd/containerd/log"
    29  	"github.com/containerd/containerd/metadata"
    30  	"github.com/containerd/containerd/mount"
    31  	"github.com/containerd/containerd/namespaces"
    32  	"github.com/containerd/containerd/platforms"
    33  	"github.com/containerd/containerd/plugin"
    34  	"github.com/containerd/containerd/runtime"
    35  	ocispec "github.com/opencontainers/image-spec/specs-go/v1"
    36  )
    37  
    38  // Config for the v2 runtime
    39  type Config struct {
    40  	// Supported platforms
    41  	Platforms []string `toml:"platforms"`
    42  }
    43  
    44  func init() {
    45  	plugin.Register(&plugin.Registration{
    46  		Type: plugin.RuntimePluginV2,
    47  		ID:   "task",
    48  		Requires: []plugin.Type{
    49  			plugin.MetadataPlugin,
    50  		},
    51  		Config: &Config{
    52  			Platforms: defaultPlatforms(),
    53  		},
    54  		InitFn: func(ic *plugin.InitContext) (interface{}, error) {
    55  			supportedPlatforms, err := parsePlatforms(ic.Config.(*Config).Platforms)
    56  			if err != nil {
    57  				return nil, err
    58  			}
    59  
    60  			ic.Meta.Platforms = supportedPlatforms
    61  			if err := os.MkdirAll(ic.Root, 0711); err != nil {
    62  				return nil, err
    63  			}
    64  			if err := os.MkdirAll(ic.State, 0711); err != nil {
    65  				return nil, err
    66  			}
    67  			m, err := ic.Get(plugin.MetadataPlugin)
    68  			if err != nil {
    69  				return nil, err
    70  			}
    71  			cs := metadata.NewContainerStore(m.(*metadata.DB))
    72  
    73  			return New(ic.Context, ic.Root, ic.State, ic.Address, ic.TTRPCAddress, ic.Events, cs)
    74  		},
    75  	})
    76  }
    77  
    78  // New task manager for v2 shims
    79  func New(ctx context.Context, root, state, containerdAddress, containerdTTRPCAddress string, events *exchange.Exchange, cs containers.Store) (*TaskManager, error) {
    80  	for _, d := range []string{root, state} {
    81  		if err := os.MkdirAll(d, 0711); err != nil {
    82  			return nil, err
    83  		}
    84  	}
    85  	m := &TaskManager{
    86  		root:                   root,
    87  		state:                  state,
    88  		containerdAddress:      containerdAddress,
    89  		containerdTTRPCAddress: containerdTTRPCAddress,
    90  		tasks:                  runtime.NewTaskList(),
    91  		events:                 events,
    92  		containers:             cs,
    93  	}
    94  	if err := m.loadExistingTasks(ctx); err != nil {
    95  		return nil, err
    96  	}
    97  	return m, nil
    98  }
    99  
   100  // TaskManager manages v2 shim's and their tasks
   101  type TaskManager struct {
   102  	root                   string
   103  	state                  string
   104  	containerdAddress      string
   105  	containerdTTRPCAddress string
   106  
   107  	tasks      *runtime.TaskList
   108  	events     *exchange.Exchange
   109  	containers containers.Store
   110  }
   111  
   112  // ID of the task manager
   113  func (m *TaskManager) ID() string {
   114  	return fmt.Sprintf("%s.%s", plugin.RuntimePluginV2, "task")
   115  }
   116  
   117  // Create a new task
   118  func (m *TaskManager) Create(ctx context.Context, id string, opts runtime.CreateOpts) (_ runtime.Task, err error) {
   119  	ns, err := namespaces.NamespaceRequired(ctx)
   120  	if err != nil {
   121  		return nil, err
   122  	}
   123  	bundle, err := NewBundle(ctx, m.root, m.state, id, opts.Spec.Value)
   124  	if err != nil {
   125  		return nil, err
   126  	}
   127  	defer func() {
   128  		if err != nil {
   129  			bundle.Delete()
   130  		}
   131  	}()
   132  	topts := opts.TaskOptions
   133  	if topts == nil {
   134  		topts = opts.RuntimeOptions
   135  	}
   136  
   137  	b := shimBinary(ctx, bundle, opts.Runtime, m.containerdAddress, m.containerdTTRPCAddress, m.events, m.tasks)
   138  	shim, err := b.Start(ctx, topts, func() {
   139  		log.G(ctx).WithField("id", id).Info("shim disconnected")
   140  		_, err := m.tasks.Get(ctx, id)
   141  		if err != nil {
   142  			// Task was never started or was already successfully deleted
   143  			return
   144  		}
   145  		cleanupAfterDeadShim(context.Background(), id, ns, m.events, b)
   146  		// Remove self from the runtime task list. Even though the cleanupAfterDeadShim()
   147  		// would publish taskExit event, but the shim.Delete() would always failed with ttrpc
   148  		// disconnect and there is no chance to remove this dead task from runtime task lists.
   149  		// Thus it's better to delete it here.
   150  		m.tasks.Delete(ctx, id)
   151  	})
   152  	if err != nil {
   153  		return nil, err
   154  	}
   155  	defer func() {
   156  		if err != nil {
   157  			shim.Shutdown(ctx)
   158  			shim.Close()
   159  		}
   160  	}()
   161  	t, err := shim.Create(ctx, opts)
   162  	if err != nil {
   163  		return nil, err
   164  	}
   165  	m.tasks.Add(ctx, t)
   166  	return t, nil
   167  }
   168  
   169  // Get a specific task
   170  func (m *TaskManager) Get(ctx context.Context, id string) (runtime.Task, error) {
   171  	return m.tasks.Get(ctx, id)
   172  }
   173  
   174  // Add a runtime task
   175  func (m *TaskManager) Add(ctx context.Context, task runtime.Task) error {
   176  	return m.tasks.Add(ctx, task)
   177  }
   178  
   179  // Delete a runtime task
   180  func (m *TaskManager) Delete(ctx context.Context, id string) {
   181  	m.tasks.Delete(ctx, id)
   182  }
   183  
   184  // Tasks lists all tasks
   185  func (m *TaskManager) Tasks(ctx context.Context, all bool) ([]runtime.Task, error) {
   186  	return m.tasks.GetAll(ctx, all)
   187  }
   188  
   189  func (m *TaskManager) loadExistingTasks(ctx context.Context) error {
   190  	nsDirs, err := ioutil.ReadDir(m.state)
   191  	if err != nil {
   192  		return err
   193  	}
   194  	for _, nsd := range nsDirs {
   195  		if !nsd.IsDir() {
   196  			continue
   197  		}
   198  		ns := nsd.Name()
   199  		// skip hidden directories
   200  		if len(ns) > 0 && ns[0] == '.' {
   201  			continue
   202  		}
   203  		log.G(ctx).WithField("namespace", ns).Debug("loading tasks in namespace")
   204  		if err := m.loadTasks(namespaces.WithNamespace(ctx, ns)); err != nil {
   205  			log.G(ctx).WithField("namespace", ns).WithError(err).Error("loading tasks in namespace")
   206  			continue
   207  		}
   208  		if err := m.cleanupWorkDirs(namespaces.WithNamespace(ctx, ns)); err != nil {
   209  			log.G(ctx).WithField("namespace", ns).WithError(err).Error("cleanup working directory in namespace")
   210  			continue
   211  		}
   212  	}
   213  	return nil
   214  }
   215  
   216  func (m *TaskManager) loadTasks(ctx context.Context) error {
   217  	ns, err := namespaces.NamespaceRequired(ctx)
   218  	if err != nil {
   219  		return err
   220  	}
   221  	shimDirs, err := ioutil.ReadDir(filepath.Join(m.state, ns))
   222  	if err != nil {
   223  		return err
   224  	}
   225  	for _, sd := range shimDirs {
   226  		if !sd.IsDir() {
   227  			continue
   228  		}
   229  		id := sd.Name()
   230  		// skip hidden directories
   231  		if len(id) > 0 && id[0] == '.' {
   232  			continue
   233  		}
   234  		bundle, err := LoadBundle(ctx, m.state, id)
   235  		if err != nil {
   236  			// fine to return error here, it is a programmer error if the context
   237  			// does not have a namespace
   238  			return err
   239  		}
   240  		// fast path
   241  		bf, err := ioutil.ReadDir(bundle.Path)
   242  		if err != nil {
   243  			bundle.Delete()
   244  			log.G(ctx).WithError(err).Errorf("fast path read bundle path for %s", bundle.Path)
   245  			continue
   246  		}
   247  		if len(bf) == 0 {
   248  			bundle.Delete()
   249  			continue
   250  		}
   251  		container, err := m.container(ctx, id)
   252  		if err != nil {
   253  			log.G(ctx).WithError(err).Errorf("loading container %s", id)
   254  			if err := mount.UnmountAll(filepath.Join(bundle.Path, "rootfs"), 0); err != nil {
   255  				log.G(ctx).WithError(err).Errorf("forceful unmount of rootfs %s", id)
   256  			}
   257  			bundle.Delete()
   258  			continue
   259  		}
   260  		binaryCall := shimBinary(ctx, bundle, container.Runtime.Name, m.containerdAddress, m.containerdTTRPCAddress, m.events, m.tasks)
   261  		shim, err := loadShim(ctx, bundle, m.events, m.tasks, func() {
   262  			log.G(ctx).WithField("id", id).Info("shim disconnected")
   263  			_, err := m.tasks.Get(ctx, id)
   264  			if err != nil {
   265  				// Task was never started or was already successfully deleted
   266  				return
   267  			}
   268  			cleanupAfterDeadShim(context.Background(), id, ns, m.events, binaryCall)
   269  			// Remove self from the runtime task list.
   270  			m.tasks.Delete(ctx, id)
   271  		})
   272  		if err != nil {
   273  			cleanupAfterDeadShim(ctx, id, ns, m.events, binaryCall)
   274  			continue
   275  		}
   276  		m.tasks.Add(ctx, shim)
   277  	}
   278  	return nil
   279  }
   280  
   281  func (m *TaskManager) container(ctx context.Context, id string) (*containers.Container, error) {
   282  	container, err := m.containers.Get(ctx, id)
   283  	if err != nil {
   284  		return nil, err
   285  	}
   286  	return &container, nil
   287  }
   288  
   289  func (m *TaskManager) cleanupWorkDirs(ctx context.Context) error {
   290  	ns, err := namespaces.NamespaceRequired(ctx)
   291  	if err != nil {
   292  		return err
   293  	}
   294  	dirs, err := ioutil.ReadDir(filepath.Join(m.root, ns))
   295  	if err != nil {
   296  		return err
   297  	}
   298  	for _, d := range dirs {
   299  		// if the task was not loaded, cleanup and empty working directory
   300  		// this can happen on a reboot where /run for the bundle state is cleaned up
   301  		// but that persistent working dir is left
   302  		if _, err := m.tasks.Get(ctx, d.Name()); err != nil {
   303  			path := filepath.Join(m.root, ns, d.Name())
   304  			if err := os.RemoveAll(path); err != nil {
   305  				log.G(ctx).WithError(err).Errorf("cleanup working dir %s", path)
   306  			}
   307  		}
   308  	}
   309  	return nil
   310  }
   311  
   312  func parsePlatforms(platformStr []string) ([]ocispec.Platform, error) {
   313  	p := make([]ocispec.Platform, len(platformStr))
   314  	for i, v := range platformStr {
   315  		parsed, err := platforms.Parse(v)
   316  		if err != nil {
   317  			return nil, err
   318  		}
   319  		p[i] = parsed
   320  	}
   321  	return p, nil
   322  }