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