Merge pull request #31710 from sanimej/drillerrr

Add verbose flag to network inspect to show all services & tasks in swarm mode
This commit is contained in:
Madhu Venugopal 2017-03-13 21:12:32 -07:00 committed by GitHub
commit cdf66ba715
37 changed files with 697 additions and 93 deletions

View file

@ -4,10 +4,12 @@ import (
"encoding/json"
"fmt"
"net/http"
"strconv"
"strings"
"golang.org/x/net/context"
"github.com/docker/docker/api/errors"
"github.com/docker/docker/api/server/httputils"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/filters"
@ -65,7 +67,7 @@ SKIP:
// run across all the networks. Starting API version 1.27, this detailed
// info is available for network specific GET API (equivalent to inspect)
if versions.LessThan(httputils.VersionFromContext(ctx), "1.27") {
nr = n.buildDetailedNetworkResources(nw)
nr = n.buildDetailedNetworkResources(nw, false)
} else {
nr = n.buildNetworkResource(nw)
}
@ -85,6 +87,16 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
}
term := vars["id"]
var (
verbose bool
err error
)
if v := r.URL.Query().Get("verbose"); v != "" {
if verbose, err = strconv.ParseBool(v); err != nil {
err = fmt.Errorf("invalid value for verbose: %s", v)
return errors.NewBadRequestError(err)
}
}
// In case multiple networks have duplicate names, return error.
// TODO (yongtang): should we wrap with version here for backward compatibility?
@ -100,17 +112,17 @@ func (n *networkRouter) getNetwork(ctx context.Context, w http.ResponseWriter, r
nw := n.backend.GetNetworks()
for _, network := range nw {
if network.ID() == term {
return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network))
return httputils.WriteJSON(w, http.StatusOK, *n.buildDetailedNetworkResources(network, verbose))
}
if network.Name() == term {
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network)
listByFullName[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
}
if strings.HasPrefix(network.ID(), term) {
// No need to check the ID collision here as we are still in
// local scope and the network ID is unique in this scope.
listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network)
listByPartialID[network.ID()] = *n.buildDetailedNetworkResources(network, verbose)
}
}
@ -294,7 +306,7 @@ func (n *networkRouter) buildNetworkResource(nw libnetwork.Network) *types.Netwo
return r
}
func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *types.NetworkResource {
func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network, verbose bool) *types.NetworkResource {
if nw == nil {
return &types.NetworkResource{}
}
@ -315,6 +327,28 @@ func (n *networkRouter) buildDetailedNetworkResources(nw libnetwork.Network) *ty
r.Containers[key] = buildEndpointResource(tmpID, e.Name(), ei)
}
if !verbose {
return r
}
services := nw.Info().Services()
r.Services = make(map[string]network.ServiceInfo)
for name, service := range services {
tasks := []network.Task{}
for _, t := range service.Tasks {
tasks = append(tasks, network.Task{
Name: t.Name,
EndpointID: t.EndpointID,
EndpointIP: t.EndpointIP,
Info: t.Info,
})
}
r.Services[name] = network.ServiceInfo{
VIP: service.VIP,
Ports: service.Ports,
Tasks: tasks,
LocalLBIndex: service.LocalLBIndex,
}
}
return r
}

View file

@ -6271,6 +6271,11 @@ paths:
description: "Network ID or name"
required: true
type: "string"
- name: "verbose"
in: "query"
description: "Detailed inspect output for troubleshooting"
type: "boolean"
default: false
tags: ["Network"]
delete:

View file

@ -60,6 +60,22 @@ type EndpointSettings struct {
MacAddress string
}
// Task carries the information about one backend task
type Task struct {
Name string
EndpointID string
EndpointIP string
Info map[string]string
}
// ServiceInfo represents service parameters with the list of service's tasks
type ServiceInfo struct {
VIP string
Ports []string
LocalLBIndex int
Tasks []Task
}
// Copy makes a deep copy of `EndpointSettings`
func (es *EndpointSettings) Copy() *EndpointSettings {
epCopy := *es

View file

@ -391,19 +391,20 @@ type MountPoint struct {
// NetworkResource is the body of the "get network" http response message
type NetworkResource struct {
Name string // Name is the requested name of the network
ID string `json:"Id"` // ID uniquely identifies a network on a single machine
Created time.Time // Created is the time the network created
Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6
IPAM network.IPAM // IPAM is the network's IP Address Management
Internal bool // Internal represents if the network is used internal only
Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
Containers map[string]EndpointResource // Containers contains endpoints belonging to the network
Options map[string]string // Options holds the network specific options to use for when creating the network
Labels map[string]string // Labels holds metadata specific to the network being created
Peers []network.PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network
Name string // Name is the requested name of the network
ID string `json:"Id"` // ID uniquely identifies a network on a single machine
Created time.Time // Created is the time the network created
Scope string // Scope describes the level at which the network exists (e.g. `global` for cluster-wide or `local` for machine level)
Driver string // Driver is the Driver name used to create the network (e.g. `bridge`, `overlay`)
EnableIPv6 bool // EnableIPv6 represents whether to enable IPv6
IPAM network.IPAM // IPAM is the network's IP Address Management
Internal bool // Internal represents if the network is used internal only
Attachable bool // Attachable represents if the global scope is manually attachable by regular containers from workers in swarm mode.
Containers map[string]EndpointResource // Containers contains endpoints belonging to the network
Options map[string]string // Options holds the network specific options to use for when creating the network
Labels map[string]string // Labels holds metadata specific to the network being created
Peers []network.PeerInfo `json:",omitempty"` // List of peer nodes for an overlay network
Services map[string]network.ServiceInfo `json:",omitempty"`
}
// EndpointResource contains network resources allocated and used for a container in a network

View file

@ -10,8 +10,9 @@ import (
)
type inspectOptions struct {
format string
names []string
format string
names []string
verbose bool
}
func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
@ -28,6 +29,7 @@ func newInspectCommand(dockerCli *command.DockerCli) *cobra.Command {
}
cmd.Flags().StringVarP(&opts.format, "format", "f", "", "Format the output using the given Go template")
cmd.Flags().BoolVarP(&opts.verbose, "verbose", "v", false, "Verbose output for diagnostics")
return cmd
}
@ -38,7 +40,7 @@ func runInspect(dockerCli *command.DockerCli, opts inspectOptions) error {
ctx := context.Background()
getNetFunc := func(name string) (interface{}, []byte, error) {
return client.NetworkInspectWithRaw(ctx, name)
return client.NetworkInspectWithRaw(ctx, name, opts.verbose)
}
return inspect.Inspect(dockerCli.Out(), opts.names, opts.format, getNetFunc)

View file

@ -140,7 +140,7 @@ func validateExternalNetworks(
client := dockerCli.Client()
for _, networkName := range externalNetworks {
network, err := client.NetworkInspect(ctx, networkName)
network, err := client.NetworkInspect(ctx, networkName, false)
if err != nil {
if dockerclient.IsErrNetworkNotFound(err) {
return fmt.Errorf("network %q is declared as external, but could not be found. You need to create the network before the stack is deployed (with overlay driver)", networkName)

View file

@ -67,7 +67,7 @@ func inspectImages(ctx context.Context, dockerCli *command.DockerCli) inspect.Ge
func inspectNetwork(ctx context.Context, dockerCli *command.DockerCli) inspect.GetRefFunc {
return func(ref string) (interface{}, []byte, error) {
return dockerCli.Client().NetworkInspectWithRaw(ctx, ref)
return dockerCli.Client().NetworkInspectWithRaw(ctx, ref, false)
}
}

View file

@ -91,8 +91,8 @@ type NetworkAPIClient interface {
NetworkConnect(ctx context.Context, networkID, container string, config *network.EndpointSettings) error
NetworkCreate(ctx context.Context, name string, options types.NetworkCreate) (types.NetworkCreateResponse, error)
NetworkDisconnect(ctx context.Context, networkID, container string, force bool) error
NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error)
NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error)
NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error)
NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error)
NetworkList(ctx context.Context, options types.NetworkListOptions) ([]types.NetworkResource, error)
NetworkRemove(ctx context.Context, networkID string) error
NetworksPrune(ctx context.Context, pruneFilter filters.Args) (types.NetworksPruneReport, error)

View file

@ -5,21 +5,30 @@ import (
"encoding/json"
"io/ioutil"
"net/http"
"net/url"
"github.com/docker/docker/api/types"
"golang.org/x/net/context"
)
// NetworkInspect returns the information for a specific network configured in the docker host.
func (cli *Client) NetworkInspect(ctx context.Context, networkID string) (types.NetworkResource, error) {
networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID)
func (cli *Client) NetworkInspect(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, error) {
networkResource, _, err := cli.NetworkInspectWithRaw(ctx, networkID, verbose)
return networkResource, err
}
// NetworkInspectWithRaw returns the information for a specific network configured in the docker host and its raw representation.
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string) (types.NetworkResource, []byte, error) {
var networkResource types.NetworkResource
resp, err := cli.get(ctx, "/networks/"+networkID, nil, nil)
func (cli *Client) NetworkInspectWithRaw(ctx context.Context, networkID string, verbose bool) (types.NetworkResource, []byte, error) {
var (
networkResource types.NetworkResource
resp serverResponse
err error
)
query := url.Values{}
if verbose {
query.Set("verbose", "true")
}
resp, err = cli.get(ctx, "/networks/"+networkID, query, nil)
if err != nil {
if resp.statusCode == http.StatusNotFound {
return networkResource, nil, networkNotFoundError{networkID}

View file

@ -10,6 +10,7 @@ import (
"testing"
"github.com/docker/docker/api/types"
"github.com/docker/docker/api/types/network"
"golang.org/x/net/context"
)
@ -18,7 +19,7 @@ func TestNetworkInspectError(t *testing.T) {
client: newMockClient(errorMock(http.StatusInternalServerError, "Server error")),
}
_, err := client.NetworkInspect(context.Background(), "nothing")
_, err := client.NetworkInspect(context.Background(), "nothing", false)
if err == nil || err.Error() != "Error response from daemon: Server error" {
t.Fatalf("expected a Server Error, got %v", err)
}
@ -29,7 +30,7 @@ func TestNetworkInspectContainerNotFound(t *testing.T) {
client: newMockClient(errorMock(http.StatusNotFound, "Server error")),
}
_, err := client.NetworkInspect(context.Background(), "unknown")
_, err := client.NetworkInspect(context.Background(), "unknown", false)
if err == nil || !IsErrNetworkNotFound(err) {
t.Fatalf("expected a networkNotFound error, got %v", err)
}
@ -46,9 +47,23 @@ func TestNetworkInspect(t *testing.T) {
return nil, fmt.Errorf("expected GET method, got %s", req.Method)
}
content, err := json.Marshal(types.NetworkResource{
Name: "mynetwork",
})
var (
content []byte
err error
)
if strings.HasPrefix(req.URL.RawQuery, "verbose=true") {
s := map[string]network.ServiceInfo{
"web": {},
}
content, err = json.Marshal(types.NetworkResource{
Name: "mynetwork",
Services: s,
})
} else {
content, err = json.Marshal(types.NetworkResource{
Name: "mynetwork",
})
}
if err != nil {
return nil, err
}
@ -59,11 +74,23 @@ func TestNetworkInspect(t *testing.T) {
}),
}
r, err := client.NetworkInspect(context.Background(), "network_id")
r, err := client.NetworkInspect(context.Background(), "network_id", false)
if err != nil {
t.Fatal(err)
}
if r.Name != "mynetwork" {
t.Fatalf("expected `mynetwork`, got %s", r.Name)
}
r, err = client.NetworkInspect(context.Background(), "network_id", true)
if err != nil {
t.Fatal(err)
}
if r.Name != "mynetwork" {
t.Fatalf("expected `mynetwork`, got %s", r.Name)
}
_, ok := r.Services["web"]
if !ok {
t.Fatalf("expected service `web` missing in the verbose output")
}
}

View file

@ -17,6 +17,7 @@ keywords: "API, Docker, rcli, REST, documentation"
[Docker Engine API v1.27](https://docs.docker.com/engine/api/v1.27/) documentation
* Optional query parameter `verbose` for `GET /networks/(id or name)` will now list all services with all the tasks, including the non-local tasks on the given network.
* `GET /containers/(id or name)/attach/ws` now returns WebSocket in binary frame format for API version >= v1.27, and returns WebSocket in text frame format for API version< v1.27, for the purpose of backward-compatibility.
* `GET /networks` is optimised only to return list of all networks and network specific information. List of all containers attached to a specific network is removed from this API and is only available using the network specific `GET /networks/{network-id}.
* `GET /containers/json` now supports `publish` and `expose` filters to filter containers that expose or publish certain ports.

View file

@ -48,7 +48,7 @@ The `network inspect` command shows the containers, by id, in its
results. For networks backed by multi-host network driver, such as Overlay,
this command also shows the container endpoints in other hosts in the
cluster. These endpoints are represented as "ep-{endpoint-id}" in the output.
However, for swarm-scoped networks, only the endpoints that are local to the
However, for swarm mode networks, only the endpoints that are local to the
node are shown.
You can specify an alternate format to execute a given
@ -201,6 +201,101 @@ $ docker network inspect ingress
]
```
### Using `verbose` option for `network inspect`
`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
details such as the service's VIP and port mappings. It also shows IPs of service tasks,
and the IPs of the nodes where the tasks are running.
Following is an example output for a overlay network `ov1` that has one service `s1`
attached to. service `s1` in this case has three replicas.
```bash
$ docker network inspect --verbose ov1
[
{
"Name": "ov1",
"Id": "ybmyjvao9vtzy3oorxbssj13b",
"Created": "2017-03-13T17:04:39.776106792Z",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
"Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
"EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
"MacAddress": "02:42:0a:00:00:04",
"IPv4Address": "10.0.0.4/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4097"
},
"Labels": {},
"Peers": [
{
"Name": "net-3-5d3cfd30a58c",
"IP": "192.168.33.13"
},
{
"Name": "net-1-6ecbc0040a73",
"IP": "192.168.33.11"
},
{
"Name": "net-2-fb80208efd75",
"IP": "192.168.33.12"
}
],
"Services": {
"s1": {
"VIP": "10.0.0.2",
"Ports": [],
"LocalLBIndex": 257,
"Tasks": [
{
"Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
"EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
"EndpointIP": "10.0.0.5",
"Info": {
"Host IP": "192.168.33.11"
}
},
{
"Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
"EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
"EndpointIP": "10.0.0.3",
"Info": {
"Host IP": "192.168.33.12"
}
},
{
"Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
"EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
"EndpointIP": "10.0.0.4",
"Info": {
"Host IP": "192.168.33.13"
}
}
]
}
}
}
]
```
## Related commands
* [network disconnect ](network_disconnect.md)

View file

@ -86,3 +86,96 @@ $ docker network inspect simple-network
}
]
```
`docker network inspect --verbose` for swarm mode overlay networks shows service-specific
details such as the service's VIP and port mappings. It also shows IPs of service tasks,
and the IPs of the nodes where the tasks are running.
Following is an example output for a overlay network `ov1` that has one service `s1`
attached to. service `s1` in this case has three replicas.
```bash
$ docker network inspect --verbose ov1
[
{
"Name": "ov1",
"Id": "ybmyjvao9vtzy3oorxbssj13b",
"Created": "2017-03-13T17:04:39.776106792Z",
"Scope": "swarm",
"Driver": "overlay",
"EnableIPv6": false,
"IPAM": {
"Driver": "default",
"Options": null,
"Config": [
{
"Subnet": "10.0.0.0/24",
"Gateway": "10.0.0.1"
}
]
},
"Internal": false,
"Attachable": false,
"Containers": {
"020403bd88a15f60747fd25d1ad5fa1272eb740e8a97fc547d8ad07b2f721c5e": {
"Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
"EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
"MacAddress": "02:42:0a:00:00:04",
"IPv4Address": "10.0.0.4/24",
"IPv6Address": ""
}
},
"Options": {
"com.docker.network.driver.overlay.vxlanid_list": "4097"
},
"Labels": {},
"Peers": [
{
"Name": "net-3-5d3cfd30a58c",
"IP": "192.168.33.13"
},
{
"Name": "net-1-6ecbc0040a73",
"IP": "192.168.33.11"
},
{
"Name": "net-2-fb80208efd75",
"IP": "192.168.33.12"
}
],
"Services": {
"s1": {
"VIP": "10.0.0.2",
"Ports": [],
"LocalLBIndex": 257,
"Tasks": [
{
"Name": "s1.2.q4hcq2aiiml25ubtrtg4q1txt",
"EndpointID": "040879b027e55fb658e8b60ae3b87c6cdac7d291e86a190a3b5ac6567b26511a",
"EndpointIP": "10.0.0.5",
"Info": {
"Host IP": "192.168.33.11"
}
},
{
"Name": "s1.3.yawl4cgkp7imkfx469kn9j6lm",
"EndpointID": "106edff9f120efe44068b834e1cddb5b39dd4a3af70211378b2f7a9e562bbad8",
"EndpointIP": "10.0.0.3",
"Info": {
"Host IP": "192.168.33.12"
}
},
{
"Name": "s1.1.pjn2ik0sfgkfzed3h0s00gs9o",
"EndpointID": "ad16946f416562d658f3bb30b9830d73ad91ccf6feae44411269cd0ff674714e",
"EndpointIP": "10.0.0.4",
"Info": {
"Host IP": "192.168.33.13"
}
}
]
}
}
}
]
```

View file

@ -23,7 +23,7 @@ github.com/RackSec/srslog 456df3a81436d29ba874f3590eeeee25d666f8a5
github.com/imdario/mergo 0.2.1
#get libnetwork packages
github.com/docker/libnetwork 1a019214c9cb80bd56219e5d6994a22caf302895
github.com/docker/libnetwork 4610dd67c7b9828bb4719d8aa2ac53a7f1f739d2
github.com/docker/go-events 18b43f1bc85d9cdd42c05a6cd2d444c7a200a894
github.com/armon/go-radix e39d623f12e8e41c7b5529e9a9dd67a1e2261f80
github.com/armon/go-metrics eb0af217e5e9747e41dd5303755356b62d28e3ec

View file

@ -44,6 +44,8 @@ type agent struct {
sync.Mutex
}
const libnetworkEPTable = "endpoint_table"
func getBindAddr(ifaceName string) (string, error) {
iface, err := net.InterfaceByName(ifaceName)
if err != nil {
@ -285,7 +287,7 @@ func (c *controller) agentInit(listenAddr, bindAddrOrInterface, advertiseAddr st
return err
}
ch, cancel := nDB.Watch("endpoint_table", "", "")
ch, cancel := nDB.Watch(libnetworkEPTable, "", "")
nodeCh, cancel := nDB.Watch(networkdb.NodeTable, "", "")
c.Lock()
@ -385,6 +387,111 @@ func (c *controller) agentClose() {
agent.networkDB.Close()
}
// Task has the backend container details
type Task struct {
Name string
EndpointID string
EndpointIP string
Info map[string]string
}
// ServiceInfo has service specific details along with the list of backend tasks
type ServiceInfo struct {
VIP string
LocalLBIndex int
Tasks []Task
Ports []string
}
type epRecord struct {
ep EndpointRecord
info map[string]string
lbIndex int
}
func (n *network) Services() map[string]ServiceInfo {
eps := make(map[string]epRecord)
if !n.isClusterEligible() {
return nil
}
agent := n.getController().getAgent()
if agent == nil {
return nil
}
// Walk through libnetworkEPTable and fetch the driver agnostic endpoint info
entries := agent.networkDB.GetTableByNetwork(libnetworkEPTable, n.id)
for eid, value := range entries {
var epRec EndpointRecord
nid := n.ID()
if err := proto.Unmarshal(value.([]byte), &epRec); err != nil {
logrus.Errorf("Unmarshal of libnetworkEPTable failed for endpoint %s in network %s, %v", eid, nid, err)
continue
}
i := n.getController().getLBIndex(epRec.ServiceID, nid, epRec.IngressPorts)
eps[eid] = epRecord{
ep: epRec,
lbIndex: i,
}
}
// Walk through the driver's tables, have the driver decode the entries
// and return the tuple {ep ID, value}. value is a string that coveys
// relevant info about the endpoint.
d, err := n.driver(true)
if err != nil {
logrus.Errorf("Could not resolve driver for network %s/%s while fetching services: %v", n.networkType, n.ID(), err)
return nil
}
for _, table := range n.driverTables {
if table.objType != driverapi.EndpointObject {
continue
}
entries := agent.networkDB.GetTableByNetwork(table.name, n.id)
for key, value := range entries {
epID, info := d.DecodeTableEntry(table.name, key, value.([]byte))
if ep, ok := eps[epID]; !ok {
logrus.Errorf("Inconsistent driver and libnetwork state for endpoint %s", epID)
} else {
ep.info = info
eps[epID] = ep
}
}
}
// group the endpoints into a map keyed by the service name
sinfo := make(map[string]ServiceInfo)
for ep, epr := range eps {
var (
s ServiceInfo
ok bool
)
if s, ok = sinfo[epr.ep.ServiceName]; !ok {
s = ServiceInfo{
VIP: epr.ep.VirtualIP,
LocalLBIndex: epr.lbIndex,
}
}
ports := []string{}
if s.Ports == nil {
for _, port := range epr.ep.IngressPorts {
p := fmt.Sprintf("Target: %d, Publish: %d", port.TargetPort, port.PublishedPort)
ports = append(ports, p)
}
s.Ports = ports
}
s.Tasks = append(s.Tasks, Task{
Name: epr.ep.Name,
EndpointID: ep,
EndpointIP: epr.ep.EndpointIP,
Info: epr.info,
})
sinfo[epr.ep.ServiceName] = s
}
return sinfo
}
func (n *network) isClusterEligible() bool {
if n.driverScope() != datastore.GlobalScope {
return false
@ -508,7 +615,7 @@ func (ep *endpoint) addServiceInfoToCluster() error {
}
if agent != nil {
if err := agent.networkDB.CreateEntry("endpoint_table", n.ID(), ep.ID(), buf); err != nil {
if err := agent.networkDB.CreateEntry(libnetworkEPTable, n.ID(), ep.ID(), buf); err != nil {
return err
}
}
@ -541,7 +648,7 @@ func (ep *endpoint) deleteServiceInfoFromCluster() error {
}
if agent != nil {
if err := agent.networkDB.DeleteEntry("endpoint_table", n.ID(), ep.ID()); err != nil {
if err := agent.networkDB.DeleteEntry(libnetworkEPTable, n.ID(), ep.ID()); err != nil {
return err
}
}
@ -559,8 +666,8 @@ func (n *network) addDriverWatches() {
if agent == nil {
return
}
for _, tableName := range n.driverTables {
ch, cancel := agent.networkDB.Watch(tableName, n.ID(), "")
for _, table := range n.driverTables {
ch, cancel := agent.networkDB.Watch(table.name, n.ID(), "")
agent.Lock()
agent.driverCancelFuncs[n.ID()] = append(agent.driverCancelFuncs[n.ID()], cancel)
agent.Unlock()
@ -571,9 +678,9 @@ func (n *network) addDriverWatches() {
return
}
agent.networkDB.WalkTable(tableName, func(nid, key string, value []byte) bool {
agent.networkDB.WalkTable(table.name, func(nid, key string, value []byte) bool {
if nid == n.ID() {
d.EventNotify(driverapi.Create, nid, tableName, key, value)
d.EventNotify(driverapi.Create, nid, table.name, key, value)
}
return false

View file

@ -72,6 +72,16 @@ type Driver interface {
// only invoked for the global scope driver.
EventNotify(event EventType, nid string, tableName string, key string, value []byte)
// DecodeTableEntry passes the driver a key, value pair from table it registered
// with libnetwork. Driver should return {object ID, map[string]string} tuple.
// If DecodeTableEntry is called for a table associated with NetworkObject or
// EndpointObject the return object ID should be the network id or endppoint id
// associated with that entry. map should have information about the object that
// can be presented to the user.
// For exampe: overlay driver returns the VTEP IP of the host that has the endpoint
// which is shown in 'network inspect --verbose'
DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string)
// Type returns the type of this driver, the network type this driver manages
Type() string
@ -84,7 +94,7 @@ type Driver interface {
type NetworkInfo interface {
// TableEventRegister registers driver interest in a given
// table name.
TableEventRegister(tableName string) error
TableEventRegister(tableName string, objType ObjectType) error
}
// InterfaceInfo provides a go interface for drivers to retrive
@ -175,3 +185,28 @@ const (
// Delete event is generated when a table entry is deleted.
Delete
)
// ObjectType represents the type of object driver wants to store in libnetwork's networkDB
type ObjectType int
const (
// EndpointObject should be set for libnetwork endpoint object related data
EndpointObject ObjectType = 1 + iota
// NetworkObject should be set for libnetwork network object related data
NetworkObject
// OpaqueObject is for driver specific data with no corresponding libnetwork object
OpaqueObject
)
// IsValidType validates the passed in type against the valid object types
func IsValidType(objType ObjectType) bool {
switch objType {
case EndpointObject:
fallthrough
case NetworkObject:
fallthrough
case OpaqueObject:
return true
}
return false
}

View file

@ -575,6 +575,10 @@ func (d *driver) NetworkFree(id string) error {
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
// Create a new network using bridge plugin
func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {

View file

@ -140,7 +140,6 @@ func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairp
hpNatRule = iptRule{table: iptables.Nat, chain: "POSTROUTING", preArgs: []string{"-t", "nat"}, args: []string{"-m", "addrtype", "--src-type", "LOCAL", "-o", bridgeIface, "-j", "MASQUERADE"}}
skipDNAT = iptRule{table: iptables.Nat, chain: DockerChain, preArgs: []string{"-t", "nat"}, args: []string{"-i", bridgeIface, "-j", "RETURN"}}
outRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-i", bridgeIface, "!", "-o", bridgeIface, "-j", "ACCEPT"}}
inRule = iptRule{table: iptables.Filter, chain: "FORWARD", args: []string{"-o", bridgeIface, "-m", "conntrack", "--ctstate", "RELATED,ESTABLISHED", "-j", "ACCEPT"}}
)
// Set NAT.
@ -173,11 +172,6 @@ func setupIPTablesInternal(bridgeIface string, addr net.Addr, icc, ipmasq, hairp
return err
}
// Set Accept on incoming packets for existing connections.
if err := programChainRule(inRule, "ACCEPT INCOMING", enable); err != nil {
return err
}
return nil
}

View file

@ -35,6 +35,10 @@ func (d *driver) NetworkFree(id string) error {
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()

View file

@ -108,3 +108,7 @@ func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}

View file

@ -110,3 +110,7 @@ func (d *driver) DiscoverDelete(dType discoverapi.DiscoveryType, data interface{
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}

View file

@ -35,6 +35,10 @@ func (d *driver) NetworkFree(id string) error {
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
d.Lock()
defer d.Unlock()

View file

@ -20,7 +20,7 @@ import (
)
const (
mark = uint32(0xD0C4E3)
r = 0xD0C4E3
timeout = 30
pktExpansion = 26 // SPI(4) + SeqN(4) + IV(8) + PadLength(1) + NextHeader(1) + ICV(8)
)
@ -31,6 +31,8 @@ const (
bidir
)
var spMark = netlink.XfrmMark{Value: uint32(r), Mask: 0xffffffff}
type key struct {
value []byte
tag uint32
@ -201,7 +203,7 @@ func programMangle(vni uint32, add bool) (err error) {
var (
p = strconv.FormatUint(uint64(vxlanPort), 10)
c = fmt.Sprintf("0>>22&0x3C@12&0xFFFFFF00=%d", int(vni)<<8)
m = strconv.FormatUint(uint64(mark), 10)
m = strconv.FormatUint(uint64(r), 10)
chain = "OUTPUT"
rule = []string{"-p", "udp", "--dport", p, "-m", "u32", "--u32", c, "-j", "MARK", "--set-mark", m}
a = "-A"
@ -271,6 +273,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f
Proto: netlink.XFRM_PROTO_ESP,
Spi: spi.reverse,
Mode: netlink.XFRM_MODE_TRANSPORT,
Reqid: r,
}
if add {
rSA.Aead = buildAeadAlgo(k, spi.reverse)
@ -296,6 +299,7 @@ func programSA(localIP, remoteIP net.IP, spi *spi, k *key, dir int, add bool) (f
Proto: netlink.XFRM_PROTO_ESP,
Spi: spi.forward,
Mode: netlink.XFRM_MODE_TRANSPORT,
Reqid: r,
}
if add {
fSA.Aead = buildAeadAlgo(k, spi.forward)
@ -325,17 +329,18 @@ func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error {
xfrmProgram = ns.NlHandle().XfrmPolicyAdd
}
fullMask := net.CIDRMask(8*len(fSA.Src), 8*len(fSA.Src))
// Create a congruent cidr
s := types.GetMinimalIP(fSA.Src)
d := types.GetMinimalIP(fSA.Dst)
fullMask := net.CIDRMask(8*len(s), 8*len(s))
fPol := &netlink.XfrmPolicy{
Src: &net.IPNet{IP: fSA.Src, Mask: fullMask},
Dst: &net.IPNet{IP: fSA.Dst, Mask: fullMask},
Src: &net.IPNet{IP: s, Mask: fullMask},
Dst: &net.IPNet{IP: d, Mask: fullMask},
Dir: netlink.XFRM_DIR_OUT,
Proto: 17,
DstPort: 4789,
Mark: &netlink.XfrmMark{
Value: mark,
},
Mark: &spMark,
Tmpls: []netlink.XfrmPolicyTmpl{
{
Src: fSA.Src,
@ -343,6 +348,7 @@ func programSP(fSA *netlink.XfrmState, rSA *netlink.XfrmState, add bool) error {
Proto: netlink.XFRM_PROTO_ESP,
Mode: netlink.XFRM_MODE_TRANSPORT,
Spi: fSA.Spi,
Reqid: r,
},
},
}
@ -426,6 +432,8 @@ func (d *driver) secMapWalk(f func(string, []*spi) ([]*spi, bool)) error {
}
func (d *driver) setKeys(keys []*key) error {
// Remove any stale policy, state
clearEncryptionStates()
// Accept the encryption keys and clear any stale encryption map
d.Lock()
d.keys = keys
@ -526,7 +534,7 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr
}
if newIdx > -1 {
// +RSA2
// +rSA2
programSA(lIP, rIP, spis[newIdx], curKeys[newIdx], reverse, true)
}
@ -535,16 +543,17 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr
fSA2, _, _ := programSA(lIP, rIP, spis[priIdx], curKeys[priIdx], forward, true)
// +fSP2, -fSP1
fullMask := net.CIDRMask(8*len(fSA2.Src), 8*len(fSA2.Src))
s := types.GetMinimalIP(fSA2.Src)
d := types.GetMinimalIP(fSA2.Dst)
fullMask := net.CIDRMask(8*len(s), 8*len(s))
fSP1 := &netlink.XfrmPolicy{
Src: &net.IPNet{IP: fSA2.Src, Mask: fullMask},
Dst: &net.IPNet{IP: fSA2.Dst, Mask: fullMask},
Src: &net.IPNet{IP: s, Mask: fullMask},
Dst: &net.IPNet{IP: d, Mask: fullMask},
Dir: netlink.XFRM_DIR_OUT,
Proto: 17,
DstPort: 4789,
Mark: &netlink.XfrmMark{
Value: mark,
},
Mark: &spMark,
Tmpls: []netlink.XfrmPolicyTmpl{
{
Src: fSA2.Src,
@ -552,6 +561,7 @@ func updateNodeKey(lIP, aIP, rIP net.IP, idxs []*spi, curKeys []*key, newIdx, pr
Proto: netlink.XFRM_PROTO_ESP,
Mode: netlink.XFRM_MODE_TRANSPORT,
Spi: fSA2.Spi,
Reqid: r,
},
},
}
@ -597,3 +607,33 @@ func (n *network) maxMTU() int {
}
return mtu
}
func clearEncryptionStates() {
nlh := ns.NlHandle()
spList, err := nlh.XfrmPolicyList(netlink.FAMILY_ALL)
if err != nil {
logrus.Warnf("Failed to retrieve SP list for cleanup: %v", err)
}
saList, err := nlh.XfrmStateList(netlink.FAMILY_ALL)
if err != nil {
logrus.Warnf("Failed to retrieve SA list for cleanup: %v", err)
}
for _, sp := range spList {
if sp.Mark != nil && sp.Mark.Value == spMark.Value {
if err := nlh.XfrmPolicyDel(&sp); err != nil {
logrus.Warnf("Failed to delete stale SP %s: %v", sp, err)
continue
}
logrus.Debugf("Removed stale SP: %s", sp)
}
}
for _, sa := range saList {
if sa.Reqid == r {
if err := nlh.XfrmStateDel(&sa); err != nil {
logrus.Warnf("Failed to delete stale SA %s: %v", sa, err)
continue
}
logrus.Debugf("Removed stale SA: %s", sa)
}
}
}

View file

@ -145,6 +145,23 @@ func (d *driver) Join(nid, eid string, sboxKey string, jinfo driverapi.JoinInfo,
return nil
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
if tablename != ovPeerTable {
logrus.Errorf("DecodeTableEntry: unexpected table name %s", tablename)
return "", nil
}
var peer PeerRecord
if err := proto.Unmarshal(value, &peer); err != nil {
logrus.Errorf("DecodeTableEntry: failed to unmarshal peer record for key %s: %v", key, err)
return "", nil
}
return key, map[string]string{
"Host IP": peer.TunnelEndpointIP,
}
}
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
if tableName != ovPeerTable {
logrus.Errorf("Unexpected table notification for table %s received", tableName)

View file

@ -159,7 +159,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
}
if nInfo != nil {
if err := nInfo.TableEventRegister(ovPeerTable); err != nil {
if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
return err
}
}

View file

@ -199,6 +199,10 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
func (d *driver) DeleteNetwork(nid string) error {
return types.NotImplementedErrorf("not implemented")
}

View file

@ -116,6 +116,10 @@ func (d *driver) NetworkFree(id string) error {
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
func (d *driver) CreateNetwork(id string, options map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
create := &api.CreateNetworkRequest{
NetworkID: id,

View file

@ -175,6 +175,10 @@ func (d *driver) NetworkFree(id string) error {
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
if len(ipV4Data) == 0 || ipV4Data[0].Pool.String() == "0.0.0.0/0" {
return types.BadRequestErrorf("ipv4 pool is empty")

View file

@ -149,6 +149,10 @@ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key stri
d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, true)
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
if err := validateID(nid, eid); err != nil {

View file

@ -153,7 +153,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
}
if nInfo != nil {
if err := nInfo.TableEventRegister(ovPeerTable); err != nil {
if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
return err
}
}

View file

@ -93,6 +93,10 @@ func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key stri
d.peerAdd(nid, eid, addr.IP, addr.Mask, mac, vtep, true)
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
// Leave method is invoked when a Sandbox detaches from an endpoint.
func (d *driver) Leave(nid, eid string) error {
if err := validateID(nid, eid); err != nil {

View file

@ -169,7 +169,7 @@ func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo d
n.interfaceName = interfaceName
if nInfo != nil {
if err := nInfo.TableEventRegister(ovPeerTable); err != nil {
if err := nInfo.TableEventRegister(ovPeerTable, driverapi.EndpointObject); err != nil {
return err
}
}

View file

@ -183,6 +183,10 @@ func (c *networkConfiguration) processIPAM(id string, ipamV4Data, ipamV6Data []d
func (d *driver) EventNotify(etype driverapi.EventType, nid, tableName, key string, value []byte) {
}
func (d *driver) DecodeTableEntry(tablename string, key string, value []byte) (string, map[string]string) {
return "", nil
}
// Create a new network
func (d *driver) CreateNetwork(id string, option map[string]interface{}, nInfo driverapi.NetworkInfo, ipV4Data, ipV6Data []driverapi.IPAMData) error {
if _, err := d.getNetwork(id); err == nil {

View file

@ -50,8 +50,7 @@ var (
bestEffortLock sync.Mutex
// ErrIptablesNotFound is returned when the rule is not found.
ErrIptablesNotFound = errors.New("Iptables not found")
probeOnce sync.Once
firewalldOnce sync.Once
initOnce sync.Once
)
// ChainInfo defines the iptables chain.
@ -86,22 +85,32 @@ func initFirewalld() {
}
}
func detectIptables() {
path, err := exec.LookPath("iptables")
if err != nil {
return
}
iptablesPath = path
supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
mj, mn, mc, err := GetVersion()
if err != nil {
logrus.Warnf("Failed to read iptables version: %v", err)
return
}
supportsCOpt = supportsCOption(mj, mn, mc)
}
func initIptables() {
probe()
initFirewalld()
detectIptables()
}
func initCheck() error {
initOnce.Do(initIptables)
if iptablesPath == "" {
probeOnce.Do(probe)
firewalldOnce.Do(initFirewalld)
path, err := exec.LookPath("iptables")
if err != nil {
return ErrIptablesNotFound
}
iptablesPath = path
supportsXlock = exec.Command(iptablesPath, "--wait", "-L", "-n").Run() == nil
mj, mn, mc, err := GetVersion()
if err != nil {
logrus.Warnf("Failed to read iptables version: %v", err)
return nil
}
supportsCOpt = supportsCOption(mj, mn, mc)
return ErrIptablesNotFound
}
return nil
}
@ -189,6 +198,26 @@ func ProgramChain(c *ChainInfo, bridgeName string, hairpinMode, enable bool) err
}
}
establish := []string{
"-o", bridgeName,
"-m", "conntrack",
"--ctstate", "RELATED,ESTABLISHED",
"-j", "ACCEPT"}
if !Exists(Filter, "FORWARD", establish...) && enable {
insert := append([]string{string(Insert), "FORWARD"}, establish...)
if output, err := Raw(insert...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Could not create establish rule to %s: %s", c.Table, output)
}
} else if Exists(Filter, "FORWARD", establish...) && !enable {
del := append([]string{string(Delete), "FORWARD"}, establish...)
if output, err := Raw(del...); err != nil {
return err
} else if len(output) != 0 {
return fmt.Errorf("Could not delete establish rule from %s: %s", c.Table, output)
}
}
}
return nil
}
@ -353,7 +382,11 @@ func exists(native bool, table Table, chain string, rule ...string) bool {
table = Filter
}
initCheck()
if err := initCheck(); err != nil {
// The exists() signature does not allow us to return an error, but at least
// we can skip the (likely invalid) exec invocation.
return false
}
if supportsCOpt {
// if exit status is 0 then return true, the rule exists
@ -436,9 +469,9 @@ func ExistChain(chain string, table Table) bool {
return false
}
// GetVersion reads the iptables version numbers
// GetVersion reads the iptables version numbers during initialization
func GetVersion() (major, minor, micro int, err error) {
out, err := Raw("--version")
out, err := exec.Command(iptablesPath, "--version").CombinedOutput()
if err == nil {
major, minor, micro = parseVersionNumbers(string(out))
}

View file

@ -74,6 +74,9 @@ type NetworkInfo interface {
// gossip cluster. For non-dynamic overlay networks and bridge networks it returns an
// empty slice
Peers() []networkdb.PeerInfo
//Services returns a map of services keyed by the service name with the details
//of all the tasks that belong to the service. Applicable only in swarm mode.
Services() map[string]ServiceInfo
}
// EndpointWalker is a client provided function which will be used to walk the Endpoints.
@ -108,6 +111,11 @@ type servicePorts struct {
target []serviceTarget
}
type networkDBTable struct {
name string
objType driverapi.ObjectType
}
// IpamConf contains all the ipam related configurations for a network
type IpamConf struct {
// The master address pool for containers and network interfaces
@ -208,7 +216,7 @@ type network struct {
attachable bool
inDelete bool
ingress bool
driverTables []string
driverTables []networkDBTable
dynamic bool
sync.Mutex
}
@ -1607,11 +1615,18 @@ func (n *network) Labels() map[string]string {
return lbls
}
func (n *network) TableEventRegister(tableName string) error {
func (n *network) TableEventRegister(tableName string, objType driverapi.ObjectType) error {
if !driverapi.IsValidType(objType) {
return fmt.Errorf("invalid object type %v in registering table, %s", objType, tableName)
}
t := networkDBTable{
name: tableName,
objType: objType,
}
n.Lock()
defer n.Unlock()
n.driverTables = append(n.driverTables, tableName)
n.driverTables = append(n.driverTables, t)
return nil
}

View file

@ -307,6 +307,22 @@ func (nDB *NetworkDB) UpdateEntry(tname, nid, key string, value []byte) error {
return nil
}
// GetTableByNetwork walks the networkdb by the give table and network id and
// returns a map of keys and values
func (nDB *NetworkDB) GetTableByNetwork(tname, nid string) map[string]interface{} {
entries := make(map[string]interface{})
nDB.indexes[byTable].WalkPrefix(fmt.Sprintf("/%s/%s", tname, nid), func(k string, v interface{}) bool {
entry := v.(*entry)
if entry.deleting {
return false
}
key := k[strings.LastIndex(k, "/")+1:]
entries[key] = entry.value
return false
})
return entries
}
// DeleteEntry deletes a table entry in NetworkDB for given (network,
// table, key) tuple and if the NetworkDB is part of the cluster
// propagates this event to the cluster.

View file

@ -18,6 +18,26 @@ func newService(name string, id string, ingressPorts []*PortConfig, aliases []st
}
}
func (c *controller) getLBIndex(sid, nid string, ingressPorts []*PortConfig) int {
skey := serviceKey{
id: sid,
ports: portConfigs(ingressPorts).String(),
}
c.Lock()
s, ok := c.serviceBindings[skey]
c.Unlock()
if !ok {
return 0
}
s.Lock()
lb := s.loadBalancers[nid]
s.Unlock()
return int(lb.fwMark)
}
func (c *controller) cleanupServiceBindings(cleanupNID string) {
var cleanupFuncs []func()