add network connect|disconnect compat endpoints

this enables the ability to connect and disconnect a container from a
given network. it is only for the compatibility layer. some code had to
be refactored to avoid circular imports.

additionally, tests are being deferred temporarily due to some
incompatibility/bug in either docker-py or our stack.

Signed-off-by: baude <bbaude@redhat.com>
This commit is contained in:
baude 2020-11-11 09:45:07 -06:00
parent 286d356db0
commit a3e0b7d117
23 changed files with 652 additions and 61 deletions

View File

@ -0,0 +1,47 @@
package network
import (
"github.com/containers/common/pkg/completion"
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
)
var (
networkConnectDescription = `Add container to a network`
networkConnectCommand = &cobra.Command{
Use: "connect [options] NETWORK CONTAINER",
Short: "network connect",
Long: networkConnectDescription,
RunE: networkConnect,
Example: `podman network connect web secondary`,
Args: cobra.ExactArgs(2),
ValidArgsFunction: common.AutocompleteNetworks,
}
)
var (
networkConnectOptions entities.NetworkConnectOptions
)
func networkConnectFlags(cmd *cobra.Command) {
flags := cmd.Flags()
aliasFlagName := "alias"
flags.StringSliceVar(&networkConnectOptions.Aliases, aliasFlagName, []string{}, "network scoped alias for container")
_ = cmd.RegisterFlagCompletionFunc(aliasFlagName, completion.AutocompleteNone)
}
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: networkConnectCommand,
Parent: networkCmd,
})
networkConnectFlags(networkConnectCommand)
}
func networkConnect(cmd *cobra.Command, args []string) error {
networkConnectOptions.Container = args[1]
return registry.ContainerEngine().NetworkConnect(registry.Context(), args[0], networkConnectOptions)
}

View File

@ -0,0 +1,45 @@
package network
import (
"github.com/containers/podman/v2/cmd/podman/common"
"github.com/containers/podman/v2/cmd/podman/registry"
"github.com/containers/podman/v2/pkg/domain/entities"
"github.com/spf13/cobra"
"github.com/spf13/pflag"
)
var (
networkDisconnectDescription = `Remove container from a network`
networkDisconnectCommand = &cobra.Command{
Use: "disconnect [options] NETWORK CONTAINER",
Short: "network rm",
Long: networkDisconnectDescription,
RunE: networkDisconnect,
Example: `podman network disconnect web secondary`,
Args: cobra.ExactArgs(2),
ValidArgsFunction: common.AutocompleteNetworks,
}
)
var (
networkDisconnectOptions entities.NetworkDisconnectOptions
)
func networkDisconnectFlags(flags *pflag.FlagSet) {
flags.BoolVarP(&networkDisconnectOptions.Force, "force", "f", false, "force removal of container from network")
}
func init() {
registry.Commands = append(registry.Commands, registry.CliCommand{
Mode: []entities.EngineMode{entities.ABIMode, entities.TunnelMode},
Command: networkDisconnectCommand,
Parent: networkCmd,
})
flags := networkDisconnectCommand.Flags()
networkDisconnectFlags(flags)
}
func networkDisconnect(cmd *cobra.Command, args []string) error {
networkDisconnectOptions.Container = args[1]
return registry.ContainerEngine().NetworkDisconnect(registry.Context(), args[0], networkDisconnectOptions)
}

View File

@ -47,6 +47,8 @@
| [podman-mount(1)](https://podman.readthedocs.io/en/latest/markdown/podman-mount.1.html) | Mount a working container's root filesystem |
| [podman-network(1)](https://podman.readthedocs.io/en/latest/network.html) | Manage Podman CNI networks |
| [podman-network-create(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-create.1.html) | Create a CNI network |
| [podman-network-connect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-connect.1.html) | Connect a container to a CNI network |
| [podman-network-disconnect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-dosconnect.1.html) | Disconnect a container from a CNI network |
| [podman-network-inspect(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-inspect.1.html) | Displays the raw CNI network configuration for one or more networks |
| [podman-network-ls(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-ls.1.html) | Display a summary of CNI networks |
| [podman-network-rm(1)](https://podman.readthedocs.io/en/latest/markdown/podman-network-rm.1.html) | Remove one or more CNI networks |

View File

@ -0,0 +1,34 @@
% podman-network-connect(1)
## NAME
podman\-network\-connect - Connect a container to a network
## SYNOPSIS
**podman network connect** [*options*] network container
## DESCRIPTION
Connects a container to a network. A container can be connected to a network by name or by ID.
Once connected, the container can communicate with other containers in the same network.
## OPTIONS
#### **--alias**
Add network-scoped alias for the container. If the network is using the `dnsname` CNI plugin, these aliases
can be used for name resolution on the given network. Multiple *--alias* options may be specificed as input.
## EXAMPLE
Connect a container named *web* to a network named *test*
```
podman network connect test web
```
Connect a container name *web* to a network named *test* with two aliases: web1 and web2
```
podman network connect --alias web1 --alias web2 test web
```
## SEE ALSO
podman(1), podman-network(1), podman-network-disconnect(1), podman-network-inspect(1)
## HISTORY
November 2020, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -0,0 +1,29 @@
% podman-network-disconnect(1)
## NAME
podman\-network\-disconnect - Disconnect a container from a network
## SYNOPSIS
**podman network disconnect** [*options*] network container
## DESCRIPTION
Disconnects a container from a network.
## OPTIONS
#### **--force**, **-f**
Force the container to disconnect from a network
## EXAMPLE
Disconnect a container named *web* from a network called *test*.
```
podman network disconnect test web
```
## SEE ALSO
podman(1), podman-network(1), podman-network-connect(1)
## HISTORY
November 2020, Originally compiled by Brent Baude <bbaude@redhat.com>

View File

@ -13,7 +13,9 @@ The network command manages CNI networks for Podman. It is not supported for roo
| Command | Man Page | Description |
| ------- | --------------------------------------------------- | ---------------------------------------------------------------------------- |
| connect | [podman-network-connect(1)](podman-network-connect.1.md)| Connect a container to a network|
| create | [podman-network-create(1)](podman-network-create.1.md)| Create a Podman CNI network|
| disconnect | [podman-network-disconnect(1)](podman-network-disconnect.1.md)| Disconnect a container from a network|
| inspect | [podman-network-inspect(1)](podman-network-inspect.1.md)| Displays the raw CNI network configuration for one or more networks|
| ls | [podman-network-ls(1)](podman-network-ls.1.md)| Display a summary of CNI networks |
| rm | [podman-network-rm(1)](podman-network-rm.1.md)| Remove one or more CNI networks |

View File

@ -1,8 +1,12 @@
Network
=======
:doc:`connect <markdown/podman-network-connect.1>` network connect
:doc:`create <markdown/podman-network-create.1>` network create
:doc:`disconnect <markdown/podman-network-disconnect.1>` network disconnect
:doc:`inspect <markdown/podman-network-inspect.1>` network inspect
:doc:`ls <markdown/podman-network-ls.1>` network list

View File

@ -206,6 +206,10 @@ type ContainerState struct {
// and not delegated to the OCI runtime.
ExtensionStageHooks map[string][]spec.Hook `json:"extensionStageHooks,omitempty"`
// NetInterfaceDescriptions describe the relationship between a CNI
// network and an interface names
NetInterfaceDescriptions ContainerNetworkDescriptions `json:"networkDescriptions,omitempty"`
// containerPlatformState holds platform-specific container state.
containerPlatformState
}
@ -244,6 +248,10 @@ type ContainerImageVolume struct {
ReadWrite bool `json:"rw"`
}
// ContainerNetworkDescriptions describes the relationship between the CNI
// network and the ethN where N is an integer
type ContainerNetworkDescriptions map[string]int
// Config accessors
// Unlocked
@ -1102,3 +1110,19 @@ func (c *Container) networksByNameIndex() (map[string]int, error) {
}
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
// network along with a bool as to whether the network existed
func (d ContainerNetworkDescriptions) getInterfaceByName(networkName string) (string, bool) {
val, exists := d[networkName]
if !exists {
return "", exists
}
return fmt.Sprintf("eth%d", val), exists
}

View File

@ -178,4 +178,7 @@ var (
// ErrStoreNotInitialized indicates that the container storage was never
// initialized.
ErrStoreNotInitialized = errors.New("the container storage was never initialized")
// ErrNoNetwork indicates that a container has no net namespace, like network=none
ErrNoNetwork = errors.New("container has no network namespace")
)

View File

@ -50,6 +50,18 @@ func (c *Container) newContainerExitedEvent(exitCode int32) {
}
}
// netNetworkEvent creates a new event based on a network connect/disconnect
func (c *Container) newNetworkEvent(status events.Status, netName string) {
e := events.NewEvent(status)
e.ID = c.ID()
e.Name = c.Name()
e.Type = events.Network
e.Network = netName
if err := c.runtime.eventer.Write(e); err != nil {
logrus.Errorf("unable to write pod event: %q", err)
}
}
// newPodEvent creates a new event for a libpod pod
func (p *Pod) newPodEvent(status events.Status) {
e := events.NewEvent(status)

View File

@ -30,6 +30,8 @@ type Event struct {
Image string `json:",omitempty"`
// Name where applicable
Name string `json:",omitempty"`
// Network is the network name in a network event
Network string `json:"network,omitempty"`
// Status describes the event that occurred
Status Status
// Time the event occurred
@ -101,6 +103,8 @@ const (
Container Type = "container"
// Image - event is related to images
Image Type = "image"
// Network - event is related to networks
Network Type = "network"
// Pod - event is related to pods
Pod Type = "pod"
// System - event is related to Podman whole and not to any specific
@ -141,6 +145,10 @@ const (
LoadFromArchive Status = "loadfromarchive"
// Mount ...
Mount Status = "mount"
// NetworkConnect
NetworkConnect Status = "connect"
// NetworkDisconnect
NetworkDisconnect Status = "disconnect"
// Pause ...
Pause Status = "pause"
// Prune ...

View File

@ -77,6 +77,8 @@ func (e *Event) ToHumanReadable() string {
}
}
humanFormat += ")"
case Network:
humanFormat = fmt.Sprintf("%s %s %s %s (container=%s, name=%s)", e.Time, e.Type, e.Status, e.ID, e.ID, e.Network)
case Image:
humanFormat = fmt.Sprintf("%s %s %s %s %s", e.Time, e.Type, e.Status, e.ID, e.Name)
case System:
@ -115,6 +117,8 @@ func StringToType(name string) (Type, error) {
return Container, nil
case Image.String():
return Image, nil
case Network.String():
return Network, nil
case Pod.String():
return Pod, nil
case System.String():
@ -162,6 +166,10 @@ func StringToStatus(name string) (Status, error) {
return LoadFromArchive, nil
case Mount.String():
return Mount, nil
case NetworkConnect.String():
return NetworkConnect, nil
case NetworkDisconnect.String():
return NetworkDisconnect, nil
case Pause.String():
return Pause, nil
case Prune.String():

View File

@ -56,6 +56,9 @@ func (e EventJournalD) Write(ee Event) error {
}
m["PODMAN_LABELS"] = string(b)
}
case Network:
m["PODMAN_ID"] = ee.ID
m["PODMAN_NETWORK_NAME"] = ee.Network
case Volume:
m["PODMAN_NAME"] = ee.Name
}
@ -197,6 +200,9 @@ func newEventFromJournalEntry(entry *sdjournal.JournalEntry) (*Event, error) { /
newEvent.Details = Details{Attributes: labels}
}
}
case Network:
newEvent.ID = entry.Fields["PODMAN_ID"]
newEvent.Network = entry.Fields["PODMAN_NETWORK_NAME"]
case Image:
newEvent.ID = entry.Fields["PODMAN_ID"]
}

View File

@ -76,7 +76,7 @@ func (e EventLogFile) Read(ctx context.Context, options ReadOptions) error {
return err
}
switch event.Type {
case Image, Volume, Pod, System, Container:
case Image, Volume, Pod, System, Container, Network:
// no-op
default:
return errors.Errorf("event type %s is not valid in %s", event.Type.String(), e.options.LogFilePath)

View File

@ -21,6 +21,7 @@ import (
cnitypes "github.com/containernetworking/cni/pkg/types/current"
"github.com/containernetworking/plugins/pkg/ns"
"github.com/containers/podman/v2/libpod/define"
"github.com/containers/podman/v2/libpod/events"
"github.com/containers/podman/v2/libpod/network"
"github.com/containers/podman/v2/pkg/errorhandling"
"github.com/containers/podman/v2/pkg/netns"
@ -34,16 +35,16 @@ import (
)
// Get an OCICNI network config
func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr) ocicni.PodNetwork {
func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, ports []ocicni.PortMapping, staticIP net.IP, staticMAC net.HardwareAddr, netDescriptions ContainerNetworkDescriptions) ocicni.PodNetwork {
var networkKey string
if len(networks) > 0 {
// This is inconsistent for >1 network, but it's probably the
// This is inconsistent for >1 ctrNetwork, but it's probably the
// best we can do.
networkKey = networks[0]
} else {
networkKey = r.netPlugin.GetDefaultNetworkName()
}
network := ocicni.PodNetwork{
ctrNetwork := ocicni.PodNetwork{
Name: name,
Namespace: name, // TODO is there something else we should put here? We don't know about Kube namespaces
ID: id,
@ -55,9 +56,12 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
// If we have extra networks, add them
if len(networks) > 0 {
network.Networks = make([]ocicni.NetAttachment, len(networks))
ctrNetwork.Networks = make([]ocicni.NetAttachment, len(networks))
for i, netName := range networks {
network.Networks[i].Name = netName
ctrNetwork.Networks[i].Name = netName
if eth, exists := netDescriptions.getInterfaceByName(netName); exists {
ctrNetwork.Networks[i].Ifname = eth
}
}
}
@ -66,8 +70,8 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
// it's just the default.
if len(networks) == 0 {
// If len(networks) == 0 this is guaranteed to be the
// default network.
network.Networks = []ocicni.NetAttachment{{Name: networkKey}}
// default ctrNetwork.
ctrNetwork.Networks = []ocicni.NetAttachment{{Name: networkKey}}
}
var rt ocicni.RuntimeConfig = ocicni.RuntimeConfig{PortMappings: ports}
if staticIP != nil {
@ -76,12 +80,12 @@ func (r *Runtime) getPodNetwork(id, name, nsPath string, networks []string, port
if staticMAC != nil {
rt.MAC = staticMAC.String()
}
network.RuntimeConfig = map[string]ocicni.RuntimeConfig{
ctrNetwork.RuntimeConfig = map[string]ocicni.RuntimeConfig{
networkKey: rt,
}
}
return network
return ctrNetwork
}
// Create and configure a new network namespace for a container
@ -110,7 +114,12 @@ func (r *Runtime) configureNetNS(ctr *Container, ctrNS ns.NetNS) ([]*cnitypes.Re
if err != nil {
return nil, err
}
podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC)
// Update container map of interface descriptions
if err := ctr.setupNetworkDescriptions(networks); err != nil {
return nil, err
}
podNetwork := r.getPodNetwork(ctr.ID(), podName, ctrNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC, ctr.state.NetInterfaceDescriptions)
aliases, err := ctr.runtime.state.GetAllNetworkAliases(ctr)
if err != nil {
return nil, err
@ -760,7 +769,7 @@ func (r *Runtime) teardownNetNS(ctr *Container) error {
requestedMAC = ctr.config.StaticMAC
}
podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC)
podNetwork := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), networks, ctr.config.PortMappings, requestedIP, requestedMAC, ContainerNetworkDescriptions{})
if err := r.netPlugin.TearDownPod(podNetwork); err != nil {
return errors.Wrapf(err, "error tearing down CNI namespace configuration for container %s", ctr.ID())
@ -934,6 +943,29 @@ func (c *Container) getContainerNetworkInfo() (*define.InspectNetworkSettings, e
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
// result
func resultToBasicNetworkConfig(result *cnitypes.Result) (define.InspectBasicNetworkConfig, error) {
@ -984,19 +1016,14 @@ func (w *logrusDebugWriter) Write(p []byte) (int, error) {
return len(p), nil
}
// DisconnectContainerFromNetwork removes a container from its CNI network
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
ctr, err := r.LookupContainer(nameOrID)
// NetworkDisconnect removes a container from the network
func (c *Container) NetworkDisconnect(nameOrID, netName string, force bool) error {
networks, err := c.networksByNameIndex()
if err != nil {
return err
}
networks, err := ctr.networksByNameIndex()
if err != nil {
return err
}
exists, err := network.Exists(r.config, netName)
exists, err := network.Exists(c.runtime.config, netName)
if err != nil {
return err
}
@ -1009,48 +1036,48 @@ func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force
return errors.Errorf("container %s is not connected to network %s", nameOrID, netName)
}
ctr.lock.Lock()
defer ctr.lock.Unlock()
if err := ctr.syncContainer(); err != nil {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return err
}
podConfig := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), []string{netName}, ctr.config.PortMappings, nil, nil)
if err := r.netPlugin.TearDownPod(podConfig); err != nil {
if c.state.State != define.ContainerStateRunning {
return errors.Wrapf(define.ErrCtrStateInvalid, "cannot disconnect container %s from networks as it is not running", nameOrID)
}
if c.state.NetNS == nil {
return errors.Wrapf(define.ErrNoNetwork, "unable to disconnect %s from %s", nameOrID, netName)
}
podConfig := c.runtime.getPodNetwork(c.ID(), c.Name(), c.state.NetNS.Path(), []string{netName}, c.config.PortMappings, nil, nil, c.state.NetInterfaceDescriptions)
if err := c.runtime.netPlugin.TearDownPod(podConfig); err != nil {
return err
}
if err := r.state.NetworkDisconnect(ctr, netName); err != nil {
if err := c.runtime.state.NetworkDisconnect(c, netName); err != nil {
return err
}
// update network status
networkStatus := ctr.state.NetworkStatus
// if len is one and we confirmed earlier that the container is in
// fact connected to the network, then just return an empty slice
if len(networkStatus) == 1 {
ctr.state.NetworkStatus = make([]*cnitypes.Result, 0)
} else {
// clip out the index of the network
networkStatus[len(networkStatus)-1], networkStatus[index] = networkStatus[index], networkStatus[len(networkStatus)-1]
// shorten the slice by one
ctr.state.NetworkStatus = networkStatus[:len(networkStatus)-1]
networkStatus := c.state.NetworkStatus
// clip out the index of the network
tmpNetworkStatus := make([]*cnitypes.Result, len(networkStatus)-1)
for k, v := range networkStatus {
if index != k {
tmpNetworkStatus = append(tmpNetworkStatus, v)
}
}
return nil
c.state.NetworkStatus = tmpNetworkStatus
c.newNetworkEvent(events.NetworkDisconnect, netName)
return c.save()
}
// ConnectContainerToNetwork connects a container to a CNI network
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error {
ctr, err := r.LookupContainer(nameOrID)
// ConnnectNetwork connects a container to a given network
func (c *Container) NetworkConnect(nameOrID, netName string, aliases []string) error {
networks, err := c.networksByNameIndex()
if err != nil {
return err
}
networks, err := ctr.networksByNameIndex()
if err != nil {
return err
}
exists, err := network.Exists(r.config, netName)
exists, err := network.Exists(c.runtime.config, netName)
if err != nil {
return err
}
@ -1058,25 +1085,34 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
return errors.Wrap(define.ErrNoSuchNetwork, netName)
}
_, nameExists := networks[netName]
if !nameExists && len(networks) > 0 {
return errors.Errorf("container %s is not connected to network %s", nameOrID, netName)
}
ctr.lock.Lock()
defer ctr.lock.Unlock()
if err := ctr.syncContainer(); err != nil {
c.lock.Lock()
defer c.lock.Unlock()
if err := c.syncContainer(); err != nil {
return err
}
if err := r.state.NetworkConnect(ctr, netName, aliases); err != nil {
if c.state.State != define.ContainerStateRunning {
return errors.Wrapf(define.ErrCtrStateInvalid, "cannot connect container %s to networks as it is not running", nameOrID)
}
if c.state.NetNS == nil {
return errors.Wrapf(define.ErrNoNetwork, "unable to connect %s to %s", nameOrID, netName)
}
if err := c.runtime.state.NetworkConnect(c, netName, aliases); err != nil {
return err
}
podConfig := r.getPodNetwork(ctr.ID(), ctr.Name(), ctr.state.NetNS.Path(), []string{netName}, ctr.config.PortMappings, nil, nil)
ctrNetworks, err := c.networks()
if err != nil {
return err
}
// Update network descriptions
if err := c.setupNetworkDescriptions(ctrNetworks); err != nil {
return err
}
podConfig := c.runtime.getPodNetwork(c.ID(), c.Name(), c.state.NetNS.Path(), []string{netName}, c.config.PortMappings, nil, nil, c.state.NetInterfaceDescriptions)
podConfig.Aliases = make(map[string][]string, 1)
podConfig.Aliases[netName] = aliases
results, err := r.netPlugin.SetUpPod(podConfig)
results, err := c.runtime.netPlugin.SetUpPod(podConfig)
if err != nil {
return err
}
@ -1094,11 +1130,11 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
}
// update network status
networkStatus := ctr.state.NetworkStatus
networkStatus := c.state.NetworkStatus
// if len is one and we confirmed earlier that the container is in
// fact connected to the network, then just return an empty slice
if len(networkStatus) == 0 {
ctr.state.NetworkStatus = append(ctr.state.NetworkStatus, networkResults...)
c.state.NetworkStatus = append(c.state.NetworkStatus, networkResults...)
} else {
// build a list of network names so we can sort and
// get the new name's index
@ -1117,5 +1153,30 @@ func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []
copy(networkStatus[index+1:], networkStatus[index:])
networkStatus[index] = networkResults[0]
}
return nil
c.newNetworkEvent(events.NetworkConnect, netName)
return c.save()
}
// DisconnectContainerFromNetwork removes a container from its CNI network
func (r *Runtime) DisconnectContainerFromNetwork(nameOrID, netName string, force bool) error {
if rootless.IsRootless() {
return errors.New("network connect is not enabled for rootless containers")
}
ctr, err := r.LookupContainer(nameOrID)
if err != nil {
return err
}
return ctr.NetworkDisconnect(nameOrID, netName, force)
}
// ConnectContainerToNetwork connects a container to a CNI network
func (r *Runtime) ConnectContainerToNetwork(nameOrID, netName string, aliases []string) error {
if rootless.IsRootless() {
return errors.New("network disconnect is not enabled for rootless containers")
}
ctr, err := r.LookupContainer(nameOrID)
if err != nil {
return err
}
return ctr.NetworkConnect(nameOrID, netName, aliases)
}

View File

@ -131,3 +131,29 @@ func InspectNetwork(w http.ResponseWriter, r *http.Request) {
}
utils.WriteResponse(w, http.StatusOK, reports)
}
// Connect adds a container to a network
func Connect(w http.ResponseWriter, r *http.Request) {
runtime := r.Context().Value("runtime").(*libpod.Runtime)
var netConnect entities.NetworkConnectOptions
if err := json.NewDecoder(r.Body).Decode(&netConnect); err != nil {
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, errors.Wrap(err, "Decode()"))
return
}
name := utils.GetName(r)
err := runtime.ConnectContainerToNetwork(netConnect.Container, name, netConnect.Aliases)
if err != nil {
if errors.Cause(err) == define.ErrNoSuchCtr {
utils.ContainerNotFound(w, netConnect.Container, err)
return
}
if errors.Cause(err) == define.ErrNoSuchNetwork {
utils.Error(w, "network not found", http.StatusNotFound, err)
return
}
utils.Error(w, "Something went wrong.", http.StatusInternalServerError, err)
return
}
utils.WriteResponse(w, http.StatusOK, "OK")
}

View File

@ -253,5 +253,59 @@ func (s *APIServer) registerNetworkHandlers(r *mux.Router) error {
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/create"), s.APIHandler(libpod.CreateNetwork)).Methods(http.MethodPost)
// swagger:operation POST /libpod/networks/{name}/connect libpod libpodConnectNetwork
// ---
// tags:
// - networks
// summary: Connect container to network
// description: Connect a container to a network.
// produces:
// - application/json
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the network
// - in: body
// name: create
// description: attributes for connecting a container to a network
// schema:
// $ref: "#/definitions/NetworkConnectRequest"
// responses:
// 200:
// description: OK
// 404:
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/{name}/connect"), s.APIHandler(libpod.Connect)).Methods(http.MethodPost)
// swagger:operation POST /libpod/networks/{name}/disconnect libpod libpodDisconnectNetwork
// ---
// tags:
// - networks
// summary: Disconnect container from network
// description: Disconnect a container from a network.
// produces:
// - application/json
// parameters:
// - in: path
// name: name
// type: string
// required: true
// description: the name of the network
// - in: body
// name: create
// description: attributes for disconnecting a container from a network
// schema:
// $ref: "#/definitions/NetworkDisconnectRequest"
// responses:
// 200:
// description: OK
// 404:
// $ref: "#/responses/NoSuchNetwork"
// 500:
// $ref: "#/responses/InternalError"
r.HandleFunc(VersionedPath("/libpod/networks/{name}/disconnect"), s.APIHandler(compat.Disconnect)).Methods(http.MethodPost)
return nil
}

View File

@ -88,3 +88,41 @@ func List(ctx context.Context, options entities.NetworkListOptions) ([]*entities
}
return netList, response.Process(&netList)
}
// Disconnect removes a container from a given network
func Disconnect(ctx context.Context, networkName string, options entities.NetworkDisconnectOptions) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
params := url.Values{}
body, err := jsoniter.MarshalToString(options)
if err != nil {
return err
}
stringReader := strings.NewReader(body)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/disconnect", params, nil, networkName)
if err != nil {
return err
}
return response.Process(nil)
}
// Connect adds a container to a network
func Connect(ctx context.Context, networkName string, options entities.NetworkConnectOptions) error {
conn, err := bindings.GetClient(ctx)
if err != nil {
return err
}
params := url.Values{}
body, err := jsoniter.MarshalToString(options)
if err != nil {
return err
}
stringReader := strings.NewReader(body)
response, err := conn.DoRequest(stringReader, http.MethodPost, "/networks/%s/connect", params, nil, networkName)
if err != nil {
return err
}
return response.Process(nil)
}

View File

@ -50,7 +50,9 @@ type ContainerEngine interface {
SystemPrune(ctx context.Context, options SystemPruneOptions) (*SystemPruneReport, error)
HealthCheckRun(ctx context.Context, nameOrID string, options HealthCheckOptions) (*define.HealthCheckResults, error)
Info(ctx context.Context) (*define.Info, error)
NetworkConnect(ctx context.Context, networkname string, options NetworkConnectOptions) error
NetworkCreate(ctx context.Context, name string, options NetworkCreateOptions) (*NetworkCreateReport, error)
NetworkDisconnect(ctx context.Context, networkname string, options NetworkDisconnectOptions) error
NetworkInspect(ctx context.Context, namesOrIds []string, options InspectOptions) ([]NetworkInspectReport, []error, error)
NetworkList(ctx context.Context, options NetworkListOptions) ([]*NetworkListReport, error)
NetworkRm(ctx context.Context, namesOrIds []string, options NetworkRmOptions) ([]*NetworkRmReport, error)

View File

@ -49,3 +49,17 @@ type NetworkCreateOptions struct {
type NetworkCreateReport struct {
Filename string
}
// NetworkDisconnectOptions describes options for disconnecting
// containers from networks
type NetworkDisconnectOptions struct {
Container string
Force bool
}
// NetworkConnectOptions describes options for connecting
// a container to a network
type NetworkConnectOptions struct {
Aliases []string
Container string
}

View File

@ -138,3 +138,12 @@ func ifPassesFilterTest(netconf *libcni.NetworkConfigList, filter []string) bool
}
return result
}
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return ic.Libpod.DisconnectContainerFromNetwork(options.Container, networkname, options.Force)
}
func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
return ic.Libpod.ConnectContainerToNetwork(options.Container, networkname, options.Aliases)
}

View File

@ -55,3 +55,13 @@ func (ic *ContainerEngine) NetworkRm(ctx context.Context, namesOrIds []string, o
func (ic *ContainerEngine) NetworkCreate(ctx context.Context, name string, options entities.NetworkCreateOptions) (*entities.NetworkCreateReport, error) {
return network.Create(ic.ClientCxt, options, &name)
}
// NetworkDisconnect removes a container from a given network
func (ic *ContainerEngine) NetworkDisconnect(ctx context.Context, networkname string, options entities.NetworkDisconnectOptions) error {
return network.Disconnect(ic.ClientCxt, networkname, options)
}
// NetworkConnect removes a container from a given network
func (ic *ContainerEngine) NetworkConnect(ctx context.Context, networkname string, options entities.NetworkConnectOptions) error {
return network.Connect(ic.ClientCxt, networkname, options)
}

View File

@ -341,4 +341,157 @@ var _ = Describe("Podman network", func() {
c3.WaitWithDefaultTimeout()
Expect(c3.ExitCode()).To(BeZero())
})
It("bad network name in disconnect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
dis := podmanTest.Podman([]string{"network", "disconnect", "foobar", "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("bad container name in network disconnect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
dis := podmanTest.Podman([]string{"network", "disconnect", netName, "foobar"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("podman network disconnect with invalid container state should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("podman network disconnect", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
ctr := podmanTest.Podman([]string{"run", "-dt", "--name", "test", "--network", netName, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
exec := podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).To(BeZero())
dis := podmanTest.Podman([]string{"network", "disconnect", netName, "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).To(BeZero())
exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).ToNot(BeZero())
})
It("bad network name in connect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
dis := podmanTest.Podman([]string{"network", "connect", "foobar", "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("bad container name in network connect should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
dis := podmanTest.Podman([]string{"network", "connect", netName, "foobar"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("podman connect on a container that already is connected to the network should error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
con := podmanTest.Podman([]string{"network", "connect", netName, "test"})
con.WaitWithDefaultTimeout()
Expect(con.ExitCode()).ToNot(BeZero())
})
It("podman network connect with invalid container state should result in error", func() {
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
ctr := podmanTest.Podman([]string{"create", "--name", "test", "--network", netName, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
dis := podmanTest.Podman([]string{"network", "connect", netName, "test"})
dis.WaitWithDefaultTimeout()
Expect(dis.ExitCode()).ToNot(BeZero())
})
It("podman network connect", func() {
SkipIfRemote("This requires a pending PR to be merged before it will work")
SkipIfRootless("network connect and disconnect are only rootfull")
netName := "aliasTest" + stringid.GenerateNonCryptoID()
session := podmanTest.Podman([]string{"network", "create", netName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(netName)
ctr := podmanTest.Podman([]string{"run", "-dt", "--name", "test", "--network", netName, ALPINE, "top"})
ctr.WaitWithDefaultTimeout()
Expect(ctr.ExitCode()).To(BeZero())
exec := podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth0"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).To(BeZero())
// Create a second network
newNetName := "aliasTest" + stringid.GenerateNonCryptoID()
session = podmanTest.Podman([]string{"network", "create", newNetName})
session.WaitWithDefaultTimeout()
Expect(session.ExitCode()).To(BeZero())
defer podmanTest.removeCNINetwork(newNetName)
connect := podmanTest.Podman([]string{"network", "connect", newNetName, "test"})
connect.WaitWithDefaultTimeout()
Expect(connect.ExitCode()).To(BeZero())
exec = podmanTest.Podman([]string{"exec", "-it", "test", "ip", "addr", "show", "eth1"})
exec.WaitWithDefaultTimeout()
Expect(exec.ExitCode()).To(BeZero())
})
})