network db rewrite: migrate existing settings

The new network db structure stores everything in the networks bucket.
Previously some network settings were not written the the network bucket
and only stored in the container config.
Instead of the old format which used the container ID as value in the
networks buckets we now use the PerNetworkoptions struct there.

To migrate existing users we use the state.GetNetworks() function. If it
fails to read the new format it will automatically migrate the old
config format to the new one. This is allows a flawless migration path.

Signed-off-by: Paul Holzinger <pholzing@redhat.com>
This commit is contained in:
Paul Holzinger
2021-12-07 19:04:13 +01:00
parent 4a060caeab
commit 5490be67b3
5 changed files with 226 additions and 266 deletions

View File

@ -2,11 +2,14 @@ package libpod
import ( import (
"bytes" "bytes"
"fmt"
"net"
"os" "os"
"strings" "strings"
"sync" "sync"
"github.com/containers/podman/v3/libpod/define" "github.com/containers/podman/v3/libpod/define"
"github.com/containers/podman/v3/libpod/network/types"
jsoniter "github.com/json-iterator/go" jsoniter "github.com/json-iterator/go"
"github.com/pkg/errors" "github.com/pkg/errors"
"github.com/sirupsen/logrus" "github.com/sirupsen/logrus"
@ -971,7 +974,7 @@ func (s *BoltState) AllContainers() ([]*Container, error) {
} }
// GetNetworks returns the CNI networks this container is a part of. // GetNetworks returns the CNI networks this container is a part of.
func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) { func (s *BoltState) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error) {
if !s.valid { if !s.valid {
return nil, define.ErrDBClosed return nil, define.ErrDBClosed
} }
@ -984,6 +987,11 @@ func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) {
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) 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)
} }
// if the network mode is not bridge return no networks
if !ctr.config.NetMode.IsBridge() {
return nil, nil
}
ctrID := []byte(ctr.ID()) ctrID := []byte(ctr.ID())
db, err := s.getDBCon() db, err := s.getDBCon()
@ -992,7 +1000,9 @@ func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) {
} }
defer s.deferredCloseDBCon(db) defer s.deferredCloseDBCon(db)
networks := []string{} networks := make(map[string]types.PerNetworkOptions)
var convertDB bool
err = db.View(func(tx *bolt.Tx) error { err = db.View(func(tx *bolt.Tx) error {
ctrBucket, err := getCtrBucket(tx) ctrBucket, err := getCtrBucket(tx)
@ -1008,17 +1018,131 @@ func (s *BoltState) GetNetworks(ctr *Container) ([]string, error) {
ctrNetworkBkt := dbCtr.Bucket(networksBkt) ctrNetworkBkt := dbCtr.Bucket(networksBkt)
if ctrNetworkBkt == nil { if ctrNetworkBkt == nil {
return errors.Wrapf(define.ErrNoSuchNetwork, "container %s is not joined to any CNI networks", ctr.ID()) // convert if needed
convertDB = true
return nil
} }
return ctrNetworkBkt.ForEach(func(network, v []byte) error { return ctrNetworkBkt.ForEach(func(network, v []byte) error {
networks = append(networks, string(network)) opts := types.PerNetworkOptions{}
if err := json.Unmarshal(v, &opts); err != nil {
// special case for backwards compat
// earlier version used the container id as value so we set a
// special error to indicate the we have to migrate the db
if !bytes.Equal(v, ctrID) {
return err
}
convertDB = true
}
networks[string(network)] = opts
return nil return nil
}) })
}) })
if err != nil { if err != nil {
return nil, err return nil, err
} }
if convertDB {
err = db.Update(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())
}
var networkList []string
ctrNetworkBkt := dbCtr.Bucket(networksBkt)
if ctrNetworkBkt == nil {
ctrNetworkBkt, err = dbCtr.CreateBucket(networksBkt)
if err != nil {
return errors.Wrapf(err, "error creating networks bucket for container %s", ctr.ID())
}
// the container has no networks in the db lookup config and write to the db
networkList = ctr.config.Networks
// if there are no networks we have to add the default
if len(networkList) == 0 {
networkList = []string{ctr.runtime.config.Network.DefaultNetwork}
}
} else {
err = ctrNetworkBkt.ForEach(func(network, v []byte) error {
networkList = append(networkList, string(network))
return nil
})
if err != nil {
return err
}
}
// the container has no networks in the db lookup config and write to the db
for i, network := range networkList {
var intName string
if ctr.state.NetInterfaceDescriptions != nil {
eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network)
}
intName = eth
} else {
intName = fmt.Sprintf("eth%d", i)
}
getAliases := func(network string) []string {
var aliases []string
ctrAliasesBkt := dbCtr.Bucket(aliasesBkt)
if ctrAliasesBkt == nil {
return nil
}
netAliasesBkt := ctrAliasesBkt.Bucket([]byte(network))
if netAliasesBkt == nil {
// No aliases for this specific network.
return nil
}
// lets ignore the error here there is nothing we can do
_ = netAliasesBkt.ForEach(func(alias, v []byte) error {
aliases = append(aliases, string(alias))
return nil
})
// also add the short container id as alias
return aliases
}
netOpts := &types.PerNetworkOptions{
InterfaceName: intName,
// we have to add the short id as alias for docker compat
Aliases: append(getAliases(network), ctr.config.ID[:12]),
}
// only set the static ip/mac on the first network
if i == 0 {
if ctr.config.StaticIP != nil {
netOpts.StaticIPs = []net.IP{ctr.config.StaticIP}
}
netOpts.StaticMAC = ctr.config.StaticMAC
}
optsBytes, err := json.Marshal(netOpts)
if err != nil {
return err
}
// insert into network map because we need to return this
networks[network] = *netOpts
err = ctrNetworkBkt.Put([]byte(network), optsBytes)
if err != nil {
return err
}
}
return nil
})
if err != nil {
return nil, err
}
}
return networks, nil return networks, nil
} }
@ -1173,7 +1297,7 @@ func (s *BoltState) GetAllNetworkAliases(ctr *Container) (map[string][]string, e
// NetworkConnect adds the given container to the given network. If aliases are // NetworkConnect adds the given container to the given network. If aliases are
// specified, those will be added to the given network. // specified, those will be added to the given network.
func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []string) error { func (s *BoltState) NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error {
if !s.valid { if !s.valid {
return define.ErrDBClosed return define.ErrDBClosed
} }
@ -1190,6 +1314,11 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []str
return errors.Wrapf(define.ErrNSMismatch, "container %s is in namespace %q, does not match our namespace %q", ctr.ID(), ctr.config.Namespace, s.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)
} }
optBytes, err := json.Marshal(opts)
if err != nil {
return errors.Wrapf(err, "error marshalling network options JSON for container %s", ctr.ID())
}
ctrID := []byte(ctr.ID()) ctrID := []byte(ctr.ID())
db, err := s.getDBCon() db, err := s.getDBCon()
@ -1210,47 +1339,20 @@ func (s *BoltState) NetworkConnect(ctr *Container, network string, aliases []str
return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", ctr.ID()) return errors.Wrapf(define.ErrNoSuchCtr, "container %s does not exist in database", 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) ctrNetworksBkt := dbCtr.Bucket(networksBkt)
if ctrNetworksBkt == nil { if ctrNetworksBkt == nil {
ctrNetworksBkt, err = dbCtr.CreateBucket(networksBkt) return errors.Wrapf(define.ErrNoSuchNetwork, "container %s does not have a network bucket", ctr.ID())
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.config.Network.DefaultNetwork}
}
// 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)) netConnected := ctrNetworksBkt.Get([]byte(network))
if netConnected != nil { if netConnected != nil {
return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to CNI network %q", ctr.ID(), network) return errors.Wrapf(define.ErrNetworkExists, "container %s is already connected to network %q", ctr.ID(), network)
} }
// Add the network // Add the network
if err := ctrNetworksBkt.Put([]byte(network), ctrID); err != nil { if err := ctrNetworksBkt.Put([]byte(network), optBytes); err != nil {
return errors.Wrapf(err, "error adding container %s to network %s in DB", ctr.ID(), network) return errors.Wrapf(err, "error adding container %s to network %s in DB", ctr.ID(), network)
} }
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 {
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 return nil
}) })
} }

View File

@ -1176,7 +1176,18 @@ func (c *Container) Networks() ([]string, bool, error) {
} }
} }
return c.networks() networks, err := c.networks()
if err != nil {
return nil, false, err
}
names := make([]string, 0, len(networks))
for name := range networks {
names = append(names, name)
}
return names, false, nil
} }
// NetworkMode gets the configured network mode for the container. // NetworkMode gets the configured network mode for the container.
@ -1220,36 +1231,8 @@ func (c *Container) NetworkMode() string {
} }
// Unlocked accessor for networks // Unlocked accessor for networks
func (c *Container) networks() ([]string, bool, error) { func (c *Container) networks() (map[string]types.PerNetworkOptions, error) {
networks, err := c.runtime.state.GetNetworks(c) return c.runtime.state.GetNetworks(c)
if err != nil && errors.Cause(err) == define.ErrNoSuchNetwork {
if len(c.config.Networks) == 0 && c.config.NetMode.IsBridge() {
return []string{c.runtime.config.Network.DefaultNetwork}, true, nil
}
return c.config.Networks, false, nil
}
return networks, false, err
}
// networksByNameIndex provides us with a map of container networks where key
// is network name and value is the index position
func (c *Container) networksByNameIndex() (map[string]int, error) {
networks, _, err := c.networks()
if err != nil {
return nil, err
}
networkNamesByIndex := make(map[string]int, len(networks))
for index, name := range networks {
networkNamesByIndex[name] = index
}
return networkNamesByIndex, nil
}
// add puts the new given CNI network name into the tracking map
// and assigns it a new integer based on the map length
func (d ContainerNetworkDescriptions) add(networkName string) {
d[networkName] = len(d)
} }
// getInterfaceByName returns a formatted interface name for a given // getInterfaceByName returns a formatted interface name for a given
@ -1270,9 +1253,7 @@ func (c *Container) getNetworkStatus() map[string]types.StatusBlock {
return c.state.NetworkStatus return c.state.NetworkStatus
} }
if c.state.NetworkStatusOld != nil { if c.state.NetworkStatusOld != nil {
// Note: NetworkStatusOld does not contain the network names so we get them extra networks, err := c.networks()
// Generally the order should be the same
networks, _, err := c.networks()
if err != nil { if err != nil {
return nil return nil
} }
@ -1280,12 +1261,16 @@ func (c *Container) getNetworkStatus() map[string]types.StatusBlock {
return nil return nil
} }
result := make(map[string]types.StatusBlock, len(c.state.NetworkStatusOld)) result := make(map[string]types.StatusBlock, len(c.state.NetworkStatusOld))
for i := range c.state.NetworkStatusOld { i := 0
// Note: NetworkStatusOld does not contain the network names so we get them extra
// We cannot guarantee the same order but after a state refresh it should work
for netName := range networks {
status, err := cni.CNIResultToStatus(c.state.NetworkStatusOld[i]) status, err := cni.CNIResultToStatus(c.state.NetworkStatusOld[i])
if err != nil { if err != nil {
return nil return nil
} }
result[networks[i]] = status result[netName] = status
i++
} }
c.state.NetworkStatus = result c.state.NetworkStatus = result
_ = c.save() _ = c.save()

View File

@ -1293,23 +1293,6 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
return nil, 0, err return nil, 0, err
} }
// If a container is restored multiple times from an exported checkpoint with
// the help of '--import --name', the restore will fail if during 'podman run'
// a static container IP was set with '--ip'. The user can tell the restore
// process to ignore the static IP with '--ignore-static-ip'
if options.IgnoreStaticIP {
c.config.StaticIP = nil
}
// If a container is restored multiple times from an exported checkpoint with
// the help of '--import --name', the restore will fail if during 'podman run'
// a static container MAC address was set with '--mac-address'. The user
// can tell the restore process to ignore the static MAC with
// '--ignore-static-mac'
if options.IgnoreStaticMAC {
c.config.StaticMAC = nil
}
// Read network configuration from checkpoint // Read network configuration from checkpoint
var netStatus map[string]types.StatusBlock var netStatus map[string]types.StatusBlock
_, err := metadata.ReadJSONFile(&netStatus, c.bundlePath(), metadata.NetworkStatusFile) _, err := metadata.ReadJSONFile(&netStatus, c.bundlePath(), metadata.NetworkStatusFile)
@ -1325,19 +1308,19 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
if err == nil && options.Name == "" && (!options.IgnoreStaticIP || !options.IgnoreStaticMAC) { if err == nil && options.Name == "" && (!options.IgnoreStaticIP || !options.IgnoreStaticMAC) {
// The file with the network.status does exist. Let's restore the // The file with the network.status does exist. Let's restore the
// container with the same networks settings as during checkpointing. // container with the same networks settings as during checkpointing.
aliases, err := c.GetAllNetworkAliases() networkOpts, err := c.networks()
if err != nil { if err != nil {
return nil, 0, err return nil, 0, err
} }
netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) netOpts := make(map[string]types.PerNetworkOptions, len(netStatus))
for network, status := range netStatus { for network, perNetOpts := range networkOpts {
perNetOpts := types.PerNetworkOptions{} // unset mac and ips before we start adding the ones from the status
for name, netInt := range status.Interfaces { perNetOpts.StaticMAC = nil
perNetOpts = types.PerNetworkOptions{ perNetOpts.StaticIPs = nil
InterfaceName: name, for name, netInt := range netStatus[network].Interfaces {
Aliases: aliases[network], perNetOpts.InterfaceName = name
} if !options.IgnoreStaticIP {
if !options.IgnoreStaticMAC {
perNetOpts.StaticMAC = netInt.MacAddress perNetOpts.StaticMAC = netInt.MacAddress
} }
if !options.IgnoreStaticIP { if !options.IgnoreStaticIP {
@ -1349,13 +1332,6 @@ func (c *Container) restore(ctx context.Context, options ContainerCheckpointOpti
// For now just use the first interface to get the ips this should be good enough for most cases. // For now just use the first interface to get the ips this should be good enough for most cases.
break break
} }
if perNetOpts.InterfaceName == "" {
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network)
if !exists {
return nil, 0, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network)
}
perNetOpts.InterfaceName = eth
}
netOpts[network] = perNetOpts netOpts[network] = perNetOpts
} }
c.perNetworkOpts = netOpts c.perNetworkOpts = netOpts

View File

@ -53,41 +53,6 @@ const (
persistentCNIDir = "/var/lib/cni" persistentCNIDir = "/var/lib/cni"
) )
// GetAllNetworkAliases returns all configured aliases for this container.
// It also adds the container short ID as alias to match docker.
func (c *Container) GetAllNetworkAliases() (map[string][]string, error) {
allAliases, err := c.runtime.state.GetAllNetworkAliases(c)
if err != nil {
return nil, err
}
// get the all attached networks, we cannot use GetAllNetworkAliases()
// since it returns nil if there are no aliases
nets, _, err := c.networks()
if err != nil {
return nil, err
}
// add container short ID as alias to match docker
for _, net := range nets {
allAliases[net] = append(allAliases[net], c.config.ID[:12])
}
return allAliases, nil
}
// GetNetworkAliases returns configured aliases for this network.
// It also adds the container short ID as alias to match docker.
func (c *Container) GetNetworkAliases(netName string) ([]string, error) {
aliases, err := c.runtime.state.GetNetworkAliases(c, netName)
if err != nil {
return nil, err
}
// add container short ID as alias to match docker
aliases = append(aliases, c.config.ID[:12])
return aliases, nil
}
// convertPortMappings will remove the HostIP part from the ports when running inside podman machine. // convertPortMappings will remove the HostIP part from the ports when running inside podman machine.
// This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports. // This is need because a HostIP of 127.0.0.1 would now allow the gvproxy forwarder to reach to open ports.
// For machine the HostIP must only be used by gvproxy and never in the VM. // For machine the HostIP must only be used by gvproxy and never in the VM.
@ -104,53 +69,20 @@ func (c *Container) convertPortMappings() []types.PortMapping {
return newPorts return newPorts
} }
func (c *Container) getNetworkOptions() (types.NetworkOptions, error) { func (c *Container) getNetworkOptions(networkOpts map[string]types.PerNetworkOptions) (types.NetworkOptions, error) {
opts := types.NetworkOptions{ opts := types.NetworkOptions{
ContainerID: c.config.ID, ContainerID: c.config.ID,
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.convertPortMappings() opts.PortMappings = c.convertPortMappings()
networks, _, err := c.networks()
if err != nil {
return opts, err
}
aliases, err := c.GetAllNetworkAliases()
if err != nil {
return opts, err
}
// If the container requested special network options use this instead of the config. // If the container requested special network options use this instead of the config.
// This is the case for container restore or network reload. // This is the case for container restore or network reload.
if c.perNetworkOpts != nil { if c.perNetworkOpts != nil {
opts.Networks = c.perNetworkOpts opts.Networks = c.perNetworkOpts
return opts, nil } else {
opts.Networks = networkOpts
} }
// Update container map of interface descriptions
if err := c.setupNetworkDescriptions(networks); err != nil {
return opts, err
}
nets := make(map[string]types.PerNetworkOptions, len(networks))
for i, network := range networks {
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(network)
if !exists {
return opts, errors.Errorf("no network interface name for container %s on network %s", c.config.ID, network)
}
netOpts := types.PerNetworkOptions{
InterfaceName: eth,
Aliases: aliases[network],
}
// only set the static ip/mac on the first network
if i == 0 {
if c.config.StaticIP != nil {
netOpts.StaticIPs = []net.IP{c.config.StaticIP}
}
netOpts.StaticMAC = c.config.StaticMAC
}
nets[network] = netOpts
}
opts.Networks = nets
return opts, nil return opts, nil
} }
@ -697,7 +629,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[str
if ctr.config.NetMode.IsSlirp4netns() { if ctr.config.NetMode.IsSlirp4netns() {
return nil, r.setupSlirp4netns(ctr, ctrNS) return nil, r.setupSlirp4netns(ctr, ctrNS)
} }
networks, _, err := ctr.networks() networks, err := ctr.networks()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -707,7 +639,7 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) (status map[str
return nil, nil return nil, nil
} }
netOpts, err := ctr.getNetworkOptions() netOpts, err := ctr.getNetworkOptions(networks)
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -862,13 +794,13 @@ func (r *Runtime) teardownCNI(ctr *Container) error {
logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID()) logrus.Debugf("Tearing down network namespace at %s for container %s", ctr.state.NetNS.Path(), ctr.ID())
networks, _, err := ctr.networks() networks, err := ctr.networks()
if err != nil { if err != nil {
return err return err
} }
if !ctr.config.NetMode.IsSlirp4netns() && len(networks) > 0 { if !ctr.config.NetMode.IsSlirp4netns() && len(networks) > 0 {
netOpts, err := ctr.getNetworkOptions() netOpts, err := ctr.getNetworkOptions(networks)
if err != nil { if err != nil {
return err return err
} }
@ -960,22 +892,17 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu
} }
} }
aliases, err := ctr.GetAllNetworkAliases() networkOpts, err := ctr.networks()
if err != nil { if err != nil {
return nil, err return nil, err
} }
// Set the same network settings as before.. // Set the same network settings as before..
netStatus := ctr.getNetworkStatus() netStatus := ctr.getNetworkStatus()
netOpts := make(map[string]types.PerNetworkOptions, len(netStatus)) for network, perNetOpts := range networkOpts {
for network, status := range netStatus { for name, netInt := range netStatus[network].Interfaces {
perNetOpts := types.PerNetworkOptions{} perNetOpts.InterfaceName = name
for name, netInt := range status.Interfaces { perNetOpts.StaticMAC = netInt.MacAddress
perNetOpts = types.PerNetworkOptions{
InterfaceName: name,
Aliases: aliases[network],
StaticMAC: netInt.MacAddress,
}
for _, netAddress := range netInt.Subnets { for _, netAddress := range netInt.Subnets {
perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP) perNetOpts.StaticIPs = append(perNetOpts.StaticIPs, netAddress.IPNet.IP)
} }
@ -983,16 +910,9 @@ func (r *Runtime) reloadContainerNetwork(ctr *Container) (map[string]types.Statu
// For now just use the first interface to get the ips this should be good enough for most cases. // For now just use the first interface to get the ips this should be good enough for most cases.
break break
} }
if perNetOpts.InterfaceName == "" { networkOpts[network] = perNetOpts
eth, exists := ctr.state.NetInterfaceDescriptions.getInterfaceByName(network)
if !exists {
return nil, errors.Errorf("no network interface name for container %s on network %s", ctr.config.ID, network)
}
perNetOpts.InterfaceName = eth
}
netOpts[network] = perNetOpts
} }
ctr.perNetworkOpts = netOpts ctr.perNetworkOpts = networkOpts
return r.configureNetNS(ctr, ctr.state.NetNS) return r.configureNetNS(ctr, ctr.state.NetNS)
} }
@ -1049,7 +969,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
settings := new(define.InspectNetworkSettings) settings := new(define.InspectNetworkSettings)
settings.Ports = makeInspectPortBindings(c.config.PortMappings, c.config.ExposedPorts) settings.Ports = makeInspectPortBindings(c.config.PortMappings, c.config.ExposedPorts)
networks, isDefault, err := c.networks() networks, err := c.networks()
if err != nil { if err != nil {
return nil, err return nil, err
} }
@ -1060,14 +980,10 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
// the container joined. // the container joined.
if len(networks) > 0 { if len(networks) > 0 {
settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks)) settings.Networks = make(map[string]*define.InspectAdditionalNetwork, len(networks))
for _, net := range networks { for net, opts := range networks {
cniNet := new(define.InspectAdditionalNetwork) cniNet := new(define.InspectAdditionalNetwork)
cniNet.NetworkID = net cniNet.NetworkID = net
aliases, err := c.GetNetworkAliases(net) cniNet.Aliases = opts.Aliases
if err != nil {
return nil, err
}
cniNet.Aliases = aliases
settings.Networks[net] = cniNet settings.Networks[net] = cniNet
} }
} }
@ -1092,7 +1008,7 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
settings.Networks = make(map[string]*define.InspectAdditionalNetwork) settings.Networks = make(map[string]*define.InspectAdditionalNetwork)
for _, name := range networks { for name, opts := range networks {
result := netStatus[name] result := netStatus[name]
addedNet := new(define.InspectAdditionalNetwork) addedNet := new(define.InspectAdditionalNetwork)
addedNet.NetworkID = name addedNet.NetworkID = name
@ -1101,19 +1017,17 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
if err != nil { if err != nil {
return nil, err return nil, err
} }
addedNet.Aliases = opts.Aliases
aliases, err := c.GetNetworkAliases(name)
if err != nil {
return nil, err
}
addedNet.Aliases = aliases
addedNet.InspectBasicNetworkConfig = basicConfig addedNet.InspectBasicNetworkConfig = basicConfig
settings.Networks[name] = addedNet settings.Networks[name] = addedNet
} }
if !isDefault { // if not only the default network is connected we can return here
// otherwise we have to populate the InspectBasicNetworkConfig settings
_, isDefaultNet := networks[c.runtime.config.Network.DefaultNetwork]
if !(len(networks) == 1 && isDefaultNet) {
return settings, nil return settings, nil
} }
} }
@ -1135,29 +1049,6 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
return settings, nil return settings, nil
} }
// setupNetworkDescriptions adds networks and eth values to the container's
// network descriptions
func (c *Container) setupNetworkDescriptions(networks []string) error {
// if the map is nil and we have networks
if c.state.NetInterfaceDescriptions == nil && len(networks) > 0 {
c.state.NetInterfaceDescriptions = make(ContainerNetworkDescriptions)
}
origLen := len(c.state.NetInterfaceDescriptions)
for _, n := range networks {
// if the network is not in the map, add it
if _, exists := c.state.NetInterfaceDescriptions[n]; !exists {
c.state.NetInterfaceDescriptions.add(n)
}
}
// if the map changed, we need to save the container state
if origLen != len(c.state.NetInterfaceDescriptions) {
if err := c.save(); err != nil {
return err
}
}
return nil
}
// resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI // resultToBasicNetworkConfig produces an InspectBasicNetworkConfig from a CNI
// result // result
func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) { func resultToBasicNetworkConfig(result types.StatusBlock) (define.InspectBasicNetworkConfig, error) {
@ -1213,7 +1104,7 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
networks, err := c.networksByNameIndex() networks, err := c.networks()
if err != nil { if err != nil {
return err return err
} }
@ -1254,14 +1145,8 @@ func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) erro
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.convertPortMappings() opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
}
opts.Networks = map[string]types.PerNetworkOptions{ opts.Networks = map[string]types.PerNetworkOptions{
netName: { netName: networks[netName],
InterfaceName: eth,
},
} }
if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil { if err := c.runtime.teardownNetwork(c.state.NetNS.Path(), opts); err != nil {
@ -1294,7 +1179,7 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
c.lock.Lock() c.lock.Lock()
defer c.lock.Unlock() defer c.lock.Unlock()
networks, err := c.networksByNameIndex() networks, err := c.networks()
if err != nil { if err != nil {
return err return err
} }
@ -1321,7 +1206,18 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
return errors.Wrapf(define.ErrInvalidArg, "cannot set network aliases for network %q because dns is disabled", netName) return errors.Wrapf(define.ErrInvalidArg, "cannot set network aliases for network %q because dns is disabled", netName)
} }
if err := c.runtime.state.NetworkConnect(c, netName, aliases); err != nil { eth := getFreeInterfaceName(networks)
if eth == "" {
return errors.New("could not find free network interface name")
}
perNetOpt := types.PerNetworkOptions{
// always add the short id as alias to match docker
Aliases: append(aliases, c.config.ID[:12]),
InterfaceName: eth,
}
if err := c.runtime.state.NetworkConnect(c, netName, perNetOpt); err != nil {
return err return err
} }
c.newNetworkEvent(events.NetworkConnect, netName) c.newNetworkEvent(events.NetworkConnect, netName)
@ -1332,30 +1228,13 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
return errors.Wrapf(define.ErrNoNetwork, "unable to connect %s to %s", nameOrID, netName) return errors.Wrapf(define.ErrNoNetwork, "unable to connect %s to %s", nameOrID, netName)
} }
ctrNetworks, _, err := c.networks()
if err != nil {
return err
}
// Update network descriptions
if err := c.setupNetworkDescriptions(ctrNetworks); err != nil {
return err
}
opts := types.NetworkOptions{ opts := types.NetworkOptions{
ContainerID: c.config.ID, ContainerID: c.config.ID,
ContainerName: getCNIPodName(c), ContainerName: getCNIPodName(c),
} }
opts.PortMappings = c.convertPortMappings() opts.PortMappings = c.convertPortMappings()
eth, exists := c.state.NetInterfaceDescriptions.getInterfaceByName(netName)
if !exists {
return errors.Errorf("no network interface name for container %s on network %s", c.config.ID, netName)
}
aliases = append(aliases, c.config.ID[:12])
opts.Networks = map[string]types.PerNetworkOptions{ opts.Networks = map[string]types.PerNetworkOptions{
netName: { netName: perNetOpt,
Aliases: aliases,
InterfaceName: eth,
},
} }
results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts) results, err := c.runtime.setUpNetwork(c.state.NetNS.Path(), opts)
@ -1385,6 +1264,22 @@ func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) e
return nil return nil
} }
// get a free interface name for a new network
// return an empty string if no free name was found
func getFreeInterfaceName(networks map[string]types.PerNetworkOptions) string {
ifNames := make([]string, 0, len(networks))
for _, opts := range networks {
ifNames = append(ifNames, opts.InterfaceName)
}
for i := 0; i < 100000; i++ {
ifName := fmt.Sprintf("eth%d", i)
if !util.StringInSlice(ifName, ifNames) {
return ifName
}
}
return ""
}
// DisconnectContainerFromNetwork removes a container from its CNI network // DisconnectContainerFromNetwork removes a container from its CNI network
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error { func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
ctr, err := r.LookupContainer(nameOrID) ctr, err := r.LookupContainer(nameOrID)

View File

@ -1,5 +1,7 @@
package libpod package libpod
import "github.com/containers/podman/v3/libpod/network/types"
// State is a storage backend for libpod's current state. // State is a storage backend for libpod's current state.
// A State is only initialized once per instance of libpod. // A State is only initialized once per instance of libpod.
// As such, initialization methods for State implementations may safely assume // As such, initialization methods for State implementations may safely assume
@ -99,14 +101,14 @@ type State interface {
AllContainers() ([]*Container, error) AllContainers() ([]*Container, error)
// Get networks the container is currently connected to. // Get networks the container is currently connected to.
GetNetworks(ctr *Container) ([]string, error) GetNetworks(ctr *Container) (map[string]types.PerNetworkOptions, error)
// Get network aliases for the given container in the given network. // Get network aliases for the given container in the given network.
GetNetworkAliases(ctr *Container, network string) ([]string, error) GetNetworkAliases(ctr *Container, network string) ([]string, error)
// Get all network aliases for the given container. // Get all network aliases for the given container.
GetAllNetworkAliases(ctr *Container) (map[string][]string, error) GetAllNetworkAliases(ctr *Container) (map[string][]string, error)
// Add the container to the given network, adding the given aliases // Add the container to the given network, adding the given aliases
// (if present). // (if present).
NetworkConnect(ctr *Container, network string, aliases []string) error NetworkConnect(ctr *Container, network string, opts types.PerNetworkOptions) error
// Remove the container from the given network, removing all aliases for // Remove the container from the given network, removing all aliases for
// the container in that network in the process. // the container in that network in the process.
NetworkDisconnect(ctr *Container, network string) error NetworkDisconnect(ctr *Container, network string) error