diff options
author | OpenShift Merge Robot <openshift-merge-robot@users.noreply.github.com> | 2020-11-04 16:53:49 +0100 |
---|---|---|
committer | GitHub <noreply@github.com> | 2020-11-04 16:53:49 +0100 |
commit | 4fb7378ee4ca2af5d521f7f75b691d4fe00b089b (patch) | |
tree | 6570b9710ca377ed764e3074dc369ef55bc159d8 /libpod | |
parent | 303191bf522db9af32cabd98a5309e3079da0eb0 (diff) | |
parent | 844d540d042fe7477e62f8cf28e524fcd756f6c8 (diff) | |
download | podman-4fb7378ee4ca2af5d521f7f75b691d4fe00b089b.tar.gz podman-4fb7378ee4ca2af5d521f7f75b691d4fe00b089b.tar.bz2 podman-4fb7378ee4ca2af5d521f7f75b691d4fe00b089b.zip |
Merge pull request #8156 from mheon/add_net_aliases_db
Add network aliases for containers to DB
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 468 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 220 | ||||
-rw-r--r-- | libpod/container_config.go | 9 | ||||
-rw-r--r-- | libpod/container_validate.go | 11 | ||||
-rw-r--r-- | libpod/define/errors.go | 10 | ||||
-rw-r--r-- | libpod/in_memory_state.go | 393 | ||||
-rw-r--r-- | libpod/options.go | 14 | ||||
-rw-r--r-- | libpod/state.go | 15 | ||||
-rw-r--r-- | libpod/state_test.go | 244 |
9 files changed, 1362 insertions, 22 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 9dd5ca465..0b9b353c7 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,463 @@ 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()) + } + + 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", ctr.ID(), network) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + // No aliases + return nil + } + + 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 +} + +// 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{} + + _ = netAliasesBkt.ForEach(func(alias, v []byte) error { + netAliases = append(netAliases, string(alias)) + return nil + }) + + aliases[string(network)] = netAliases + 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 + }) +} + +// 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 2f485318c..a48de3092 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -26,6 +26,7 @@ const ( volName = "vol" allVolsName = "allVolumes" execName = "exec" + aliasesName = "aliases" runtimeConfigName = "runtime-config" configName = "config" @@ -36,6 +37,7 @@ const ( containersName = "containers" podIDName = "pod-id" namespaceName = "namespace" + networksName = "networks" staticDirName = "static-dir" tmpDirName = "tmp-dir" @@ -47,26 +49,28 @@ const ( ) var ( - idRegistryBkt = []byte(idRegistryName) - nameRegistryBkt = []byte(nameRegistryName) - nsRegistryBkt = []byte(nsRegistryName) - ctrBkt = []byte(ctrName) - allCtrsBkt = []byte(allCtrsName) - podBkt = []byte(podName) - allPodsBkt = []byte(allPodsName) - volBkt = []byte(volName) - allVolsBkt = []byte(allVolsName) - execBkt = []byte(execName) - runtimeConfigBkt = []byte(runtimeConfigName) - - configKey = []byte(configName) - stateKey = []byte(stateName) + idRegistryBkt = []byte(idRegistryName) + nameRegistryBkt = []byte(nameRegistryName) + nsRegistryBkt = []byte(nsRegistryName) + ctrBkt = []byte(ctrName) + allCtrsBkt = []byte(allCtrsName) + podBkt = []byte(podName) + allPodsBkt = []byte(allPodsName) + volBkt = []byte(volName) + allVolsBkt = []byte(allVolsName) + execBkt = []byte(execName) + aliasesBkt = []byte(aliasesName) + runtimeConfigBkt = []byte(runtimeConfigName) dependenciesBkt = []byte(dependenciesName) volDependenciesBkt = []byte(volCtrDependencies) - netNSKey = []byte(netNSName) - containersBkt = []byte(containersName) - podIDKey = []byte(podIDName) - namespaceKey = []byte(namespaceName) + networksBkt = []byte(networksName) + + configKey = []byte(configName) + stateKey = []byte(stateName) + netNSKey = []byte(netNSName) + containersBkt = []byte(containersName) + podIDKey = []byte(podIDName) + namespaceKey = []byte(namespaceName) staticDirKey = []byte(staticDirName) tmpDirKey = []byte(tmpDirName) @@ -350,6 +354,14 @@ func getExecBucket(tx *bolt.Tx) (*bolt.Bucket, error) { return bkt, nil } +func getAliasesBucket(tx *bolt.Tx) (*bolt.Bucket, error) { + bkt := tx.Bucket(aliasesBkt) + if bkt == nil { + return nil, errors.Wrapf(define.ErrDBBadConfig, "aliases bucket not found in DB") + } + return bkt, nil +} + func getRuntimeConfigBucket(tx *bolt.Tx) (*bolt.Bucket, error) { bkt := tx.Bucket(runtimeConfigBkt) if bkt == nil { @@ -572,6 +584,11 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return err } + allAliasesBkt, err := getAliasesBucket(tx) + if err != nil { + return err + } + // If a pod was given, check if it exists var podDB *bolt.Bucket var podCtrs *bolt.Bucket @@ -618,6 +635,44 @@ 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. + for _, alias := range aliases { + aliasExist := namesBucket.Get([]byte(alias)) + if aliasExist != nil { + return errors.Wrapf(define.ErrCtrExists, "alias %q conflicts with existing container/pod name", alias) + } + } + + netAliasesBkt := allAliasesBkt.Bucket([]byte(net)) + if netAliasesBkt != nil { + for _, alias := range aliases { + aliasExist := netAliasesBkt.Get([]byte(alias)) + if aliasExist != nil { + return errors.Wrapf(define.ErrAliasExists, "network alias %q already exists for network %q", net, alias) + } + } + } + hasNet := false + for _, testNet := range ctr.config.Networks { + if testNet == net { + hasNet = true + break + } + } + if !hasNet { + return errors.Wrapf(define.ErrInvalidArg, "container %s has network aliases for network %q but is not part of that network", ctr.ID(), net) + } + } + // No overlapping containers // Add the new container to the DB if err := idsBucket.Put(ctrID, ctrName); err != nil { @@ -635,6 +690,63 @@ 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 { + return errors.Wrapf(err, "error creating network aliases bucket for network %q", net) + } + for _, alias := range aliases { + if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network alias %q to network %q", ctr.ID(), alias, net) + } + } + } + newCtrBkt, err := ctrBucket.CreateBucket(ctrID) if err != nil { return errors.Wrapf(err, "error adding container %s bucket to DB", ctr.ID()) @@ -661,6 +773,35 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "error adding container %s netns path to DB", ctr.ID()) } } + if ctr.config.Networks != nil { + ctrNetworksBkt, err := newCtrBkt.CreateBucket(networksBkt) + if err != nil { + return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) + } + for _, network := range ctr.config.Networks { + if err := ctrNetworksBkt.Put([]byte(network), ctrID); err != nil { + return errors.Wrapf(err, "error adding network %q to networks bucket for container %s", network, ctr.ID()) + } + } + } + if ctr.config.NetworkAliases != nil { + ctrAliasesBkt, err := newCtrBkt.CreateBucket(aliasesBkt) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for container %s", ctr.ID()) + } + for net, aliases := range ctr.config.NetworkAliases { + netAliasesBkt, err := ctrAliasesBkt.CreateBucket([]byte(net)) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for network %q in container %s", net, ctr.ID()) + } + for _, alias := range aliases { + if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error creating network alias %q in network %q for container %s", alias, net, ctr.ID()) + } + } + } + } + if _, err := newCtrBkt.CreateBucket(dependenciesBkt); err != nil { return errors.Wrapf(err, "error creating dependencies bucket for container %s", ctr.ID()) } @@ -857,6 +998,49 @@ func (s *BoltState) removeContainer(ctr *Container, pod *Pod, tx *bolt.Tx) error return errors.Wrapf(define.ErrCtrExists, "container %s is a dependency of the following containers: %s", ctr.ID(), strings.Join(deps, ", ")) } + // Does the container have any network aliases? + ctrNetAliasesBkt := ctrExists.Bucket(aliasesBkt) + if ctrNetAliasesBkt != nil { + allAliasesBkt, err := getAliasesBucket(tx) + if err != nil { + return err + } + ctrNetworksBkt := ctrExists.Bucket(networksBkt) + // Internal state mismatch if this doesn't exist - we'll just + // assume there are no aliases in that case. + if ctrNetworksBkt != nil { + // This is a little gross. Iterate through all networks + // the container is joined to. Check if we have aliases + // for them. If we do have such aliases, remove all of + // then from the global aliases table for that network. + err = ctrNetworksBkt.ForEach(func(network, v []byte) error { + netAliasesBkt := ctrNetAliasesBkt.Bucket(network) + if netAliasesBkt == nil { + return nil + } + netAllAliasesBkt := allAliasesBkt.Bucket(network) + if netAllAliasesBkt == nil { + // Again the state is inconsistent here, + // but the best we can do is try and + // recover by ignoring it. + return nil + } + return netAliasesBkt.ForEach(func(alias, v []byte) error { + // We don't want to hard-fail on a + // missing alias, so continue if we hit + // errors. + if err := netAllAliasesBkt.Delete(alias); err != nil { + logrus.Errorf("Error removing alias %q from network %q when removing container %s", string(alias), string(network), ctr.ID()) + } + return nil + }) + }) + if err != nil { + return err + } + } + } + if err := ctrBucket.DeleteBucket(ctrID); err != nil { return errors.Wrapf(define.ErrInternal, "error deleting container %s from DB", ctr.ID()) } diff --git a/libpod/container_config.go b/libpod/container_config.go index 0006613bc..d73fbb42f 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -241,6 +241,15 @@ type ContainerNetworkConfig struct { NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` // NetworkOptions are additional options for each network NetworkOptions map[string][]string `json:"network_options,omitempty"` + // NetworkAliases are aliases that will be added to each network. + // These are additional names that this container can be accessed as via + // DNS when the CNI dnsname plugin is in use. + // Please note that these can be altered at runtime. As such, the actual + // list is stored in the database and should be retrieved from there; + // this is only the set of aliases the container was *created with*. + // Formatted as map of network name to aliases. All network names must + // be present in the Networks list above. + NetworkAliases map[string][]string `json:"network_alises,omitempty"` } // ContainerImageConfig is an embedded sub-config providing image configuration diff --git a/libpod/container_validate.go b/libpod/container_validate.go index 68cc095b7..fa809436e 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -115,5 +115,16 @@ func (c *Container) validate() error { destinations[vol.Dest] = true } + // Check that networks and network aliases match up. + ctrNets := make(map[string]bool) + for _, net := range c.config.Networks { + ctrNets[net] = true + } + for net := range c.config.NetworkAliases { + if _, ok := ctrNets[net]; !ok { + return errors.Wrapf(define.ErrNoSuchNetwork, "container tried to set network aliases for network %s but is not connected to the network", net) + } + } + return nil } diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 1b21cd1ce..27c5febf4 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -30,6 +30,13 @@ var ( // not exist. ErrNoSuchExecSession = errors.New("no such exec session") + // ErrNoAliases indicates that the container does not have any network + // aliases. + ErrNoAliases = errors.New("no aliases for container") + // ErrNoAliasesForNetwork indicates that the container has no aliases + // for a specific network. + ErrNoAliasesForNetwork = errors.New("no aliases for network") + // ErrCtrExists indicates a container with the same name or ID already // exists ErrCtrExists = errors.New("container already exists") @@ -42,6 +49,9 @@ var ( // ErrExecSessionExists indicates an exec session with the same ID // already exists. ErrExecSessionExists = errors.New("exec session already exists") + // ErrAliasExists indicates that a network alias with the same name + // already exists in the network. + ErrAliasExists = errors.New("alias already exists") // ErrCtrStateInvalid indicates a container is in an improper state for // the requested operation diff --git a/libpod/in_memory_state.go b/libpod/in_memory_state.go index 0de25a6ef..ba4c70c6b 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -31,6 +31,10 @@ type InMemoryState struct { ctrExecSessions map[string][]string // Maps pod ID to a map of container ID to container struct. podContainers map[string]map[string]*Container + // Maps network name to alias to container ID + networkAliases map[string]map[string]string + // Maps container ID to network name to list of aliases. + ctrNetworkAliases map[string]map[string][]string // Global name registry - ensures name uniqueness and performs lookups. nameIndex *registrar.Registrar // Global ID registry - ensures ID uniqueness and performs lookups. @@ -65,6 +69,9 @@ func NewInMemoryState() (State, error) { state.podContainers = make(map[string]map[string]*Container) + state.networkAliases = make(map[string]map[string]string) + state.ctrNetworkAliases = make(map[string]map[string][]string) + state.nameIndex = registrar.NewRegistrar() state.idIndex = truncindex.NewTruncIndex([]string{}) @@ -278,6 +285,40 @@ 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 + for _, net := range ctr.config.Networks { + if net == network { + inNet = true + break + } + } + if !inNet { + return errors.Wrapf(define.ErrInvalidArg, "container %s has network aliases for network %q but is not joined to network", ctr.ID(), network) + } + + 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 + } + } + } + } + // There are potential race conditions with this // But in-memory state is intended purely for testing and not production // use, so this should be fine. @@ -334,6 +375,48 @@ 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] + if !ok { + allNetAliases = make(map[string]string) + s.networkAliases[network] = allNetAliases + } + + for _, alias := range aliases { + allNetAliases[alias] = ctr.ID() + } + } + s.ctrNetworkAliases[ctr.ID()] = ctr.config.NetworkAliases + return nil } @@ -396,6 +479,20 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { s.removeCtrFromVolDependsMap(ctr.ID(), vol.Name) } + // Remove our network aliases + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if ok { + for network, aliases := range ctrAliases { + netAliases, ok := s.networkAliases[network] + if ok { + for _, alias := range aliases { + delete(netAliases, alias) + } + } + } + delete(s.ctrNetworkAliases, ctr.ID()) + } + return nil } @@ -472,6 +569,207 @@ func (s *InMemoryState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetNetworkAliases returns network aliases for the given container in the +// given network. +func (s *InMemoryState) GetNetworkAliases(ctr *Container, network string) ([]string, error) { + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + if network == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + ctr, ok := s.containers[ctr.ID()] + if !ok { + return nil, define.ErrNoSuchCtr + } + + inNet := false + for _, net := range ctr.config.Networks { + if net == network { + inNet = true + } + } + if !inNet { + return nil, define.ErrInvalidArg + } + + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if !ok { + return []string{}, nil + } + netAliases, ok := ctrAliases[network] + if !ok { + return []string{}, nil + } + + 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 { + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + ctr, ok := s.containers[ctr.ID()] + if !ok { + return define.ErrNoSuchCtr + } + + inNet := false + for _, net := range ctr.config.Networks { + if net == network { + inNet = true + } + } + if !inNet { + return define.ErrInvalidArg + } + + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if !ok { + ctrAliases = make(map[string][]string) + s.ctrNetworkAliases[ctr.ID()] = ctrAliases + } + netAliases, ok := ctrAliases[network] + if !ok { + netAliases = []string{} + ctrAliases[network] = netAliases + } + + allAliases, ok := s.networkAliases[network] + if !ok { + allAliases = make(map[string]string) + s.networkAliases[network] = allAliases + } + + for _, alias := range netAliases { + delete(allAliases, alias) + } + + for _, newAlias := range aliases { + if _, ok := allAliases[newAlias]; ok { + return define.ErrAliasExists + } + allAliases[newAlias] = ctr.ID() + } + + ctrAliases[network] = aliases + + return nil +} + +// RemoveNetworkAliases removes network aliases from the given container in the +// given network. +func (s *InMemoryState) RemoveNetworkAliases(ctr *Container, network string) error { + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + ctr, ok := s.containers[ctr.ID()] + if !ok { + return define.ErrNoSuchCtr + } + + inNet := false + for _, net := range ctr.config.Networks { + if net == network { + inNet = true + } + } + if !inNet { + return define.ErrInvalidArg + } + + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if !ok { + ctrAliases = make(map[string][]string) + s.ctrNetworkAliases[ctr.ID()] = ctrAliases + } + netAliases, ok := ctrAliases[network] + if !ok { + netAliases = []string{} + ctrAliases[network] = netAliases + } + + allAliases, ok := s.networkAliases[network] + if !ok { + allAliases = make(map[string]string) + s.networkAliases[network] = allAliases + } + + for _, alias := range netAliases { + delete(allAliases, alias) + } + + 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) @@ -1116,6 +1414,40 @@ 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 + for _, net := range ctr.config.Networks { + if net == network { + inNet = true + break + } + } + if !inNet { + return errors.Wrapf(define.ErrInvalidArg, "container %s has network aliases for network %q but is not joined to network", ctr.ID(), network) + } + + 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 + } + } + } + } + // Retrieve pod containers list podCtrs, ok := s.podContainers[pod.ID()] if !ok { @@ -1188,6 +1520,53 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { s.addCtrToDependsMap(ctr.ID(), depCtr) } + // Add container to volume dependencies + for _, vol := range ctr.config.NamedVolumes { + 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] + if !ok { + allNetAliases = make(map[string]string) + s.networkAliases[network] = allNetAliases + } + + for _, alias := range aliases { + allNetAliases[alias] = ctr.ID() + } + } + s.ctrNetworkAliases[ctr.ID()] = ctr.config.NetworkAliases + return nil } @@ -1268,6 +1647,20 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { s.removeCtrFromDependsMap(ctr.ID(), depCtr) } + // Remove our network aliases + ctrAliases, ok := s.ctrNetworkAliases[ctr.ID()] + if ok { + for network, aliases := range ctrAliases { + netAliases, ok := s.networkAliases[network] + if ok { + for _, alias := range aliases { + delete(netAliases, alias) + } + } + } + delete(s.ctrNetworkAliases, ctr.ID()) + } + return nil } diff --git a/libpod/options.go b/libpod/options.go index 060887b7e..0f55f34a3 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1506,6 +1506,20 @@ func WithCreateWorkingDir() CtrCreateOption { } } +// WithNetworkAliases sets network aliases for the container. +// Accepts a map of network name to aliases. +func WithNetworkAliases(aliases map[string][]string) CtrCreateOption { + return func(ctr *Container) error { + if ctr.valid { + return define.ErrCtrFinalized + } + + ctr.config.NetworkAliases = aliases + + return nil + } +} + // Volume Creation Options // WithVolumeName sets the name of the volume. diff --git a/libpod/state.go b/libpod/state.go index 44632b02f..183f773b5 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -98,6 +98,21 @@ type State interface { // returned. AllContainers() ([]*Container, error) + // 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. + 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) |