diff options
| -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)  | 
