diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2019-08-05 21:59:24 +0200 |
---|---|---|
committer | GitHub <noreply@github.com> | 2019-08-05 21:59:24 +0200 |
commit | d46c7644cf631a67d86abb74b397096ec56bda6f (patch) | |
tree | d5ffcbc28ec1769cab3ecf53fe9abfdc55c65841 /libpod | |
parent | 3b1ee6990c0e90e0a7ee4233e3ce3bc6487c8a4f (diff) | |
parent | 29c137ff665314f18a65cf55ba55522e702987b3 (diff) | |
download | podman-d46c7644cf631a67d86abb74b397096ec56bda6f.tar.gz podman-d46c7644cf631a67d86abb74b397096ec56bda6f.tar.bz2 podman-d46c7644cf631a67d86abb74b397096ec56bda6f.zip |
Merge pull request #3724 from mheon/v1.4.2-stable1
V1.4.2 stable1
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 70 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 186 | ||||
-rw-r--r-- | libpod/container_api.go | 11 | ||||
-rw-r--r-- | libpod/container_internal.go | 20 | ||||
-rw-r--r-- | libpod/events.go | 55 | ||||
-rw-r--r-- | libpod/events/config.go | 13 | ||||
-rw-r--r-- | libpod/events/events.go | 25 | ||||
-rw-r--r-- | libpod/events/events_linux.go | 4 | ||||
-rw-r--r-- | libpod/events/journal_linux.go | 4 | ||||
-rw-r--r-- | libpod/events/logfile.go | 2 | ||||
-rw-r--r-- | libpod/options.go | 20 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 7 |
12 files changed, 303 insertions, 114 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 63e40a98f..12c364993 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -73,42 +73,50 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { // As such, just a db.Close() is fine here. defer db.Close() - // Perform initial database setup - err = db.Update(func(tx *bolt.Tx) error { - if _, err := tx.CreateBucketIfNotExists(idRegistryBkt); err != nil { - return errors.Wrapf(err, "error creating id-registry bucket") - } - if _, err := tx.CreateBucketIfNotExists(nameRegistryBkt); err != nil { - return errors.Wrapf(err, "error creating name-registry bucket") - } - if _, err := tx.CreateBucketIfNotExists(nsRegistryBkt); err != nil { - return errors.Wrapf(err, "error creating ns-registry bucket") - } - if _, err := tx.CreateBucketIfNotExists(ctrBkt); err != nil { - return errors.Wrapf(err, "error creating containers bucket") - } - if _, err := tx.CreateBucketIfNotExists(allCtrsBkt); err != nil { - return errors.Wrapf(err, "error creating all containers bucket") - } - if _, err := tx.CreateBucketIfNotExists(podBkt); err != nil { - return errors.Wrapf(err, "error creating pods bucket") - } - if _, err := tx.CreateBucketIfNotExists(allPodsBkt); err != nil { - return errors.Wrapf(err, "error creating all pods bucket") - } - if _, err := tx.CreateBucketIfNotExists(volBkt); err != nil { - return errors.Wrapf(err, "error creating volume bucket") - } - if _, err := tx.CreateBucketIfNotExists(allVolsBkt); err != nil { - return errors.Wrapf(err, "error creating all volumes bucket") + createBuckets := [][]byte{ + idRegistryBkt, + nameRegistryBkt, + nsRegistryBkt, + ctrBkt, + allCtrsBkt, + podBkt, + allPodsBkt, + volBkt, + allVolsBkt, + runtimeConfigBkt, + } + + // Does the DB need an update? + needsUpdate := false + err = db.View(func(tx *bolt.Tx) error { + for _, bkt := range createBuckets { + if test := tx.Bucket(bkt); test == nil { + needsUpdate = true + break + } } - if _, err := tx.CreateBucketIfNotExists(runtimeConfigBkt); err != nil { - return errors.Wrapf(err, "error creating runtime-config bucket") + return nil + }) + if err != nil { + return nil, errors.Wrapf(err, "error checking DB schema") + } + + if !needsUpdate { + state.valid = true + return state, nil + } + + // Ensure schema is properly created in DB + err = db.Update(func(tx *bolt.Tx) error { + for _, bkt := range createBuckets { + if _, err := tx.CreateBucketIfNotExists(bkt); err != nil { + return errors.Wrapf(err, "error creating bucket %s", string(bkt)) + } } return nil }) if err != nil { - return nil, errors.Wrapf(err, "error creating initial database layout") + return nil, errors.Wrapf(err, "error creating buckets for DB") } state.valid = true diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 313e5f4d7..e06de631d 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -72,98 +72,160 @@ var ( volPathKey = []byte(volPathName) ) +// This represents a field in the runtime configuration that will be validated +// against the DB to ensure no configuration mismatches occur. +type dbConfigValidation struct { + name string // Only used for error messages + runtimeValue string + key []byte + defaultValue string +} + // Check if the configuration of the database is compatible with the // configuration of the runtime opening it // If there is no runtime configuration loaded, load our own func checkRuntimeConfig(db *bolt.DB, rt *Runtime) error { - err := db.Update(func(tx *bolt.Tx) error { + storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID()) + if err != nil { + return err + } + + // We need to validate the following things + checks := []dbConfigValidation{ + { + "OS", + runtime.GOOS, + osKey, + runtime.GOOS, + }, + { + "libpod root directory (staticdir)", + rt.config.StaticDir, + staticDirKey, + "", + }, + { + "libpod temporary files directory (tmpdir)", + rt.config.TmpDir, + tmpDirKey, + "", + }, + { + "storage temporary directory (runroot)", + rt.config.StorageConfig.RunRoot, + runRootKey, + storeOpts.RunRoot, + }, + { + "storage graph root directory (graphroot)", + rt.config.StorageConfig.GraphRoot, + graphRootKey, + storeOpts.GraphRoot, + }, + { + "storage graph driver", + rt.config.StorageConfig.GraphDriverName, + graphDriverKey, + storeOpts.GraphDriverName, + }, + { + "volume path", + rt.config.VolumePath, + volPathKey, + "", + }, + } + + // These fields were missing and will have to be recreated. + missingFields := []dbConfigValidation{} + + // Let's try and validate read-only first + err = db.View(func(tx *bolt.Tx) error { configBkt, err := getRuntimeConfigBucket(tx) if err != nil { return err } - if err := validateDBAgainstConfig(configBkt, "OS", runtime.GOOS, osKey, runtime.GOOS); err != nil { - return err + for _, check := range checks { + exists, err := readOnlyValidateConfig(configBkt, check) + if err != nil { + return err + } + if !exists { + missingFields = append(missingFields, check) + } } - if err := validateDBAgainstConfig(configBkt, "libpod root directory (staticdir)", - rt.config.StaticDir, staticDirKey, ""); err != nil { - return err - } + return nil + }) + if err != nil { + return err + } - if err := validateDBAgainstConfig(configBkt, "libpod temporary files directory (tmpdir)", - rt.config.TmpDir, tmpDirKey, ""); err != nil { - return err - } + if len(missingFields) == 0 { + return nil + } - storeOpts, err := storage.DefaultStoreOptions(rootless.IsRootless(), rootless.GetRootlessUID()) + // Populate missing fields + return db.Update(func(tx *bolt.Tx) error { + configBkt, err := getRuntimeConfigBucket(tx) if err != nil { return err } - if err := validateDBAgainstConfig(configBkt, "storage temporary directory (runroot)", - rt.config.StorageConfig.RunRoot, runRootKey, - storeOpts.RunRoot); err != nil { - return err - } - if err := validateDBAgainstConfig(configBkt, "storage graph root directory (graphroot)", - rt.config.StorageConfig.GraphRoot, graphRootKey, - storeOpts.GraphRoot); err != nil { - return err - } + for _, missing := range missingFields { + dbValue := []byte(missing.runtimeValue) + if missing.runtimeValue == "" && missing.defaultValue != "" { + dbValue = []byte(missing.defaultValue) + } - if err := validateDBAgainstConfig(configBkt, "storage graph driver", - rt.config.StorageConfig.GraphDriverName, - graphDriverKey, - storeOpts.GraphDriverName); err != nil { - return err + if err := configBkt.Put(missing.key, dbValue); err != nil { + return errors.Wrapf(err, "error updating %s in DB runtime config", missing.name) + } } - return validateDBAgainstConfig(configBkt, "volume path", - rt.config.VolumePath, volPathKey, "") + return nil }) - - return err } -// Validate a configuration entry in the DB against current runtime config -// If the given configuration key does not exist it will be created -// If the given runtimeValue or value retrieved from the database are the empty -// string and defaultValue is not, defaultValue will be checked instead. This -// ensures that we will not fail on configuration changes in configured c/storage. -func validateDBAgainstConfig(bucket *bolt.Bucket, fieldName, runtimeValue string, keyName []byte, defaultValue string) error { - keyBytes := bucket.Get(keyName) +// Attempt a read-only validation of a configuration entry in the DB against an +// element of the current runtime configuration. +// If the configuration key in question does not exist, (false, nil) will be +// returned. +// If the configuration key does exist, and matches the runtime configuration +// successfully, (true, nil) is returned. +// An error is only returned when validation fails. +// if the given runtimeValue or value retrieved from the database are empty, +// and defaultValue is not, defaultValue will be checked instead. This ensures +// that we will not fail on configuration changes in c/storage (where we may +// pass the empty string to use defaults). +func readOnlyValidateConfig(bucket *bolt.Bucket, toCheck dbConfigValidation) (bool, error) { + keyBytes := bucket.Get(toCheck.key) if keyBytes == nil { - dbValue := []byte(runtimeValue) - if runtimeValue == "" && defaultValue != "" { - dbValue = []byte(defaultValue) - } + // False return indicates missing key + return false, nil + } - if err := bucket.Put(keyName, dbValue); err != nil { - return errors.Wrapf(err, "error updating %s in DB runtime config", fieldName) - } - } else { - if runtimeValue != string(keyBytes) { - // If runtimeValue is the empty string, check against - // the default - if runtimeValue == "" && defaultValue != "" && - string(keyBytes) == defaultValue { - return nil - } + dbValue := string(keyBytes) - // If DB value is the empty string, check that the - // runtime value is the default - if string(keyBytes) == "" && defaultValue != "" && - runtimeValue == defaultValue { - return nil - } + if toCheck.runtimeValue != dbValue { + // If the runtime value is the empty string and default is not, + // check against default. + if toCheck.runtimeValue == "" && toCheck.defaultValue != "" && dbValue == toCheck.defaultValue { + return true, nil + } - return errors.Wrapf(ErrDBBadConfig, "database %s %s does not match our %s %s", - fieldName, string(keyBytes), fieldName, runtimeValue) + // If the DB value is the empty string, check that the runtime + // value is the default. + if dbValue == "" && toCheck.defaultValue != "" && toCheck.runtimeValue == toCheck.defaultValue { + return true, nil } + + return true, errors.Wrapf(ErrDBBadConfig, "database %s %q does not match our %s %q", + toCheck.name, dbValue, toCheck.name, toCheck.runtimeValue) } - return nil + return true, nil } // Open a connection to the database. diff --git a/libpod/container_api.go b/libpod/container_api.go index 0e877d04e..1de956521 100644 --- a/libpod/container_api.go +++ b/libpod/container_api.go @@ -187,7 +187,7 @@ func (c *Container) StopWithTimeout(timeout uint) error { c.state.State == ContainerStateExited { return ErrCtrStopped } - defer c.newContainerEvent(events.Stop) + return c.stop(timeout) } @@ -301,6 +301,11 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir if err != nil { if exited { // If the runtime exited, propagate the error we got from the process. + // We need to remove PID files to ensure no memory leaks + if err2 := os.Remove(pidFile); err2 != nil { + logrus.Errorf("Error removing exit file for container %s exec session %s: %v", c.ID(), sessionID, err2) + } + return err } return errors.Wrapf(err, "timed out waiting for runtime to create pidfile for exec session in container %s", c.ID()) @@ -308,6 +313,10 @@ func (c *Container) Exec(tty, privileged bool, env, cmd []string, user, workDir // Pidfile exists, read it contents, err := ioutil.ReadFile(pidFile) + // We need to remove PID files to ensure no memory leaks + if err2 := os.Remove(pidFile); err2 != nil { + logrus.Errorf("Error removing exit file for container %s exec session %s: %v", c.ID(), sessionID, err2) + } if err != nil { // We don't know the PID of the exec session // However, it may still be alive diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 9245a8840..ab0ad6516 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -544,19 +544,15 @@ func (c *Container) removeConmonFiles() error { return errors.Wrapf(err, "error removing container %s OOM file", c.ID()) } - // Instead of outright deleting the exit file, rename it (if it exists). - // We want to retain it so we can get the exit code of containers which - // are removed (at least until we have a workable events system) + // Remove the exit file so we don't leak memory in tmpfs exitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, c.ID()) - oldExitFile := filepath.Join(c.runtime.ociRuntime.exitsDir, fmt.Sprintf("%s-old", c.ID())) if _, err := os.Stat(exitFile); err != nil { if !os.IsNotExist(err) { return errors.Wrapf(err, "error running stat on container %s exit file", c.ID()) } - } else if err == nil { - // Rename should replace the old exit file (if it exists) - if err := os.Rename(exitFile, oldExitFile); err != nil { - return errors.Wrapf(err, "error renaming container %s exit file", c.ID()) + } else { + if err := os.Remove(exitFile); err != nil { + return errors.Wrapf(err, "error removing container %s exit file", c.ID()) } } @@ -1048,7 +1044,13 @@ func (c *Container) stop(timeout uint) error { } // Wait until we have an exit file, and sync once we do - return c.waitForExitFileAndSync() + if err := c.waitForExitFileAndSync(); err != nil { + return err + } + + c.newContainerEvent(events.Stop) + + return nil } // Internal, non-locking function to pause a container diff --git a/libpod/events.go b/libpod/events.go index 13bb5bdde..be21e510a 100644 --- a/libpod/events.go +++ b/libpod/events.go @@ -1,7 +1,10 @@ package libpod import ( + "fmt" + "github.com/containers/libpod/libpod/events" + "github.com/pkg/errors" "github.com/sirupsen/logrus" ) @@ -79,3 +82,55 @@ func (r *Runtime) Events(options events.ReadOptions) error { } return eventer.Read(options) } + +// GetEvents reads the event log and returns events based on input filters +func (r *Runtime) GetEvents(filters []string) ([]*events.Event, error) { + var ( + logEvents []*events.Event + readErr error + ) + eventChannel := make(chan *events.Event) + options := events.ReadOptions{ + EventChannel: eventChannel, + Filters: filters, + FromStart: true, + Stream: false, + } + eventer, err := r.newEventer() + if err != nil { + return nil, err + } + go func() { + readErr = eventer.Read(options) + }() + if readErr != nil { + return nil, readErr + } + for e := range eventChannel { + logEvents = append(logEvents, e) + } + return logEvents, nil +} + +// GetLastContainerEvent takes a container name or ID and an event status and returns +// the last occurrence of the container event +func (r *Runtime) GetLastContainerEvent(nameOrID string, containerEvent events.Status) (*events.Event, error) { + // check to make sure the event.Status is valid + if _, err := events.StringToStatus(containerEvent.String()); err != nil { + return nil, err + } + filters := []string{ + fmt.Sprintf("container=%s", nameOrID), + fmt.Sprintf("event=%s", containerEvent), + "type=container", + } + containerEvents, err := r.GetEvents(filters) + if err != nil { + return nil, err + } + if len(containerEvents) < 1 { + return nil, errors.Wrapf(events.ErrEventNotFound, "%s not found", containerEvent.String()) + } + // return the last element in the slice + return containerEvents[len(containerEvents)-1], nil +} diff --git a/libpod/events/config.go b/libpod/events/config.go index 810988205..96172d47b 100644 --- a/libpod/events/config.go +++ b/libpod/events/config.go @@ -2,6 +2,8 @@ package events import ( "time" + + "github.com/pkg/errors" ) // EventerType ... @@ -12,6 +14,8 @@ const ( LogFile EventerType = iota // Journald indicates journald should be used to log events Journald EventerType = iota + // Null is a no-op events logger. It does not read or write events. + Null EventerType = iota ) // Event describes the attributes of a libpod event @@ -158,3 +162,12 @@ const ( // EventFilter for filtering events type EventFilter func(*Event) bool + +var ( + // ErrEventTypeBlank indicates the event log found something done by podman + // but it isnt likely an event + ErrEventTypeBlank = errors.New("event type blank") + + // ErrEventNotFound indicates that the event was not found in the event log + ErrEventNotFound = errors.New("unable to find event") +) diff --git a/libpod/events/events.go b/libpod/events/events.go index 1ec79bcd7..5e828bc8a 100644 --- a/libpod/events/events.go +++ b/libpod/events/events.go @@ -16,11 +16,30 @@ var ErrNoJournaldLogging = errors.New("No support for journald logging") // String returns a string representation of EventerType func (et EventerType) String() string { - if et == LogFile { + switch et { + case LogFile: return "file" + case Journald: + return "journald" + case Null: + return "none" + default: + return "invalid" + } +} +// IsValidEventer checks if the given string is a valid eventer type. +func IsValidEventer(eventer string) bool { + switch eventer { + case LogFile.String(): + return true + case Journald.String(): + return true + case Null.String(): + return true + default: + return false } - return "journald" } // NewEvent creates a event struct and populates with @@ -95,6 +114,8 @@ func StringToType(name string) (Type, error) { return System, nil case Volume.String(): return Volume, nil + case "": + return "", ErrEventTypeBlank } return "", errors.Errorf("unknown event type %q", name) } diff --git a/libpod/events/events_linux.go b/libpod/events/events_linux.go index 11f309574..ffb100be8 100644 --- a/libpod/events/events_linux.go +++ b/libpod/events/events_linux.go @@ -18,8 +18,10 @@ func NewEventer(options EventerOptions) (eventer Eventer, err error) { } case strings.ToUpper(LogFile.String()): eventer = EventLogFile{options} + case strings.ToUpper(Null.String()): + eventer = NewNullEventer() default: - return eventer, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType)) + return nil, errors.Errorf("unknown event logger type: %s", strings.ToUpper(options.EventerType)) } return eventer, nil } diff --git a/libpod/events/journal_linux.go b/libpod/events/journal_linux.go index 78a630e9a..d5bce4334 100644 --- a/libpod/events/journal_linux.go +++ b/libpod/events/journal_linux.go @@ -101,7 +101,9 @@ func (e EventJournalD) Read(options ReadOptions) error { // We can't decode this event. // Don't fail hard - that would make events unusable. // Instead, log and continue. - logrus.Errorf("Unable to decode event: %v", err) + if errors.Cause(err) != ErrEventTypeBlank { + logrus.Errorf("Unable to decode event: %v", err) + } continue } include := true diff --git a/libpod/events/logfile.go b/libpod/events/logfile.go index e5efc09bb..30d72b9fc 100644 --- a/libpod/events/logfile.go +++ b/libpod/events/logfile.go @@ -55,7 +55,7 @@ func (e EventLogFile) Read(options ReadOptions) error { return err } switch event.Type { - case Image, Volume, Pod, Container: + case Image, Volume, Pod, System, Container: // no-op default: return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath) diff --git a/libpod/options.go b/libpod/options.go index cdac09654..62571957f 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -8,6 +8,7 @@ import ( "syscall" "github.com/containers/image/manifest" + "github.com/containers/libpod/libpod/events" "github.com/containers/libpod/pkg/namespaces" "github.com/containers/libpod/pkg/rootless" "github.com/containers/libpod/pkg/util" @@ -421,6 +422,25 @@ func WithDefaultInfraCommand(cmd string) RuntimeOption { } } +// WithEventsLogger sets the events backend to use. +// Currently supported values are "file" for file backend and "journald" for +// journald backend. +func WithEventsLogger(logger string) RuntimeOption { + return func(rt *Runtime) error { + if rt.valid { + return ErrRuntimeFinalized + } + + if !events.IsValidEventer(logger) { + return errors.Wrapf(ErrInvalidArg, "%q is not a valid events backend", logger) + } + + rt.config.EventsLogger = logger + + return nil + } +} + // WithRenumber instructs libpod to perform a lock renumbering while // initializing. This will handle migrations from early versions of libpod with // file locks to newer versions with SHM locking, as well as changes in the diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index 0871b83a7..7ef7b8828 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -376,14 +376,9 @@ func (r *Runtime) removeContainer(ctx context.Context, c *Container, force bool, // Check that the container's in a good state to be removed if c.state.State == ContainerStateRunning { - if err := r.ociRuntime.stopContainer(c, c.StopTimeout()); err != nil { + if err := c.stop(c.StopTimeout()); err != nil { return errors.Wrapf(err, "cannot remove container %s as it could not be stopped", c.ID()) } - - // Need to update container state to make sure we know it's stopped - if err := c.waitForExitFileAndSync(); err != nil { - return err - } } // Check that all of our exec sessions have finished |