podman

Форк
0
/
boltdb_state.go 
3543 строки · 84.6 Кб
1
//go:build !remote
2

3
package libpod
4

5
import (
6
	"bytes"
7
	"errors"
8
	"fmt"
9
	"io/fs"
10
	"net"
11
	"os"
12
	"strconv"
13
	"strings"
14
	"sync"
15
	"time"
16

17
	"github.com/containers/common/libnetwork/types"
18
	"github.com/containers/podman/v5/libpod/define"
19
	"github.com/containers/storage/pkg/fileutils"
20
	"github.com/sirupsen/logrus"
21
	bolt "go.etcd.io/bbolt"
22
)
23

24
// BoltState is a state implementation backed by a Bolt DB
25
type BoltState struct {
26
	valid   bool
27
	dbPath  string
28
	dbLock  sync.Mutex
29
	runtime *Runtime
30
}
31

32
// A brief description of the format of the BoltDB state:
33
// At the top level, the following buckets are created:
34
// - idRegistryBkt: Maps ID to Name for containers and pods.
35
//   Used to ensure container and pod IDs are globally unique.
36
// - nameRegistryBkt: Maps Name to ID for containers and pods.
37
//   Used to ensure container and pod names are globally unique.
38
// - ctrBkt: Contains a sub-bucket for each container in the state.
39
//   Each sub-bucket has config and state keys holding the container's JSON
40
//   encoded configuration and state (respectively), an optional netNS key
41
//   containing the path to the container's network namespace, a dependencies
42
//   bucket containing the container's dependencies, and an optional pod key
43
//   containing the ID of the pod the container is joined to.
44
//   After updates to include exec sessions, may also include an exec bucket
45
//   with the IDs of exec sessions currently in use by the container.
46
// - allCtrsBkt: Map of ID to name containing only containers. Used for
47
//   container lookup operations.
48
// - podBkt: Contains a sub-bucket for each pod in the state.
49
//   Each sub-bucket has config and state keys holding the pod's JSON encoded
50
//   configuration and state, plus a containers sub bucket holding the IDs of
51
//   containers in the pod.
52
// - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup
53
//   operations.
54
// - execBkt: Map of exec session ID to container ID - used for resolving
55
//   exec session IDs to the containers that hold the exec session.
56
// - networksBkt: Contains all network names as key with their options json
57
//   encoded as value.
58
// - aliasesBkt - Deprecated, use the networksBkt. Used to contain a bucket
59
//   for each CNI network which contain a map of network alias (an extra name
60
//   for containers in DNS) to the ID of the container holding the alias.
61
//   Aliases must be unique per-network, and cannot conflict with names
62
//   registered in nameRegistryBkt.
63
// - runtimeConfigBkt: Contains configuration of the libpod instance that
64
//   initially created the database. This must match for any further instances
65
//   that access the database, to ensure that state mismatches with
66
//   containers/storage do not occur.
67
// - exitCodeBucket/exitCodeTimeStampBucket: (#14559) exit codes must be part
68
//   of the database to resolve a previous race condition when one process waits
69
//   for the exit file to be written and another process removes it along with
70
//   the container during auto-removal.  The same race would happen trying to
71
//   read the exit code from the containers bucket.  Hence, exit codes go into
72
//   their own bucket.  To avoid the rather expensive JSON (un)marshalling, we
73
//   have two buckets: one for the exit codes, the other for the timestamps.
74

75
// NewBoltState creates a new bolt-backed state database
76
func NewBoltState(path string, runtime *Runtime) (State, error) {
77
	logrus.Info("Using boltdb as database backend")
78
	state := new(BoltState)
79
	state.dbPath = path
80
	state.runtime = runtime
81

82
	logrus.Debugf("Initializing boltdb state at %s", path)
83

84
	// BoltDB is deprecated and, as of Podman 5.0, we no longer allow the
85
	// creation of new Bolt states.
86
	// If the DB does not already exist, error out.
87
	// To continue testing in CI, allow creation iff an undocumented env
88
	// var is set.
89
	if os.Getenv("CI_DESIRED_DATABASE") != "boltdb" {
90
		if err := fileutils.Exists(path); err != nil && errors.Is(err, fs.ErrNotExist) {
91
			return nil, fmt.Errorf("the BoltDB backend has been deprecated, no new BoltDB databases can be created: %w", define.ErrInvalidArg)
92
		}
93
	} else {
94
		logrus.Debugf("Allowing deprecated database backend due to CI_DESIRED_DATABASE.")
95
	}
96

97
	db, err := bolt.Open(path, 0600, nil)
98
	if err != nil {
99
		return nil, fmt.Errorf("opening database %s: %w", path, err)
100
	}
101
	// Everywhere else, we use s.deferredCloseDBCon(db) to ensure the state's DB
102
	// mutex is also unlocked.
103
	// However, here, the mutex has not been locked, since we just created
104
	// the DB connection, and it hasn't left this function yet - no risk of
105
	// concurrent access.
106
	// As such, just a db.Close() is fine here.
107
	defer db.Close()
108

109
	createBuckets := [][]byte{
110
		idRegistryBkt,
111
		nameRegistryBkt,
112
		ctrBkt,
113
		allCtrsBkt,
114
		podBkt,
115
		allPodsBkt,
116
		volBkt,
117
		allVolsBkt,
118
		execBkt,
119
		runtimeConfigBkt,
120
		exitCodeBkt,
121
		exitCodeTimeStampBkt,
122
		volCtrsBkt,
123
	}
124

125
	// Does the DB need an update?
126
	needsUpdate := false
127
	err = db.View(func(tx *bolt.Tx) error {
128
		for _, bkt := range createBuckets {
129
			if test := tx.Bucket(bkt); test == nil {
130
				needsUpdate = true
131
				break
132
			}
133
		}
134
		return nil
135
	})
136
	if err != nil {
137
		return nil, fmt.Errorf("checking DB schema: %w", err)
138
	}
139

140
	if !needsUpdate {
141
		state.valid = true
142
		return state, nil
143
	}
144

145
	// Ensure schema is properly created in DB
146
	err = db.Update(func(tx *bolt.Tx) error {
147
		for _, bkt := range createBuckets {
148
			if _, err := tx.CreateBucketIfNotExists(bkt); err != nil {
149
				return fmt.Errorf("creating bucket %s: %w", string(bkt), err)
150
			}
151
		}
152
		return nil
153
	})
154
	if err != nil {
155
		return nil, fmt.Errorf("creating buckets for DB: %w", err)
156
	}
157

158
	state.valid = true
159

160
	return state, nil
161
}
162

163
// Close closes the state and prevents further use
164
func (s *BoltState) Close() error {
165
	s.valid = false
166
	return nil
167
}
168

169
// Refresh clears container and pod states after a reboot
170
func (s *BoltState) Refresh() error {
171
	if !s.valid {
172
		return define.ErrDBClosed
173
	}
174

175
	db, err := s.getDBCon()
176
	if err != nil {
177
		return err
178
	}
179
	defer s.deferredCloseDBCon(db)
180

181
	err = db.Update(func(tx *bolt.Tx) error {
182
		idBucket, err := getIDBucket(tx)
183
		if err != nil {
184
			return err
185
		}
186

187
		namesBucket, err := getNamesBucket(tx)
188
		if err != nil {
189
			return err
190
		}
191

192
		ctrsBucket, err := getCtrBucket(tx)
193
		if err != nil {
194
			return err
195
		}
196

197
		podsBucket, err := getPodBucket(tx)
198
		if err != nil {
199
			return err
200
		}
201

202
		allVolsBucket, err := getAllVolsBucket(tx)
203
		if err != nil {
204
			return err
205
		}
206

207
		volBucket, err := getVolBucket(tx)
208
		if err != nil {
209
			return err
210
		}
211

212
		execBucket, err := getExecBucket(tx)
213
		if err != nil {
214
			return err
215
		}
216

217
		exitCodeBucket, err := getExitCodeBucket(tx)
218
		if err != nil {
219
			return err
220
		}
221

222
		timeStampBucket, err := getExitCodeTimeStampBucket(tx)
223
		if err != nil {
224
			return err
225
		}
226

227
		// Clear all exec exit codes
228
		toRemoveExitCodes := []string{}
229
		err = exitCodeBucket.ForEach(func(id, _ []byte) error {
230
			toRemoveExitCodes = append(toRemoveExitCodes, string(id))
231
			return nil
232
		})
233
		if err != nil {
234
			return fmt.Errorf("reading exit codes bucket: %w", err)
235
		}
236
		for _, id := range toRemoveExitCodes {
237
			if err := exitCodeBucket.Delete([]byte(id)); err != nil {
238
				return fmt.Errorf("removing exit code for ID %s: %w", id, err)
239
			}
240
		}
241

242
		toRemoveTimeStamps := []string{}
243
		err = timeStampBucket.ForEach(func(id, _ []byte) error {
244
			toRemoveTimeStamps = append(toRemoveTimeStamps, string(id))
245
			return nil
246
		})
247
		if err != nil {
248
			return fmt.Errorf("reading timestamps bucket: %w", err)
249
		}
250
		for _, id := range toRemoveTimeStamps {
251
			if err := timeStampBucket.Delete([]byte(id)); err != nil {
252
				return fmt.Errorf("removing timestamp for ID %s: %w", id, err)
253
			}
254
		}
255

256
		// Iterate through all IDs. Check if they are containers.
257
		// If they are, unmarshal their state, and then clear
258
		// PID, mountpoint, and state for all of them
259
		// Then save the modified state
260
		// Also clear all network namespaces
261
		toRemoveIDs := []string{}
262
		err = idBucket.ForEach(func(id, name []byte) error {
263
			ctrBkt := ctrsBucket.Bucket(id)
264
			if ctrBkt == nil {
265
				// It's a pod
266
				podBkt := podsBucket.Bucket(id)
267
				if podBkt == nil {
268
					// This is neither a pod nor a container
269
					// Something is seriously wrong, but
270
					// continue on and try to clean up the
271
					// state and become consistent.
272
					// Just note what needs to be removed
273
					// for now - ForEach says you shouldn't
274
					// remove things from the table during
275
					// it.
276
					logrus.Errorf("Database issue: dangling ID %s found (not a pod or container) - removing", string(id))
277
					toRemoveIDs = append(toRemoveIDs, string(id))
278
					return nil
279
				}
280

281
				// Get the state
282
				stateBytes := podBkt.Get(stateKey)
283
				if stateBytes == nil {
284
					return fmt.Errorf("pod %s missing state key: %w", string(id), define.ErrInternal)
285
				}
286

287
				state := new(podState)
288

289
				if err := json.Unmarshal(stateBytes, state); err != nil {
290
					return fmt.Errorf("unmarshalling state for pod %s: %w", string(id), err)
291
				}
292

293
				// Refresh the state
294
				resetPodState(state)
295

296
				newStateBytes, err := json.Marshal(state)
297
				if err != nil {
298
					return fmt.Errorf("marshalling modified state for pod %s: %w", string(id), err)
299
				}
300

301
				if err := podBkt.Put(stateKey, newStateBytes); err != nil {
302
					return fmt.Errorf("updating state for pod %s in DB: %w", string(id), err)
303
				}
304

305
				// It's not a container, nothing to do
306
				return nil
307
			}
308

309
			// First, delete the network namespace
310
			if err := ctrBkt.Delete(netNSKey); err != nil {
311
				return fmt.Errorf("removing network namespace for container %s: %w", string(id), err)
312
			}
313

314
			stateBytes := ctrBkt.Get(stateKey)
315
			if stateBytes == nil {
316
				// Badly formatted container bucket
317
				return fmt.Errorf("container %s missing state in DB: %w", string(id), define.ErrInternal)
318
			}
319

320
			state := new(ContainerState)
321

322
			if err := json.Unmarshal(stateBytes, state); err != nil {
323
				return fmt.Errorf("unmarshalling state for container %s: %w", string(id), err)
324
			}
325

326
			resetContainerState(state)
327

328
			newStateBytes, err := json.Marshal(state)
329
			if err != nil {
330
				return fmt.Errorf("marshalling modified state for container %s: %w", string(id), err)
331
			}
332

333
			if err := ctrBkt.Put(stateKey, newStateBytes); err != nil {
334
				return fmt.Errorf("updating state for container %s in DB: %w", string(id), err)
335
			}
336

337
			// Delete all exec sessions, if there are any
338
			ctrExecBkt := ctrBkt.Bucket(execBkt)
339
			if ctrExecBkt != nil {
340
				// Can't delete in a ForEach, so build a list of
341
				// what to remove then remove.
342
				toRemove := []string{}
343
				err = ctrExecBkt.ForEach(func(id, unused []byte) error {
344
					toRemove = append(toRemove, string(id))
345
					return nil
346
				})
347
				if err != nil {
348
					return err
349
				}
350
				for _, execID := range toRemove {
351
					if err := ctrExecBkt.Delete([]byte(execID)); err != nil {
352
						return fmt.Errorf("removing exec session %s from container %s: %w", execID, string(id), err)
353
					}
354
				}
355
			}
356

357
			return nil
358
		})
359
		if err != nil {
360
			return err
361
		}
362

363
		// Remove dangling IDs.
364
		for _, id := range toRemoveIDs {
365
			// Look up the ID to see if we also have a dangling name
366
			// in the DB.
367
			name := idBucket.Get([]byte(id))
368
			if name != nil {
369
				if testID := namesBucket.Get(name); testID != nil {
370
					logrus.Infof("Found dangling name %s (ID %s) in database", string(name), id)
371
					if err := namesBucket.Delete(name); err != nil {
372
						return fmt.Errorf("removing dangling name %s (ID %s) from database: %w", string(name), id, err)
373
					}
374
				}
375
			}
376
			if err := idBucket.Delete([]byte(id)); err != nil {
377
				return fmt.Errorf("removing dangling ID %s from database: %w", id, err)
378
			}
379
		}
380

381
		// Now refresh volumes
382
		err = allVolsBucket.ForEach(func(id, name []byte) error {
383
			dbVol := volBucket.Bucket(id)
384
			if dbVol == nil {
385
				return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal)
386
			}
387

388
			// Get the state
389
			volStateBytes := dbVol.Get(stateKey)
390
			if volStateBytes == nil {
391
				// If the volume doesn't have a state, nothing to do
392
				return nil
393
			}
394

395
			oldState := new(VolumeState)
396

397
			if err := json.Unmarshal(volStateBytes, oldState); err != nil {
398
				return fmt.Errorf("unmarshalling state for volume %s: %w", string(id), err)
399
			}
400

401
			resetVolumeState(oldState)
402

403
			newState, err := json.Marshal(oldState)
404
			if err != nil {
405
				return fmt.Errorf("marshalling state for volume %s: %w", string(id), err)
406
			}
407

408
			if err := dbVol.Put(stateKey, newState); err != nil {
409
				return fmt.Errorf("storing new state for volume %s: %w", string(id), err)
410
			}
411

412
			return nil
413
		})
414
		if err != nil {
415
			return err
416
		}
417

418
		// Now refresh exec sessions
419
		// We want to remove them all, but for-each can't modify buckets
420
		// So we have to make a list of what to operate on, then do the
421
		// work.
422
		toRemoveExec := []string{}
423
		err = execBucket.ForEach(func(id, unused []byte) error {
424
			toRemoveExec = append(toRemoveExec, string(id))
425
			return nil
426
		})
427
		if err != nil {
428
			return err
429
		}
430

431
		for _, execSession := range toRemoveExec {
432
			if err := execBucket.Delete([]byte(execSession)); err != nil {
433
				return fmt.Errorf("deleting exec session %s registry from database: %w", execSession, err)
434
			}
435
		}
436

437
		return nil
438
	})
439
	return err
440
}
441

442
// GetDBConfig retrieves runtime configuration fields that were created when
443
// the database was first initialized
444
func (s *BoltState) GetDBConfig() (*DBConfig, error) {
445
	if !s.valid {
446
		return nil, define.ErrDBClosed
447
	}
448

449
	cfg := new(DBConfig)
450

451
	db, err := s.getDBCon()
452
	if err != nil {
453
		return nil, err
454
	}
455
	defer s.deferredCloseDBCon(db)
456

457
	err = db.View(func(tx *bolt.Tx) error {
458
		configBucket, err := getRuntimeConfigBucket(tx)
459
		if err != nil {
460
			return err
461
		}
462

463
		// Some of these may be nil
464
		// When we convert to string, Go will coerce them to ""
465
		// That's probably fine - we could raise an error if the key is
466
		// missing, but just not including it is also OK.
467
		libpodRoot := configBucket.Get(staticDirKey)
468
		libpodTmp := configBucket.Get(tmpDirKey)
469
		storageRoot := configBucket.Get(graphRootKey)
470
		storageTmp := configBucket.Get(runRootKey)
471
		graphDriver := configBucket.Get(graphDriverKey)
472
		volumePath := configBucket.Get(volPathKey)
473

474
		cfg.LibpodRoot = string(libpodRoot)
475
		cfg.LibpodTmp = string(libpodTmp)
476
		cfg.StorageRoot = string(storageRoot)
477
		cfg.StorageTmp = string(storageTmp)
478
		cfg.GraphDriver = string(graphDriver)
479
		cfg.VolumePath = string(volumePath)
480

481
		return nil
482
	})
483
	if err != nil {
484
		return nil, err
485
	}
486

487
	return cfg, nil
488
}
489

490
// ValidateDBConfig validates paths in the given runtime against the database
491
func (s *BoltState) ValidateDBConfig(runtime *Runtime) error {
492
	if !s.valid {
493
		return define.ErrDBClosed
494
	}
495

496
	db, err := s.getDBCon()
497
	if err != nil {
498
		return err
499
	}
500
	defer s.deferredCloseDBCon(db)
501

502
	// Check runtime configuration
503
	if err := checkRuntimeConfig(db, runtime); err != nil {
504
		return err
505
	}
506

507
	return nil
508
}
509

510
// GetContainerName returns the name associated with a given ID.
511
// Returns ErrNoSuchCtr if the ID does not exist.
512
func (s *BoltState) GetContainerName(id string) (string, error) {
513
	if id == "" {
514
		return "", define.ErrEmptyID
515
	}
516

517
	if !s.valid {
518
		return "", define.ErrDBClosed
519
	}
520

521
	idBytes := []byte(id)
522

523
	db, err := s.getDBCon()
524
	if err != nil {
525
		return "", err
526
	}
527
	defer s.deferredCloseDBCon(db)
528

529
	name := ""
530

531
	err = db.View(func(tx *bolt.Tx) error {
532
		idBkt, err := getIDBucket(tx)
533
		if err != nil {
534
			return err
535
		}
536

537
		ctrsBkt, err := getCtrBucket(tx)
538
		if err != nil {
539
			return err
540
		}
541

542
		nameBytes := idBkt.Get(idBytes)
543
		if nameBytes == nil {
544
			return define.ErrNoSuchCtr
545
		}
546

547
		ctrExists := ctrsBkt.Bucket(idBytes)
548
		if ctrExists == nil {
549
			return define.ErrNoSuchCtr
550
		}
551

552
		name = string(nameBytes)
553
		return nil
554
	})
555
	if err != nil {
556
		return "", err
557
	}
558

559
	return name, nil
560
}
561

562
// GetPodName returns the name associated with a given ID.
563
// Returns ErrNoSuchPod if the ID does not exist.
564
func (s *BoltState) GetPodName(id string) (string, error) {
565
	if id == "" {
566
		return "", define.ErrEmptyID
567
	}
568

569
	if !s.valid {
570
		return "", define.ErrDBClosed
571
	}
572

573
	idBytes := []byte(id)
574

575
	db, err := s.getDBCon()
576
	if err != nil {
577
		return "", err
578
	}
579
	defer s.deferredCloseDBCon(db)
580

581
	name := ""
582

583
	err = db.View(func(tx *bolt.Tx) error {
584
		idBkt, err := getIDBucket(tx)
585
		if err != nil {
586
			return err
587
		}
588

589
		podBkt, err := getPodBucket(tx)
590
		if err != nil {
591
			return err
592
		}
593

594
		nameBytes := idBkt.Get(idBytes)
595
		if nameBytes == nil {
596
			return define.ErrNoSuchPod
597
		}
598

599
		podExists := podBkt.Bucket(idBytes)
600
		if podExists == nil {
601
			return define.ErrNoSuchPod
602
		}
603

604
		name = string(nameBytes)
605
		return nil
606
	})
607
	if err != nil {
608
		return "", err
609
	}
610

611
	return name, nil
612
}
613

614
// Container retrieves a single container from the state by its full ID
615
func (s *BoltState) Container(id string) (*Container, error) {
616
	if id == "" {
617
		return nil, define.ErrEmptyID
618
	}
619

620
	if !s.valid {
621
		return nil, define.ErrDBClosed
622
	}
623

624
	ctrID := []byte(id)
625

626
	ctr := new(Container)
627
	ctr.config = new(ContainerConfig)
628
	ctr.state = new(ContainerState)
629

630
	db, err := s.getDBCon()
631
	if err != nil {
632
		return nil, err
633
	}
634
	defer s.deferredCloseDBCon(db)
635

636
	err = db.View(func(tx *bolt.Tx) error {
637
		ctrBucket, err := getCtrBucket(tx)
638
		if err != nil {
639
			return err
640
		}
641

642
		return s.getContainerFromDB(ctrID, ctr, ctrBucket, false)
643
	})
644
	if err != nil {
645
		return nil, err
646
	}
647

648
	return ctr, nil
649
}
650

651
// LookupContainerID retrieves a container ID from the state by full or unique
652
// partial ID or name
653
func (s *BoltState) LookupContainerID(idOrName string) (string, error) {
654
	if idOrName == "" {
655
		return "", define.ErrEmptyID
656
	}
657

658
	if !s.valid {
659
		return "", define.ErrDBClosed
660
	}
661

662
	db, err := s.getDBCon()
663
	if err != nil {
664
		return "", err
665
	}
666
	defer s.deferredCloseDBCon(db)
667

668
	var id []byte
669
	err = db.View(func(tx *bolt.Tx) error {
670
		ctrBucket, err := getCtrBucket(tx)
671
		if err != nil {
672
			return err
673
		}
674

675
		namesBucket, err := getNamesBucket(tx)
676
		if err != nil {
677
			return err
678
		}
679

680
		fullID, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket)
681
		id = fullID
682
		return err
683
	})
684

685
	if err != nil {
686
		return "", err
687
	}
688

689
	retID := string(id)
690
	return retID, nil
691
}
692

693
// LookupContainer retrieves a container from the state by full or unique
694
// partial ID or name
695
func (s *BoltState) LookupContainer(idOrName string) (*Container, error) {
696
	if idOrName == "" {
697
		return nil, define.ErrEmptyID
698
	}
699

700
	if !s.valid {
701
		return nil, define.ErrDBClosed
702
	}
703

704
	ctr := new(Container)
705
	ctr.config = new(ContainerConfig)
706
	ctr.state = new(ContainerState)
707

708
	db, err := s.getDBCon()
709
	if err != nil {
710
		return nil, err
711
	}
712
	defer s.deferredCloseDBCon(db)
713

714
	err = db.View(func(tx *bolt.Tx) error {
715
		ctrBucket, err := getCtrBucket(tx)
716
		if err != nil {
717
			return err
718
		}
719

720
		namesBucket, err := getNamesBucket(tx)
721
		if err != nil {
722
			return err
723
		}
724

725
		id, err := s.lookupContainerID(idOrName, ctrBucket, namesBucket)
726
		if err != nil {
727
			return err
728
		}
729

730
		return s.getContainerFromDB(id, ctr, ctrBucket, false)
731
	})
732
	if err != nil {
733
		return nil, err
734
	}
735

736
	return ctr, nil
737
}
738

739
// HasContainer checks if a container is present in the state
740
func (s *BoltState) HasContainer(id string) (bool, error) {
741
	if id == "" {
742
		return false, define.ErrEmptyID
743
	}
744

745
	if !s.valid {
746
		return false, define.ErrDBClosed
747
	}
748

749
	ctrID := []byte(id)
750

751
	db, err := s.getDBCon()
752
	if err != nil {
753
		return false, err
754
	}
755
	defer s.deferredCloseDBCon(db)
756

757
	exists := false
758

759
	err = db.View(func(tx *bolt.Tx) error {
760
		ctrBucket, err := getCtrBucket(tx)
761
		if err != nil {
762
			return err
763
		}
764

765
		ctrDB := ctrBucket.Bucket(ctrID)
766
		if ctrDB != nil {
767
			exists = true
768
		}
769

770
		return nil
771
	})
772
	if err != nil {
773
		return false, err
774
	}
775

776
	return exists, nil
777
}
778

779
// AddContainer adds a container to the state
780
// The container being added cannot belong to a pod
781
func (s *BoltState) AddContainer(ctr *Container) error {
782
	if !s.valid {
783
		return define.ErrDBClosed
784
	}
785

786
	if !ctr.valid {
787
		return define.ErrCtrRemoved
788
	}
789

790
	if ctr.config.Pod != "" {
791
		return fmt.Errorf("cannot add a container that belongs to a pod with AddContainer - use AddContainerToPod: %w", define.ErrInvalidArg)
792
	}
793

794
	return s.addContainer(ctr, nil)
795
}
796

797
// RemoveContainer removes a container from the state
798
// Only removes containers not in pods - for containers that are a member of a
799
// pod, use RemoveContainerFromPod
800
func (s *BoltState) RemoveContainer(ctr *Container) error {
801
	if !s.valid {
802
		return define.ErrDBClosed
803
	}
804

805
	if ctr.config.Pod != "" {
806
		return fmt.Errorf("container %s is part of a pod, use RemoveContainerFromPod instead: %w", ctr.ID(), define.ErrPodExists)
807
	}
808

809
	db, err := s.getDBCon()
810
	if err != nil {
811
		return err
812
	}
813
	defer s.deferredCloseDBCon(db)
814

815
	err = db.Update(func(tx *bolt.Tx) error {
816
		return s.removeContainer(ctr, nil, tx)
817
	})
818
	return err
819
}
820

821
// UpdateContainer updates a container's state from the database
822
func (s *BoltState) UpdateContainer(ctr *Container) error {
823
	if !s.valid {
824
		return define.ErrDBClosed
825
	}
826

827
	if !ctr.valid {
828
		return define.ErrCtrRemoved
829
	}
830

831
	ctrID := []byte(ctr.ID())
832

833
	db, err := s.getDBCon()
834
	if err != nil {
835
		return err
836
	}
837
	defer s.deferredCloseDBCon(db)
838

839
	return db.View(func(tx *bolt.Tx) error {
840
		ctrBucket, err := getCtrBucket(tx)
841
		if err != nil {
842
			return err
843
		}
844
		return s.getContainerStateDB(ctrID, ctr, ctrBucket)
845
	})
846
}
847

848
// SaveContainer saves a container's current state in the database
849
func (s *BoltState) SaveContainer(ctr *Container) error {
850
	if !s.valid {
851
		return define.ErrDBClosed
852
	}
853

854
	if !ctr.valid {
855
		return define.ErrCtrRemoved
856
	}
857

858
	stateJSON, err := json.Marshal(ctr.state)
859
	if err != nil {
860
		return fmt.Errorf("marshalling container %s state to JSON: %w", ctr.ID(), err)
861
	}
862
	netNSPath := ctr.state.NetNS
863

864
	ctrID := []byte(ctr.ID())
865

866
	db, err := s.getDBCon()
867
	if err != nil {
868
		return err
869
	}
870
	defer s.deferredCloseDBCon(db)
871

872
	err = db.Update(func(tx *bolt.Tx) error {
873
		ctrBucket, err := getCtrBucket(tx)
874
		if err != nil {
875
			return err
876
		}
877

878
		ctrToSave := ctrBucket.Bucket(ctrID)
879
		if ctrToSave == nil {
880
			ctr.valid = false
881
			return fmt.Errorf("container %s does not exist in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
882
		}
883

884
		// Update the state
885
		if err := ctrToSave.Put(stateKey, stateJSON); err != nil {
886
			return fmt.Errorf("updating container %s state in DB: %w", ctr.ID(), err)
887
		}
888

889
		if netNSPath == "" {
890
			// Delete the existing network namespace
891
			if err := ctrToSave.Delete(netNSKey); err != nil {
892
				return fmt.Errorf("removing network namespace path for container %s in DB: %w", ctr.ID(), err)
893
			}
894
		}
895

896
		return nil
897
	})
898
	return err
899
}
900

901
// ContainerInUse checks if other containers depend on the given container
902
// It returns a slice of the IDs of the containers depending on the given
903
// container. If the slice is empty, no containers depend on the given container
904
func (s *BoltState) ContainerInUse(ctr *Container) ([]string, error) {
905
	if !s.valid {
906
		return nil, define.ErrDBClosed
907
	}
908

909
	if !ctr.valid {
910
		return nil, define.ErrCtrRemoved
911
	}
912

913
	depCtrs := []string{}
914

915
	db, err := s.getDBCon()
916
	if err != nil {
917
		return nil, err
918
	}
919
	defer s.deferredCloseDBCon(db)
920

921
	err = db.View(func(tx *bolt.Tx) error {
922
		ctrBucket, err := getCtrBucket(tx)
923
		if err != nil {
924
			return err
925
		}
926

927
		ctrDB := ctrBucket.Bucket([]byte(ctr.ID()))
928
		if ctrDB == nil {
929
			ctr.valid = false
930
			return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
931
		}
932

933
		dependsBkt := ctrDB.Bucket(dependenciesBkt)
934
		if dependsBkt == nil {
935
			return fmt.Errorf("container %s has no dependencies bucket: %w", ctr.ID(), define.ErrInternal)
936
		}
937

938
		// Iterate through and add dependencies
939
		err = dependsBkt.ForEach(func(id, value []byte) error {
940
			depCtrs = append(depCtrs, string(id))
941

942
			return nil
943
		})
944
		if err != nil {
945
			return err
946
		}
947

948
		return nil
949
	})
950
	if err != nil {
951
		return nil, err
952
	}
953

954
	return depCtrs, nil
955
}
956

957
// AllContainers retrieves all the containers in the database
958
// If `loadState` is set, the containers' state will be loaded as well.
959
func (s *BoltState) AllContainers(loadState bool) ([]*Container, error) {
960
	if !s.valid {
961
		return nil, define.ErrDBClosed
962
	}
963

964
	ctrs := []*Container{}
965

966
	db, err := s.getDBCon()
967
	if err != nil {
968
		return nil, err
969
	}
970
	defer s.deferredCloseDBCon(db)
971

972
	err = db.View(func(tx *bolt.Tx) error {
973
		allCtrsBucket, err := getAllCtrsBucket(tx)
974
		if err != nil {
975
			return err
976
		}
977

978
		ctrBucket, err := getCtrBucket(tx)
979
		if err != nil {
980
			return err
981
		}
982

983
		return allCtrsBucket.ForEach(func(id, name []byte) error {
984
			// If performance becomes an issue, this check can be
985
			// removed. But the error messages that come back will
986
			// be much less helpful.
987
			ctrExists := ctrBucket.Bucket(id)
988
			if ctrExists == nil {
989
				return fmt.Errorf("state is inconsistent - container ID %s in all containers, but container not found: %w", string(id), define.ErrInternal)
990
			}
991

992
			ctr := new(Container)
993
			ctr.config = new(ContainerConfig)
994
			ctr.state = new(ContainerState)
995

996
			if err := s.getContainerFromDB(id, ctr, ctrBucket, loadState); err != nil {
997
				logrus.Errorf("Error retrieving container from database: %v", err)
998
			} else {
999
				ctrs = append(ctrs, ctr)
1000
			}
1001

1002
			return nil
1003
		})
1004
	})
1005
	if err != nil {
1006
		return nil, err
1007
	}
1008

1009
	return ctrs, nil
1010
}
1011

1012
// GetNetworks returns the networks this container is a part of.
1013
func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) {
1014
	if !s.valid {
1015
		return nil, define.ErrDBClosed
1016
	}
1017

1018
	if !ctr.valid {
1019
		return nil, define.ErrCtrRemoved
1020
	}
1021

1022
	// if the network mode is not bridge return no networks
1023
	if !ctr.config.NetMode.IsBridge() {
1024
		return nil, nil
1025
	}
1026

1027
	ctrID := []byte(ctr.ID())
1028

1029
	db, err := s.getDBCon()
1030
	if err != nil {
1031
		return nil, err
1032
	}
1033
	defer s.deferredCloseDBCon(db)
1034

1035
	networks := make(map[string]types.PerNetworkOptions)
1036

1037
	var convertDB bool
1038

1039
	err = db.View(func(tx *bolt.Tx) error {
1040
		ctrBucket, err := getCtrBucket(tx)
1041
		if err != nil {
1042
			return err
1043
		}
1044

1045
		dbCtr := ctrBucket.Bucket(ctrID)
1046
		if dbCtr == nil {
1047
			ctr.valid = false
1048
			return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
1049
		}
1050

1051
		ctrNetworkBkt := dbCtr.Bucket(networksBkt)
1052
		if ctrNetworkBkt == nil {
1053
			// convert if needed
1054
			convertDB = true
1055
			return nil
1056
		}
1057

1058
		return ctrNetworkBkt.ForEach(func(network, v []byte) error {
1059
			opts := types.PerNetworkOptions{}
1060
			if err := json.Unmarshal(v, &opts); err != nil {
1061
				// special case for backwards compat
1062
				// earlier version used the container id as value so we set a
1063
				// special error to indicate the we have to migrate the db
1064
				if !bytes.Equal(v, ctrID) {
1065
					return err
1066
				}
1067
				convertDB = true
1068
			}
1069
			networks[string(network)] = opts
1070
			return nil
1071
		})
1072
	})
1073
	if err != nil {
1074
		return nil, err
1075
	}
1076
	if convertDB {
1077
		err = db.Update(func(tx *bolt.Tx) error {
1078
			ctrBucket, err := getCtrBucket(tx)
1079
			if err != nil {
1080
				return err
1081
			}
1082

1083
			dbCtr := ctrBucket.Bucket(ctrID)
1084
			if dbCtr == nil {
1085
				ctr.valid = false
1086
				return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
1087
			}
1088

1089
			var networkList []string
1090

1091
			ctrNetworkBkt := dbCtr.Bucket(networksBkt)
1092
			if ctrNetworkBkt == nil {
1093
				ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt)
1094
				if err != nil {
1095
					return fmt.Errorf("creating networks bucket for container %s: %w", ctr.ID(), err)
1096
				}
1097
				// the container has no networks in the db lookup config and write to the db
1098
				networkList = ctr.config.NetworksDeprecated
1099
				// if there are no networks we have to add the default
1100
				if len(networkList) == 0 {
1101
					networkList = []string{ctr.runtime.config.Network.DefaultNetwork}
1102
				}
1103
			} else {
1104
				err = ctrNetworkBkt.ForEach(func(network, v []byte) error {
1105
					networkList = append(networkList, string(network))
1106
					return nil
1107
				})
1108
				if err != nil {
1109
					return err
1110
				}
1111
			}
1112

1113
			// the container has no networks in the db lookup config and write to the db
1114
			for i, network := range networkList {
1115
				var intName string
1116
				if ctr.state.NetInterfaceDescriptions != nil {
1117
					eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network)
1118
					if !exists {
1119
						return fmt.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network)
1120
					}
1121
					intName = eth
1122
				} else {
1123
					intName = fmt.Sprintf("eth%d", i)
1124
				}
1125
				getAliases := func(network string) []string {
1126
					var aliases []string
1127
					ctrAliasesBkt := dbCtr.Bucket(aliasesBkt)
1128
					if ctrAliasesBkt == nil {
1129
						return nil
1130
					}
1131
					netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network))
1132
					if netAliasesBkt == nil {
1133
						// No aliases for this specific network.
1134
						return nil
1135
					}
1136

1137
					// let's ignore the error here there is nothing we can do
1138
					_ = netAliasesBkt.ForEach(func(alias, v []byte) error {
1139
						aliases = append(aliases, string(alias))
1140
						return nil
1141
					})
1142
					// also add the short container id as alias
1143
					return aliases
1144
				}
1145

1146
				netOpts := &types.PerNetworkOptions{
1147
					InterfaceName: intName,
1148
					// we have to add the short id as alias for docker compat
1149
					Aliases: append(getAliases(network), ctr.config.ID[:12]),
1150
				}
1151
				// only set the static ip/mac on the first network
1152
				if i == 0 {
1153
					if ctr.config.StaticIP != nil {
1154
						netOpts.StaticIPs = []net.IP{ctr.config.StaticIP}
1155
					}
1156
					netOpts.StaticMAC = ctr.config.StaticMAC
1157
				}
1158

1159
				optsBytes, err := json.Marshal(netOpts)
1160
				if err != nil {
1161
					return err
1162
				}
1163
				// insert into network map because we need to return this
1164
				networks[network] = *netOpts
1165

1166
				err = ctrNetworkBkt.Put([]byte(network), optsBytes)
1167
				if err != nil {
1168
					return err
1169
				}
1170
			}
1171
			return nil
1172
		})
1173
		if err != nil {
1174
			return nil, err
1175
		}
1176
	}
1177

1178
	return networks, nil
1179
}
1180

1181
// NetworkConnect adds the given container to the given network. If aliases are
1182
// specified, those will be added to the given network.
1183
func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error {
1184
	return s.networkModify(ctr, network, opts, true)
1185
}
1186

1187
// NetworkModify will allow you to set new options on an existing connected network
1188
func (s *BoltState) NetworkModify(ctr *Container, network string, opts types.PerNetworkOptions) error {
1189
	return s.networkModify(ctr, network, opts, false)
1190
}
1191

1192
// networkModify allows you to modify or add a new network, to add a new network use the new bool
1193
func (s *BoltState) networkModify(ctr *Container, network string, opts types.PerNetworkOptions, new bool) error {
1194
	if !s.valid {
1195
		return define.ErrDBClosed
1196
	}
1197

1198
	if !ctr.valid {
1199
		return define.ErrCtrRemoved
1200
	}
1201

1202
	if network == "" {
1203
		return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg)
1204
	}
1205

1206
	optBytes, err := json.Marshal(opts)
1207
	if err != nil {
1208
		return fmt.Errorf("marshalling network options JSON for container %s: %w", ctr.ID(), err)
1209
	}
1210

1211
	ctrID := []byte(ctr.ID())
1212

1213
	db, err := s.getDBCon()
1214
	if err != nil {
1215
		return err
1216
	}
1217
	defer s.deferredCloseDBCon(db)
1218

1219
	return db.Update(func(tx *bolt.Tx) error {
1220
		ctrBucket, err := getCtrBucket(tx)
1221
		if err != nil {
1222
			return err
1223
		}
1224

1225
		dbCtr := ctrBucket.Bucket(ctrID)
1226
		if dbCtr == nil {
1227
			ctr.valid = false
1228
			return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
1229
		}
1230

1231
		ctrNetworksBkt := dbCtr.Bucket(networksBkt)
1232
		if ctrNetworksBkt == nil {
1233
			return fmt.Errorf("container %s does not have a network bucket: %w", ctr.ID(), define.ErrNoSuchNetwork)
1234
		}
1235
		netConnected := ctrNetworksBkt.Get([]byte(network))
1236

1237
		if new && netConnected != nil {
1238
			return fmt.Errorf("container %s is already connected to network %q: %w", ctr.ID(), network, define.ErrNetworkConnected)
1239
		} else if !new && netConnected == nil {
1240
			return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork)
1241
		}
1242

1243
		// Modify/Add the network
1244
		if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil {
1245
			return fmt.Errorf("adding container %s to network %s in DB: %w", ctr.ID(), network, err)
1246
		}
1247

1248
		return nil
1249
	})
1250
}
1251

1252
// NetworkDisconnect disconnects the container from the given network, also
1253
// removing any aliases in the network.
1254
func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error {
1255
	if !s.valid {
1256
		return define.ErrDBClosed
1257
	}
1258

1259
	if !ctr.valid {
1260
		return define.ErrCtrRemoved
1261
	}
1262

1263
	if network == "" {
1264
		return fmt.Errorf("network names must not be empty: %w", define.ErrInvalidArg)
1265
	}
1266

1267
	ctrID := []byte(ctr.ID())
1268

1269
	db, err := s.getDBCon()
1270
	if err != nil {
1271
		return err
1272
	}
1273
	defer s.deferredCloseDBCon(db)
1274

1275
	return db.Update(func(tx *bolt.Tx) error {
1276
		ctrBucket, err := getCtrBucket(tx)
1277
		if err != nil {
1278
			return err
1279
		}
1280

1281
		dbCtr := ctrBucket.Bucket(ctrID)
1282
		if dbCtr == nil {
1283
			ctr.valid = false
1284
			return fmt.Errorf("container %s does not exist in database: %w", ctr.ID(), define.ErrNoSuchCtr)
1285
		}
1286

1287
		ctrAliasesBkt := dbCtr.Bucket(aliasesBkt)
1288
		ctrNetworksBkt := dbCtr.Bucket(networksBkt)
1289
		if ctrNetworksBkt == nil {
1290
			return fmt.Errorf("container %s is not connected to any networks, so cannot disconnect: %w", ctr.ID(), define.ErrNoSuchNetwork)
1291
		}
1292
		netConnected := ctrNetworksBkt.Get([]byte(network))
1293
		if netConnected == nil {
1294
			return fmt.Errorf("container %s is not connected to network %q: %w", ctr.ID(), network, define.ErrNoSuchNetwork)
1295
		}
1296

1297
		if err := ctrNetworksBkt.Delete([]byte(network)); err != nil {
1298
			return fmt.Errorf("removing container %s from network %s: %w", ctr.ID(), network, err)
1299
		}
1300

1301
		if ctrAliasesBkt != nil {
1302
			bktExists := ctrAliasesBkt.Bucket([]byte(network))
1303
			if bktExists == nil {
1304
				return nil
1305
			}
1306

1307
			if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil {
1308
				return fmt.Errorf("removing container %s network aliases for network %s: %w", ctr.ID(), network, err)
1309
			}
1310
		}
1311

1312
		return nil
1313
	})
1314
}
1315

1316
// GetContainerConfig returns a container config from the database by full ID
1317
func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) {
1318
	if len(id) == 0 {
1319
		return nil, define.ErrEmptyID
1320
	}
1321

1322
	if !s.valid {
1323
		return nil, define.ErrDBClosed
1324
	}
1325

1326
	config := new(ContainerConfig)
1327

1328
	db, err := s.getDBCon()
1329
	if err != nil {
1330
		return nil, err
1331
	}
1332
	defer s.deferredCloseDBCon(db)
1333

1334
	err = db.View(func(tx *bolt.Tx) error {
1335
		ctrBucket, err := getCtrBucket(tx)
1336
		if err != nil {
1337
			return err
1338
		}
1339

1340
		return s.getContainerConfigFromDB([]byte(id), config, ctrBucket)
1341
	})
1342
	if err != nil {
1343
		return nil, err
1344
	}
1345

1346
	return config, nil
1347
}
1348

1349
// AddContainerExitCode adds the exit code for the specified container to the database.
1350
func (s *BoltState) AddContainerExitCode(id string, exitCode int32) error {
1351
	if len(id) == 0 {
1352
		return define.ErrEmptyID
1353
	}
1354

1355
	if !s.valid {
1356
		return define.ErrDBClosed
1357
	}
1358

1359
	db, err := s.getDBCon()
1360
	if err != nil {
1361
		return err
1362
	}
1363
	defer s.deferredCloseDBCon(db)
1364

1365
	rawID := []byte(id)
1366
	rawExitCode := []byte(strconv.Itoa(int(exitCode)))
1367
	rawTimeStamp, err := time.Now().MarshalText()
1368
	if err != nil {
1369
		return fmt.Errorf("marshalling exit-code time stamp: %w", err)
1370
	}
1371

1372
	return db.Update(func(tx *bolt.Tx) error {
1373
		exitCodeBucket, err := getExitCodeBucket(tx)
1374
		if err != nil {
1375
			return err
1376
		}
1377
		timeStampBucket, err := getExitCodeTimeStampBucket(tx)
1378
		if err != nil {
1379
			return err
1380
		}
1381

1382
		if err := exitCodeBucket.Put(rawID, rawExitCode); err != nil {
1383
			return fmt.Errorf("adding exit code of container %s to DB: %w", id, err)
1384
		}
1385
		if err := timeStampBucket.Put(rawID, rawTimeStamp); err != nil {
1386
			if rmErr := exitCodeBucket.Delete(rawID); rmErr != nil {
1387
				logrus.Errorf("Removing exit code of container %s from DB: %v", id, rmErr)
1388
			}
1389
			return fmt.Errorf("adding exit-code time stamp of container %s to DB: %w", id, err)
1390
		}
1391

1392
		return nil
1393
	})
1394
}
1395

1396
// GetContainerExitCode returns the exit code for the specified container.
1397
func (s *BoltState) GetContainerExitCode(id string) (int32, error) {
1398
	if len(id) == 0 {
1399
		return -1, define.ErrEmptyID
1400
	}
1401

1402
	if !s.valid {
1403
		return -1, define.ErrDBClosed
1404
	}
1405

1406
	db, err := s.getDBCon()
1407
	if err != nil {
1408
		return -1, err
1409
	}
1410
	defer s.deferredCloseDBCon(db)
1411

1412
	rawID := []byte(id)
1413
	result := int32(-1)
1414
	return result, db.View(func(tx *bolt.Tx) error {
1415
		exitCodeBucket, err := getExitCodeBucket(tx)
1416
		if err != nil {
1417
			return err
1418
		}
1419

1420
		rawExitCode := exitCodeBucket.Get(rawID)
1421
		if rawExitCode == nil {
1422
			return fmt.Errorf("getting exit code of container %s from DB: %w", id, define.ErrNoSuchExitCode)
1423
		}
1424

1425
		exitCode, err := strconv.Atoi(string(rawExitCode))
1426
		if err != nil {
1427
			return fmt.Errorf("converting raw exit code %v of container %s: %w", rawExitCode, id, err)
1428
		}
1429

1430
		result = int32(exitCode)
1431
		return nil
1432
	})
1433
}
1434

1435
// GetContainerExitCodeTimeStamp returns the time stamp when the exit code of
1436
// the specified container was added to the database.
1437
func (s *BoltState) GetContainerExitCodeTimeStamp(id string) (*time.Time, error) {
1438
	if len(id) == 0 {
1439
		return nil, define.ErrEmptyID
1440
	}
1441

1442
	if !s.valid {
1443
		return nil, define.ErrDBClosed
1444
	}
1445

1446
	db, err := s.getDBCon()
1447
	if err != nil {
1448
		return nil, err
1449
	}
1450
	defer s.deferredCloseDBCon(db)
1451

1452
	rawID := []byte(id)
1453
	var result time.Time
1454
	return &result, db.View(func(tx *bolt.Tx) error {
1455
		timeStampBucket, err := getExitCodeTimeStampBucket(tx)
1456
		if err != nil {
1457
			return err
1458
		}
1459

1460
		rawTimeStamp := timeStampBucket.Get(rawID)
1461
		if rawTimeStamp == nil {
1462
			return fmt.Errorf("getting exit-code time stamp of container %s from DB: %w", id, define.ErrNoSuchExitCode)
1463
		}
1464

1465
		if err := result.UnmarshalText(rawTimeStamp); err != nil {
1466
			return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, id, err)
1467
		}
1468

1469
		return nil
1470
	})
1471
}
1472

1473
// PruneContainerExitCodes removes exit codes older than 5 minutes unless the associated
1474
// container still exists.
1475
func (s *BoltState) PruneContainerExitCodes() error {
1476
	if !s.valid {
1477
		return define.ErrDBClosed
1478
	}
1479

1480
	db, err := s.getDBCon()
1481
	if err != nil {
1482
		return err
1483
	}
1484
	defer s.deferredCloseDBCon(db)
1485

1486
	toRemoveIDs := []string{}
1487

1488
	threshold := time.Minute * 5
1489
	err = db.View(func(tx *bolt.Tx) error {
1490
		timeStampBucket, err := getExitCodeTimeStampBucket(tx)
1491
		if err != nil {
1492
			return err
1493
		}
1494

1495
		ctrsBucket, err := getCtrBucket(tx)
1496
		if err != nil {
1497
			return err
1498
		}
1499

1500
		return timeStampBucket.ForEach(func(rawID, rawTimeStamp []byte) error {
1501
			if ctrsBucket.Bucket(rawID) != nil {
1502
				// If the container still exists, don't prune
1503
				// its exit code since we may still need it.
1504
				return nil
1505
			}
1506
			var timeStamp time.Time
1507
			if err := timeStamp.UnmarshalText(rawTimeStamp); err != nil {
1508
				return fmt.Errorf("converting raw time stamp %v of container %s from DB: %w", rawTimeStamp, string(rawID), err)
1509
			}
1510
			if time.Since(timeStamp) > threshold {
1511
				toRemoveIDs = append(toRemoveIDs, string(rawID))
1512
			}
1513
			return nil
1514
		})
1515
	})
1516
	if err != nil {
1517
		return fmt.Errorf("reading exit codes to prune: %w", err)
1518
	}
1519

1520
	if len(toRemoveIDs) > 0 {
1521
		err = db.Update(func(tx *bolt.Tx) error {
1522
			exitCodeBucket, err := getExitCodeBucket(tx)
1523
			if err != nil {
1524
				return err
1525
			}
1526
			timeStampBucket, err := getExitCodeTimeStampBucket(tx)
1527
			if err != nil {
1528
				return err
1529
			}
1530

1531
			var finalErr error
1532
			for _, id := range toRemoveIDs {
1533
				rawID := []byte(id)
1534
				if err := exitCodeBucket.Delete(rawID); err != nil {
1535
					if finalErr != nil {
1536
						logrus.Error(finalErr)
1537
					}
1538
					finalErr = fmt.Errorf("removing exit code of container %s from DB: %w", id, err)
1539
				}
1540
				if err := timeStampBucket.Delete(rawID); err != nil {
1541
					if finalErr != nil {
1542
						logrus.Error(finalErr)
1543
					}
1544
					finalErr = fmt.Errorf("removing exit code timestamp of container %s from DB: %w", id, err)
1545
				}
1546
			}
1547

1548
			return finalErr
1549
		})
1550
		if err != nil {
1551
			return fmt.Errorf("pruning exit codes: %w", err)
1552
		}
1553
	}
1554

1555
	return nil
1556
}
1557

1558
// AddExecSession adds an exec session to the state.
1559
func (s *BoltState) AddExecSession(ctr *Container, session *ExecSession) error {
1560
	if !s.valid {
1561
		return define.ErrDBClosed
1562
	}
1563

1564
	if !ctr.valid {
1565
		return define.ErrCtrRemoved
1566
	}
1567

1568
	db, err := s.getDBCon()
1569
	if err != nil {
1570
		return err
1571
	}
1572
	defer s.deferredCloseDBCon(db)
1573

1574
	ctrID := []byte(ctr.ID())
1575
	sessionID := []byte(session.ID())
1576

1577
	err = db.Update(func(tx *bolt.Tx) error {
1578
		execBucket, err := getExecBucket(tx)
1579
		if err != nil {
1580
			return err
1581
		}
1582
		ctrBucket, err := getCtrBucket(tx)
1583
		if err != nil {
1584
			return err
1585
		}
1586

1587
		dbCtr := ctrBucket.Bucket(ctrID)
1588
		if dbCtr == nil {
1589
			ctr.valid = false
1590
			return fmt.Errorf("container %s is not present in the database: %w", ctr.ID(), define.ErrNoSuchCtr)
1591
		}
1592

1593
		ctrExecSessionBucket, err := dbCtr.CreateBucketIfNotExists(execBkt)
1594
		if err != nil {
1595
			return fmt.Errorf("creating exec sessions bucket for container %s: %w", ctr.ID(), err)
1596
		}
1597

1598
		execExists := execBucket.Get(sessionID)
1599
		if execExists != nil {
1600
			return fmt.Errorf("an exec session with ID %s already exists: %w", session.ID(), define.ErrExecSessionExists)
1601
		}
1602

1603
		if err := execBucket.Put(sessionID, ctrID); err != nil {
1604
			return fmt.Errorf("adding exec session %s to DB: %w", session.ID(), err)
1605
		}
1606

1607
		if err := ctrExecSessionBucket.Put(sessionID, ctrID); err != nil {
1608
			return fmt.Errorf("adding exec session %s to container %s in DB: %w", session.ID(), ctr.ID(), err)
1609
		}
1610

1611
		return nil
1612
	})
1613
	return err
1614
}
1615

1616
// GetExecSession returns the ID of the container an exec session is associated
1617
// with.
1618
func (s *BoltState) GetExecSession(id string) (string, error) {
1619
	if !s.valid {
1620
		return "", define.ErrDBClosed
1621
	}
1622

1623
	if id == "" {
1624
		return "", define.ErrEmptyID
1625
	}
1626

1627
	db, err := s.getDBCon()
1628
	if err != nil {
1629
		return "", err
1630
	}
1631
	defer s.deferredCloseDBCon(db)
1632

1633
	ctrID := ""
1634
	err = db.View(func(tx *bolt.Tx) error {
1635
		execBucket, err := getExecBucket(tx)
1636
		if err != nil {
1637
			return err
1638
		}
1639

1640
		ctr := execBucket.Get([]byte(id))
1641
		if ctr == nil {
1642
			return fmt.Errorf("no exec session with ID %s found: %w", id, define.ErrNoSuchExecSession)
1643
		}
1644
		ctrID = string(ctr)
1645
		return nil
1646
	})
1647
	return ctrID, err
1648
}
1649

1650
// RemoveExecSession removes references to the given exec session in the
1651
// database.
1652
func (s *BoltState) RemoveExecSession(session *ExecSession) error {
1653
	if !s.valid {
1654
		return define.ErrDBClosed
1655
	}
1656

1657
	db, err := s.getDBCon()
1658
	if err != nil {
1659
		return err
1660
	}
1661
	defer s.deferredCloseDBCon(db)
1662

1663
	sessionID := []byte(session.ID())
1664
	containerID := []byte(session.ContainerID())
1665
	err = db.Update(func(tx *bolt.Tx) error {
1666
		execBucket, err := getExecBucket(tx)
1667
		if err != nil {
1668
			return err
1669
		}
1670
		ctrBucket, err := getCtrBucket(tx)
1671
		if err != nil {
1672
			return err
1673
		}
1674

1675
		sessionExists := execBucket.Get(sessionID)
1676
		if sessionExists == nil {
1677
			return define.ErrNoSuchExecSession
1678
		}
1679
		// Check that container ID matches
1680
		if string(sessionExists) != session.ContainerID() {
1681
			return fmt.Errorf("database inconsistency: exec session %s points to container %s in state but %s in database: %w", session.ID(), session.ContainerID(), string(sessionExists), define.ErrInternal)
1682
		}
1683

1684
		if err := execBucket.Delete(sessionID); err != nil {
1685
			return fmt.Errorf("removing exec session %s from database: %w", session.ID(), err)
1686
		}
1687

1688
		dbCtr := ctrBucket.Bucket(containerID)
1689
		if dbCtr == nil {
1690
			// State is inconsistent. We refer to a container that
1691
			// is no longer in the state.
1692
			// Return without error, to attempt to recover.
1693
			return nil
1694
		}
1695

1696
		ctrExecBucket := dbCtr.Bucket(execBkt)
1697
		if ctrExecBucket == nil {
1698
			// Again, state is inconsistent. We should have an exec
1699
			// bucket, and it should have this session.
1700
			// Again, nothing we can do, so proceed and try to
1701
			// recover.
1702
			return nil
1703
		}
1704

1705
		ctrSessionExists := ctrExecBucket.Get(sessionID)
1706
		if ctrSessionExists != nil {
1707
			if err := ctrExecBucket.Delete(sessionID); err != nil {
1708
				return fmt.Errorf("removing exec session %s from container %s in database: %w", session.ID(), session.ContainerID(), err)
1709
			}
1710
		}
1711

1712
		return nil
1713
	})
1714
	return err
1715
}
1716

1717
// GetContainerExecSessions retrieves the IDs of all exec sessions running in a
1718
// container that the database is aware of (IE, were added via AddExecSession).
1719
func (s *BoltState) GetContainerExecSessions(ctr *Container) ([]string, error) {
1720
	if !s.valid {
1721
		return nil, define.ErrDBClosed
1722
	}
1723

1724
	if !ctr.valid {
1725
		return nil, define.ErrCtrRemoved
1726
	}
1727

1728
	db, err := s.getDBCon()
1729
	if err != nil {
1730
		return nil, err
1731
	}
1732
	defer s.deferredCloseDBCon(db)
1733

1734
	ctrID := []byte(ctr.ID())
1735
	sessions := []string{}
1736
	err = db.View(func(tx *bolt.Tx) error {
1737
		ctrBucket, err := getCtrBucket(tx)
1738
		if err != nil {
1739
			return err
1740
		}
1741

1742
		dbCtr := ctrBucket.Bucket(ctrID)
1743
		if dbCtr == nil {
1744
			ctr.valid = false
1745
			return define.ErrNoSuchCtr
1746
		}
1747

1748
		ctrExecSessions := dbCtr.Bucket(execBkt)
1749
		if ctrExecSessions == nil {
1750
			return nil
1751
		}
1752

1753
		return ctrExecSessions.ForEach(func(id, unused []byte) error {
1754
			sessions = append(sessions, string(id))
1755
			return nil
1756
		})
1757
	})
1758
	if err != nil {
1759
		return nil, err
1760
	}
1761

1762
	return sessions, nil
1763
}
1764

1765
// RemoveContainerExecSessions removes all exec sessions attached to a given
1766
// container.
1767
func (s *BoltState) RemoveContainerExecSessions(ctr *Container) error {
1768
	if !s.valid {
1769
		return define.ErrDBClosed
1770
	}
1771

1772
	if !ctr.valid {
1773
		return define.ErrCtrRemoved
1774
	}
1775

1776
	db, err := s.getDBCon()
1777
	if err != nil {
1778
		return err
1779
	}
1780
	defer s.deferredCloseDBCon(db)
1781

1782
	ctrID := []byte(ctr.ID())
1783
	sessions := []string{}
1784

1785
	err = db.Update(func(tx *bolt.Tx) error {
1786
		execBucket, err := getExecBucket(tx)
1787
		if err != nil {
1788
			return err
1789
		}
1790
		ctrBucket, err := getCtrBucket(tx)
1791
		if err != nil {
1792
			return err
1793
		}
1794

1795
		dbCtr := ctrBucket.Bucket(ctrID)
1796
		if dbCtr == nil {
1797
			ctr.valid = false
1798
			return define.ErrNoSuchCtr
1799
		}
1800

1801
		ctrExecSessions := dbCtr.Bucket(execBkt)
1802
		if ctrExecSessions == nil {
1803
			return nil
1804
		}
1805

1806
		err = ctrExecSessions.ForEach(func(id, unused []byte) error {
1807
			sessions = append(sessions, string(id))
1808
			return nil
1809
		})
1810
		if err != nil {
1811
			return err
1812
		}
1813

1814
		for _, session := range sessions {
1815
			if err := ctrExecSessions.Delete([]byte(session)); err != nil {
1816
				return fmt.Errorf("removing container %s exec session %s from database: %w", ctr.ID(), session, err)
1817
			}
1818
			// Check if the session exists in the global table
1819
			// before removing. It should, but in cases where the DB
1820
			// has become inconsistent, we should try and proceed
1821
			// so we can recover.
1822
			sessionExists := execBucket.Get([]byte(session))
1823
			if sessionExists == nil {
1824
				continue
1825
			}
1826
			if string(sessionExists) != ctr.ID() {
1827
				return fmt.Errorf("database mismatch: exec session %s is associated with containers %s and %s: %w", session, ctr.ID(), string(sessionExists), define.ErrInternal)
1828
			}
1829
			if err := execBucket.Delete([]byte(session)); err != nil {
1830
				return fmt.Errorf("removing container %s exec session %s from exec sessions: %w", ctr.ID(), session, err)
1831
			}
1832
		}
1833

1834
		return nil
1835
	})
1836
	return err
1837
}
1838

1839
// RewriteContainerConfig rewrites a container's configuration.
1840
// WARNING: This function is DANGEROUS. Do not use without reading the full
1841
// comment on this function in state.go.
1842
func (s *BoltState) RewriteContainerConfig(ctr *Container, newCfg *ContainerConfig) error {
1843
	if !s.valid {
1844
		return define.ErrDBClosed
1845
	}
1846

1847
	if !ctr.valid {
1848
		return define.ErrCtrRemoved
1849
	}
1850

1851
	newCfgJSON, err := json.Marshal(newCfg)
1852
	if err != nil {
1853
		return fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err)
1854
	}
1855

1856
	db, err := s.getDBCon()
1857
	if err != nil {
1858
		return err
1859
	}
1860
	defer s.deferredCloseDBCon(db)
1861

1862
	err = db.Update(func(tx *bolt.Tx) error {
1863
		ctrBkt, err := getCtrBucket(tx)
1864
		if err != nil {
1865
			return err
1866
		}
1867

1868
		ctrDB := ctrBkt.Bucket([]byte(ctr.ID()))
1869
		if ctrDB == nil {
1870
			ctr.valid = false
1871
			return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
1872
		}
1873

1874
		if err := ctrDB.Put(configKey, newCfgJSON); err != nil {
1875
			return fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err)
1876
		}
1877

1878
		return nil
1879
	})
1880
	return err
1881
}
1882

1883
// SafeRewriteContainerConfig rewrites a container's configuration in a more
1884
// limited fashion than RewriteContainerConfig. It is marked as safe to use
1885
// under most circumstances, unlike RewriteContainerConfig.
1886
// DO NOT USE TO: Change container dependencies, change pod membership, change
1887
// locks, change container ID.
1888
func (s *BoltState) SafeRewriteContainerConfig(ctr *Container, oldName, newName string, newCfg *ContainerConfig) error {
1889
	if !s.valid {
1890
		return define.ErrDBClosed
1891
	}
1892

1893
	if !ctr.valid {
1894
		return define.ErrCtrRemoved
1895
	}
1896

1897
	if newName != "" && newCfg.Name != newName {
1898
		return fmt.Errorf("new name %s for container %s must match name in given container config: %w", newName, ctr.ID(), define.ErrInvalidArg)
1899
	}
1900
	if newName != "" && oldName == "" {
1901
		return fmt.Errorf("must provide old name for container if a new name is given: %w", define.ErrInvalidArg)
1902
	}
1903

1904
	newCfgJSON, err := json.Marshal(newCfg)
1905
	if err != nil {
1906
		return fmt.Errorf("marshalling new configuration JSON for container %s: %w", ctr.ID(), err)
1907
	}
1908

1909
	db, err := s.getDBCon()
1910
	if err != nil {
1911
		return err
1912
	}
1913
	defer s.deferredCloseDBCon(db)
1914

1915
	err = db.Update(func(tx *bolt.Tx) error {
1916
		if newName != "" {
1917
			idBkt, err := getIDBucket(tx)
1918
			if err != nil {
1919
				return err
1920
			}
1921
			namesBkt, err := getNamesBucket(tx)
1922
			if err != nil {
1923
				return err
1924
			}
1925
			allCtrsBkt, err := getAllCtrsBucket(tx)
1926
			if err != nil {
1927
				return err
1928
			}
1929

1930
			needsRename := true
1931
			if exists := namesBkt.Get([]byte(newName)); exists != nil {
1932
				if string(exists) == ctr.ID() {
1933
					// Name already associated with the ID
1934
					// of this container. No need for a
1935
					// rename.
1936
					needsRename = false
1937
				} else {
1938
					return fmt.Errorf("name %s already in use, cannot rename container %s: %w", newName, ctr.ID(), define.ErrCtrExists)
1939
				}
1940
			}
1941

1942
			if needsRename {
1943
				// We do have to remove the old name. The other
1944
				// buckets are ID-indexed so we just need to
1945
				// overwrite the values there.
1946
				if err := namesBkt.Delete([]byte(oldName)); err != nil {
1947
					return fmt.Errorf("deleting container %s old name from DB for rename: %w", ctr.ID(), err)
1948
				}
1949
				if err := idBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil {
1950
					return fmt.Errorf("renaming container %s in ID bucket in DB: %w", ctr.ID(), err)
1951
				}
1952
				if err := namesBkt.Put([]byte(newName), []byte(ctr.ID())); err != nil {
1953
					return fmt.Errorf("adding new name %s for container %s in DB: %w", newName, ctr.ID(), err)
1954
				}
1955
				if err := allCtrsBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil {
1956
					return fmt.Errorf("renaming container %s in all containers bucket in DB: %w", ctr.ID(), err)
1957
				}
1958
				if ctr.config.Pod != "" {
1959
					podsBkt, err := getPodBucket(tx)
1960
					if err != nil {
1961
						return err
1962
					}
1963
					podBkt := podsBkt.Bucket([]byte(ctr.config.Pod))
1964
					if podBkt == nil {
1965
						return fmt.Errorf("bucket for pod %s does not exist: %w", ctr.config.Pod, define.ErrInternal)
1966
					}
1967
					podCtrBkt := podBkt.Bucket(containersBkt)
1968
					if podCtrBkt == nil {
1969
						return fmt.Errorf("pod %s does not have a containers bucket: %w", ctr.config.Pod, define.ErrInternal)
1970
					}
1971
					if err := podCtrBkt.Put([]byte(ctr.ID()), []byte(newName)); err != nil {
1972
						return fmt.Errorf("renaming container %s in pod %s members bucket: %w", ctr.ID(), ctr.config.Pod, err)
1973
					}
1974
				}
1975
			}
1976
		}
1977

1978
		ctrBkt, err := getCtrBucket(tx)
1979
		if err != nil {
1980
			return err
1981
		}
1982

1983
		ctrDB := ctrBkt.Bucket([]byte(ctr.ID()))
1984
		if ctrDB == nil {
1985
			ctr.valid = false
1986
			return fmt.Errorf("no container with ID %q found in DB: %w", ctr.ID(), define.ErrNoSuchCtr)
1987
		}
1988

1989
		if err := ctrDB.Put(configKey, newCfgJSON); err != nil {
1990
			return fmt.Errorf("updating container %s config JSON: %w", ctr.ID(), err)
1991
		}
1992

1993
		return nil
1994
	})
1995
	return err
1996
}
1997

1998
// RewritePodConfig rewrites a pod's configuration.
1999
// WARNING: This function is DANGEROUS. Do not use without reading the full
2000
// comment on this function in state.go.
2001
func (s *BoltState) RewritePodConfig(pod *Pod, newCfg *PodConfig) error {
2002
	if !s.valid {
2003
		return define.ErrDBClosed
2004
	}
2005

2006
	if !pod.valid {
2007
		return define.ErrPodRemoved
2008
	}
2009

2010
	newCfgJSON, err := json.Marshal(newCfg)
2011
	if err != nil {
2012
		return fmt.Errorf("marshalling new configuration JSON for pod %s: %w", pod.ID(), err)
2013
	}
2014

2015
	db, err := s.getDBCon()
2016
	if err != nil {
2017
		return err
2018
	}
2019
	defer s.deferredCloseDBCon(db)
2020

2021
	err = db.Update(func(tx *bolt.Tx) error {
2022
		podBkt, err := getPodBucket(tx)
2023
		if err != nil {
2024
			return err
2025
		}
2026

2027
		podDB := podBkt.Bucket([]byte(pod.ID()))
2028
		if podDB == nil {
2029
			pod.valid = false
2030
			return fmt.Errorf("no pod with ID %s found in DB: %w", pod.ID(), define.ErrNoSuchPod)
2031
		}
2032

2033
		if err := podDB.Put(configKey, newCfgJSON); err != nil {
2034
			return fmt.Errorf("updating pod %s config JSON: %w", pod.ID(), err)
2035
		}
2036

2037
		return nil
2038
	})
2039
	return err
2040
}
2041

2042
// RewriteVolumeConfig rewrites a volume's configuration.
2043
// WARNING: This function is DANGEROUS. Do not use without reading the full
2044
// comment on this function in state.go.
2045
func (s *BoltState) RewriteVolumeConfig(volume *Volume, newCfg *VolumeConfig) error {
2046
	if !s.valid {
2047
		return define.ErrDBClosed
2048
	}
2049

2050
	if !volume.valid {
2051
		return define.ErrVolumeRemoved
2052
	}
2053

2054
	newCfgJSON, err := json.Marshal(newCfg)
2055
	if err != nil {
2056
		return fmt.Errorf("marshalling new configuration JSON for volume %q: %w", volume.Name(), err)
2057
	}
2058

2059
	db, err := s.getDBCon()
2060
	if err != nil {
2061
		return err
2062
	}
2063
	defer s.deferredCloseDBCon(db)
2064

2065
	err = db.Update(func(tx *bolt.Tx) error {
2066
		volBkt, err := getVolBucket(tx)
2067
		if err != nil {
2068
			return err
2069
		}
2070

2071
		volDB := volBkt.Bucket([]byte(volume.Name()))
2072
		if volDB == nil {
2073
			volume.valid = false
2074
			return fmt.Errorf("no volume with name %q found in DB: %w", volume.Name(), define.ErrNoSuchVolume)
2075
		}
2076

2077
		if err := volDB.Put(configKey, newCfgJSON); err != nil {
2078
			return fmt.Errorf("updating volume %q config JSON: %w", volume.Name(), err)
2079
		}
2080

2081
		return nil
2082
	})
2083
	return err
2084
}
2085

2086
// Pod retrieves a pod given its full ID
2087
func (s *BoltState) Pod(id string) (*Pod, error) {
2088
	if id == "" {
2089
		return nil, define.ErrEmptyID
2090
	}
2091

2092
	if !s.valid {
2093
		return nil, define.ErrDBClosed
2094
	}
2095

2096
	podID := []byte(id)
2097

2098
	pod := new(Pod)
2099
	pod.config = new(PodConfig)
2100
	pod.state = new(podState)
2101

2102
	db, err := s.getDBCon()
2103
	if err != nil {
2104
		return nil, err
2105
	}
2106
	defer s.deferredCloseDBCon(db)
2107

2108
	err = db.View(func(tx *bolt.Tx) error {
2109
		podBkt, err := getPodBucket(tx)
2110
		if err != nil {
2111
			return err
2112
		}
2113

2114
		return s.getPodFromDB(podID, pod, podBkt)
2115
	})
2116
	if err != nil {
2117
		return nil, err
2118
	}
2119

2120
	return pod, nil
2121
}
2122

2123
// LookupPod retrieves a pod from full or unique partial ID or name
2124
func (s *BoltState) LookupPod(idOrName string) (*Pod, error) {
2125
	if idOrName == "" {
2126
		return nil, define.ErrEmptyID
2127
	}
2128

2129
	if !s.valid {
2130
		return nil, define.ErrDBClosed
2131
	}
2132

2133
	pod := new(Pod)
2134
	pod.config = new(PodConfig)
2135
	pod.state = new(podState)
2136

2137
	db, err := s.getDBCon()
2138
	if err != nil {
2139
		return nil, err
2140
	}
2141
	defer s.deferredCloseDBCon(db)
2142

2143
	err = db.View(func(tx *bolt.Tx) error {
2144
		podBkt, err := getPodBucket(tx)
2145
		if err != nil {
2146
			return err
2147
		}
2148

2149
		namesBkt, err := getNamesBucket(tx)
2150
		if err != nil {
2151
			return err
2152
		}
2153

2154
		// First, check if the ID given was the actual pod ID
2155
		var id []byte
2156
		podExists := podBkt.Bucket([]byte(idOrName))
2157
		if podExists != nil {
2158
			// A full pod ID was given.
2159
			id = []byte(idOrName)
2160
			return s.getPodFromDB(id, pod, podBkt)
2161
		}
2162

2163
		// Next, check if the full name was given
2164
		isCtr := false
2165
		fullID := namesBkt.Get([]byte(idOrName))
2166
		if fullID != nil {
2167
			// The name exists and maps to an ID.
2168
			// However, we aren't yet sure if the ID is a pod.
2169
			podExists = podBkt.Bucket(fullID)
2170
			if podExists != nil {
2171
				// A pod bucket matching the full ID was found.
2172
				return s.getPodFromDB(fullID, pod, podBkt)
2173
			}
2174
			// Don't error if we have a name match but it's not a
2175
			// pod - there's a chance we have a pod with an ID
2176
			// starting with those characters.
2177
			// However, so we can return a good error, note whether
2178
			// this is a container.
2179
			isCtr = true
2180
		}
2181
		// They did not give us a full pod name or ID.
2182
		// Search for partial ID matches.
2183
		exists := false
2184
		err = podBkt.ForEach(func(checkID, checkName []byte) error {
2185
			if strings.HasPrefix(string(checkID), idOrName) {
2186
				if exists {
2187
					return fmt.Errorf("more than one result for ID or name %s: %w", idOrName, define.ErrPodExists)
2188
				}
2189
				id = checkID
2190
				exists = true
2191
			}
2192

2193
			return nil
2194
		})
2195
		if err != nil {
2196
			return err
2197
		} else if !exists {
2198
			if isCtr {
2199
				return fmt.Errorf("%s is a container, not a pod: %w", idOrName, define.ErrNoSuchPod)
2200
			}
2201
			return fmt.Errorf("no pod with name or ID %s found: %w", idOrName, define.ErrNoSuchPod)
2202
		}
2203

2204
		// We might have found a container ID, but it's OK
2205
		// We'll just fail in getPodFromDB with ErrNoSuchPod
2206
		return s.getPodFromDB(id, pod, podBkt)
2207
	})
2208
	if err != nil {
2209
		return nil, err
2210
	}
2211

2212
	return pod, nil
2213
}
2214

2215
// HasPod checks if a pod with the given ID exists in the state
2216
func (s *BoltState) HasPod(id string) (bool, error) {
2217
	if id == "" {
2218
		return false, define.ErrEmptyID
2219
	}
2220

2221
	if !s.valid {
2222
		return false, define.ErrDBClosed
2223
	}
2224

2225
	podID := []byte(id)
2226

2227
	exists := false
2228

2229
	db, err := s.getDBCon()
2230
	if err != nil {
2231
		return false, err
2232
	}
2233
	defer s.deferredCloseDBCon(db)
2234

2235
	err = db.View(func(tx *bolt.Tx) error {
2236
		podBkt, err := getPodBucket(tx)
2237
		if err != nil {
2238
			return err
2239
		}
2240

2241
		podDB := podBkt.Bucket(podID)
2242
		if podDB != nil {
2243
			exists = true
2244
		}
2245

2246
		return nil
2247
	})
2248
	if err != nil {
2249
		return false, err
2250
	}
2251

2252
	return exists, nil
2253
}
2254

2255
// PodHasContainer checks if the given pod has a container with the given ID
2256
func (s *BoltState) PodHasContainer(pod *Pod, id string) (bool, error) {
2257
	if id == "" {
2258
		return false, define.ErrEmptyID
2259
	}
2260

2261
	if !s.valid {
2262
		return false, define.ErrDBClosed
2263
	}
2264

2265
	if !pod.valid {
2266
		return false, define.ErrPodRemoved
2267
	}
2268

2269
	ctrID := []byte(id)
2270
	podID := []byte(pod.ID())
2271

2272
	exists := false
2273

2274
	db, err := s.getDBCon()
2275
	if err != nil {
2276
		return false, err
2277
	}
2278
	defer s.deferredCloseDBCon(db)
2279

2280
	err = db.View(func(tx *bolt.Tx) error {
2281
		podBkt, err := getPodBucket(tx)
2282
		if err != nil {
2283
			return err
2284
		}
2285

2286
		// Get pod itself
2287
		podDB := podBkt.Bucket(podID)
2288
		if podDB == nil {
2289
			pod.valid = false
2290
			return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod)
2291
		}
2292

2293
		// Get pod containers bucket
2294
		podCtrs := podDB.Bucket(containersBkt)
2295
		if podCtrs == nil {
2296
			return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal)
2297
		}
2298

2299
		ctr := podCtrs.Get(ctrID)
2300
		if ctr != nil {
2301
			exists = true
2302
		}
2303

2304
		return nil
2305
	})
2306
	if err != nil {
2307
		return false, err
2308
	}
2309

2310
	return exists, nil
2311
}
2312

2313
// PodContainersByID returns the IDs of all containers present in the given pod
2314
func (s *BoltState) PodContainersByID(pod *Pod) ([]string, error) {
2315
	if !s.valid {
2316
		return nil, define.ErrDBClosed
2317
	}
2318

2319
	if !pod.valid {
2320
		return nil, define.ErrPodRemoved
2321
	}
2322

2323
	podID := []byte(pod.ID())
2324

2325
	ctrs := []string{}
2326

2327
	db, err := s.getDBCon()
2328
	if err != nil {
2329
		return nil, err
2330
	}
2331
	defer s.deferredCloseDBCon(db)
2332

2333
	err = db.View(func(tx *bolt.Tx) error {
2334
		podBkt, err := getPodBucket(tx)
2335
		if err != nil {
2336
			return err
2337
		}
2338

2339
		// Get pod itself
2340
		podDB := podBkt.Bucket(podID)
2341
		if podDB == nil {
2342
			pod.valid = false
2343
			return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod)
2344
		}
2345

2346
		// Get pod containers bucket
2347
		podCtrs := podDB.Bucket(containersBkt)
2348
		if podCtrs == nil {
2349
			return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal)
2350
		}
2351

2352
		// Iterate through all containers in the pod
2353
		err = podCtrs.ForEach(func(id, val []byte) error {
2354
			ctrs = append(ctrs, string(id))
2355

2356
			return nil
2357
		})
2358
		if err != nil {
2359
			return err
2360
		}
2361

2362
		return nil
2363
	})
2364
	if err != nil {
2365
		return nil, err
2366
	}
2367

2368
	return ctrs, nil
2369
}
2370

2371
// PodContainers returns all the containers present in the given pod
2372
func (s *BoltState) PodContainers(pod *Pod) ([]*Container, error) {
2373
	if !s.valid {
2374
		return nil, define.ErrDBClosed
2375
	}
2376

2377
	if !pod.valid {
2378
		return nil, define.ErrPodRemoved
2379
	}
2380

2381
	podID := []byte(pod.ID())
2382

2383
	ctrs := []*Container{}
2384

2385
	db, err := s.getDBCon()
2386
	if err != nil {
2387
		return nil, err
2388
	}
2389
	defer s.deferredCloseDBCon(db)
2390

2391
	err = db.View(func(tx *bolt.Tx) error {
2392
		podBkt, err := getPodBucket(tx)
2393
		if err != nil {
2394
			return err
2395
		}
2396

2397
		ctrBkt, err := getCtrBucket(tx)
2398
		if err != nil {
2399
			return err
2400
		}
2401

2402
		// Get pod itself
2403
		podDB := podBkt.Bucket(podID)
2404
		if podDB == nil {
2405
			pod.valid = false
2406
			return fmt.Errorf("pod %s not found in database: %w", pod.ID(), define.ErrNoSuchPod)
2407
		}
2408

2409
		// Get pod containers bucket
2410
		podCtrs := podDB.Bucket(containersBkt)
2411
		if podCtrs == nil {
2412
			return fmt.Errorf("pod %s missing containers bucket in DB: %w", pod.ID(), define.ErrInternal)
2413
		}
2414

2415
		// Iterate through all containers in the pod
2416
		err = podCtrs.ForEach(func(id, val []byte) error {
2417
			newCtr := new(Container)
2418
			newCtr.config = new(ContainerConfig)
2419
			newCtr.state = new(ContainerState)
2420
			ctrs = append(ctrs, newCtr)
2421

2422
			return s.getContainerFromDB(id, newCtr, ctrBkt, false)
2423
		})
2424
		if err != nil {
2425
			return err
2426
		}
2427

2428
		return nil
2429
	})
2430
	if err != nil {
2431
		return nil, err
2432
	}
2433

2434
	return ctrs, nil
2435
}
2436

2437
// AddVolume adds the given volume to the state. It also adds ctrDepID to
2438
// the sub bucket holding the container dependencies that this volume has
2439
func (s *BoltState) AddVolume(volume *Volume) error {
2440
	if !s.valid {
2441
		return define.ErrDBClosed
2442
	}
2443

2444
	if !volume.valid {
2445
		return define.ErrVolumeRemoved
2446
	}
2447

2448
	volName := []byte(volume.Name())
2449

2450
	volConfigJSON, err := json.Marshal(volume.config)
2451
	if err != nil {
2452
		return fmt.Errorf("marshalling volume %s config to JSON: %w", volume.Name(), err)
2453
	}
2454

2455
	// Volume state is allowed to not exist
2456
	var volStateJSON []byte
2457
	if volume.state != nil {
2458
		volStateJSON, err = json.Marshal(volume.state)
2459
		if err != nil {
2460
			return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err)
2461
		}
2462
	}
2463

2464
	db, err := s.getDBCon()
2465
	if err != nil {
2466
		return err
2467
	}
2468
	defer s.deferredCloseDBCon(db)
2469

2470
	err = db.Update(func(tx *bolt.Tx) error {
2471
		volBkt, err := getVolBucket(tx)
2472
		if err != nil {
2473
			return err
2474
		}
2475

2476
		allVolsBkt, err := getAllVolsBucket(tx)
2477
		if err != nil {
2478
			return err
2479
		}
2480

2481
		volCtrsBkt, err := getVolumeContainersBucket(tx)
2482
		if err != nil {
2483
			return err
2484
		}
2485

2486
		// Check if we already have a volume with the given name
2487
		volExists := allVolsBkt.Get(volName)
2488
		if volExists != nil {
2489
			return fmt.Errorf("name %s is in use: %w", volume.Name(), define.ErrVolumeExists)
2490
		}
2491

2492
		// We are good to add the volume
2493
		// Make a bucket for it
2494
		newVol, err := volBkt.CreateBucket(volName)
2495
		if err != nil {
2496
			return fmt.Errorf("creating bucket for volume %s: %w", volume.Name(), err)
2497
		}
2498

2499
		// Make a subbucket for the containers using the volume. Dependent container IDs will be addedremoved to
2500
		// this bucket in addcontainer/removeContainer
2501
		if _, err := newVol.CreateBucket(volDependenciesBkt); err != nil {
2502
			return fmt.Errorf("creating bucket for containers using volume %s: %w", volume.Name(), err)
2503
		}
2504

2505
		if err := newVol.Put(configKey, volConfigJSON); err != nil {
2506
			return fmt.Errorf("storing volume %s configuration in DB: %w", volume.Name(), err)
2507
		}
2508

2509
		if volStateJSON != nil {
2510
			if err := newVol.Put(stateKey, volStateJSON); err != nil {
2511
				return fmt.Errorf("storing volume %s state in DB: %w", volume.Name(), err)
2512
			}
2513
		}
2514

2515
		if volume.config.StorageID != "" {
2516
			if err := volCtrsBkt.Put([]byte(volume.config.StorageID), volName); err != nil {
2517
				return fmt.Errorf("storing volume %s container ID in DB: %w", volume.Name(), err)
2518
			}
2519
		}
2520

2521
		if err := allVolsBkt.Put(volName, volName); err != nil {
2522
			return fmt.Errorf("storing volume %s in all volumes bucket in DB: %w", volume.Name(), err)
2523
		}
2524

2525
		return nil
2526
	})
2527
	return err
2528
}
2529

2530
// RemoveVolume removes the given volume from the state
2531
func (s *BoltState) RemoveVolume(volume *Volume) error {
2532
	if !s.valid {
2533
		return define.ErrDBClosed
2534
	}
2535

2536
	volName := []byte(volume.Name())
2537

2538
	db, err := s.getDBCon()
2539
	if err != nil {
2540
		return err
2541
	}
2542
	defer s.deferredCloseDBCon(db)
2543

2544
	err = db.Update(func(tx *bolt.Tx) error {
2545
		volBkt, err := getVolBucket(tx)
2546
		if err != nil {
2547
			return err
2548
		}
2549

2550
		allVolsBkt, err := getAllVolsBucket(tx)
2551
		if err != nil {
2552
			return err
2553
		}
2554

2555
		ctrBkt, err := getCtrBucket(tx)
2556
		if err != nil {
2557
			return err
2558
		}
2559

2560
		volCtrIDBkt, err := getVolumeContainersBucket(tx)
2561
		if err != nil {
2562
			return err
2563
		}
2564

2565
		// Check if the volume exists
2566
		volDB := volBkt.Bucket(volName)
2567
		if volDB == nil {
2568
			volume.valid = false
2569
			return fmt.Errorf("volume %s does not exist in DB: %w", volume.Name(), define.ErrNoSuchVolume)
2570
		}
2571

2572
		// Check if volume is not being used by any container
2573
		// This should never be nil
2574
		// But if it is, we can assume that no containers are using
2575
		// the volume.
2576
		volCtrsBkt := volDB.Bucket(volDependenciesBkt)
2577
		if volCtrsBkt != nil {
2578
			var deps []string
2579
			err = volCtrsBkt.ForEach(func(id, value []byte) error {
2580
				// Alright, this is ugly.
2581
				// But we need it to work around the change in
2582
				// volume dependency handling, to make sure that
2583
				// older Podman versions don't cause DB
2584
				// corruption.
2585
				// Look up all dependencies and see that they
2586
				// still exist before appending.
2587
				ctrExists := ctrBkt.Bucket(id)
2588
				if ctrExists == nil {
2589
					return nil
2590
				}
2591

2592
				deps = append(deps, string(id))
2593
				return nil
2594
			})
2595
			if err != nil {
2596
				return fmt.Errorf("getting list of dependencies from dependencies bucket for volumes %q: %w", volume.Name(), err)
2597
			}
2598
			if len(deps) > 0 {
2599
				return fmt.Errorf("volume %s is being used by container(s) %s: %w", volume.Name(), strings.Join(deps, ","), define.ErrVolumeBeingUsed)
2600
			}
2601
		}
2602

2603
		// volume is ready for removal
2604
		// Let's kick it out
2605
		if err := allVolsBkt.Delete(volName); err != nil {
2606
			return fmt.Errorf("removing volume %s from all volumes bucket in DB: %w", volume.Name(), err)
2607
		}
2608
		if err := volBkt.DeleteBucket(volName); err != nil {
2609
			return fmt.Errorf("removing volume %s from DB: %w", volume.Name(), err)
2610
		}
2611
		if volume.config.StorageID != "" {
2612
			if err := volCtrIDBkt.Delete([]byte(volume.config.StorageID)); err != nil {
2613
				return fmt.Errorf("removing volume %s container ID from DB: %w", volume.Name(), err)
2614
			}
2615
		}
2616

2617
		return nil
2618
	})
2619
	return err
2620
}
2621

2622
// UpdateVolume updates the volume's state from the database.
2623
func (s *BoltState) UpdateVolume(volume *Volume) error {
2624
	if !s.valid {
2625
		return define.ErrDBClosed
2626
	}
2627

2628
	if !volume.valid {
2629
		return define.ErrVolumeRemoved
2630
	}
2631

2632
	newState := new(VolumeState)
2633
	volumeName := []byte(volume.Name())
2634

2635
	db, err := s.getDBCon()
2636
	if err != nil {
2637
		return err
2638
	}
2639
	defer s.deferredCloseDBCon(db)
2640

2641
	err = db.View(func(tx *bolt.Tx) error {
2642
		volBucket, err := getVolBucket(tx)
2643
		if err != nil {
2644
			return err
2645
		}
2646

2647
		volToUpdate := volBucket.Bucket(volumeName)
2648
		if volToUpdate == nil {
2649
			volume.valid = false
2650
			return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume)
2651
		}
2652

2653
		stateBytes := volToUpdate.Get(stateKey)
2654
		if stateBytes == nil {
2655
			// Having no state is valid.
2656
			// Return nil, use the empty state.
2657
			return nil
2658
		}
2659

2660
		if err := json.Unmarshal(stateBytes, newState); err != nil {
2661
			return fmt.Errorf("unmarshalling volume %s state: %w", volume.Name(), err)
2662
		}
2663

2664
		return nil
2665
	})
2666
	if err != nil {
2667
		return err
2668
	}
2669

2670
	volume.state = newState
2671

2672
	return nil
2673
}
2674

2675
// SaveVolume saves the volume's state to the database.
2676
func (s *BoltState) SaveVolume(volume *Volume) error {
2677
	if !s.valid {
2678
		return define.ErrDBClosed
2679
	}
2680

2681
	if !volume.valid {
2682
		return define.ErrVolumeRemoved
2683
	}
2684

2685
	volumeName := []byte(volume.Name())
2686

2687
	var newStateJSON []byte
2688
	if volume.state != nil {
2689
		stateJSON, err := json.Marshal(volume.state)
2690
		if err != nil {
2691
			return fmt.Errorf("marshalling volume %s state to JSON: %w", volume.Name(), err)
2692
		}
2693
		newStateJSON = stateJSON
2694
	}
2695

2696
	db, err := s.getDBCon()
2697
	if err != nil {
2698
		return err
2699
	}
2700
	defer s.deferredCloseDBCon(db)
2701

2702
	err = db.Update(func(tx *bolt.Tx) error {
2703
		volBucket, err := getVolBucket(tx)
2704
		if err != nil {
2705
			return err
2706
		}
2707

2708
		volToUpdate := volBucket.Bucket(volumeName)
2709
		if volToUpdate == nil {
2710
			volume.valid = false
2711
			return fmt.Errorf("no volume with name %s found in database: %w", volume.Name(), define.ErrNoSuchVolume)
2712
		}
2713

2714
		return volToUpdate.Put(stateKey, newStateJSON)
2715
	})
2716
	return err
2717
}
2718

2719
// AllVolumes returns all volumes present in the state
2720
func (s *BoltState) AllVolumes() ([]*Volume, error) {
2721
	if !s.valid {
2722
		return nil, define.ErrDBClosed
2723
	}
2724

2725
	volumes := []*Volume{}
2726

2727
	db, err := s.getDBCon()
2728
	if err != nil {
2729
		return nil, err
2730
	}
2731
	defer s.deferredCloseDBCon(db)
2732

2733
	err = db.View(func(tx *bolt.Tx) error {
2734
		allVolsBucket, err := getAllVolsBucket(tx)
2735
		if err != nil {
2736
			return err
2737
		}
2738

2739
		volBucket, err := getVolBucket(tx)
2740
		if err != nil {
2741
			return err
2742
		}
2743
		err = allVolsBucket.ForEach(func(id, name []byte) error {
2744
			volExists := volBucket.Bucket(id)
2745
			// This check can be removed if performance becomes an
2746
			// issue, but much less helpful errors will be produced
2747
			if volExists == nil {
2748
				return fmt.Errorf("inconsistency in state - volume %s is in all volumes bucket but volume not found: %w", string(id), define.ErrInternal)
2749
			}
2750

2751
			volume := new(Volume)
2752
			volume.config = new(VolumeConfig)
2753
			volume.state = new(VolumeState)
2754

2755
			if err := s.getVolumeFromDB(id, volume, volBucket); err != nil {
2756
				if !errors.Is(err, define.ErrNSMismatch) {
2757
					logrus.Errorf("Retrieving volume %s from the database: %v", string(id), err)
2758
				}
2759
			} else {
2760
				volumes = append(volumes, volume)
2761
			}
2762

2763
			return nil
2764
		})
2765
		return err
2766
	})
2767
	if err != nil {
2768
		return nil, err
2769
	}
2770

2771
	return volumes, nil
2772
}
2773

2774
// Volume retrieves a volume from full name
2775
func (s *BoltState) Volume(name string) (*Volume, error) {
2776
	if name == "" {
2777
		return nil, define.ErrEmptyID
2778
	}
2779

2780
	if !s.valid {
2781
		return nil, define.ErrDBClosed
2782
	}
2783

2784
	volName := []byte(name)
2785

2786
	volume := new(Volume)
2787
	volume.config = new(VolumeConfig)
2788
	volume.state = new(VolumeState)
2789

2790
	db, err := s.getDBCon()
2791
	if err != nil {
2792
		return nil, err
2793
	}
2794
	defer s.deferredCloseDBCon(db)
2795

2796
	err = db.View(func(tx *bolt.Tx) error {
2797
		volBkt, err := getVolBucket(tx)
2798
		if err != nil {
2799
			return err
2800
		}
2801

2802
		return s.getVolumeFromDB(volName, volume, volBkt)
2803
	})
2804
	if err != nil {
2805
		return nil, err
2806
	}
2807

2808
	return volume, nil
2809
}
2810

2811
// LookupVolume locates a volume from a partial name.
2812
func (s *BoltState) LookupVolume(name string) (*Volume, error) {
2813
	if name == "" {
2814
		return nil, define.ErrEmptyID
2815
	}
2816

2817
	if !s.valid {
2818
		return nil, define.ErrDBClosed
2819
	}
2820

2821
	volName := []byte(name)
2822

2823
	volume := new(Volume)
2824
	volume.config = new(VolumeConfig)
2825
	volume.state = new(VolumeState)
2826

2827
	db, err := s.getDBCon()
2828
	if err != nil {
2829
		return nil, err
2830
	}
2831
	defer s.deferredCloseDBCon(db)
2832

2833
	err = db.View(func(tx *bolt.Tx) error {
2834
		volBkt, err := getVolBucket(tx)
2835
		if err != nil {
2836
			return err
2837
		}
2838

2839
		allVolsBkt, err := getAllVolsBucket(tx)
2840
		if err != nil {
2841
			return err
2842
		}
2843

2844
		// Check for exact match on name
2845
		volDB := volBkt.Bucket(volName)
2846
		if volDB != nil {
2847
			return s.getVolumeFromDB(volName, volume, volBkt)
2848
		}
2849

2850
		// No exact match. Search all names.
2851
		foundMatch := false
2852
		err = allVolsBkt.ForEach(func(checkName, checkName2 []byte) error {
2853
			if strings.HasPrefix(string(checkName), name) {
2854
				if foundMatch {
2855
					return fmt.Errorf("more than one result for volume name %q: %w", name, define.ErrVolumeExists)
2856
				}
2857
				foundMatch = true
2858
				volName = checkName
2859
			}
2860
			return nil
2861
		})
2862
		if err != nil {
2863
			return err
2864
		}
2865

2866
		if !foundMatch {
2867
			return fmt.Errorf("no volume with name %q found: %w", name, define.ErrNoSuchVolume)
2868
		}
2869

2870
		return s.getVolumeFromDB(volName, volume, volBkt)
2871
	})
2872
	if err != nil {
2873
		return nil, err
2874
	}
2875

2876
	return volume, nil
2877
}
2878

2879
// HasVolume returns true if the given volume exists in the state, otherwise it returns false
2880
func (s *BoltState) HasVolume(name string) (bool, error) {
2881
	if name == "" {
2882
		return false, define.ErrEmptyID
2883
	}
2884

2885
	if !s.valid {
2886
		return false, define.ErrDBClosed
2887
	}
2888

2889
	volName := []byte(name)
2890

2891
	exists := false
2892

2893
	db, err := s.getDBCon()
2894
	if err != nil {
2895
		return false, err
2896
	}
2897
	defer s.deferredCloseDBCon(db)
2898

2899
	err = db.View(func(tx *bolt.Tx) error {
2900
		volBkt, err := getVolBucket(tx)
2901
		if err != nil {
2902
			return err
2903
		}
2904

2905
		volDB := volBkt.Bucket(volName)
2906
		if volDB != nil {
2907
			exists = true
2908
		}
2909

2910
		return nil
2911
	})
2912
	if err != nil {
2913
		return false, err
2914
	}
2915

2916
	return exists, nil
2917
}
2918

2919
// VolumeInUse checks if any container is using the volume
2920
// It returns a slice of the IDs of the containers using the given
2921
// volume. If the slice is empty, no containers use the given volume
2922
func (s *BoltState) VolumeInUse(volume *Volume) ([]string, error) {
2923
	if !s.valid {
2924
		return nil, define.ErrDBClosed
2925
	}
2926

2927
	if !volume.valid {
2928
		return nil, define.ErrVolumeRemoved
2929
	}
2930

2931
	depCtrs := []string{}
2932

2933
	db, err := s.getDBCon()
2934
	if err != nil {
2935
		return nil, err
2936
	}
2937
	defer s.deferredCloseDBCon(db)
2938

2939
	err = db.View(func(tx *bolt.Tx) error {
2940
		volBucket, err := getVolBucket(tx)
2941
		if err != nil {
2942
			return err
2943
		}
2944

2945
		ctrBucket, err := getCtrBucket(tx)
2946
		if err != nil {
2947
			return err
2948
		}
2949

2950
		volDB := volBucket.Bucket([]byte(volume.Name()))
2951
		if volDB == nil {
2952
			volume.valid = false
2953
			return fmt.Errorf("no volume with name %s found in DB: %w", volume.Name(), define.ErrNoSuchVolume)
2954
		}
2955

2956
		dependsBkt := volDB.Bucket(volDependenciesBkt)
2957
		if dependsBkt == nil {
2958
			return fmt.Errorf("volume %s has no dependencies bucket: %w", volume.Name(), define.ErrInternal)
2959
		}
2960

2961
		// Iterate through and add dependencies
2962
		err = dependsBkt.ForEach(func(id, value []byte) error {
2963
			// Look up all dependencies and see that they
2964
			// still exist before appending.
2965
			ctrExists := ctrBucket.Bucket(id)
2966
			if ctrExists == nil {
2967
				return nil
2968
			}
2969

2970
			depCtrs = append(depCtrs, string(id))
2971

2972
			return nil
2973
		})
2974
		if err != nil {
2975
			return err
2976
		}
2977

2978
		return nil
2979
	})
2980
	if err != nil {
2981
		return nil, err
2982
	}
2983

2984
	return depCtrs, nil
2985
}
2986

2987
// AddPod adds the given pod to the state.
2988
func (s *BoltState) AddPod(pod *Pod) error {
2989
	if !s.valid {
2990
		return define.ErrDBClosed
2991
	}
2992

2993
	if !pod.valid {
2994
		return define.ErrPodRemoved
2995
	}
2996

2997
	podID := []byte(pod.ID())
2998
	podName := []byte(pod.Name())
2999

3000
	podConfigJSON, err := json.Marshal(pod.config)
3001
	if err != nil {
3002
		return fmt.Errorf("marshalling pod %s config to JSON: %w", pod.ID(), err)
3003
	}
3004

3005
	podStateJSON, err := json.Marshal(pod.state)
3006
	if err != nil {
3007
		return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err)
3008
	}
3009

3010
	db, err := s.getDBCon()
3011
	if err != nil {
3012
		return err
3013
	}
3014
	defer s.deferredCloseDBCon(db)
3015

3016
	err = db.Update(func(tx *bolt.Tx) error {
3017
		podBkt, err := getPodBucket(tx)
3018
		if err != nil {
3019
			return err
3020
		}
3021

3022
		allPodsBkt, err := getAllPodsBucket(tx)
3023
		if err != nil {
3024
			return err
3025
		}
3026

3027
		idsBkt, err := getIDBucket(tx)
3028
		if err != nil {
3029
			return err
3030
		}
3031

3032
		namesBkt, err := getNamesBucket(tx)
3033
		if err != nil {
3034
			return err
3035
		}
3036

3037
		// Check if we already have something with the given ID and name
3038
		idExist := idsBkt.Get(podID)
3039
		if idExist != nil {
3040
			err = define.ErrPodExists
3041
			if allPodsBkt.Get(idExist) == nil {
3042
				err = define.ErrCtrExists
3043
			}
3044
			return fmt.Errorf("ID \"%s\" is in use: %w", pod.ID(), err)
3045
		}
3046
		nameExist := namesBkt.Get(podName)
3047
		if nameExist != nil {
3048
			err = define.ErrPodExists
3049
			if allPodsBkt.Get(nameExist) == nil {
3050
				err = define.ErrCtrExists
3051
			}
3052
			return fmt.Errorf("name \"%s\" is in use: %w", pod.Name(), err)
3053
		}
3054

3055
		// We are good to add the pod
3056
		// Make a bucket for it
3057
		newPod, err := podBkt.CreateBucket(podID)
3058
		if err != nil {
3059
			return fmt.Errorf("creating bucket for pod %s: %w", pod.ID(), err)
3060
		}
3061

3062
		// Make a subbucket for pod containers
3063
		if _, err := newPod.CreateBucket(containersBkt); err != nil {
3064
			return fmt.Errorf("creating bucket for pod %s containers: %w", pod.ID(), err)
3065
		}
3066

3067
		if err := newPod.Put(configKey, podConfigJSON); err != nil {
3068
			return fmt.Errorf("storing pod %s configuration in DB: %w", pod.ID(), err)
3069
		}
3070

3071
		if err := newPod.Put(stateKey, podStateJSON); err != nil {
3072
			return fmt.Errorf("storing pod %s state JSON in DB: %w", pod.ID(), err)
3073
		}
3074

3075
		// Add us to the ID and names buckets
3076
		if err := idsBkt.Put(podID, podName); err != nil {
3077
			return fmt.Errorf("storing pod %s ID in DB: %w", pod.ID(), err)
3078
		}
3079
		if err := namesBkt.Put(podName, podID); err != nil {
3080
			return fmt.Errorf("storing pod %s name in DB: %w", pod.Name(), err)
3081
		}
3082
		if err := allPodsBkt.Put(podID, podName); err != nil {
3083
			return fmt.Errorf("storing pod %s in all pods bucket in DB: %w", pod.ID(), err)
3084
		}
3085

3086
		return nil
3087
	})
3088
	if err != nil {
3089
		return err
3090
	}
3091

3092
	return nil
3093
}
3094

3095
// RemovePod removes the given pod from the state
3096
// Only empty pods can be removed
3097
func (s *BoltState) RemovePod(pod *Pod) error {
3098
	if !s.valid {
3099
		return define.ErrDBClosed
3100
	}
3101

3102
	if !pod.valid {
3103
		return define.ErrPodRemoved
3104
	}
3105

3106
	podID := []byte(pod.ID())
3107
	podName := []byte(pod.Name())
3108

3109
	db, err := s.getDBCon()
3110
	if err != nil {
3111
		return err
3112
	}
3113
	defer s.deferredCloseDBCon(db)
3114

3115
	err = db.Update(func(tx *bolt.Tx) error {
3116
		podBkt, err := getPodBucket(tx)
3117
		if err != nil {
3118
			return err
3119
		}
3120

3121
		allPodsBkt, err := getAllPodsBucket(tx)
3122
		if err != nil {
3123
			return err
3124
		}
3125

3126
		idsBkt, err := getIDBucket(tx)
3127
		if err != nil {
3128
			return err
3129
		}
3130

3131
		namesBkt, err := getNamesBucket(tx)
3132
		if err != nil {
3133
			return err
3134
		}
3135

3136
		// Check if the pod exists
3137
		podDB := podBkt.Bucket(podID)
3138
		if podDB == nil {
3139
			pod.valid = false
3140
			return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod)
3141
		}
3142

3143
		// Check if pod is empty
3144
		// This should never be nil
3145
		// But if it is, we can assume there are no containers in the
3146
		// pod.
3147
		// So let's eject the malformed pod without error.
3148
		podCtrsBkt := podDB.Bucket(containersBkt)
3149
		if podCtrsBkt != nil {
3150
			cursor := podCtrsBkt.Cursor()
3151
			if id, _ := cursor.First(); id != nil {
3152
				return fmt.Errorf("pod %s is not empty: %w", pod.ID(), define.ErrCtrExists)
3153
			}
3154
		}
3155

3156
		// Pod is empty, and ready for removal
3157
		// Let's kick it out
3158
		if err := idsBkt.Delete(podID); err != nil {
3159
			return fmt.Errorf("removing pod %s ID from DB: %w", pod.ID(), err)
3160
		}
3161
		if err := namesBkt.Delete(podName); err != nil {
3162
			return fmt.Errorf("removing pod %s name (%s) from DB: %w", pod.ID(), pod.Name(), err)
3163
		}
3164
		if err := allPodsBkt.Delete(podID); err != nil {
3165
			return fmt.Errorf("removing pod %s ID from all pods bucket in DB: %w", pod.ID(), err)
3166
		}
3167
		if err := podBkt.DeleteBucket(podID); err != nil {
3168
			return fmt.Errorf("removing pod %s from DB: %w", pod.ID(), err)
3169
		}
3170

3171
		return nil
3172
	})
3173
	if err != nil {
3174
		return err
3175
	}
3176

3177
	return nil
3178
}
3179

3180
// RemovePodContainers removes all containers in a pod
3181
func (s *BoltState) RemovePodContainers(pod *Pod) error {
3182
	if !s.valid {
3183
		return define.ErrDBClosed
3184
	}
3185

3186
	if !pod.valid {
3187
		return define.ErrPodRemoved
3188
	}
3189

3190
	podID := []byte(pod.ID())
3191

3192
	db, err := s.getDBCon()
3193
	if err != nil {
3194
		return err
3195
	}
3196
	defer s.deferredCloseDBCon(db)
3197

3198
	err = db.Update(func(tx *bolt.Tx) error {
3199
		podBkt, err := getPodBucket(tx)
3200
		if err != nil {
3201
			return err
3202
		}
3203

3204
		ctrBkt, err := getCtrBucket(tx)
3205
		if err != nil {
3206
			return err
3207
		}
3208

3209
		allCtrsBkt, err := getAllCtrsBucket(tx)
3210
		if err != nil {
3211
			return err
3212
		}
3213

3214
		idsBkt, err := getIDBucket(tx)
3215
		if err != nil {
3216
			return err
3217
		}
3218

3219
		namesBkt, err := getNamesBucket(tx)
3220
		if err != nil {
3221
			return err
3222
		}
3223

3224
		// Check if the pod exists
3225
		podDB := podBkt.Bucket(podID)
3226
		if podDB == nil {
3227
			pod.valid = false
3228
			return fmt.Errorf("pod %s does not exist in DB: %w", pod.ID(), define.ErrNoSuchPod)
3229
		}
3230

3231
		podCtrsBkt := podDB.Bucket(containersBkt)
3232
		if podCtrsBkt == nil {
3233
			return fmt.Errorf("pod %s does not have a containers bucket: %w", pod.ID(), define.ErrInternal)
3234
		}
3235

3236
		// Traverse all containers in the pod with a cursor
3237
		// for-each has issues with data mutation
3238
		err = podCtrsBkt.ForEach(func(id, name []byte) error {
3239
			// Get the container so we can check dependencies
3240
			ctr := ctrBkt.Bucket(id)
3241
			if ctr == nil {
3242
				// This should never happen
3243
				// State is inconsistent
3244
				return fmt.Errorf("pod %s referenced nonexistent container %s: %w", pod.ID(), string(id), define.ErrNoSuchCtr)
3245
			}
3246
			ctrDeps := ctr.Bucket(dependenciesBkt)
3247
			// This should never be nil, but if it is, we're
3248
			// removing it anyways, so continue if it is
3249
			if ctrDeps != nil {
3250
				err = ctrDeps.ForEach(func(depID, name []byte) error {
3251
					exists := podCtrsBkt.Get(depID)
3252
					if exists == nil {
3253
						return fmt.Errorf("container %s has dependency %s outside of pod %s: %w", string(id), string(depID), pod.ID(), define.ErrCtrExists)
3254
					}
3255
					return nil
3256
				})
3257
				if err != nil {
3258
					return err
3259
				}
3260
			}
3261

3262
			// Dependencies are set, we're clear to remove
3263

3264
			if err := ctrBkt.DeleteBucket(id); err != nil {
3265
				return fmt.Errorf("deleting container %s from DB: %w", string(id), define.ErrInternal)
3266
			}
3267

3268
			if err := idsBkt.Delete(id); err != nil {
3269
				return fmt.Errorf("deleting container %s ID in DB: %w", string(id), err)
3270
			}
3271

3272
			if err := namesBkt.Delete(name); err != nil {
3273
				return fmt.Errorf("deleting container %s name in DB: %w", string(id), err)
3274
			}
3275

3276
			if err := allCtrsBkt.Delete(id); err != nil {
3277
				return fmt.Errorf("deleting container %s ID from all containers bucket in DB: %w", string(id), err)
3278
			}
3279

3280
			return nil
3281
		})
3282
		if err != nil {
3283
			return err
3284
		}
3285

3286
		// Delete and recreate the bucket to empty it
3287
		if err := podDB.DeleteBucket(containersBkt); err != nil {
3288
			return fmt.Errorf("removing pod %s containers bucket: %w", pod.ID(), err)
3289
		}
3290
		if _, err := podDB.CreateBucket(containersBkt); err != nil {
3291
			return fmt.Errorf("recreating pod %s containers bucket: %w", pod.ID(), err)
3292
		}
3293

3294
		return nil
3295
	})
3296
	if err != nil {
3297
		return err
3298
	}
3299

3300
	return nil
3301
}
3302

3303
// AddContainerToPod adds the given container to an existing pod
3304
// The container will be added to the state and the pod
3305
func (s *BoltState) AddContainerToPod(pod *Pod, ctr *Container) error {
3306
	if !s.valid {
3307
		return define.ErrDBClosed
3308
	}
3309

3310
	if !pod.valid {
3311
		return define.ErrPodRemoved
3312
	}
3313

3314
	if !ctr.valid {
3315
		return define.ErrCtrRemoved
3316
	}
3317

3318
	if ctr.config.Pod != pod.ID() {
3319
		return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrNoSuchCtr)
3320
	}
3321

3322
	return s.addContainer(ctr, pod)
3323
}
3324

3325
// RemoveContainerFromPod removes a container from an existing pod
3326
// The container will also be removed from the state
3327
func (s *BoltState) RemoveContainerFromPod(pod *Pod, ctr *Container) error {
3328
	if !s.valid {
3329
		return define.ErrDBClosed
3330
	}
3331

3332
	if !pod.valid {
3333
		return define.ErrPodRemoved
3334
	}
3335

3336
	if ctr.config.Pod == "" {
3337
		return fmt.Errorf("container %s is not part of a pod, use RemoveContainer instead: %w", ctr.ID(), define.ErrNoSuchPod)
3338
	}
3339

3340
	if ctr.config.Pod != pod.ID() {
3341
		return fmt.Errorf("container %s is not part of pod %s: %w", ctr.ID(), pod.ID(), define.ErrInvalidArg)
3342
	}
3343

3344
	db, err := s.getDBCon()
3345
	if err != nil {
3346
		return err
3347
	}
3348
	defer s.deferredCloseDBCon(db)
3349

3350
	err = db.Update(func(tx *bolt.Tx) error {
3351
		return s.removeContainer(ctr, pod, tx)
3352
	})
3353
	return err
3354
}
3355

3356
// UpdatePod updates a pod's state from the database
3357
func (s *BoltState) UpdatePod(pod *Pod) error {
3358
	if !s.valid {
3359
		return define.ErrDBClosed
3360
	}
3361

3362
	if !pod.valid {
3363
		return define.ErrPodRemoved
3364
	}
3365

3366
	newState := new(podState)
3367

3368
	db, err := s.getDBCon()
3369
	if err != nil {
3370
		return err
3371
	}
3372
	defer s.deferredCloseDBCon(db)
3373

3374
	podID := []byte(pod.ID())
3375

3376
	err = db.View(func(tx *bolt.Tx) error {
3377
		podBkt, err := getPodBucket(tx)
3378
		if err != nil {
3379
			return err
3380
		}
3381

3382
		podDB := podBkt.Bucket(podID)
3383
		if podDB == nil {
3384
			pod.valid = false
3385
			return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod)
3386
		}
3387

3388
		// Get the pod state JSON
3389
		podStateBytes := podDB.Get(stateKey)
3390
		if podStateBytes == nil {
3391
			return fmt.Errorf("pod %s is missing state key in DB: %w", pod.ID(), define.ErrInternal)
3392
		}
3393

3394
		if err := json.Unmarshal(podStateBytes, newState); err != nil {
3395
			return fmt.Errorf("unmarshalling pod %s state JSON: %w", pod.ID(), err)
3396
		}
3397

3398
		return nil
3399
	})
3400
	if err != nil {
3401
		return err
3402
	}
3403

3404
	pod.state = newState
3405

3406
	return nil
3407
}
3408

3409
// SavePod saves a pod's state to the database
3410
func (s *BoltState) SavePod(pod *Pod) error {
3411
	if !s.valid {
3412
		return define.ErrDBClosed
3413
	}
3414

3415
	if !pod.valid {
3416
		return define.ErrPodRemoved
3417
	}
3418

3419
	stateJSON, err := json.Marshal(pod.state)
3420
	if err != nil {
3421
		return fmt.Errorf("marshalling pod %s state to JSON: %w", pod.ID(), err)
3422
	}
3423

3424
	db, err := s.getDBCon()
3425
	if err != nil {
3426
		return err
3427
	}
3428
	defer s.deferredCloseDBCon(db)
3429

3430
	podID := []byte(pod.ID())
3431

3432
	err = db.Update(func(tx *bolt.Tx) error {
3433
		podBkt, err := getPodBucket(tx)
3434
		if err != nil {
3435
			return err
3436
		}
3437

3438
		podDB := podBkt.Bucket(podID)
3439
		if podDB == nil {
3440
			pod.valid = false
3441
			return fmt.Errorf("no pod with ID %s found in database: %w", pod.ID(), define.ErrNoSuchPod)
3442
		}
3443

3444
		// Set the pod state JSON
3445
		if err := podDB.Put(stateKey, stateJSON); err != nil {
3446
			return fmt.Errorf("updating pod %s state in database: %w", pod.ID(), err)
3447
		}
3448

3449
		return nil
3450
	})
3451
	if err != nil {
3452
		return err
3453
	}
3454

3455
	return nil
3456
}
3457

3458
// AllPods returns all pods present in the state
3459
func (s *BoltState) AllPods() ([]*Pod, error) {
3460
	if !s.valid {
3461
		return nil, define.ErrDBClosed
3462
	}
3463

3464
	pods := []*Pod{}
3465

3466
	db, err := s.getDBCon()
3467
	if err != nil {
3468
		return nil, err
3469
	}
3470
	defer s.deferredCloseDBCon(db)
3471

3472
	err = db.View(func(tx *bolt.Tx) error {
3473
		allPodsBucket, err := getAllPodsBucket(tx)
3474
		if err != nil {
3475
			return err
3476
		}
3477

3478
		podBucket, err := getPodBucket(tx)
3479
		if err != nil {
3480
			return err
3481
		}
3482

3483
		err = allPodsBucket.ForEach(func(id, name []byte) error {
3484
			podExists := podBucket.Bucket(id)
3485
			// This check can be removed if performance becomes an
3486
			// issue, but much less helpful errors will be produced
3487
			if podExists == nil {
3488
				return fmt.Errorf("inconsistency in state - pod %s is in all pods bucket but pod not found: %w", string(id), define.ErrInternal)
3489
			}
3490

3491
			pod := new(Pod)
3492
			pod.config = new(PodConfig)
3493
			pod.state = new(podState)
3494

3495
			if err := s.getPodFromDB(id, pod, podBucket); err != nil {
3496
				if !errors.Is(err, define.ErrNSMismatch) {
3497
					logrus.Errorf("Retrieving pod %s from the database: %v", string(id), err)
3498
				}
3499
			} else {
3500
				pods = append(pods, pod)
3501
			}
3502

3503
			return nil
3504
		})
3505
		return err
3506
	})
3507
	if err != nil {
3508
		return nil, err
3509
	}
3510

3511
	return pods, nil
3512
}
3513

3514
// ContainerIDIsVolume checks if the given c/storage container ID is used as
3515
// backing storage for a volume.
3516
func (s *BoltState) ContainerIDIsVolume(id string) (bool, error) {
3517
	if !s.valid {
3518
		return false, define.ErrDBClosed
3519
	}
3520

3521
	isVol := false
3522

3523
	db, err := s.getDBCon()
3524
	if err != nil {
3525
		return false, err
3526
	}
3527
	defer s.deferredCloseDBCon(db)
3528

3529
	err = db.View(func(tx *bolt.Tx) error {
3530
		volCtrsBkt, err := getVolumeContainersBucket(tx)
3531
		if err != nil {
3532
			return err
3533
		}
3534

3535
		volName := volCtrsBkt.Get([]byte(id))
3536
		if volName != nil {
3537
			isVol = true
3538
		}
3539

3540
		return nil
3541
	})
3542
	return isVol, err
3543
}
3544

Использование cookies

Мы используем файлы cookie в соответствии с Политикой конфиденциальности и Политикой использования cookies.

Нажимая кнопку «Принимаю», Вы даете АО «СберТех» согласие на обработку Ваших персональных данных в целях совершенствования нашего веб-сайта и Сервиса GitVerse, а также повышения удобства их использования.

Запретить использование cookies Вы можете самостоятельно в настройках Вашего браузера.