...

Source file src/github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils/net-attach-def.go

Documentation: github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/utils

     1  // Copyright (c) 2019 Kubernetes Network Plumbing Working Group
     2  //
     3  // Licensed under the Apache License, Version 2.0 (the "License");
     4  // you may not use this file except in compliance with the License.
     5  // You may obtain a copy of the License at
     6  //
     7  //     http://www.apache.org/licenses/LICENSE-2.0
     8  //
     9  // Unless required by applicable law or agreed to in writing, software
    10  // distributed under the License is distributed on an "AS IS" BASIS,
    11  // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    12  // See the License for the specific language governing permissions and
    13  // limitations under the License.
    14  
    15  package utils
    16  
    17  import (
    18  	"encoding/json"
    19  	"fmt"
    20  	"regexp"
    21  	"strings"
    22  
    23  	cnitypes "github.com/containernetworking/cni/pkg/types"
    24  	"github.com/containernetworking/cni/pkg/types/current"
    25  	metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
    26  
    27  	v1 "github.com/k8snetworkplumbingwg/network-attachment-definition-client/pkg/apis/k8s.cni.cncf.io/v1"
    28  
    29  	corev1 "k8s.io/api/core/v1"
    30  	"k8s.io/client-go/kubernetes"
    31  	"k8s.io/client-go/util/retry"
    32  )
    33  
    34  // convertDNS converts CNI's DNS type to client DNS
    35  func convertDNS(dns cnitypes.DNS) *v1.DNS {
    36  	var v1dns v1.DNS
    37  
    38  	v1dns.Nameservers = append([]string{}, dns.Nameservers...)
    39  	v1dns.Domain = dns.Domain
    40  	v1dns.Search = append([]string{}, dns.Search...)
    41  	v1dns.Options = append([]string{}, dns.Options...)
    42  
    43  	return &v1dns
    44  }
    45  
    46  // SetNetworkStatus updates the Pod status
    47  func SetNetworkStatus(client kubernetes.Interface, pod *corev1.Pod, statuses []v1.NetworkStatus) error {
    48  	if client == nil {
    49  		return fmt.Errorf("no client set")
    50  	}
    51  
    52  	if pod == nil {
    53  		return fmt.Errorf("no pod set")
    54  	}
    55  
    56  	var networkStatus []string
    57  	if statuses != nil {
    58  		for _, status := range statuses {
    59  			data, err := json.MarshalIndent(status, "", "    ")
    60  			if err != nil {
    61  				return fmt.Errorf("SetNetworkStatus: error with Marshal Indent: %v", err)
    62  			}
    63  			networkStatus = append(networkStatus, string(data))
    64  		}
    65  	}
    66  
    67  	_, err := setPodNetworkStatus(client, pod, fmt.Sprintf("[%s]", strings.Join(networkStatus, ",")))
    68  	if err != nil {
    69  		return fmt.Errorf("SetNetworkStatus: failed to update the pod %s in out of cluster comm: %v", pod.Name, err)
    70  	}
    71  	return nil
    72  }
    73  
    74  func setPodNetworkStatus(client kubernetes.Interface, pod *corev1.Pod, networkstatus string) (*corev1.Pod, error) {
    75  	if len(pod.Annotations) == 0 {
    76  		pod.Annotations = make(map[string]string)
    77  	}
    78  
    79  	coreClient := client.CoreV1()
    80  
    81  	pod.Annotations[v1.NetworkStatusAnnot] = networkstatus
    82  	pod = pod.DeepCopy()
    83  	var err error
    84  
    85  	if resultErr := retry.RetryOnConflict(retry.DefaultBackoff, func() error {
    86  		if err != nil {
    87  			// Re-get the pod unless it's the first attempt to update
    88  			pod, err = coreClient.Pods(pod.Namespace).Get(pod.Name, metav1.GetOptions{})
    89  			if err != nil {
    90  				return err
    91  			}
    92  		}
    93  
    94  		pod, err = coreClient.Pods(pod.Namespace).UpdateStatus(pod)
    95  		return err
    96  	}); resultErr != nil {
    97  		return nil, fmt.Errorf("status update failed for pod %s/%s: %v", pod.Namespace, pod.Name, resultErr)
    98  	}
    99  	return pod, nil
   100  }
   101  
   102  // GetNetworkStatus returns pod's network status
   103  func GetNetworkStatus(pod *corev1.Pod) ([]v1.NetworkStatus, error) {
   104  	if pod == nil {
   105  		return nil, fmt.Errorf("cannot find pod")
   106  	}
   107  	if pod.Annotations == nil {
   108  		return nil, fmt.Errorf("cannot find pod annotation")
   109  	}
   110  
   111  	netStatusesJson, ok := pod.Annotations[v1.NetworkStatusAnnot]
   112  	if !ok {
   113  		return nil, fmt.Errorf("cannot find network status")
   114  	}
   115  
   116  	var netStatuses []v1.NetworkStatus
   117  	err := json.Unmarshal([]byte(netStatusesJson), &netStatuses)
   118  
   119  	return netStatuses, err
   120  }
   121  
   122  // CreateNetworkStatus create NetworkStatus from CNI result
   123  func CreateNetworkStatus(r cnitypes.Result, networkName string, defaultNetwork bool) (*v1.NetworkStatus, error) {
   124  	netStatus := &v1.NetworkStatus{}
   125  	netStatus.Name = networkName
   126  	netStatus.Default = defaultNetwork
   127  
   128  	// Convert whatever the IPAM result was into the current Result type
   129  	result, err := current.NewResultFromResult(r)
   130  	if err != nil {
   131  		return netStatus, fmt.Errorf("error convert the type.Result to current.Result: %v", err)
   132  	}
   133  
   134  	for _, ifs := range result.Interfaces {
   135  		// Only pod interfaces can have sandbox information
   136  		if ifs.Sandbox != "" {
   137  			netStatus.Interface = ifs.Name
   138  			netStatus.Mac = ifs.Mac
   139  		}
   140  	}
   141  
   142  	for _, ipconfig := range result.IPs {
   143  		if ipconfig.Version == "4" && ipconfig.Address.IP.To4() != nil {
   144  			netStatus.IPs = append(netStatus.IPs, ipconfig.Address.IP.String())
   145  		}
   146  
   147  		if ipconfig.Version == "6" && ipconfig.Address.IP.To16() != nil {
   148  			netStatus.IPs = append(netStatus.IPs, ipconfig.Address.IP.String())
   149  		}
   150  	}
   151  
   152  	v1dns := convertDNS(result.DNS)
   153  	netStatus.DNS = *v1dns
   154  
   155  	return netStatus, nil
   156  }
   157  
   158  // ParsePodNetworkAnnotation parses Pod annotation for net-attach-def and get NetworkSelectionElement
   159  func ParsePodNetworkAnnotation(pod *corev1.Pod) ([]*v1.NetworkSelectionElement, error) {
   160  	netAnnot := pod.Annotations[v1.NetworkAttachmentAnnot]
   161  	defaultNamespace := pod.Namespace
   162  
   163  	if len(netAnnot) == 0 {
   164  		return nil, &v1.NoK8sNetworkError{Message: "no kubernetes network found"}
   165  	}
   166  
   167  	networks, err := ParseNetworkAnnotation(netAnnot, defaultNamespace)
   168  	if err != nil {
   169  		return nil, err
   170  	}
   171  	return networks, nil
   172  }
   173  
   174  // ParseNetworkAnnotation parses actual annotation string and get NetworkSelectionElement
   175  func ParseNetworkAnnotation(podNetworks, defaultNamespace string) ([]*v1.NetworkSelectionElement, error) {
   176  	var networks []*v1.NetworkSelectionElement
   177  
   178  	if podNetworks == "" {
   179  		return nil, fmt.Errorf("parsePodNetworkAnnotation: pod annotation not having \"network\" as key")
   180  	}
   181  
   182  	if strings.IndexAny(podNetworks, "[{\"") >= 0 {
   183  		if err := json.Unmarshal([]byte(podNetworks), &networks); err != nil {
   184  			return nil, fmt.Errorf("parsePodNetworkAnnotation: failed to parse pod Network Attachment Selection Annotation JSON format: %v", err)
   185  		}
   186  	} else {
   187  		// Comma-delimited list of network attachment object names
   188  		for _, item := range strings.Split(podNetworks, ",") {
   189  			// Remove leading and trailing whitespace.
   190  			item = strings.TrimSpace(item)
   191  
   192  			// Parse network name (i.e. <namespace>/<network name>@<ifname>)
   193  			netNsName, networkName, netIfName, err := parsePodNetworkObjectText(item)
   194  			if err != nil {
   195  				return nil, fmt.Errorf("parsePodNetworkAnnotation: %v", err)
   196  			}
   197  
   198  			networks = append(networks, &v1.NetworkSelectionElement{
   199  				Name:             networkName,
   200  				Namespace:        netNsName,
   201  				InterfaceRequest: netIfName,
   202  			})
   203  		}
   204  	}
   205  
   206  	for _, net := range networks {
   207  		if net.Namespace == "" {
   208  			net.Namespace = defaultNamespace
   209  		}
   210  	}
   211  
   212  	return networks, nil
   213  }
   214  
   215  // parsePodNetworkObjectText parses annotation text and returns
   216  // its triplet, (namespace, name, interface name).
   217  func parsePodNetworkObjectText(podnetwork string) (string, string, string, error) {
   218  	var netNsName string
   219  	var netIfName string
   220  	var networkName string
   221  
   222  	slashItems := strings.Split(podnetwork, "/")
   223  	if len(slashItems) == 2 {
   224  		netNsName = strings.TrimSpace(slashItems[0])
   225  		networkName = slashItems[1]
   226  	} else if len(slashItems) == 1 {
   227  		networkName = slashItems[0]
   228  	} else {
   229  		return "", "", "", fmt.Errorf("Invalid network object (failed at '/')")
   230  	}
   231  
   232  	atItems := strings.Split(networkName, "@")
   233  	networkName = strings.TrimSpace(atItems[0])
   234  	if len(atItems) == 2 {
   235  		netIfName = strings.TrimSpace(atItems[1])
   236  	} else if len(atItems) != 1 {
   237  		return "", "", "", fmt.Errorf("Invalid network object (failed at '@')")
   238  	}
   239  
   240  	// Check and see if each item matches the specification for valid attachment name.
   241  	// "Valid attachment names must be comprised of units of the DNS-1123 label format"
   242  	// [a-z0-9]([-a-z0-9]*[a-z0-9])?
   243  	// And we allow at (@), and forward slash (/) (units separated by commas)
   244  	// It must start and end alphanumerically.
   245  	allItems := []string{netNsName, networkName, netIfName}
   246  	for i := range allItems {
   247  		matched, _ := regexp.MatchString("^[a-z0-9]([-a-z0-9]*[a-z0-9])?$", allItems[i])
   248  		if !matched && len([]rune(allItems[i])) > 0 {
   249  			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]))
   250  		}
   251  	}
   252  
   253  	return netNsName, networkName, netIfName, nil
   254  }
   255  

View as plain text