From 6af7e544636ae66ce237489ce6948123e1b3249d Mon Sep 17 00:00:00 2001 From: Matthew Heon Date: Mon, 26 Oct 2020 17:17:45 -0400 Subject: Add network aliases for containers to DB This adds the database backend for network aliases. Aliases are additional names for a container that are used with the CNI dnsname plugin - the container will be accessible by these names in addition to its name. Aliases are allowed to change over time as the container connects to and disconnects from networks. Aliases are implemented as another bucket in the database to register all aliases, plus two buckets for each container (one to hold connected CNI networks, a second to hold its aliases). The aliases are only unique per-network, to the global and per-container aliases buckets have a sub-bucket for each CNI network that has aliases, and the aliases are stored within that sub-bucket. Aliases are formatted as alias (key) to container ID (value) in both cases. Three DB functions are defined for aliases: retrieving current aliases for a given network, setting aliases for a given network, and removing all aliases for a given network. Signed-off-by: Matthew Heon --- libpod/boltdb_state.go | 270 +++++++++++++++++++++++++++++++++++++++- libpod/boltdb_state_internal.go | 174 +++++++++++++++++++++++--- libpod/container_config.go | 9 ++ libpod/container_validate.go | 11 ++ libpod/define/errors.go | 10 ++ libpod/in_memory_state.go | 261 ++++++++++++++++++++++++++++++++++++++ libpod/options.go | 14 +++ libpod/state.go | 7 ++ 8 files changed, 734 insertions(+), 22 deletions(-) (limited to 'libpod') diff --git a/libpod/boltdb_state.go b/libpod/boltdb_state.go index 9dd5ca465..e0db92082 100644 --- a/libpod/boltdb_state.go +++ b/libpod/boltdb_state.go @@ -50,10 +50,12 @@ type BoltState struct { // containers in the pod. // - allPodsBkt: Map of ID to name containing only pods. Used for pod lookup // operations. -// - execBkt: Map of exec session ID to exec session - contains a sub-bucket for -// each exec session in the DB. -// - execRegistryBkt: Map of exec session ID to nothing. Contains one entry for -// each exec session. Used for iterating through all exec sessions. +// - execBkt: Map of exec session ID to container ID - used for resolving +// exec session IDs to the containers that hold the exec session. +// - aliasesBkt - Contains a bucket for each CNI network, which contain a map of +// network alias (an extra name for containers in DNS) to the ID of the +// container holding the alias. Aliases must be unique per-network, and cannot +// conflict with names registered in nameRegistryBkt. // - runtimeConfigBkt: Contains configuration of the libpod instance that // initially created the database. This must match for any further instances // that access the database, to ensure that state mismatches with @@ -92,6 +94,7 @@ func NewBoltState(path string, runtime *Runtime) (State, error) { volBkt, allVolsBkt, execBkt, + aliasesBkt, runtimeConfigBkt, } @@ -969,6 +972,265 @@ func (s *BoltState) AllContainers() ([]*Container, error) { return ctrs, nil } +// GetNetworkAliases retrieves the network aliases for the given container in +// the given CNI network. +func (s *BoltState) GetNetworkAliases(ctr *Container, network string) ([]string, error) { + if !s.valid { + return nil, define.ErrDBClosed + } + + if !ctr.valid { + return nil, define.ErrCtrRemoved + } + + if network == "" { + return nil, errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return nil, errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return nil, err + } + defer s.deferredCloseDBCon(db) + + aliases := []string{} + + err = db.View(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliasesForNetwork, "container %s has no aliases for network %q", ctr.ID(), network) + } + + return netAliasesBkt.ForEach(func(alias, v []byte) error { + aliases = append(aliases, string(alias)) + return nil + }) + }) + if err != nil { + return nil, err + } + + return aliases, nil +} + +// SetNetworkAliases sets network aliases for the given container in the given +// network. All existing aliases for that network (if any exist) will be removed, +// to be replaced by the new aliases given. +func (s *BoltState) SetNetworkAliases(ctr *Container, network string, aliases []string) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allAliasesBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + netAllAliasesBucket, err := allAliasesBucket.CreateBucketIfNotExists([]byte(network)) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for network %s", network) + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to any CNI networks, so cannot add aliases", ctr.ID()) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + if netConnected == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to CNI network %q, so cannot add aliases for this network", ctr.ID(), network) + } + + namesBucket, err := getNamesBucket(tx) + if err != nil { + return err + } + + // Check if the container already has network aliases for this network. + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt != nil { + // We have aliases. Have to remove them. + forEachErr := netAliasesBkt.ForEach(func(alias, v []byte) error { + // Relies on errors.Wrapf(nil, ...) returning + // nil. + return errors.Wrapf(netAllAliasesBucket.Delete(alias), "error removing alias %q from network %q when changing aliases for container %s", string(alias), network, ctr.ID()) + }) + if forEachErr != nil { + return forEachErr + } + } + + if netAliasesBkt == nil { + newBkt, err := ctrAliasesBkt.CreateBucket([]byte(network)) + if err != nil { + return errors.Wrapf(err, "could not create bucket for network aliases for network %q", network) + } + netAliasesBkt = newBkt + } + + for _, alias := range aliases { + // Check if safe to use + aliasExists := netAllAliasesBucket.Get([]byte(alias)) + if aliasExists != nil { + return errors.Wrapf(define.ErrAliasExists, "network alias %q already exists in network %q (used by container %s)", alias, network, string(aliasExists)) + } + nameExists := namesBucket.Get([]byte(alias)) + if nameExists != nil { + return errors.Wrapf(define.ErrCtrExists, "a container or pod already uses the name %q, cannot add network alias for container %s", alias, ctr.ID()) + } + + // Add alias + if err := netAliasesBkt.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network %q alias %q to DB", ctr.ID(), network, alias) + } + if err := netAllAliasesBucket.Put([]byte(alias), ctrID); err != nil { + return errors.Wrapf(err, "error adding container %s network %q alias %q to all aliases in DB", ctr.ID(), network, alias) + } + } + + return nil + }) +} + +// RemoveNetworkAliases removes network aliases of the given container in the +// given network. +func (s *BoltState) RemoveNetworkAliases(ctr *Container, network string) error { + if !s.valid { + return define.ErrDBClosed + } + + if !ctr.valid { + return define.ErrCtrRemoved + } + + if network == "" { + return errors.Wrapf(define.ErrInvalidArg, "network names must not be empty") + } + + if s.namespace != "" && s.namespace != ctr.config.Namespace { + return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.namespace) + } + + ctrID := []byte(ctr.ID()) + + db, err := s.getDBCon() + if err != nil { + return err + } + defer s.deferredCloseDBCon(db) + + return db.Update(func(tx *bolt.Tx) error { + ctrBucket, err := getCtrBucket(tx) + if err != nil { + return err + } + + allAliasesBucket, err := getAliasesBucket(tx) + if err != nil { + return err + } + + netAllAliasesBucket, err := allAliasesBucket.CreateBucketIfNotExists([]byte(network)) + if err != nil { + return errors.Wrapf(err, "error creating network aliases bucket for network %s", network) + } + + dbCtr := ctrBucket.Bucket(ctrID) + if dbCtr == nil { + ctr.valid = false + return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) + } + + ctrAliasesBkt := dbCtr.Bucket(aliasesBkt) + if ctrAliasesBkt == nil { + return errors.Wrapf(define.ErrNoAliases, "container %s has no network aliases", ctr.ID()) + } + + ctrNetworksBkt := dbCtr.Bucket(networksBkt) + if ctrNetworksBkt == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to any CNI networks, so cannot add aliases", ctr.ID()) + } + netConnected := ctrNetworksBkt.Get([]byte(network)) + if netConnected == nil { + return errors.Wrapf(define.ErrInvalidArg, "container %s is not connected to CNI network %q, so cannot add aliases for this network", ctr.ID(), network) + } + + // Check if the container already has network aliases for this network. + netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network)) + if netAliasesBkt != nil { + // We have aliases. Remove them. + forEachErr := netAliasesBkt.ForEach(func(alias, v []byte) error { + // Relies on errors.Wrapf(nil, ...) returning + // nil. + return errors.Wrapf(netAllAliasesBucket.Delete(alias), "error removing alias %q from network %q when changing aliases for container %s", string(alias), network, ctr.ID()) + }) + if forEachErr != nil { + return forEachErr + } + } + + return nil + }) +} + // GetContainerConfig returns a container config from the database by full ID func (s *BoltState) GetContainerConfig(id string) (*ContainerConfig, error) { if len(id) == 0 { diff --git a/libpod/boltdb_state_internal.go b/libpod/boltdb_state_internal.go index e195ca314..1ecf99661 100644 --- a/libpod/boltdb_state_internal.go +++ b/libpod/boltdb_state_internal.go @@ -25,6 +25,7 @@ const ( volName = "vol" allVolsName = "allVolumes" execName = "exec" + aliasesName = "aliases" runtimeConfigName = "runtime-config" configName = "config" @@ -35,6 +36,7 @@ const ( containersName = "containers" podIDName = "pod-id" namespaceName = "namespace" + networksName = "networks" staticDirName = "static-dir" tmpDirName = "tmp-dir" @@ -46,26 +48,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) @@ -349,6 +353,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 { @@ -571,6 +583,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 @@ -617,6 +634,37 @@ func (s *BoltState) addContainer(ctr *Container, pod *Pod) error { return errors.Wrapf(err, "name \"%s\" is in use", ctr.Name()) } + // 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 { @@ -634,6 +682,24 @@ 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()) } + 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 aliasa %q to network %q", ctr.ID(), alias, net) + } + } + // If the container's name is present in the aliases - remove it. + if namePresent := netAliasesBkt.Get(ctrName); namePresent != nil { + if err := netAliasesBkt.Delete(ctrName); err != nil { + return errors.Wrapf(err, "error cleaning container name %q from network %q aliases", ctr.Name(), net) + } + } + } + newCtrBkt, err := ctrBucket.CreateBucket(ctrID) if err != nil { return errors.Wrapf(err, "error adding container %s bucket to DB", ctr.ID()) @@ -660,6 +726,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()) } @@ -856,6 +951,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 e264da4da..fb1ba373b 100644 --- a/libpod/container_config.go +++ b/libpod/container_config.go @@ -239,6 +239,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 b78168cd1..ee3c8583c 100644 --- a/libpod/container_validate.go +++ b/libpod/container_validate.go @@ -107,5 +107,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 627928ef7..36a919cf6 100644 --- a/libpod/define/errors.go +++ b/libpod/define/errors.go @@ -27,6 +27,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") @@ -39,6 +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") // 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..66c2a5cbd 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,29 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { return err } + // 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 { + 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 +364,20 @@ func (s *InMemoryState) AddContainer(ctr *Container) error { s.addCtrToVolDependsMap(ctr.ID(), vol.Name) } + // 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 +440,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 +530,153 @@ 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 +} + +// 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 +} + // 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 +1321,29 @@ func (s *InMemoryState) AddContainerToPod(pod *Pod, ctr *Container) error { return err } + // 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 { + if _, ok := allNetAliases[alias]; ok { + return define.ErrAliasExists + } + } + } + } + // Retrieve pod containers list podCtrs, ok := s.podContainers[pod.ID()] if !ok { @@ -1188,6 +1416,25 @@ 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) + } + + // 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 +1515,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 1ffb78da9..da2fc983a 100644 --- a/libpod/options.go +++ b/libpod/options.go @@ -1487,6 +1487,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..fca0548c4 100644 --- a/libpod/state.go +++ b/libpod/state.go @@ -98,6 +98,13 @@ 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) + // 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 + // Return a container config from the database by full ID GetContainerConfig(id string) (*ContainerConfig, error) -- cgit v1.2.3-54-g00ecf