From 6af7e544636ae66ce237489ce6948123e1b3249d Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 26 Oct 2020 17:17:45 -0400 Subject: Add network aliases for containers to DB This adds the database backend for network aliases. Aliases are additional names for a container that are used with the CNI dnsname plugin - the container will be accessible by these names in addition to its name. Aliases are allowed to change over time as the container connects to and disconnects from networks. Aliases are implemented as another bucket in the database to register all aliases, plus two buckets for each container (one to hold connected CNI networks, a second to hold its aliases). The aliases are only unique per-network, to the global and per-container aliases buckets have a sub-bucket for each CNI network that has aliases, and the aliases are stored within that sub-bucket. Aliases are formatted as alias (key) to container ID (value) in both cases. Three DB functions are defined for aliases: retrieving current aliases for a given network, setting aliases for a given network, and removing all aliases for a given network. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 270 ++++++++++++++++++++++++++++++++++++++++++++++++- 1 file changed, 266 insertions(+), 4 deletions(-) (limited to 'libpod/boltdb_state.go') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 9dd5ca465..e0db92082 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -50,10 +50,12 @@ type BoltState struct { // containers in the pod. // - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup // operations. -// - execBkt: Map of exec session ID to exec session - contains a sub-bucket for -// each exec session in the DB. -// - execRegistryBkt: Map of exec session ID to nothing. Contains one entry for -// each exec session. Used for iterating through all exec sessions. +// - execBkt: Map of exec session ID to container ID - used for resolving +// exec session IDs to the containers that hold the exec session. +// - aliasesBkt - Contains a bucket for each CNI network, which contain a map of +// network alias (an extra name for containers in DNS) to the ID of the +// container holding the alias. Aliases must be unique per-network, and cannot +// conflict with names registered in nameRegistryBkt. // - runtimeConfigBkt: Contains configuration of the libpod instance that // initially created the database. This must match for any further instances // that access the database, to ensure that state mismatches with @@ -92,6 +94,7 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { volBkt, allVolsBkt, execBkt, + aliasesBkt, runtimeConfigBkt, } @@ -969,6 +972,265 @@ func (s *BoltState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetNetworkAliases retrieves the network aliases for the given container in +// the given CNI network. +func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + if network == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + aliases := []string{} + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliasesForNetwork, "container %s has no aliases for network %q", ctr.ID(), network) + } + + return netAliasesBkt.ForEach(func(alias, v []byte) error { + aliases = append(aliases, string(alias)) + return nil + }) + }) + if err != nil { + return nil, err + } + + return aliases, nil +} + +// SetNetworkAliases sets network aliases for the given container in the given +// network. All existing aliases for that network (if any exist) will be removed, +// to be replaced by the new aliases given. +func (s *BoltState) SetNetworkAliases(ctr *Container, network string, aliases []string) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allAliasesBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + netAllAliasesBucket, err := allAliasesBucket.CreateBucketIfNotExists([]byte(network)) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for network %s", network) + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to any CNI networks, so cannot add aliases", ctr.ID()) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + if netConnected == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to CNI network %q, so cannot add aliases for this network", ctr.ID(), network) + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + // Check if the container already has network aliases for this network. + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt != nil { + // We have aliases. Have to remove them. + forEachErr := netAliasesBkt.ForEach(func(alias, v []byte) error { + // Relies on errors.Wrapf(nil, ...) returning + // nil. + return errors.Wrapf(netAllAliasesBucket.Delete(alias), "error removing alias %q from network %q when changing aliases for container %s", string(alias), network, ctr.ID()) + }) + if forEachErr != nil { + return forEachErr + } + } + + if netAliasesBkt == nil { + newBkt, err := ctrAliasesBkt.CreateBucket([]byte(network)) + if err != nil { + return errors.Wrapf(err, "could not create bucket for network aliases for network %q", network) + } + netAliasesBkt = newBkt + } + + for _, alias := range aliases { + // Check if safe to use + aliasExists := netAllAliasesBucket.Get([]byte(alias)) + if aliasExists != nil { + return errors.Wrapf(define.ErrAliasExists, "network alias %q already exists in network %q (used by container %s)", alias, network, string(aliasExists)) + } + nameExists := namesBucket.Get([]byte(alias)) + if nameExists != nil { + return errors.Wrapf(define.ErrCtrExists, "a container or pod already uses the name %q, cannot add network alias for container %s", alias, ctr.ID()) + } + + // Add alias + if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network %q alias %q to DB", ctr.ID(), network, alias) + } + if err := netAllAliasesBucket.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network %q alias %q to all aliases in DB", ctr.ID(), network, alias) + } + } + + return nil + }) +} + +// RemoveNetworkAliases removes network aliases of the given container in the +// given network. +func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allAliasesBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + netAllAliasesBucket, err := allAliasesBucket.CreateBucketIfNotExists([]byte(network)) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for network %s", network) + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to any CNI networks, so cannot add aliases", ctr.ID()) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + if netConnected == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to CNI network %q, so cannot add aliases for this network", ctr.ID(), network) + } + + // Check if the container already has network aliases for this network. + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt != nil { + // We have aliases. Remove them. + forEachErr := netAliasesBkt.ForEach(func(alias, v []byte) error { + // Relies on errors.Wrapf(nil, ...) returning + // nil. + return errors.Wrapf(netAllAliasesBucket.Delete(alias), "error removing alias %q from network %q when changing aliases for container %s", string(alias), network, ctr.ID()) + }) + if forEachErr != nil { + return forEachErr + } + } + + return nil + }) +} + // GetContainerConfig returns a container config from the database by full ID func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { if len(id) == 0 { -- cgit v1.2.3-54-g00ecf From 63efde15f14b18f5af385e89936b4ab0868cb357 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Tue, 27 Oct 2020 15:50:03 -0400 Subject: Add a way to retrieve all network aliases for a ctr The original interface only allowed retrieving aliases for a specific network, not for all networks. This will allow aliases to be retrieved for every network the container is present in, in a single DB operation. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 85 ++++++++++++++++++++++++++++++++++++++++++++++- libpod/in_memory_state.go | 19 +++++++++++ libpod/state.go | 2 ++ 3 files changed, 105 insertions(+), 1 deletion(-) (limited to 'libpod/boltdb_state.go') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index e0db92082..4a94d9090 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1013,9 +1013,21 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) } + ctrNetworkBkt := dbCtr.Bucket(networksBkt) + if ctrNetworkBkt == nil { + // No networks joined, so no aliases + return nil + } + + inNetwork := ctrNetworkBkt.Get([]byte(network)) + if inNetwork == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s is not part of network %s, no aliases found") + } + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) if ctrAliasesBkt == nil { - return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + // No aliases + return nil } netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) @@ -1035,6 +1047,77 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, return aliases, nil } +// GetAllNetworkAliases retrieves the network aliases for the given container in +// all CNI networks. +func (s *BoltState) GetAllNetworkAliases(ctr *Container) (map[string][]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + aliases := make(map[string][]string) + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + // No aliases present + return nil + } + + ctrNetworkBkt := dbCtr.Bucket(networksBkt) + if ctrNetworkBkt == nil { + // No networks joined, so no aliases + return nil + } + + return ctrNetworkBkt.ForEach(func(network, v []byte) error { + netAliasesBkt := ctrAliasesBkt.Bucket(network) + if netAliasesBkt == nil { + return nil + } + + netAliases := []string{} + aliases[string(network)] = netAliases + + return netAliasesBkt.ForEach(func(alias, v []byte) error { + netAliases = append(netAliases, string(alias)) + return nil + }) + }) + }) + if err != nil { + return nil, err + } + + return aliases, nil +} + // SetNetworkAliases sets network aliases for the given container in the given // network. All existing aliases for that network (if any exist) will be removed, // to be replaced by the new aliases given. diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 66c2a5cbd..3b88c9664 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -568,6 +568,25 @@ func (s *InMemoryState) GetNetworkAliases(ctr *Container, network string) ([]str return netAliases, nil } +// GetAllNetworkAliases gets all network aliases for the given container. +func (s *InMemoryState) GetAllNetworkAliases(ctr *Container) (map[string][]string, error) { + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + ctr, ok := s.containers[ctr.ID()] + if !ok { + return nil, define.ErrNoSuchCtr + } + + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if !ok { + return map[string][]string{}, nil + } + + return ctrAliases, nil +} + // SetNetworkAliases sets network aliases for the given container in the given // network. func (s *InMemoryState) SetNetworkAliases(ctr *Container, network string, aliases []string) error { diff --git a/libpod/state.go b/libpod/state.go index fca0548c4..6b178ec58 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -100,6 +100,8 @@ type State interface { // Get network aliases for the given container in the given network. GetNetworkAliases(ctr *Container, network string) ([]string, error) + // Get all network aliases for the given container. + GetAllNetworkAliases(ctr *Container) (map[string][]string, error) // Set network aliases for the given container in the given network. SetNetworkAliases(ctr *Container, network string, aliases []string) error // Remove network aliases for the given container in the given network. -- cgit v1.2.3-54-g00ecf From 844d540d042fe7477e62f8cf28e524fcd756f6c8 Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Thu, 29 Oct 2020 14:27:36 -0400 Subject: Add tests for network aliases As part of this, we need two new functions, for retrieving all aliases for a network and removing all aliases for a network, both required to test. Also, rework handling for some things the tests discovered were broken (notably conflicts between container name and existing aliases). Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 121 +++++++++++++++++++- libpod/boltdb_state_internal.go | 60 ++++++++-- libpod/in_memory_state.go | 113 +++++++++++++++++++ libpod/state.go | 6 + libpod/state_test.go | 244 ++++++++++++++++++++++++++++++++++++++++ 5 files changed, 534 insertions(+), 10 deletions(-) (limited to 'libpod/boltdb_state.go') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 4a94d9090..0b9b353c7 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -1021,7 +1021,7 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, inNetwork := ctrNetworkBkt.Get([]byte(network)) if inNetwork == nil { - return errors.Wrapf(define.ErrNoAliases, "container %s is not part of network %s, no aliases found") + return errors.Wrapf(define.ErrNoAliases, "container %s is not part of network %s, no aliases found", ctr.ID(), network) } ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) @@ -1103,12 +1103,14 @@ func (s *BoltState) GetAllNetworkAliases(ctr *Container) (map[string][]string, e } netAliases := []string{} - aliases[string(network)] = netAliases - return netAliasesBkt.ForEach(func(alias, v []byte) error { + _ = netAliasesBkt.ForEach(func(alias, v []byte) error { netAliases = append(netAliases, string(alias)) return nil }) + + aliases[string(network)] = netAliases + return nil }) }) if err != nil { @@ -1314,6 +1316,119 @@ func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { }) } +// Get all network aliases for a single CNI network. Returns a map of alias to +// container ID. +func (s *BoltState) GetAllAliasesForNetwork(network string) (map[string]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if network == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "network name must not be empty") + } + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + aliases := make(map[string]string) + + err = db.View(func(tx *bolt.Tx) error { + aliasBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + dbAlias := aliasBucket.Bucket([]byte(network)) + if dbAlias == nil { + // We can't tell if the network exists, or doesn't exist + // So... Assume it exists, but has no aliases. + return nil + } + + return dbAlias.ForEach(func(alias, ctrId []byte) error { + aliases[string(alias)] = string(ctrId) + return nil + }) + }) + if err != nil { + return nil, err + } + + return aliases, nil +} + +// RemoveAllAliasesForNetwork removes all the aliases in a given CNI network, as +// part of that network being removed. +func (s *BoltState) RemoveAllAliasesForNetwork(network string) error { + if !s.valid { + return define.ErrDBClosed + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + allCtrsBucket, err := getAllCtrsBucket(tx) + if err != nil { + return err + } + + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allAliasesBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + checkAliasesBucketExists := allAliasesBucket.Bucket([]byte(network)) + if checkAliasesBucketExists != nil { + if err := allAliasesBucket.DeleteBucket([]byte(network)); err != nil { + return errors.Wrapf(err, "error removing network %s aliases bucket from DB", network) + } + } + + // Iterate through all containers and remove their aliases + // bucket for the network. + return allCtrsBucket.ForEach(func(ctrID, ctrName []byte) error { + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + // DB State is inconsistent... but we can't do + // anything about it. + // Log and move on. + logrus.Errorf("Container %s listed in all containers, but has no bucket!", string(ctrID)) + return nil + } + + dbCtrAliases := dbCtr.Bucket(aliasesBkt) + if dbCtrAliases == nil { + // Container has no aliases, this is OK. + return nil + } + + ctrNetAliases := dbCtrAliases.Bucket([]byte(network)) + if ctrNetAliases != nil { + if err := dbCtrAliases.DeleteBucket([]byte(network)); err != nil { + return errors.Wrapf(err, "error removing bucket for network aliases for network %s from container %s", network, string(ctrID)) + } + } + return nil + }) + }) +} + // GetContainerConfig returns a container config from the database by full ID func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { if len(id) == 0 { diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index 1ecf99661..d5c755a52 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -634,6 +634,13 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "name \"%s\" is in use", ctr.Name()) } + // Check that we don't have any empty network names + for _, net := range ctr.config.Networks { + if net == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names cannot be an empty string") + } + } + // If we have network aliases, check if they are already in use. for net, aliases := range ctr.config.NetworkAliases { // Aliases cannot conflict with container names. @@ -682,6 +689,51 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "error adding container %s to all containers bucket in DB", ctr.ID()) } + // Check aliases for all networks, remove conflicts with the + // container name. + for _, net := range ctr.config.Networks { + netAliasesBkt := allAliasesBkt.Bucket([]byte(net)) + if netAliasesBkt == nil { + continue + } + + otherCtrID := netAliasesBkt.Get(ctrName) + if otherCtrID == nil { + continue + } + + if err := netAliasesBkt.Delete(ctrName); err != nil { + return errors.Wrapf(err, "error removing container %s name from network aliases for network %s", ctr.ID(), net) + } + + // We now need to remove from the other container. + // To do this, we work through the container bucket, + // then its aliases bucket, then its aliases for this + // specific network, then we remove the alias. + // Just slightly ridiculous. Just slightly. + otherCtr := ctrBucket.Bucket(otherCtrID) + if otherCtr == nil { + // The state is inconsistent, but we can't do + // much... + logrus.Errorf("Container %s referred to by network alias but not present in state", string(otherCtrID)) + continue + } + otherCtrAliases := otherCtr.Bucket(aliasesBkt) + if otherCtrAliases == nil { + logrus.Errorf("Container %s is missing aliases but but has an alias", string(otherCtrID)) + continue + } + otherCtrNetworkAliases := otherCtrAliases.Bucket([]byte(net)) + if otherCtrNetworkAliases == nil { + logrus.Errorf("Container %s is missing network aliases bucket for network %s but has alias in that network", string(otherCtrID), net) + } + if otherCtrNetworkAliases.Get(ctrName) != nil { + if err := otherCtrNetworkAliases.Delete(ctrName); err != nil { + return errors.Wrapf(err, "error removing container %s name from network %s aliases of container %s", ctr.Name(), net, string(otherCtrID)) + } + } + } + for net, aliases := range ctr.config.NetworkAliases { netAliasesBkt, err := allAliasesBkt.CreateBucketIfNotExists([]byte(net)) if err != nil { @@ -689,13 +741,7 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { } for _, alias := range aliases { if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { - return errors.Wrapf(err, "error adding container %s network aliasa %q to network %q", ctr.ID(), alias, net) - } - } - // If the container's name is present in the aliases - remove it. - if namePresent := netAliasesBkt.Get(ctrName); namePresent != nil { - if err := netAliasesBkt.Delete(ctrName); err != nil { - return errors.Wrapf(err, "error cleaning container name %q from network %q aliases", ctr.Name(), net) + return errors.Wrapf(err, "error adding container %s network alias %q to network %q", ctr.ID(), alias, net) } } } diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 3b88c9664..ba4c70c6b 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -285,6 +285,13 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { return err } + // Check networks + for _, net := range ctr.config.Networks { + if net == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names cannot be empty") + } + } + // Check network aliases for network, aliases := range ctr.config.NetworkAliases { inNet := false @@ -301,6 +308,10 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { allNetAliases, ok := s.networkAliases[network] if ok { for _, alias := range aliases { + // Check if alias is a name + if _, err := s.nameIndex.Get(alias); err == nil { + return define.ErrInvalidArg + } if _, ok := allNetAliases[alias]; ok { return define.ErrAliasExists } @@ -364,6 +375,34 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } + for _, network := range ctr.config.Networks { + allNetAliases, ok := s.networkAliases[network] + if !ok { + continue + } + otherCtrID, ok := allNetAliases[ctr.Name()] + if !ok { + continue + } + delete(allNetAliases, ctr.Name()) + + otherCtrAliases, ok := s.ctrNetworkAliases[otherCtrID] + if !ok { + continue + } + otherCtrNetAliases, ok := otherCtrAliases[network] + if !ok { + continue + } + newAliases := []string{} + for _, alias := range otherCtrNetAliases { + if alias != ctr.Name() { + newAliases = append(newAliases, alias) + } + } + otherCtrAliases[network] = newAliases + } + // Add network aliases for network, aliases := range ctr.config.NetworkAliases { allNetAliases, ok := s.networkAliases[network] @@ -696,6 +735,41 @@ func (s *InMemoryState) RemoveNetworkAliases(ctr *Container, network string) err return nil } +// GetAllAliasesForNetwork gets all the aliases for a single network. +func (s *InMemoryState) GetAllAliasesForNetwork(network string) (map[string]string, error) { + if network == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + allAliases, ok := s.networkAliases[network] + if !ok { + // Can't tell if the network exists. + // Assume it does. + return map[string]string{}, nil + } + + return allAliases, nil +} + +// RemoveAllAliasesForNetwork removes all the aliases for a given network. +func (s *InMemoryState) RemoveAllAliasesForNetwork(network string) error { + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if _, ok := s.networkAliases[network]; ok { + delete(s.networkAliases, network) + } + + for _, ctrAliases := range s.ctrNetworkAliases { + if _, ok := ctrAliases[network]; ok { + delete(ctrAliases, network) + } + } + + return nil +} + // GetContainerConfig returns a container config from the database by full ID func (s *InMemoryState) GetContainerConfig(id string) (*ContainerConfig, error) { ctr, err := s.LookupContainer(id) @@ -1340,6 +1414,13 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { return err } + // Check networks + for _, net := range ctr.config.Networks { + if net == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names cannot be empty") + } + } + // Check network aliases for network, aliases := range ctr.config.NetworkAliases { inNet := false @@ -1356,6 +1437,10 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { allNetAliases, ok := s.networkAliases[network] if ok { for _, alias := range aliases { + // Check if alias is a name + if _, err := s.nameIndex.Get(alias); err == nil { + return define.ErrInvalidArg + } if _, ok := allNetAliases[alias]; ok { return define.ErrAliasExists } @@ -1440,6 +1525,34 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } + for _, network := range ctr.config.Networks { + allNetAliases, ok := s.networkAliases[network] + if !ok { + continue + } + otherCtrID, ok := allNetAliases[ctr.Name()] + if !ok { + continue + } + delete(allNetAliases, ctr.Name()) + + otherCtrAliases, ok := s.ctrNetworkAliases[otherCtrID] + if !ok { + continue + } + otherCtrNetAliases, ok := otherCtrAliases[network] + if !ok { + continue + } + newAliases := []string{} + for _, alias := range otherCtrNetAliases { + if alias != ctr.Name() { + newAliases = append(newAliases, alias) + } + } + otherCtrAliases[network] = newAliases + } + // Add network aliases for network, aliases := range ctr.config.NetworkAliases { allNetAliases, ok := s.networkAliases[network] diff --git a/libpod/state.go b/libpod/state.go index 6b178ec58..183f773b5 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -106,6 +106,12 @@ type State interface { SetNetworkAliases(ctr *Container, network string, aliases []string) error // Remove network aliases for the given container in the given network. RemoveNetworkAliases(ctr *Container, network string) error + // GetAllAliasesForNetwork returns all the aliases for a given + // network. Returns a map of alias to container ID. + GetAllAliasesForNetwork(network string) (map[string]string, error) + // RemoveAllAliasesForNetwork removes all the aliases for a given + // network. + RemoveAllAliasesForNetwork(network string) error // Return a container config from the database by full ID GetContainerConfig(id string) (*ContainerConfig, error) diff --git a/libpod/state_test.go b/libpod/state_test.go index 373feb6e0..cf41270bf 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -1319,6 +1319,250 @@ func TestCannotUsePodAsDependency(t *testing.T) { }) } +func TestAddContainerEmptyNetworkNameErrors(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr, err := getTestCtr1(manager) + assert.NoError(t, err) + + testCtr.config.Networks = []string{""} + + err = state.AddContainer(testCtr) + assert.Error(t, err) + }) +} + +func TestAddContainerNetworkAliasesButNoMatchingNetwork(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr, err := getTestCtr1(manager) + assert.NoError(t, err) + + testCtr.config.Networks = []string{"test1"} + testCtr.config.NetworkAliases = make(map[string][]string) + testCtr.config.NetworkAliases["test2"] = []string{"alias1"} + + err = state.AddContainer(testCtr) + assert.Error(t, err) + }) +} + +func TestAddContainerNetworkAliasConflictWithName(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr1, err := getTestCtr1(manager) + assert.NoError(t, err) + + netName := "testnet" + testCtr1.config.Networks = []string{netName} + testCtr1.config.NetworkAliases = make(map[string][]string) + testCtr1.config.NetworkAliases[netName] = []string{"alias1"} + + testCtr2, err := getTestCtr2(manager) + assert.NoError(t, err) + + testCtr2.config.Networks = []string{netName} + testCtr2.config.NetworkAliases = make(map[string][]string) + testCtr2.config.NetworkAliases[netName] = []string{testCtr1.Name()} + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) + }) +} + +func TestAddContainerNetworkAliasConflictWithAlias(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr1, err := getTestCtr1(manager) + assert.NoError(t, err) + + netName := "testnet" + aliasName := "alias1" + testCtr1.config.Networks = []string{netName} + testCtr1.config.NetworkAliases = make(map[string][]string) + testCtr1.config.NetworkAliases[netName] = []string{aliasName} + + testCtr2, err := getTestCtr2(manager) + assert.NoError(t, err) + + testCtr2.config.Networks = []string{netName} + testCtr2.config.NetworkAliases = make(map[string][]string) + testCtr2.config.NetworkAliases[netName] = []string{aliasName} + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.Error(t, err) + }) +} + +func TestAddContainerNetworkAliasConflictWithAliasButDifferentNets(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr1, err := getTestCtr1(manager) + assert.NoError(t, err) + + netName := "testnet" + aliasName := "alias1" + testCtr1.config.Networks = []string{netName} + testCtr1.config.NetworkAliases = make(map[string][]string) + testCtr1.config.NetworkAliases[netName] = []string{aliasName} + + testCtr2, err := getTestCtr2(manager) + assert.NoError(t, err) + + netName2 := "testnet2" + testCtr2.config.Networks = []string{netName2} + testCtr2.config.NetworkAliases = make(map[string][]string) + testCtr2.config.NetworkAliases[netName2] = []string{aliasName} + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + }) +} + +func TestAddContainerNameConflictsWithAliasRemovesAlias(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr1, err := getTestCtr1(manager) + assert.NoError(t, err) + + testCtr2, err := getTestCtr2(manager) + assert.NoError(t, err) + + netName := "testnet" + aliasName := testCtr2.Name() + testCtr1.config.Networks = []string{netName} + testCtr1.config.NetworkAliases = make(map[string][]string) + testCtr1.config.NetworkAliases[netName] = []string{aliasName} + + testCtr2.config.Networks = []string{netName} + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + aliases, err := state.GetNetworkAliases(testCtr1, netName) + assert.NoError(t, err) + assert.Equal(t, 0, len(aliases)) + }) +} + +func TestNetworkAliasAddAndRemoveSingleContainer(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr, err := getTestCtr1(manager) + assert.NoError(t, err) + + netName := "testnet" + testCtr.config.Networks = []string{netName} + testCtr.config.NetworkAliases = make(map[string][]string) + testCtr.config.NetworkAliases[netName] = []string{"alias1"} + + startAliases, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 0, len(startAliases)) + + err = state.AddContainer(testCtr) + assert.NoError(t, err) + + oneAlias, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 1, len(oneAlias)) + assert.Equal(t, testCtr.ID(), oneAlias["alias1"]) + + allAliases, err := state.GetAllNetworkAliases(testCtr) + assert.NoError(t, err) + assert.Equal(t, 1, len(allAliases)) + netAliases, ok := allAliases[netName] + assert.True(t, ok) + assert.Equal(t, 1, len(netAliases)) + assert.Equal(t, "alias1", netAliases[0]) + + ctrNetAliases, err := state.GetNetworkAliases(testCtr, netName) + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrNetAliases)) + assert.Equal(t, "alias1", ctrNetAliases[0]) + + err = state.RemoveContainer(testCtr) + assert.NoError(t, err) + + noAliases, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 0, len(noAliases)) + }) +} + +func TestNetworkAliasAddAndRemoveTwoContainers(t *testing.T) { + runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { + testCtr1, err := getTestCtr1(manager) + assert.NoError(t, err) + + netName := "testnet" + testCtr1.config.Networks = []string{netName} + testCtr1.config.NetworkAliases = make(map[string][]string) + testCtr1.config.NetworkAliases[netName] = []string{"alias1"} + + testCtr2, err := getTestCtr2(manager) + assert.NoError(t, err) + + testCtr2.config.Networks = []string{netName} + testCtr2.config.NetworkAliases = make(map[string][]string) + testCtr2.config.NetworkAliases[netName] = []string{"alias2"} + + startAliases, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 0, len(startAliases)) + + err = state.AddContainer(testCtr1) + assert.NoError(t, err) + + oneAlias, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 1, len(oneAlias)) + assert.Equal(t, testCtr1.ID(), oneAlias["alias1"]) + + err = state.AddContainer(testCtr2) + assert.NoError(t, err) + + twoAliases, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 2, len(twoAliases)) + assert.Equal(t, testCtr1.ID(), twoAliases["alias1"]) + assert.Equal(t, testCtr2.ID(), twoAliases["alias2"]) + + allAliases, err := state.GetAllNetworkAliases(testCtr1) + assert.NoError(t, err) + assert.Equal(t, 1, len(allAliases)) + netAliases, ok := allAliases[netName] + assert.True(t, ok) + assert.Equal(t, 1, len(netAliases)) + assert.Equal(t, "alias1", netAliases[0]) + + ctrNetAliases, err := state.GetNetworkAliases(testCtr1, netName) + assert.NoError(t, err) + assert.Equal(t, 1, len(ctrNetAliases)) + assert.Equal(t, "alias1", ctrNetAliases[0]) + + err = state.RemoveContainer(testCtr2) + assert.NoError(t, err) + + oneAlias, err = state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 1, len(oneAlias)) + assert.Equal(t, testCtr1.ID(), oneAlias["alias1"]) + + err = state.RemoveContainer(testCtr1) + assert.NoError(t, err) + + noAliases, err := state.GetAllAliasesForNetwork(netName) + assert.NoError(t, err) + assert.Equal(t, 0, len(noAliases)) + }) +} + func TestCannotUseBadIDAsDependency(t *testing.T) { runForAllStates(t, func(t *testing.T, state State, manager lock.Manager) { testCtr, err := getTestCtr1(manager) -- cgit v1.2.3-54-g00ecf