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 17package container 18 19import ( 20 "encoding/json" 21 "io/ioutil" 22 "os" 23 "path/filepath" 24 "sync" 25 26 "github.com/containerd/continuity" 27 "github.com/pkg/errors" 28 runtime "k8s.io/cri-api/pkg/apis/runtime/v1alpha2" 29) 30 31// The container state machine in the CRI plugin: 32// 33// + + 34// | | 35// | Create | Load 36// | | 37// +----v----+ | 38// | | | 39// | CREATED <---------+-----------+ 40// | | | | 41// +----+----- | | 42// | | | 43// | Start | | 44// | | | 45// +----v----+ | | 46// Exec +--------+ | | | 47// Attach | | RUNNING <---------+ | 48// LogReopen +--------> | | | 49// +----+----+ | | 50// | | | 51// | Stop/Exit | | 52// | | | 53// +----v----+ | | 54// | <---------+ +----v----+ 55// | EXITED | | | 56// | <----------------+ UNKNOWN | 57// +----+----+ Stop | | 58// | +---------+ 59// | Remove 60// v 61// DELETED 62 63// statusVersion is current version of container status. 64const statusVersion = "v1" // nolint 65 66// versionedStatus is the internal used versioned container status. 67// nolint 68type versionedStatus struct { 69 // Version indicates the version of the versioned container status. 70 Version string 71 Status 72} 73 74// Status is the status of a container. 75type Status struct { 76 // Pid is the init process id of the container. 77 Pid uint32 78 // CreatedAt is the created timestamp. 79 CreatedAt int64 80 // StartedAt is the started timestamp. 81 StartedAt int64 82 // FinishedAt is the finished timestamp. 83 FinishedAt int64 84 // ExitCode is the container exit code. 85 ExitCode int32 86 // CamelCase string explaining why container is in its current state. 87 Reason string 88 // Human-readable message indicating details about why container is in its 89 // current state. 90 Message string 91 // Starting indicates that the container is in starting state. 92 // This field doesn't need to be checkpointed. 93 Starting bool `json:"-"` 94 // Removing indicates that the container is in removing state. 95 // This field doesn't need to be checkpointed. 96 Removing bool `json:"-"` 97 // Unknown indicates that the container status is not fully loaded. 98 // This field doesn't need to be checkpointed. 99 Unknown bool `json:"-"` 100} 101 102// State returns current state of the container based on the container status. 103func (s Status) State() runtime.ContainerState { 104 if s.Unknown { 105 return runtime.ContainerState_CONTAINER_UNKNOWN 106 } 107 if s.FinishedAt != 0 { 108 return runtime.ContainerState_CONTAINER_EXITED 109 } 110 if s.StartedAt != 0 { 111 return runtime.ContainerState_CONTAINER_RUNNING 112 } 113 if s.CreatedAt != 0 { 114 return runtime.ContainerState_CONTAINER_CREATED 115 } 116 return runtime.ContainerState_CONTAINER_UNKNOWN 117} 118 119// encode encodes Status into bytes in json format. 120func (s *Status) encode() ([]byte, error) { 121 return json.Marshal(&versionedStatus{ 122 Version: statusVersion, 123 Status: *s, 124 }) 125} 126 127// decode decodes Status from bytes. 128func (s *Status) decode(data []byte) error { 129 versioned := &versionedStatus{} 130 if err := json.Unmarshal(data, versioned); err != nil { 131 return err 132 } 133 // Handle old version after upgrade. 134 switch versioned.Version { 135 case statusVersion: 136 *s = versioned.Status 137 return nil 138 } 139 return errors.New("unsupported version") 140} 141 142// UpdateFunc is function used to update the container status. If there 143// is an error, the update will be rolled back. 144type UpdateFunc func(Status) (Status, error) 145 146// StatusStorage manages the container status with a storage backend. 147type StatusStorage interface { 148 // Get a container status. 149 Get() Status 150 // UpdateSync updates the container status and the on disk checkpoint. 151 // Note that the update MUST be applied in one transaction. 152 UpdateSync(UpdateFunc) error 153 // Update the container status. Note that the update MUST be applied 154 // in one transaction. 155 Update(UpdateFunc) error 156 // Delete the container status. 157 // Note: 158 // * Delete should be idempotent. 159 // * The status must be deleted in one transaction. 160 Delete() error 161} 162 163// StoreStatus creates the storage containing the passed in container status with the 164// specified id. 165// The status MUST be created in one transaction. 166func StoreStatus(root, id string, status Status) (StatusStorage, error) { 167 data, err := status.encode() 168 if err != nil { 169 return nil, errors.Wrap(err, "failed to encode status") 170 } 171 path := filepath.Join(root, "status") 172 if err := continuity.AtomicWriteFile(path, data, 0600); err != nil { 173 return nil, errors.Wrapf(err, "failed to checkpoint status to %q", path) 174 } 175 return &statusStorage{ 176 path: path, 177 status: status, 178 }, nil 179} 180 181// LoadStatus loads container status from checkpoint. There shouldn't be threads 182// writing to the file during loading. 183func LoadStatus(root, id string) (Status, error) { 184 path := filepath.Join(root, "status") 185 data, err := ioutil.ReadFile(path) 186 if err != nil { 187 return Status{}, errors.Wrapf(err, "failed to read status from %q", path) 188 } 189 var status Status 190 if err := status.decode(data); err != nil { 191 return Status{}, errors.Wrapf(err, "failed to decode status %q", data) 192 } 193 return status, nil 194} 195 196type statusStorage struct { 197 sync.RWMutex 198 path string 199 status Status 200} 201 202// Get a copy of container status. 203func (s *statusStorage) Get() Status { 204 s.RLock() 205 defer s.RUnlock() 206 return s.status 207} 208 209// UpdateSync updates the container status and the on disk checkpoint. 210func (s *statusStorage) UpdateSync(u UpdateFunc) error { 211 s.Lock() 212 defer s.Unlock() 213 newStatus, err := u(s.status) 214 if err != nil { 215 return err 216 } 217 data, err := newStatus.encode() 218 if err != nil { 219 return errors.Wrap(err, "failed to encode status") 220 } 221 if err := continuity.AtomicWriteFile(s.path, data, 0600); err != nil { 222 return errors.Wrapf(err, "failed to checkpoint status to %q", s.path) 223 } 224 s.status = newStatus 225 return nil 226} 227 228// Update the container status. 229func (s *statusStorage) Update(u UpdateFunc) error { 230 s.Lock() 231 defer s.Unlock() 232 newStatus, err := u(s.status) 233 if err != nil { 234 return err 235 } 236 s.status = newStatus 237 return nil 238} 239 240// Delete deletes the container status from disk atomically. 241func (s *statusStorage) Delete() error { 242 temp := filepath.Dir(s.path) + ".del-" + filepath.Base(s.path) 243 if err := os.Rename(s.path, temp); err != nil && !os.IsNotExist(err) { 244 return err 245 } 246 return os.RemoveAll(temp) 247} 248