diff options
Diffstat (limited to 'libpod')
-rw-r--r-- | libpod/boltdb_state.go | 295 | ||||
-rw-r--r-- | libpod/boltdb_state_internal.go | 148 | ||||
-rw-r--r-- | libpod/container.go | 28 | ||||
-rw-r--r-- | libpod/container_config.go | 3 | ||||
-rw-r--r-- | libpod/container_internal.go | 18 | ||||
-rw-r--r-- | libpod/define/container_inspect.go | 2 | ||||
-rw-r--r-- | libpod/define/errors.go | 9 | ||||
-rw-r--r-- | libpod/in_memory_state.go | 281 | ||||
-rw-r--r-- | libpod/network/config.go | 5 | ||||
-rw-r--r-- | libpod/network/create.go | 138 | ||||
-rw-r--r-- | libpod/network/create_test.go | 131 | ||||
-rw-r--r-- | libpod/network/files.go | 1 | ||||
-rw-r--r-- | libpod/network/netconflist.go | 21 | ||||
-rw-r--r-- | libpod/network/netconflist_test.go | 70 | ||||
-rw-r--r-- | libpod/networking_linux.go | 54 | ||||
-rw-r--r-- | libpod/rootless_cni_linux.go | 22 | ||||
-rw-r--r-- | libpod/runtime_ctr.go | 7 | ||||
-rw-r--r-- | libpod/state.go | 18 | ||||
-rw-r--r-- | libpod/state_test.go | 218 |
19 files changed, 608 insertions, 861 deletions
diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 0b9b353c7..be0adfe6a 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -94,7 +94,6 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { volBkt, allVolsBkt, execBkt, - aliasesBkt, runtimeConfigBkt, } @@ -972,6 +971,58 @@ func (s *BoltState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetNetworks returns the CNI networks this container is a part of. +func (s *BoltState) GetNetworks(ctr *Container) ([]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) + + networks := []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 { + return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not joined to any CNI networks", ctr.ID()) + } + + return ctrNetworkBkt.ForEach(func(network, v []byte) error { + networks = append(networks, string(network)) + return nil + }) + }) + if err != nil { + return nil, err + } + return networks, 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) { @@ -1032,7 +1083,8 @@ func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) if netAliasesBkt == nil { - return errors.Wrapf(define.ErrNoAliasesForNetwork, "container %s has no aliases for network %q", ctr.ID(), network) + // No aliases for this specific network. + return nil } return netAliasesBkt.ForEach(func(alias, v []byte) error { @@ -1120,10 +1172,9 @@ func (s *BoltState) GetAllNetworkAliases(ctr *Container) (map[string][]string, e 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 { +// NetworkConnect adds the given container to the given network. If aliases are +// specified, those will be added to the given network. +func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []string) error { if !s.valid { return define.ErrDBClosed } @@ -1154,90 +1205,60 @@ func (s *BoltState) SetNetworkAliases(ctr *Container, network string, aliases [] 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()) + ctrAliasesBkt, err := dbCtr.CreateBucketIfNotExists(aliasesBkt) + if err != nil { + return errors.Wrapf(err, "error creating aliases bucket for container %s", 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()) + ctrNetworksBkt, err = dbCtr.CreateBucket(networksBkt) + if err != nil { + return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID()) + } + ctrNetworks := ctr.config.Networks + if len(ctrNetworks) == 0 { + ctrNetworks = []string{ctr.runtime.netPlugin.GetDefaultNetworkName()} + } + // Copy in all the container's CNI networks + for _, net := range ctrNetworks { + if err := ctrNetworksBkt.Put([]byte(net), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network %s to DB", ctr.ID(), net) + } + } } 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 + if netConnected != nil { + return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to CNI network %q", 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. 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 - } + // Add the network + if err := ctrNetworksBkt.Put([]byte(network), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s to network %s in DB", ctr.ID(), network) } - 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 + ctrNetAliasesBkt, err := ctrAliasesBkt.CreateBucketIfNotExists([]byte(network)) + if err != nil { + return errors.Wrapf(err, "error adding container %s network aliases bucket for network %s", ctr.ID(), network) } - 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) + if err := ctrNetAliasesBkt.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network alias %s for network %s", ctr.ID(), alias, network) } } - return nil }) } -// RemoveNetworkAliases removes network aliases of the given container in the -// given network. -func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { +// NetworkDisconnect disconnects the container from the given network, also +// removing any aliases in the network. +func (s *BoltState) NetworkDisconnect(ctr *Container, network string) error { if !s.valid { return define.ErrDBClosed } @@ -1268,16 +1289,6 @@ func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { 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 @@ -1291,141 +1302,27 @@ func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { 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()) + return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not connected to any CNI networks, so cannot disconnect", 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 errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not connected to CNI network %q", ctr.ID(), network) } - 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 + if err := ctrNetworksBkt.Delete([]byte(network)); err != nil { + return errors.Wrapf(err, "error removing container %s from network %s", ctr.ID(), network) } - 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. + bktExists := ctrAliasesBkt.Bucket([]byte(network)) + if bktExists == nil { 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 + if err := ctrAliasesBkt.DeleteBucket([]byte(network)); err != nil { + return errors.Wrapf(err, "error removing container %s network aliases for network %s", ctr.ID(), network) } - 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 - }) + return nil }) } diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index a48de3092..c06fedd3e 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -354,14 +354,6 @@ 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 { @@ -584,11 +576,6 @@ 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 @@ -635,41 +622,20 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "name \"%s\" is in use", ctr.Name()) } + allNets := make(map[string]bool) + // 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") } + allNets[net] = true } - // 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) + // Each network we have aliases for, must exist in networks + for net := range ctr.config.NetworkAliases { + if !allNets[net] { + return errors.Wrapf(define.ErrNoSuchNetwork, "container %s has network aliases for network %q but is not part of that network", ctr.ID(), net) } } @@ -690,63 +656,6 @@ 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()) @@ -998,49 +907,6 @@ 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.go b/libpod/container.go index ea5a6e09c..580fa7b3d 100644 --- a/libpod/container.go +++ b/libpod/container.go @@ -1085,3 +1085,31 @@ func (c *Container) Timezone() string { func (c *Container) Umask() string { return c.config.Umask } + +// Networks gets all the networks this container is connected to. +// Please do NOT use ctr.config.Networks, as this can be changed from those +// values at runtime via network connect and disconnect. +// If the container is configured to use CNI and this function returns an empty +// array, the container will still be connected to the default network. +func (c *Container) Networks() ([]string, error) { + if !c.batched { + c.lock.Lock() + defer c.lock.Unlock() + + if err := c.syncContainer(); err != nil { + return nil, err + } + } + + return c.networks() +} + +// Unlocked accessor for networks +func (c *Container) networks() ([]string, error) { + networks, err := c.runtime.state.GetNetworks(c) + if err != nil && errors.Cause(err) == define.ErrNoSuchNetwork { + return c.config.Networks, nil + } + + return networks, err +} diff --git a/libpod/container_config.go b/libpod/container_config.go index d73fbb42f..cc3ad25ea 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -236,6 +236,9 @@ type ContainerNetworkConfig struct { // Will be appended to host's host file HostAdd []string `json:"hostsAdd,omitempty"` // Network names (CNI) to add container to. Empty to use default network. + // Please note that these can be altered at runtime. The actual list is + // stored in the DB and should be retrieved from there; this is only the + // set of networks the container was *created* with. Networks []string `json:"networks,omitempty"` // Network mode specified for the default network. NetMode namespaces.NetworkMode `json:"networkMode,omitempty"` diff --git a/libpod/container_internal.go b/libpod/container_internal.go index 0aeaae43d..108954bad 100644 --- a/libpod/container_internal.go +++ b/libpod/container_internal.go @@ -641,12 +641,17 @@ func (c *Container) removeIPv4Allocations() error { cniDefaultNetwork = c.runtime.netPlugin.GetDefaultNetworkName() } + networks, err := c.networks() + if err != nil { + return err + } + switch { - case len(c.config.Networks) > 0 && len(c.config.Networks) != len(c.state.NetworkStatus): - return errors.Wrapf(define.ErrInternal, "network mismatch: asked to join %d CNI networks but got %d CNI results", len(c.config.Networks), len(c.state.NetworkStatus)) - case len(c.config.Networks) == 0 && len(c.state.NetworkStatus) != 1: + case len(networks) > 0 && len(networks) != len(c.state.NetworkStatus): + return errors.Wrapf(define.ErrInternal, "network mismatch: asked to join %d CNI networks but got %d CNI results", len(networks), len(c.state.NetworkStatus)) + case len(networks) == 0 && len(c.state.NetworkStatus) != 1: return errors.Wrapf(define.ErrInternal, "network mismatch: did not specify CNI networks but joined more than one (%d)", len(c.state.NetworkStatus)) - case len(c.config.Networks) == 0 && cniDefaultNetwork == "": + case len(networks) == 0 && cniDefaultNetwork == "": return errors.Wrapf(define.ErrInternal, "could not retrieve name of CNI default network") } @@ -656,11 +661,11 @@ func (c *Container) removeIPv4Allocations() error { continue } candidate := "" - if len(c.config.Networks) > 0 { + if len(networks) > 0 { // CNI returns networks in order we passed them. // So our index into results should be our index // into networks. - candidate = filepath.Join(cniNetworksDir, c.config.Networks[index], ctrIP.Address.IP.String()) + candidate = filepath.Join(cniNetworksDir, networks[index], ctrIP.Address.IP.String()) } else { candidate = filepath.Join(cniNetworksDir, cniDefaultNetwork, ctrIP.Address.IP.String()) } @@ -1503,6 +1508,7 @@ func (c *Container) mountStorage() (_ string, deferredErr error) { // config. // Returns the volume that was mounted. func (c *Container) mountNamedVolume(v *ContainerNamedVolume, mountpoint string) (*Volume, error) { + logrus.Debugf("Going to mount named volume %s", v.Name) vol, err := c.runtime.state.Volume(v.Name) if err != nil { return nil, errors.Wrapf(err, "error retrieving named volume %s for container %s", v.Name, c.ID()) diff --git a/libpod/define/container_inspect.go b/libpod/define/container_inspect.go index 38b3a6686..775965477 100644 --- a/libpod/define/container_inspect.go +++ b/libpod/define/container_inspect.go @@ -575,6 +575,8 @@ type InspectAdditionalNetwork struct { // Links is presently unused and maintained exclusively for // compatibility. Links []string `json:"Links"` + // Aliases are any network aliases the container has in this network. + Aliases []string `json:"Aliases,omitempty"` } // InspectNetworkSettings holds information about the network settings of the diff --git a/libpod/define/errors.go b/libpod/define/errors.go index 27c5febf4..471827b7c 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -33,9 +33,6 @@ var ( // 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 @@ -49,9 +46,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") + // ErrNetworkExists indicates that a network with the given name already + // exists. + ErrNetworkExists = errors.New("network 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 ba4c70c6b..6c0cde531 100644 --- a/libpod/in_memory_state.go +++ b/libpod/in_memory_state.go @@ -31,8 +31,7 @@ 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 + ctrNetworks 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. @@ -69,7 +68,7 @@ func NewInMemoryState() (State, error) { state.podContainers = make(map[string]map[string]*Container) - state.networkAliases = make(map[string]map[string]string) + state.ctrNetworks = make(map[string][]string) state.ctrNetworkAliases = make(map[string]map[string][]string) state.nameIndex = registrar.NewRegistrar() @@ -293,7 +292,7 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { } // Check network aliases - for network, aliases := range ctr.config.NetworkAliases { + for network := range ctr.config.NetworkAliases { inNet := false for _, net := range ctr.config.Networks { if net == network { @@ -304,19 +303,6 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { 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 @@ -375,46 +361,17 @@ 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) - } + // Add networks + newNets := make([]string, 0, len(ctr.config.Networks)) + for _, net := range ctr.config.Networks { + if net == "" { + return define.ErrInvalidArg } - otherCtrAliases[network] = newAliases + newNets = append(newNets, net) } + s.ctrNetworks[ctr.ID()] = newNets // 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 @@ -480,18 +437,12 @@ func (s *InMemoryState) RemoveContainer(ctr *Container) error { } // 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) - } - } - } + if _, ok := s.ctrNetworkAliases[ctr.ID()]; ok { delete(s.ctrNetworkAliases, ctr.ID()) } + if _, ok := s.ctrNetworks[ctr.ID()]; ok { + delete(s.ctrNetworks, ctr.ID()) + } return nil } @@ -569,6 +520,26 @@ func (s *InMemoryState) AllContainers() ([]*Container, error) { return ctrs, nil } +// Get all networks this container is present in. +func (s *InMemoryState) GetNetworks(ctr *Container) ([]string, error) { + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + ctr, ok := s.containers[ctr.ID()] + if !ok { + ctr.valid = false + return nil, define.ErrNoSuchCtr + } + + ctrNetworks, ok := s.ctrNetworks[ctr.ID()] + if !ok { + return nil, define.ErrNoSuchNetwork + } + + return ctrNetworks, nil +} + // GetNetworkAliases returns network aliases for the given container in the // given network. func (s *InMemoryState) GetNetworkAliases(ctr *Container, network string) ([]string, error) { @@ -582,6 +553,7 @@ func (s *InMemoryState) GetNetworkAliases(ctr *Container, network string) ([]str ctr, ok := s.containers[ctr.ID()] if !ok { + ctr.valid = false return nil, define.ErrNoSuchCtr } @@ -615,6 +587,7 @@ func (s *InMemoryState) GetAllNetworkAliases(ctr *Container) (map[string][]strin ctr, ok := s.containers[ctr.ID()] if !ok { + ctr.valid = false return nil, define.ErrNoSuchCtr } @@ -626,9 +599,8 @@ func (s *InMemoryState) GetAllNetworkAliases(ctr *Container) (map[string][]strin 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 { +// NetworkConnect connects to the given network +func (s *InMemoryState) NetworkConnect(ctr *Container, network string, aliases []string) error { if !ctr.valid { return define.ErrCtrRemoved } @@ -639,55 +611,37 @@ func (s *InMemoryState) SetNetworkAliases(ctr *Container, network string, aliase ctr, ok := s.containers[ctr.ID()] if !ok { + ctr.valid = false return define.ErrNoSuchCtr } inNet := false - for _, net := range ctr.config.Networks { + ctrNetworks, ok := s.ctrNetworks[ctr.ID()] + if !ok { + return define.ErrNoSuchNetwork + } + for _, net := range ctrNetworks { if net == network { inNet = true } } - if !inNet { - return define.ErrInvalidArg + if inNet { + return define.ErrNoSuchNetwork } + s.ctrNetworks[ctr.ID()] = append(ctrNetworks, network) 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 { +// Disconnect from the given network and remove all aliases in it. +func (s *InMemoryState) NetworkDisconnect(ctr *Container, network string) error { if !ctr.valid { return define.ErrCtrRemoved } @@ -698,73 +652,36 @@ func (s *InMemoryState) RemoveNetworkAliases(ctr *Container, network string) err ctr, ok := s.containers[ctr.ID()] if !ok { + ctr.valid = false return define.ErrNoSuchCtr } + ctrNetworks, ok := s.ctrNetworks[ctr.ID()] + if !ok { + return define.ErrNoSuchNetwork + } inNet := false - for _, net := range ctr.config.Networks { + remainingNets := make([]string, 0, len(ctrNetworks)) + for _, net := range ctrNetworks { if net == network { inNet = true + break + } else { + remainingNets = append(remainingNets, net) } } if !inNet { - return define.ErrInvalidArg + return define.ErrNoSuchNetwork } + s.ctrNetworks[ctr.ID()] = remainingNets 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) - } + if _, ok := ctrAliases[network]; ok { + delete(ctrAliases, network) } return nil @@ -1422,7 +1339,7 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { } // Check network aliases - for network, aliases := range ctr.config.NetworkAliases { + for network := range ctr.config.NetworkAliases { inNet := false for _, net := range ctr.config.Networks { if net == network { @@ -1433,19 +1350,6 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { 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 @@ -1525,46 +1429,17 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } - for _, network := range ctr.config.Networks { - allNetAliases, ok := s.networkAliases[network] - if !ok { - continue - } - otherCtrID, ok := allNetAliases[ctr.Name()] - if !ok { - continue - } - delete(allNetAliases, ctr.Name()) - - otherCtrAliases, ok := s.ctrNetworkAliases[otherCtrID] - if !ok { - continue - } - otherCtrNetAliases, ok := otherCtrAliases[network] - if !ok { - continue - } - newAliases := []string{} - for _, alias := range otherCtrNetAliases { - if alias != ctr.Name() { - newAliases = append(newAliases, alias) - } + // Add networks + newNets := make([]string, 0, len(ctr.config.Networks)) + for _, net := range ctr.config.Networks { + if net == "" { + return define.ErrInvalidArg } - otherCtrAliases[network] = newAliases + newNets = append(newNets, net) } + s.ctrNetworks[ctr.ID()] = newNets // 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 @@ -1648,18 +1523,12 @@ func (s *InMemoryState) RemoveContainerFromPod(pod *Pod, ctr *Container) error { } // 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) - } - } - } + if _, ok := s.ctrNetworkAliases[ctr.ID()]; ok { delete(s.ctrNetworkAliases, ctr.ID()) } + if _, ok := s.ctrNetworks[ctr.ID()]; ok { + delete(s.ctrNetworks, ctr.ID()) + } return nil } diff --git a/libpod/network/config.go b/libpod/network/config.go index a08e684d8..ce8a4446c 100644 --- a/libpod/network/config.go +++ b/libpod/network/config.go @@ -131,8 +131,9 @@ func (f FirewallConfig) Bytes() ([]byte, error) { // DNSNameConfig describes the dns container name resolution plugin config type DNSNameConfig struct { - PluginType string `json:"type"` - DomainName string `json:"domainName"` + PluginType string `json:"type"` + DomainName string `json:"domainName"` + Capabilities map[string]bool `json:"capabilities"` } // Bytes outputs the configuration as []byte diff --git a/libpod/network/create.go b/libpod/network/create.go index bf11631bf..c11904ecf 100644 --- a/libpod/network/create.go +++ b/libpod/network/create.go @@ -15,6 +15,7 @@ import ( "github.com/pkg/errors" ) +// Create the CNI network func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtime) (*entities.NetworkCreateReport, error) { var fileName string if err := isSupportedDriver(options.Driver); err != nil { @@ -41,60 +42,120 @@ func Create(name string, options entities.NetworkCreateOptions, r *libpod.Runtim return &entities.NetworkCreateReport{Filename: fileName}, nil } +// validateBridgeOptions validate the bridge networking options +func validateBridgeOptions(options entities.NetworkCreateOptions) error { + subnet := &options.Subnet + ipRange := &options.Range + gateway := options.Gateway + // if IPv6 is set an IPv6 subnet MUST be specified + if options.IPv6 && ((subnet.IP == nil) || (subnet.IP != nil && !IsIPv6(subnet.IP))) { + return errors.Errorf("ipv6 option requires an IPv6 --subnet to be provided") + } + // range and gateway depend on subnet + if subnet.IP == nil && (ipRange.IP != nil || gateway != nil) { + return errors.Errorf("every ip-range or gateway must have a corresponding subnet") + } + + // if a range is given, we need to ensure it is "in" the network range. + if ipRange.IP != nil { + firstIP, err := FirstIPInSubnet(ipRange) + if err != nil { + return errors.Wrapf(err, "failed to get first IP address from ip-range") + } + lastIP, err := LastIPInSubnet(ipRange) + if err != nil { + return errors.Wrapf(err, "failed to get last IP address from ip-range") + } + if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) { + return errors.Errorf("the ip range %s does not fall within the subnet range %s", ipRange.String(), subnet.String()) + } + } + + // if network is provided and if gateway is provided, make sure it is "in" network + if gateway != nil && !subnet.Contains(gateway) { + return errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String()) + } + + return nil + +} + // createBridge creates a CNI network func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreateOptions) (string, error) { isGateway := true ipMasq := true - subnet := &options.Subnet - ipRange := options.Range runtimeConfig, err := r.GetConfig() if err != nil { return "", err } - // if range is provided, make sure it is "in" network - if subnet.IP != nil { - // if network is provided, does it conflict with existing CNI or live networks - err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet) - } else { - // if no network is provided, figure out network - subnet, err = GetFreeNetwork(runtimeConfig) - } + + // validate options + err = validateBridgeOptions(options) if err != nil { return "", err } + + // For compatibility with the docker implementation: + // if IPv6 is enabled (it really means dual-stack) then an IPv6 subnet has to be provided, and one free network is allocated for IPv4 + // if IPv6 is not specified the subnet may be specified and can be either IPv4 or IPv6 (podman, unlike docker, allows IPv6 only networks) + // If not subnet is specified an IPv4 subnet will be allocated + subnet := &options.Subnet + ipRange := &options.Range gateway := options.Gateway - if gateway == nil { - // if no gateway is provided, provide it as first ip of network - gateway = CalcGatewayIP(subnet) - } - // if network is provided and if gateway is provided, make sure it is "in" network - if options.Subnet.IP != nil && options.Gateway != nil { - if !subnet.Contains(gateway) { - return "", errors.Errorf("gateway %s is not in valid for subnet %s", gateway.String(), subnet.String()) + var ipamRanges [][]IPAMLocalHostRangeConf + var routes []IPAMRoute + if subnet.IP != nil { + // if network is provided, does it conflict with existing CNI or live networks + err = ValidateUserNetworkIsAvailable(runtimeConfig, subnet) + if err != nil { + return "", err } - } - if options.Internal { - isGateway = false - ipMasq = false - } - - // if a range is given, we need to ensure it is "in" the network range. - if options.Range.IP != nil { - if options.Subnet.IP == nil { - return "", errors.New("you must define a subnet range to define an ip-range") + // obtain CNI subnet default route + defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP)) + if err != nil { + return "", err } - firstIP, err := FirstIPInSubnet(&options.Range) + routes = append(routes, defaultRoute) + // obtain CNI range + ipamRange, err := NewIPAMLocalHostRange(subnet, ipRange, gateway) if err != nil { return "", err } - lastIP, err := LastIPInSubnet(&options.Range) + ipamRanges = append(ipamRanges, ipamRange) + } + // if no network is provided or IPv6 flag used, figure out the IPv4 network + if options.IPv6 || len(routes) == 0 { + subnetV4, err := GetFreeNetwork(runtimeConfig) if err != nil { return "", err } - if !subnet.Contains(firstIP) || !subnet.Contains(lastIP) { - return "", errors.Errorf("the ip range %s does not fall within the subnet range %s", options.Range.String(), subnet.String()) + // obtain IPv4 default route + defaultRoute, err := NewIPAMDefaultRoute(false) + if err != nil { + return "", err } + routes = append(routes, defaultRoute) + // the CNI bridge plugin does not need to set + // the range or gateway options explicitly + ipamRange, err := NewIPAMLocalHostRange(subnetV4, nil, nil) + if err != nil { + return "", err + } + ipamRanges = append(ipamRanges, ipamRange) + } + + // create CNI config + ipamConfig, err := NewIPAMHostLocalConf(routes, ipamRanges) + if err != nil { + return "", err } + + if options.Internal { + isGateway = false + ipMasq = false + } + + // obtain host bridge name bridgeDeviceName, err := GetFreeDeviceName(runtimeConfig) if err != nil { return "", err @@ -113,20 +174,9 @@ func createBridge(r *libpod.Runtime, name string, options entities.NetworkCreate name = bridgeDeviceName } + // create CNI plugin configuration ncList := NewNcList(name, version.Current()) var plugins []CNIPlugins - var routes []IPAMRoute - - defaultRoute, err := NewIPAMDefaultRoute(IsIPv6(subnet.IP)) - if err != nil { - return "", err - } - routes = append(routes, defaultRoute) - ipamConfig, err := NewIPAMHostLocalConf(subnet, routes, ipRange, gateway) - if err != nil { - return "", err - } - // TODO need to iron out the role of isDefaultGW and IPMasq bridge := NewHostLocalBridge(bridgeDeviceName, isGateway, false, ipMasq, ipamConfig) plugins = append(plugins, bridge) diff --git a/libpod/network/create_test.go b/libpod/network/create_test.go new file mode 100644 index 000000000..16188e497 --- /dev/null +++ b/libpod/network/create_test.go @@ -0,0 +1,131 @@ +package network + +import ( + "net" + "testing" + + "github.com/containers/podman/v2/pkg/domain/entities" +) + +func Test_validateBridgeOptions(t *testing.T) { + + tests := []struct { + name string + subnet net.IPNet + ipRange net.IPNet + gateway net.IP + isIPv6 bool + wantErr bool + }{ + { + name: "IPv4 subnet only", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + }, + { + name: "IPv4 subnet and range", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + }, + { + name: "IPv4 subnet and gateway", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + gateway: net.ParseIP("192.168.0.10"), + }, + { + name: "IPv4 subnet, range and gateway", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + gateway: net.ParseIP("192.168.0.10"), + }, + { + name: "IPv6 subnet only", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + }, + { + name: "IPv6 subnet and range", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, + isIPv6: true, + }, + { + name: "IPv6 subnet and gateway", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + gateway: net.ParseIP("2001:DB8::2"), + isIPv6: true, + }, + { + name: "IPv6 subnet, range and gateway", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, + gateway: net.ParseIP("2001:DB8::2"), + isIPv6: true, + }, + { + name: "IPv6 subnet, range and gateway without IPv6 option (PODMAN SUPPORTS IT UNLIKE DOCKEr)", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + ipRange: net.IPNet{IP: net.ParseIP("2001:DB8:0:0:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, + gateway: net.ParseIP("2001:DB8::2"), + isIPv6: false, + }, + { + name: "range provided but not subnet", + ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + wantErr: true, + }, + { + name: "gateway provided but not subnet", + gateway: net.ParseIP("192.168.0.10"), + wantErr: true, + }, + { + name: "IPv4 subnet but IPv6 required", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + gateway: net.ParseIP("192.168.0.10"), + isIPv6: true, + wantErr: true, + }, + { + name: "IPv6 required but IPv4 options used", + subnet: net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + ipRange: net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + gateway: net.ParseIP("192.168.0.10"), + isIPv6: true, + wantErr: true, + }, + { + name: "IPv6 required but not subnet provided", + isIPv6: true, + wantErr: true, + }, + { + name: "range out of the subnet", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + ipRange: net.IPNet{IP: net.ParseIP("2001:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, + gateway: net.ParseIP("2001:DB8::2"), + isIPv6: true, + wantErr: true, + }, + { + name: "gateway out of the subnet", + subnet: net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + gateway: net.ParseIP("2001::2"), + isIPv6: true, + wantErr: true, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + options := entities.NetworkCreateOptions{ + Subnet: tt.subnet, + Range: tt.ipRange, + Gateway: tt.gateway, + IPv6: tt.isIPv6, + } + if err := validateBridgeOptions(options); (err != nil) != tt.wantErr { + t.Errorf("validateBridgeOptions() error = %v, wantErr %v", err, tt.wantErr) + } + }) + } +} diff --git a/libpod/network/files.go b/libpod/network/files.go index a2090491f..846e5c62d 100644 --- a/libpod/network/files.go +++ b/libpod/network/files.go @@ -14,6 +14,7 @@ import ( "github.com/pkg/errors" ) +// GetCNIConfDir get CNI configuration directory func GetCNIConfDir(configArg *config.Config) string { if len(configArg.Network.NetworkConfigDir) < 1 { dc, err := config.DefaultConfig() diff --git a/libpod/network/netconflist.go b/libpod/network/netconflist.go index 8187fdb39..111f1715c 100644 --- a/libpod/network/netconflist.go +++ b/libpod/network/netconflist.go @@ -42,8 +42,7 @@ func NewHostLocalBridge(name string, isGateWay, isDefaultGW, ipMasq bool, ipamCo } // NewIPAMHostLocalConf creates a new IPAMHostLocal configfuration -func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPNet, gw net.IP) (IPAMHostLocalConf, error) { - var ipamRanges [][]IPAMLocalHostRangeConf +func NewIPAMHostLocalConf(routes []IPAMRoute, ipamRanges [][]IPAMLocalHostRangeConf) (IPAMHostLocalConf, error) { ipamConf := IPAMHostLocalConf{ PluginType: "host-local", Routes: routes, @@ -51,22 +50,19 @@ func NewIPAMHostLocalConf(subnet *net.IPNet, routes []IPAMRoute, ipRange net.IPN //ResolveConf: "", //DataDir: "" } - IPAMRange, err := newIPAMLocalHostRange(subnet, &ipRange, &gw) - if err != nil { - return ipamConf, err - } - ipamRanges = append(ipamRanges, IPAMRange) + ipamConf.Ranges = ipamRanges return ipamConf, nil } -func newIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw *net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer +// NewIPAMLocalHostRange create a new IPAM range +func NewIPAMLocalHostRange(subnet *net.IPNet, ipRange *net.IPNet, gw net.IP) ([]IPAMLocalHostRangeConf, error) { //nolint:interfacer var ranges []IPAMLocalHostRangeConf hostRange := IPAMLocalHostRangeConf{ Subnet: subnet.String(), } // an user provided a range, we add it here - if ipRange.IP != nil { + if ipRange != nil && ipRange.IP != nil { first, err := FirstIPInSubnet(ipRange) if err != nil { return nil, err @@ -126,9 +122,12 @@ func NewFirewallPlugin() FirewallConfig { // NewDNSNamePlugin creates the dnsname config with a given // domainname func NewDNSNamePlugin(domainName string) DNSNameConfig { + caps := make(map[string]bool, 1) + caps["aliases"] = true return DNSNameConfig{ - PluginType: "dnsname", - DomainName: domainName, + PluginType: "dnsname", + DomainName: domainName, + Capabilities: caps, } } diff --git a/libpod/network/netconflist_test.go b/libpod/network/netconflist_test.go index 5893bf985..6bf1a9777 100644 --- a/libpod/network/netconflist_test.go +++ b/libpod/network/netconflist_test.go @@ -1,6 +1,7 @@ package network import ( + "net" "reflect" "testing" ) @@ -36,3 +37,72 @@ func TestNewIPAMDefaultRoute(t *testing.T) { }) } } + +func TestNewIPAMLocalHostRange(t *testing.T) { + tests := []struct { + name string + subnet *net.IPNet + ipRange *net.IPNet + gw net.IP + want []IPAMLocalHostRangeConf + }{ + { + name: "IPv4 subnet", + subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + want: []IPAMLocalHostRangeConf{ + { + Subnet: "192.168.0.0/24", + }, + }, + }, + { + name: "IPv4 subnet, range and gateway", + subnet: &net.IPNet{IP: net.IPv4(192, 168, 0, 0), Mask: net.IPv4Mask(255, 255, 255, 0)}, + ipRange: &net.IPNet{IP: net.IPv4(192, 168, 0, 128), Mask: net.IPv4Mask(255, 255, 255, 128)}, + gw: net.ParseIP("192.168.0.10"), + want: []IPAMLocalHostRangeConf{ + { + Subnet: "192.168.0.0/24", + RangeStart: "192.168.0.129", + RangeEnd: "192.168.0.255", + Gateway: "192.168.0.10", + }, + }, + }, + { + name: "IPv6 subnet", + subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + want: []IPAMLocalHostRangeConf{ + { + Subnet: "2001:db8::/48", + }, + }, + }, + { + name: "IPv6 subnet, range and gateway", + subnet: &net.IPNet{IP: net.ParseIP("2001:DB8::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff::"))}, + ipRange: &net.IPNet{IP: net.ParseIP("2001:DB8:1:1::"), Mask: net.IPMask(net.ParseIP("ffff:ffff:ffff:ffff::"))}, + gw: net.ParseIP("2001:DB8::2"), + want: []IPAMLocalHostRangeConf{ + { + Subnet: "2001:db8::/48", + RangeStart: "2001:db8:1:1::1", + RangeEnd: "2001:db8:1:1:ffff:ffff:ffff:ffff", + Gateway: "2001:db8::2", + }, + }, + }, + } + for _, tt := range tests { + tt := tt + t.Run(tt.name, func(t *testing.T) { + got, err := NewIPAMLocalHostRange(tt.subnet, tt.ipRange, tt.gw) + if err != nil { + t.Errorf("no error expected: %v", err) + } + if !reflect.DeepEqual(got, tt.want) { + t.Errorf("NewIPAMLocalHostRange() = %v, want %v", got, tt.want) + } + }) + } +} diff --git a/libpod/networking_linux.go b/libpod/networking_linux.go index 28dca8dd8..1a7740085 100644 --- a/libpod/networking_linux.go +++ b/libpod/networking_linux.go @@ -104,7 +104,18 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re podName := getCNIPodName(ctr) - podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) + networks, err := ctr.networks() + if err != nil { + return nil, err + } + podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC) + aliases, err := ctr.runtime.state.GetAllNetworkAliases(ctr) + if err != nil { + return nil, err + } + if len(aliases) > 0 { + podNetwork.Aliases = aliases + } results, err := r.netPlugin.SetUpPod(podNetwork) if err != nil { @@ -202,7 +213,11 @@ func (r *Runtime) setupRootlessNetNS(ctr *Container) error { if ctr.config.NetMode.IsSlirp4netns() { return r.setupSlirp4netns(ctr) } - if len(ctr.config.Networks) > 0 { + networks, err := ctr.networks() + if err != nil { + return err + } + if len(networks) > 0 { // set up port forwarder for CNI-in-slirp4netns netnsPath := ctr.state.NetNS.Path() // TODO: support slirp4netns port forwarder as well @@ -718,6 +733,11 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) + networks, err := ctr.networks() + if err != nil { + return err + } + // rootless containers do not use the CNI plugin directly if !rootless.IsRootless() && !ctr.config.NetMode.IsSlirp4netns() { var requestedIP net.IP @@ -738,7 +758,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { requestedMAC = ctr.config.StaticMAC } - podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), ctr.config.Networks, ctr.config.PortMappings, requestedIP, requestedMAC) + podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC) if err := r.netPlugin.TearDownPod(podNetwork); err != nil { return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID()) @@ -746,7 +766,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error { } // CNI-in-slirp4netns - if rootless.IsRootless() && len(ctr.config.Networks) != 0 { + if rootless.IsRootless() && len(networks) != 0 { if err := DeallocRootlessCNI(context.Background(), ctr); err != nil { return errors.Wrapf(err, "error tearing down CNI-in-slirp4netns for container %s", ctr.ID()) } @@ -832,13 +852,18 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e settings := new(define.InspectNetworkSettings) settings.Ports = makeInspectPortBindings(c.config.PortMappings) + networks, err := c.networks() + if err != nil { + return nil, err + } + // We can't do more if the network is down. if c.state.NetNS == nil { // We still want to make dummy configurations for each CNI net // the container joined. - if len(c.config.Networks) > 0 { - settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(c.config.Networks)) - for _, net := range c.config.Networks { + if len(networks) > 0 { + settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) + for _, net := range networks { cniNet := new(define.InspectAdditionalNetwork) cniNet.NetworkID = net settings.Networks[net] = cniNet @@ -857,16 +882,16 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e } // If we have CNI networks - handle that here - if len(c.config.Networks) > 0 { - if len(c.config.Networks) != len(c.state.NetworkStatus) { - return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(c.config.Networks), len(c.state.NetworkStatus)) + if len(networks) > 0 { + if len(networks) != len(c.state.NetworkStatus) { + return nil, errors.Wrapf(define.ErrInternal, "network inspection mismatch: asked to join %d CNI networks but have information on %d networks", len(networks), len(c.state.NetworkStatus)) } settings.Networks = make(map[string]*define.InspectAdditionalNetwork) // CNI results should be in the same order as the list of // networks we pass into CNI. - for index, name := range c.config.Networks { + for index, name := range networks { cniResult := c.state.NetworkStatus[index] addedNet := new(define.InspectAdditionalNetwork) addedNet.NetworkID = name @@ -875,6 +900,13 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e if err != nil { return nil, err } + + aliases, err := c.runtime.state.GetNetworkAliases(c, name) + if err != nil { + return nil, err + } + addedNet.Aliases = aliases + addedNet.InspectBasicNetworkConfig = basicConfig settings.Networks[name] = addedNet diff --git a/libpod/rootless_cni_linux.go b/libpod/rootless_cni_linux.go index 3d4ff6e86..1d6158cc2 100644 --- a/libpod/rootless_cni_linux.go +++ b/libpod/rootless_cni_linux.go @@ -40,8 +40,12 @@ const ( // // AllocRootlessCNI does not lock c. c should be already locked. func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes.Result, error) { - if len(c.config.Networks) == 0 { - return nil, nil, errors.New("allocRootlessCNI shall not be called when len(c.config.Networks) == 0") + networks, err := c.networks() + if err != nil { + return nil, nil, err + } + if len(networks) == 0 { + return nil, nil, errors.New("rootless CNI networking requires that the container has joined at least one CNI network") } l, err := getRootlessCNIInfraLock(c.runtime) if err != nil { @@ -54,8 +58,8 @@ func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes. return nil, nil, err } k8sPodName := getCNIPodName(c) // passed to CNI as K8S_POD_NAME - cniResults := make([]*cnitypes.Result, len(c.config.Networks)) - for i, nw := range c.config.Networks { + cniResults := make([]*cnitypes.Result, len(networks)) + for i, nw := range networks { cniRes, err := rootlessCNIInfraCallAlloc(infra, c.ID(), nw, k8sPodName) if err != nil { return nil, nil, err @@ -77,8 +81,12 @@ func AllocRootlessCNI(ctx context.Context, c *Container) (ns.NetNS, []*cnitypes. // // DeallocRootlessCNI does not lock c. c should be already locked. func DeallocRootlessCNI(ctx context.Context, c *Container) error { - if len(c.config.Networks) == 0 { - return errors.New("deallocRootlessCNI shall not be called when len(c.config.Networks) == 0") + networks, err := c.networks() + if err != nil { + return err + } + if len(networks) == 0 { + return errors.New("rootless CNI networking requires that the container has joined at least one CNI network") } l, err := getRootlessCNIInfraLock(c.runtime) if err != nil { @@ -91,7 +99,7 @@ func DeallocRootlessCNI(ctx context.Context, c *Container) error { return nil } var errs *multierror.Error - for _, nw := range c.config.Networks { + for _, nw := range networks { err := rootlessCNIInfraCallDelloc(infra, c.ID(), nw) if err != nil { errs = multierror.Append(errs, err) diff --git a/libpod/runtime_ctr.go b/libpod/runtime_ctr.go index c84268889..14b537ca2 100644 --- a/libpod/runtime_ctr.go +++ b/libpod/runtime_ctr.go @@ -345,8 +345,15 @@ func (r *Runtime) setupContainer(ctx context.Context, ctr *Container) (_ *Contai // Lock all named volumes we are adding ourself to, to ensure we can't // use a volume being removed. + volsLocked := make(map[string]bool) for _, namedVol := range ctrNamedVolumes { toLock := namedVol + // Ensure that we don't double-lock a named volume that is used + // more than once. + if volsLocked[namedVol.Name()] { + continue + } + volsLocked[namedVol.Name()] = true toLock.lock.Lock() defer toLock.lock.Unlock() } diff --git a/libpod/state.go b/libpod/state.go index 183f773b5..074d21740 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -98,20 +98,18 @@ type State interface { // returned. AllContainers() ([]*Container, error) + // Get networks the container is currently connected to. + GetNetworks(ctr *Container) ([]string, 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 + // Add the container to the given network, adding the given aliases + // (if present). + NetworkConnect(ctr *Container, network string, aliases []string) error + // Remove the container from the given network, removing all aliases for + // the container in that network in the process. + NetworkDisconnect(ctr *Container, 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 cf41270bf..da28f3d3f 100644 --- a/libpod/state_test.go +++ b/libpod/state_test.go @@ -1345,224 +1345,6 @@ func TestAddContainerNetworkAliasesButNoMatchingNetwork(t *testing.T) { }) } -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) |