2018-04-20 17:30:12 +00:00
// Copyright (c) 2017 Intel Corporation
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package k8sclient
import (
"bytes"
"encoding/json"
"fmt"
2018-05-03 20:36:49 +00:00
"regexp"
2018-05-09 10:47:37 +00:00
"strings"
2018-04-20 17:30:12 +00:00
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/client-go/kubernetes"
"k8s.io/client-go/tools/clientcmd"
"github.com/Intel-Corp/multus-cni/types"
2018-04-27 18:43:20 +00:00
"github.com/containernetworking/cni/libcni"
2018-04-20 17:30:12 +00:00
"github.com/containernetworking/cni/pkg/skel"
cnitypes "github.com/containernetworking/cni/pkg/types"
)
// NoK8sNetworkError indicates error, no network in kubernetes
2018-04-23 06:51:11 +00:00
type NoK8sNetworkError struct {
message string
}
2018-04-20 17:30:12 +00:00
2018-04-23 06:51:11 +00:00
func ( e * NoK8sNetworkError ) Error ( ) string { return string ( e . message ) }
2018-04-20 17:30:12 +00:00
func createK8sClient ( kubeconfig string ) ( * kubernetes . Clientset , error ) {
// uses the current context in kubeconfig
config , err := clientcmd . BuildConfigFromFlags ( "" , kubeconfig )
if err != nil {
2018-04-24 02:29:44 +00:00
return nil , fmt . Errorf ( "createK8sClient: failed to get context for the kubeconfig %v, refer Multus README.md for the usage guide: %v" , kubeconfig , err )
2018-04-20 17:30:12 +00:00
}
// creates the clientset
return kubernetes . NewForConfig ( config )
}
func getPodNetworkAnnotation ( client * kubernetes . Clientset , k8sArgs types . K8sArgs ) ( string , error ) {
var annot string
var err error
pod , err := client . Pods ( string ( k8sArgs . K8S_POD_NAMESPACE ) ) . Get ( fmt . Sprintf ( "%s" , string ( k8sArgs . K8S_POD_NAME ) ) , metav1 . GetOptions { } )
if err != nil {
2018-04-24 02:29:44 +00:00
return annot , fmt . Errorf ( "getPodNetworkAnnotation: failed to query the pod %v in out of cluster comm: %v" , string ( k8sArgs . K8S_POD_NAME ) , err )
2018-04-20 17:30:12 +00:00
}
return pod . Annotations [ "kubernetes.v1.cni.cncf.io/networks" ] , nil
}
func parsePodNetworkObjectName ( podnetwork string ) ( string , string , string , error ) {
var netNsName string
var netIfName string
var networkName string
slashItems := strings . Split ( podnetwork , "/" )
if len ( slashItems ) == 2 {
netNsName = strings . TrimSpace ( slashItems [ 0 ] )
networkName = slashItems [ 1 ]
} else if len ( slashItems ) == 1 {
networkName = slashItems [ 0 ]
} else {
return "" , "" , "" , fmt . Errorf ( "Invalid network object (failed at '/')" )
}
atItems := strings . Split ( networkName , "@" )
networkName = strings . TrimSpace ( atItems [ 0 ] )
if len ( atItems ) == 2 {
netIfName = strings . TrimSpace ( atItems [ 1 ] )
} else if len ( atItems ) != 1 {
return "" , "" , "" , fmt . Errorf ( "Invalid network object (failed at '@')" )
}
2018-05-03 20:36:49 +00:00
// Check and see if each item matches the specification for valid attachment name.
// "Valid attachment names must be comprised of units of the DNS-1123 label format"
// [a-z0-9]([-a-z0-9]*[a-z0-9])?
// And we allow at (@), and forward slash (/) (units separated by commas)
// It must start and end alphanumerically.
allItems := [ ] string { netNsName , networkName , netIfName }
for i := range allItems {
matched , _ := regexp . MatchString ( "^[a-z0-9]([-a-z0-9]*[a-z0-9])?$" , allItems [ i ] )
2018-05-09 10:47:37 +00:00
if ! matched && len ( [ ] rune ( allItems [ i ] ) ) > 0 {
return "" , "" , "" , fmt . Errorf ( fmt . Sprintf ( "Failed to parse: one or more items did not match comma-delimited format (must consist of lower case alphanumeric characters). Must start and end with an alphanumeric character), mismatch @ '%v'" , allItems [ i ] ) )
2018-05-03 20:36:49 +00:00
}
}
2018-04-20 17:30:12 +00:00
return netNsName , networkName , netIfName , nil
}
func parsePodNetworkObject ( podnetwork string ) ( [ ] map [ string ] interface { } , error ) {
var podNet [ ] map [ string ] interface { }
if podnetwork == "" {
return nil , fmt . Errorf ( "parsePodNetworkObject: pod annotation not having \"network\" as key, refer Multus README.md for the usage guide" )
}
// Parse the podnetwork string, and assume it is JSON.
if err := json . Unmarshal ( [ ] byte ( podnetwork ) , & podNet ) ; err != nil {
2018-05-03 20:36:49 +00:00
// If JSON doesn't parse, assume comma-delimited.
2018-04-20 17:30:12 +00:00
commaItems := strings . Split ( podnetwork , "," )
2018-05-03 20:36:49 +00:00
2018-04-20 17:30:12 +00:00
// Build a map from the comma delimited items.
for i := range commaItems {
2018-05-03 20:36:49 +00:00
// Remove leading and trailing whitespace.
commaItems [ i ] = strings . TrimSpace ( commaItems [ i ] )
2018-04-20 17:30:12 +00:00
// Parse network name (i.e. <namespace>/<network name>@<ifname>)
netNsName , networkName , netIfName , err := parsePodNetworkObjectName ( commaItems [ i ] )
if err != nil {
return nil , fmt . Errorf ( "parsePodNetworkObject: %v" , err )
}
m := make ( map [ string ] interface { } )
m [ "name" ] = networkName
if netNsName != "" {
m [ "namespace" ] = netNsName
}
if netIfName != "" {
m [ "interfaceRequest" ] = netIfName
}
podNet = append ( podNet , m )
}
}
return podNet , nil
}
2018-05-09 10:47:37 +00:00
func getCNIConfig ( name string , primary bool , ifname string , confdir string ) ( string , error ) {
2018-04-27 18:43:20 +00:00
2018-04-23 17:30:41 +00:00
// In the absence of valid keys in a Spec, the runtime (or
// meta-plugin) should load and execute a CNI .configlist
// or .config (in that order) file on-disk whose JSON
// “name” key matches this Network object’ s name.
2018-04-20 17:30:12 +00:00
2018-04-27 18:43:20 +00:00
//Todo
// support conflist for chaining mechanism
// In part, adapted from K8s pkg/kubelet/dockershim/network/cni/cni.go#getDefaultCNINetwork
2018-05-09 10:47:37 +00:00
files , err := libcni . ConfFiles ( confdir , [ ] string { ".conf" , ".json" } )
2018-04-27 18:43:20 +00:00
switch {
case err != nil :
2018-05-09 10:47:37 +00:00
fmt . Errorf ( "No networks found in %s" , confdir )
2018-04-27 18:43:20 +00:00
case len ( files ) == 0 :
2018-05-09 10:47:37 +00:00
fmt . Errorf ( "No networks found in %s" , confdir )
2018-04-27 18:43:20 +00:00
}
for _ , confFile := range files {
conf , err := libcni . ConfFromFile ( confFile )
if err != nil {
return "" , fmt . Errorf ( "Error loading CNI config file %s: %v" , confFile , err )
}
if conf . Network . Name == name {
// Ensure the config has a "type" so we know what plugin to run.
// Also catches the case where somebody put a conflist into a conf file.
if conf . Network . Type == "" {
return "" , fmt . Errorf ( "Error loading CNI config file %s: no 'type'; perhaps this is a .conflist?" , confFile )
}
return getConfig ( string ( conf . Bytes [ : ] ) , primary , ifname ) , nil
}
}
2018-05-09 10:47:37 +00:00
return "" , fmt . Errorf ( "no network available in the name %s in cni dir %s" , name , confdir )
2018-04-23 17:30:41 +00:00
}
func getPlugin ( plugin string , name string , primary bool , ifname string ) string {
tmpconfig := [ ] string { }
tmpconfig = append ( tmpconfig , fmt . Sprintf ( ` { "cniVersion": "0.3.1" , "name": "%s", "type": "%s" ` , name , plugin ) )
if primary != false {
tmpconfig = append ( tmpconfig , ` , "masterplugin": true ` )
2018-04-20 17:30:12 +00:00
}
2018-04-23 17:30:41 +00:00
if ifname != "" {
tmpconfig = append ( tmpconfig , fmt . Sprintf ( ` , "ifnameRequest": "%s" ` , ifname ) )
}
tmpconfig = append ( tmpconfig , "}" )
2018-04-27 18:43:20 +00:00
return strings . Join ( tmpconfig , "" )
2018-04-23 17:30:41 +00:00
}
func getConfig ( config string , primary bool , ifname string ) string {
tmpconfig := [ ] string { }
2018-05-07 08:25:43 +00:00
config = strings . TrimSpace ( config )
2018-04-23 17:30:41 +00:00
tmpconfig = append ( tmpconfig , config [ : 1 ] )
2018-04-20 17:30:12 +00:00
if primary != false {
2018-04-23 17:30:41 +00:00
tmpconfig = append ( tmpconfig , ` "masterplugin": true, ` )
2018-04-20 17:30:12 +00:00
}
if ifname != "" {
2018-04-23 17:30:41 +00:00
tmpconfig = append ( tmpconfig , fmt . Sprintf ( ` "ifnameRequest": "%s", ` , ifname ) )
2018-04-20 17:30:12 +00:00
}
2018-04-23 17:30:41 +00:00
tmpconfig = append ( tmpconfig , config [ 1 : ] )
2018-04-27 18:43:20 +00:00
return strings . Join ( tmpconfig , "" )
2018-04-23 17:30:41 +00:00
}
func getNetSpec ( ns types . NetworkSpec , name string , primary bool , ifname string ) ( string , error ) {
2018-04-20 17:30:12 +00:00
2018-04-23 17:30:41 +00:00
if ns . Plugin == "" && ns . Config == "" {
return "" , fmt . Errorf ( "Network Object spec plugin and config can't be empty" )
2018-04-20 17:30:12 +00:00
}
2018-04-23 17:30:41 +00:00
if ns . Plugin != "" && ns . Config != "" {
return "" , fmt . Errorf ( "Network Object spec can't have both plugin and config" )
}
if ns . Plugin != "" {
// Plugin contains the name of a CNI plugin on-disk in a
// runtime-defined path (eg /opt/cni/bin and/or other paths.
// This plugin should be executed with a basic CNI JSON
// configuration on stdin containing the Network object
// name and the plugin:
// { “cniVersion”: “0.3.1”, “type”: <Plugin>, “name”: <Network.Name> }
// and any additional “runtimeConfig” field per the
// CNI specification and conventions.
return getPlugin ( ns . Plugin , name , primary , ifname ) , nil
}
// Config contains a standard JSON-encoded CNI configuration
// or configuration list which defines the plugin chain to
// execute. If present, this key takes precedence over
// ‘ Plugin’ .
return getConfig ( ns . Config , primary , ifname ) , nil
}
2018-05-09 10:47:37 +00:00
func getNetObject ( net types . Network , primary bool , ifname string , confdir string ) ( string , error ) {
2018-04-23 17:30:41 +00:00
var config string
var err error
if ( types . NetworkSpec { } ) == net . Spec {
2018-05-09 10:47:37 +00:00
config , err = getCNIConfig ( net . Metadata . Name , primary , ifname , confdir )
2018-04-23 17:30:41 +00:00
if err != nil {
return "" , fmt . Errorf ( "getNetObject: err in getCNIConfig: %v" , err )
}
} else {
config , err = getNetSpec ( net . Spec , net . Metadata . Name , primary , ifname )
if err != nil {
return "" , fmt . Errorf ( "getNetObject: err in getNetSpec: %v" , err )
}
}
2018-04-20 17:30:12 +00:00
2018-04-23 17:30:41 +00:00
return config , nil
2018-04-20 17:30:12 +00:00
}
2018-05-09 10:47:37 +00:00
func getnetplugin ( client * kubernetes . Clientset , networkinfo map [ string ] interface { } , primary bool , confdir string ) ( string , error ) {
2018-04-20 17:30:12 +00:00
networkname := networkinfo [ "name" ] . ( string )
if networkname == "" {
return "" , fmt . Errorf ( "getnetplugin: network name can't be empty" )
}
netNsName := "default"
if networkinfo [ "namespace" ] != nil {
netNsName = networkinfo [ "namespace" ] . ( string )
}
tprclient := fmt . Sprintf ( "/apis/kubernetes.cni.cncf.io/v1/namespaces/%s/networks/%s" , netNsName , networkname )
netobjdata , err := client . ExtensionsV1beta1 ( ) . RESTClient ( ) . Get ( ) . AbsPath ( tprclient ) . DoRaw ( )
if err != nil {
return "" , fmt . Errorf ( "getnetplugin: failed to get CRD (result: %s), refer Multus README.md for the usage guide: %v" , netobjdata , err )
}
2018-04-23 17:30:41 +00:00
netobj := types . Network { }
if err := json . Unmarshal ( netobjdata , & netobj ) ; err != nil {
2018-04-20 17:30:12 +00:00
return "" , fmt . Errorf ( "getnetplugin: failed to get the netplugin data: %v" , err )
}
ifnameRequest := ""
if networkinfo [ "interfaceRequest" ] != nil {
ifnameRequest = networkinfo [ "interfaceRequest" ] . ( string )
}
2018-05-09 10:47:37 +00:00
netargs , err := getNetObject ( netobj , primary , ifnameRequest , confdir )
2018-04-20 17:30:12 +00:00
if err != nil {
return "" , err
}
return netargs , nil
}
2018-05-09 10:47:37 +00:00
func getPodNetworkObj ( client * kubernetes . Clientset , netObjs [ ] map [ string ] interface { } , confdir string ) ( string , error ) {
2018-04-20 17:30:12 +00:00
var np string
var err error
var str bytes . Buffer
str . WriteString ( "[" )
for index , net := range netObjs {
var primary bool
if index == 0 {
primary = true
}
2018-05-09 10:47:37 +00:00
np , err = getnetplugin ( client , net , primary , confdir )
2018-04-20 17:30:12 +00:00
if err != nil {
return "" , fmt . Errorf ( "getPodNetworkObj: failed in getting the netplugin: %v" , err )
}
str . WriteString ( np )
if index != ( len ( netObjs ) - 1 ) {
str . WriteString ( "," )
}
}
str . WriteString ( "]" )
netconf := str . String ( )
return netconf , nil
}
func getMultusDelegates ( delegate string ) ( [ ] map [ string ] interface { } , error ) {
tmpNetconf := & types . NetConf { }
tmpDelegate := "{\"delegates\": " + delegate + "}"
if delegate == "" {
return nil , fmt . Errorf ( "getMultusDelegates: TPR network obj data can't be empty" )
}
if err := json . Unmarshal ( [ ] byte ( tmpDelegate ) , tmpNetconf ) ; err != nil {
2018-04-23 17:30:41 +00:00
return nil , fmt . Errorf ( "getMultusDelegates: failed to load netconf for delegate %v: %v" , delegate , err )
2018-04-20 17:30:12 +00:00
}
if tmpNetconf . Delegates == nil {
return nil , fmt . Errorf ( ` getMultusDelegates: "delegates" is must, refer Multus README.md for the usage guide ` )
}
return tmpNetconf . Delegates , nil
}
2018-05-09 10:47:37 +00:00
func GetK8sNetwork ( args * skel . CmdArgs , kubeconfig string , confdir string ) ( [ ] map [ string ] interface { } , error ) {
2018-04-20 17:30:12 +00:00
k8sArgs := types . K8sArgs { }
var podNet [ ] map [ string ] interface { }
err := cnitypes . LoadArgs ( args . Args , & k8sArgs )
if err != nil {
return podNet , err
}
k8sclient , err := createK8sClient ( kubeconfig )
if err != nil {
return podNet , err
}
netAnnot , err := getPodNetworkAnnotation ( k8sclient , k8sArgs )
if err != nil {
return podNet , err
}
if len ( netAnnot ) == 0 {
2018-04-23 06:51:11 +00:00
return podNet , & NoK8sNetworkError { "no kubernetes network found" }
2018-04-20 17:30:12 +00:00
}
netObjs , err := parsePodNetworkObject ( netAnnot )
if err != nil {
return podNet , err
}
2018-05-09 10:47:37 +00:00
multusDelegates , err := getPodNetworkObj ( k8sclient , netObjs , confdir )
2018-04-20 17:30:12 +00:00
if err != nil {
return podNet , err
}
podNet , err = getMultusDelegates ( multusDelegates )
if err != nil {
return podNet , err
}
return podNet , nil
}