2022-02-22 08:15:12 +00:00
|
|
|
package server
|
thick-plugin: refactor multus
Multus is refactored as a thick plugin, featuring 2 main components:
- a server listening to a unix domain socket, running in a pod
- a shim, a binary on the host that will send JSON requests built from
its environment / stdin values to the aforementioned server.
The pod where the multus daemon is running must share the host's PID
namespace.
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
react to maintainers review
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
thick, deployment: update the daemonset spec
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
thick, config: validate the cni config passed by the runtime
Without this patch, we're blindly trusting anything sent by the server.
This way, we assure the requests arriving at the multus controller are
valid before hand.
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
thick: model client / server config
Also add a new command line parameter on the multus controller, pointing
it to the server configuration.
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
SQUASH candidate, thick, config: cleanup the configuration
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
multus: use args.args instead of an env variable
CNI is already filling the args structure; we should consume that
rather than rely on the environment variables.
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
unit tests: remove weird tests that check an impossible scenario
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
docs, thick: document the thick plugin variant
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
thick, server, multus: re-use common types
Signed-off-by: Miguel Duarte Barroso <mdbarroso@redhat.com>
2021-12-16 17:18:35 +00:00
|
|
|
|
|
|
|
import (
|
|
|
|
"bytes"
|
|
|
|
"encoding/json"
|
|
|
|
"fmt"
|
|
|
|
"io/ioutil"
|
|
|
|
"net"
|
|
|
|
"net/http"
|
|
|
|
"os"
|
|
|
|
"strings"
|
|
|
|
|
|
|
|
"github.com/containernetworking/cni/pkg/skel"
|
|
|
|
cnitypes "github.com/containernetworking/cni/pkg/types"
|
|
|
|
|
|
|
|
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/logging"
|
|
|
|
"gopkg.in/k8snetworkplumbingwg/multus-cni.v3/pkg/types"
|
|
|
|
)
|
|
|
|
|
|
|
|
const (
|
|
|
|
defaultMultusRunDir = "/var/run/multus-cni/"
|
|
|
|
)
|
|
|
|
|
|
|
|
// CmdAdd implements the CNI spec ADD command handler
|
|
|
|
func CmdAdd(args *skel.CmdArgs) error {
|
|
|
|
response, err := postRequest(args)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.Verbosef("CmdAdd (shim): %v", *response.Result)
|
|
|
|
return cnitypes.PrintResult(response.Result, response.Result.CNIVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CmdCheck implements the CNI spec CHECK command handler
|
|
|
|
func CmdCheck(args *skel.CmdArgs) error {
|
|
|
|
response, err := postRequest(args)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.Verbosef("CmdCheck (shim): %v", *response.Result)
|
|
|
|
return cnitypes.PrintResult(response.Result, response.Result.CNIVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
// CmdDel implements the CNI spec DEL command handler
|
|
|
|
func CmdDel(args *skel.CmdArgs) error {
|
|
|
|
response, err := postRequest(args)
|
|
|
|
if err != nil {
|
|
|
|
return err
|
|
|
|
}
|
|
|
|
|
|
|
|
logging.Verbosef("CmdDel (shim): %v", *response.Result)
|
|
|
|
return cnitypes.PrintResult(response.Result, response.Result.CNIVersion)
|
|
|
|
}
|
|
|
|
|
|
|
|
func postRequest(args *skel.CmdArgs) (*Response, error) {
|
|
|
|
multusShimConfig, err := shimConfig(args.StdinData)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("invalid CNI configuration passed to multus-shim: %w", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
cniRequest, err := newCNIRequest(args)
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
body, err := DoCNI("http://dummy/", cniRequest, SocketPath(multusShimConfig.MultusSocketDir))
|
|
|
|
if err != nil {
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
|
|
|
|
response := &Response{}
|
|
|
|
if err = json.Unmarshal(body, response); err != nil {
|
|
|
|
err = fmt.Errorf("failed to unmarshal response '%s': %v", string(body), err)
|
|
|
|
return nil, err
|
|
|
|
}
|
|
|
|
return response, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// Create and fill a Request with this Plugin's environment and stdin which
|
|
|
|
// contain the CNI variables and configuration
|
|
|
|
func newCNIRequest(args *skel.CmdArgs) (*Request, error) {
|
|
|
|
envMap := make(map[string]string)
|
|
|
|
for _, item := range os.Environ() {
|
|
|
|
idx := strings.Index(item, "=")
|
|
|
|
if idx > 0 {
|
|
|
|
envMap[strings.TrimSpace(item[:idx])] = item[idx+1:]
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
return &Request{
|
|
|
|
Env: envMap,
|
|
|
|
Config: args.StdinData,
|
|
|
|
}, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
func shimConfig(cniConfig []byte) (*types.ShimNetConf, error) {
|
|
|
|
multusConfig := &types.ShimNetConf{}
|
|
|
|
if err := json.Unmarshal(cniConfig, multusConfig); err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to gather the multus configuration: %w", err)
|
|
|
|
}
|
|
|
|
if multusConfig.MultusSocketDir == "" {
|
|
|
|
multusConfig.MultusSocketDir = defaultMultusRunDir
|
|
|
|
}
|
|
|
|
// Logging
|
|
|
|
logging.SetLogStderr(multusConfig.LogToStderr)
|
|
|
|
if multusConfig.LogFile != "" {
|
|
|
|
logging.SetLogFile(multusConfig.LogFile)
|
|
|
|
}
|
|
|
|
if multusConfig.LogLevel != "" {
|
|
|
|
logging.SetLogLevel(multusConfig.LogLevel)
|
|
|
|
}
|
|
|
|
return multusConfig, nil
|
|
|
|
}
|
|
|
|
|
|
|
|
// DoCNI sends a CNI request to the CNI server via JSON + HTTP over a root-owned unix socket,
|
|
|
|
// and returns the result
|
|
|
|
func DoCNI(url string, req interface{}, socketPath string) ([]byte, error) {
|
|
|
|
data, err := json.Marshal(req)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to marshal CNI request %v: %v", req, err)
|
|
|
|
}
|
|
|
|
|
|
|
|
client := &http.Client{
|
|
|
|
Transport: &http.Transport{
|
|
|
|
Dial: func(proto, addr string) (net.Conn, error) {
|
|
|
|
return net.Dial("unix", socketPath)
|
|
|
|
},
|
|
|
|
},
|
|
|
|
}
|
|
|
|
|
|
|
|
resp, err := client.Post(url, "application/json", bytes.NewReader(data))
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to send CNI request: %v", err)
|
|
|
|
}
|
|
|
|
defer resp.Body.Close()
|
|
|
|
|
|
|
|
body, err := ioutil.ReadAll(resp.Body)
|
|
|
|
if err != nil {
|
|
|
|
return nil, fmt.Errorf("failed to read CNI result: %v", err)
|
|
|
|
}
|
|
|
|
|
|
|
|
if resp.StatusCode != http.StatusOK {
|
|
|
|
return nil, fmt.Errorf("CNI request failed with status %v: '%s'", resp.StatusCode, string(body))
|
|
|
|
}
|
|
|
|
|
|
|
|
return body, nil
|
|
|
|
}
|