forked from openshift/ovn-kubernetes
-
Notifications
You must be signed in to change notification settings - Fork 0
/
cniserver.go
154 lines (135 loc) · 4.59 KB
/
cniserver.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
package cni
import (
"encoding/json"
"fmt"
"io/ioutil"
"net/http"
"strings"
"github.com/gorilla/mux"
"github.com/sirupsen/logrus"
"github.com/openvswitch/ovn-kubernetes/go-controller/pkg/config"
)
// *** The Server is PRIVATE API between OVN components and may be
// changed at any time. It is in no way a supported interface or API. ***
//
// The Server accepts pod setup/teardown requests from the OVN
// CNI plugin, which is itself called by kubelet when pod networking
// should be set up or torn down. The OVN CNI plugin gathers up
// the standard CNI environment variables and network configuration provided
// on stdin and forwards them to the Server over a private, root-only
// Unix domain socket, using HTTP as the transport and JSON as the protocol.
//
// The Server interprets standard CNI environment variables as specified
// by the Container Network Interface (CNI) specification available here:
// https://github.com/containernetworking/cni/blob/master/SPEC.md
// While the Server interface is not itself versioned, as the CNI
// specification requires that CNI network configuration is versioned, and
// since the OVN CNI plugin passes that configuration to the
// Server, versioning is ensured in exactly the same way as an executable
// CNI plugin would be versioned.
//
// Security: since the Unix domain socket created by the Server is owned
// by root and inaccessible to any other user, no unprivileged process may
// access the Server. The Unix domain socket and its parent directory are
// removed and re-created with 0700 permissions each time ovnkube on the node is
// started.
// NewCNIServer creates and returns a new Server object which will listen on a socket in the given path
func NewCNIServer(rundir string) *Server {
if len(rundir) == 0 {
rundir = serverRunDir
}
router := mux.NewRouter()
s := &Server{
Server: http.Server{
Handler: router,
},
rundir: rundir,
}
router.NotFoundHandler = http.HandlerFunc(http.NotFound)
router.HandleFunc("/", s.handleCNIRequest).Methods("POST")
return s
}
// Split the "CNI_ARGS" environment variable's value into a map. CNI_ARGS
// contains arbitrary key/value pairs separated by ';' and is for runtime or
// plugin specific uses. Kubernetes passes the pod namespace and name in
// CNI_ARGS.
func gatherCNIArgs(env map[string]string) (map[string]string, error) {
cniArgs, ok := env["CNI_ARGS"]
if !ok {
return nil, fmt.Errorf("missing CNI_ARGS: '%s'", env)
}
mapArgs := make(map[string]string)
for _, arg := range strings.Split(cniArgs, ";") {
parts := strings.Split(arg, "=")
if len(parts) != 2 {
return nil, fmt.Errorf("invalid CNI_ARG '%s'", arg)
}
mapArgs[strings.TrimSpace(parts[0])] = strings.TrimSpace(parts[1])
}
return mapArgs, nil
}
func cniRequestToPodRequest(r *http.Request) (*PodRequest, error) {
var cr Request
b, _ := ioutil.ReadAll(r.Body)
if err := json.Unmarshal(b, &cr); err != nil {
return nil, fmt.Errorf("JSON unmarshal error: %v", err)
}
cmd, ok := cr.Env["CNI_COMMAND"]
if !ok {
return nil, fmt.Errorf("unexpected or missing CNI_COMMAND")
}
req := &PodRequest{
Command: command(cmd),
Result: make(chan *PodResult),
}
req.SandboxID, ok = cr.Env["CNI_CONTAINERID"]
if !ok {
return nil, fmt.Errorf("missing CNI_CONTAINERID")
}
req.Netns, ok = cr.Env["CNI_NETNS"]
if !ok {
return nil, fmt.Errorf("missing CNI_NETNS")
}
req.IfName, ok = cr.Env["CNI_IFNAME"]
if !ok {
req.IfName = "eth0"
}
cniArgs, err := gatherCNIArgs(cr.Env)
if err != nil {
return nil, err
}
req.PodNamespace, ok = cniArgs["K8S_POD_NAMESPACE"]
if !ok {
return nil, fmt.Errorf("missing K8S_POD_NAMESPACE")
}
req.PodName, ok = cniArgs["K8S_POD_NAME"]
if !ok {
return nil, fmt.Errorf("missing K8S_POD_NAME")
}
conf, err := config.ReadCNIConfig(cr.Config)
if err != nil {
return nil, fmt.Errorf("broken stdin args")
}
req.CNIConf = conf
return req, nil
}
// Dispatch a pod request to the request handler and return the result to the
// CNI server client
func (s *Server) handleCNIRequest(w http.ResponseWriter, r *http.Request) {
req, err := cniRequestToPodRequest(r)
if err != nil {
http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
return
}
logrus.Infof("Waiting for %s result for pod %s/%s", req.Command, req.PodNamespace, req.PodName)
result, err := s.requestFunc(req)
if err != nil {
http.Error(w, fmt.Sprintf("%v", err), http.StatusBadRequest)
} else {
// Empty response JSON means success with no body
w.Header().Set("Content-Type", "application/json")
if _, err := w.Write(result); err != nil {
logrus.Warningf("Error writing %s HTTP response: %v", req.Command, err)
}
}
}