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 }