blob: eabcfd315de063d95c5a2eea0114344b551092fe [file] [log] [blame]
// Copyright The Monogon Project Authors.
// SPDX-License-Identifier: Apache-2.0
// Package cni implements an adapter between the go-cni interface and
// the Monogon gRPC Workload Attachment interface. As we do not intend to
// actually implement a CNI-compliant plugin it makes more sense to just cut
// out as much unnecessary logic and take over at the containerd API boundary.
package cni
import (
"context"
"fmt"
"net"
"google.golang.org/grpc"
"google.golang.org/grpc/credentials/insecure"
wlapi "source.monogon.dev/metropolis/node/core/network/workloads/spec"
)
func New(_ ...Opt) (CNI, error) {
conn, err := grpc.NewClient("unix:/ephemeral/workloadnet.sock", grpc.WithTransportCredentials(insecure.NewCredentials()))
if err != nil {
panic(err)
}
wlClient := wlapi.NewWorkloadNetworkingClient(conn)
return &adapter{
client: wlClient,
}, nil
}
type NamespaceOpts func(n *Namespace) error
// Namespace differs significantly from upstream as we do not have the actual
// underlying CNI interface and thus we do not need to transform the data into
// JSON keys.
type Namespace struct {
labels map[string]string
annotations map[string]string
portMapping []PortMapping
bandwidth BandWidth
dns DNS
cgroupPath string
}
func WithLabels(labels map[string]string) NamespaceOpts {
return func(n *Namespace) error {
n.labels = labels
return nil
}
}
func WithCapability(name string, capability interface{}) NamespaceOpts {
return func(n *Namespace) error {
if name == "io.kubernetes.cri.pod-annotations" {
n.annotations = capability.(map[string]string)
}
return nil
}
}
func WithCapabilityPortMap(portMapping []PortMapping) NamespaceOpts {
return func(c *Namespace) error {
c.portMapping = portMapping
return nil
}
}
func WithCapabilityBandWidth(bandWidth BandWidth) NamespaceOpts {
return func(c *Namespace) error {
c.bandwidth = bandWidth
return nil
}
}
func WithCapabilityDNS(dns DNS) NamespaceOpts {
return func(c *Namespace) error {
c.dns = dns
return nil
}
}
func WithCapabilityCgroupPath(cgroupPath string) NamespaceOpts {
return func(c *Namespace) error {
c.cgroupPath = cgroupPath
return nil
}
}
type adapter struct {
client wlapi.WorkloadNetworkingClient
}
func (s *adapter) Setup(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
var n Namespace
for _, opt := range opts {
opt(&n)
}
res, err := s.client.Attach(ctx, &wlapi.AttachRequest{
WorkloadId: n.labels["K8S_POD_UID"],
Netns: &wlapi.NetNSAttachment{
NetnsPath: path,
IfName: "eth0",
},
})
if err != nil {
return nil, fmt.Errorf("while requesting workload network attachment: %w", err)
}
// Provide IP to containerd/CRI, rest is ignored anyways.
var ipConfigs []*IPConfig
for _, ip := range res.Ip {
ipConfigs = append(ipConfigs, &IPConfig{IP: net.IP(ip)})
}
return &Result{
Interfaces: map[string]*Config{
"eth0": {
IPConfigs: ipConfigs,
},
},
}, nil
}
func (s *adapter) SetupSerially(ctx context.Context, id string, path string, opts ...NamespaceOpts) (*Result, error) {
// We do not support multiple plugins, the distinction between serial or
// parallel does not exist. Just forward the call.
return s.Setup(ctx, id, path, opts...)
}
func (s *adapter) Remove(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
var n Namespace
for _, opt := range opts {
opt(&n)
}
_, err := s.client.Detach(ctx, &wlapi.DetachRequest{
WorkloadId: n.labels["K8S_POD_UID"],
Netns: &wlapi.NetNSAttachment{
NetnsPath: path,
IfName: "eth0",
},
})
return err
}
func (s *adapter) Check(ctx context.Context, id string, path string, opts ...NamespaceOpts) error {
return nil
}
func (s *adapter) Load(opts ...Opt) error {
// Stub, we do not actually have any CNI config.
return nil
}
func (s *adapter) Status() error {
_, err := s.client.Status(context.Background(), &wlapi.StatusRequest{})
return err
}
func (s *adapter) GetConfig() *ConfigResult {
return &ConfigResult{}
}