k8s.io/kubernetes@v1.31.0-alpha.0.0.20240520171757-56147500dadc/test/images/pets/peer-finder/peer-finder.go (about)

     1  /*
     2  Copyright 2014 The Kubernetes Authors.
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  // A small utility program to lookup hostnames of endpoints in a service.
    18  package main
    19  
    20  import (
    21  	"flag"
    22  	"fmt"
    23  	"io"
    24  	"log"
    25  	"net"
    26  	"os"
    27  	"os/exec"
    28  	"regexp"
    29  	"sort"
    30  	"strings"
    31  	"time"
    32  
    33  	"k8s.io/apimachinery/pkg/util/sets"
    34  )
    35  
    36  const (
    37  	pollPeriod = 1 * time.Second
    38  )
    39  
    40  var (
    41  	onChange  = flag.String("on-change", "", "Script to run on change, must accept a new line separated list of peers via stdin.")
    42  	onStart   = flag.String("on-start", "", "Script to run on start, must accept a new line separated list of peers via stdin.")
    43  	svc       = flag.String("service", "", "Governing service responsible for the DNS records of the domain this pod is in.")
    44  	namespace = flag.String("ns", "", "The namespace this pod is running in. If unspecified, the POD_NAMESPACE env var is used.")
    45  	domain    = flag.String("domain", "", "The Cluster Domain which is used by the Cluster, if not set tries to determine it from /etc/resolv.conf file.")
    46  )
    47  
    48  func lookup(svcName string) (sets.String, error) {
    49  	endpoints := sets.NewString()
    50  	_, srvRecords, err := net.LookupSRV("", "", svcName)
    51  	if err != nil {
    52  		return endpoints, err
    53  	}
    54  	for _, srvRecord := range srvRecords {
    55  		// The SRV records ends in a "." for the root domain
    56  		ep := fmt.Sprintf("%v", srvRecord.Target[:len(srvRecord.Target)-1])
    57  		endpoints.Insert(ep)
    58  	}
    59  	return endpoints, nil
    60  }
    61  
    62  func shellOut(sendStdin, script string) {
    63  	log.Printf("execing: %v with stdin: %v", script, sendStdin)
    64  
    65  	cmd := exec.Command(script)
    66  	stdin, err := cmd.StdinPipe()
    67  	if err != nil {
    68  		log.Fatalf("Failed to get stdin pipe: %v", err)
    69  	}
    70  	stdout, err := cmd.StdoutPipe()
    71  	if err != nil {
    72  		log.Fatalf("Failed to get stdout pipe: %v", err)
    73  	}
    74  
    75  	cmd.Start()
    76  
    77  	stdin.Write([]byte(sendStdin))
    78  	stdin.Close()
    79  
    80  	out, err := io.ReadAll(stdout)
    81  	if err != nil {
    82  		log.Fatalf("Failed to execute %v: %v, err: %v", script, string(out), err)
    83  	}
    84  
    85  	log.Print(string(out))
    86  }
    87  
    88  func main() {
    89  	flag.Parse()
    90  
    91  	ns := *namespace
    92  	if ns == "" {
    93  		ns = os.Getenv("POD_NAMESPACE")
    94  	}
    95  	if *svc == "" || ns == "" || (*onChange == "" && *onStart == "") {
    96  		log.Fatalf("Incomplete args, require -on-change and/or -on-start, -service and -ns or an env var for POD_NAMESPACE.")
    97  	}
    98  
    99  	hostname, err := os.Hostname()
   100  	if err != nil {
   101  		log.Fatalf("Failed to get hostname: %s", err)
   102  	}
   103  	var domainName string
   104  
   105  	// If domain is not provided, try to get it from resolv.conf
   106  	if *domain == "" {
   107  		resolvConfBytes, err := os.ReadFile("/etc/resolv.conf")
   108  		resolvConf := string(resolvConfBytes)
   109  		if err != nil {
   110  			log.Fatal("Unable to read /etc/resolv.conf")
   111  		}
   112  
   113  		var re *regexp.Regexp
   114  		if ns == "" {
   115  			// Looking for a domain that looks like with *.svc.**
   116  			re, err = regexp.Compile(`\A(.*\n)*search\s{1,}(.*\s{1,})*(?P<goal>[a-zA-Z0-9-]{1,63}.svc.([a-zA-Z0-9-]{1,63}\.)*[a-zA-Z0-9]{2,63})`)
   117  		} else {
   118  			// Looking for a domain that looks like svc.**
   119  			re, err = regexp.Compile(`\A(.*\n)*search\s{1,}(.*\s{1,})*(?P<goal>svc.([a-zA-Z0-9-]{1,63}\.)*[a-zA-Z0-9]{2,63})`)
   120  		}
   121  		if err != nil {
   122  			log.Fatalf("Failed to create regular expression: %v", err)
   123  		}
   124  
   125  		groupNames := re.SubexpNames()
   126  		result := re.FindStringSubmatch(resolvConf)
   127  		for k, v := range result {
   128  			if groupNames[k] == "goal" {
   129  				if ns == "" {
   130  					// Domain is complete if ns is empty
   131  					domainName = v
   132  				} else {
   133  					// Need to convert svc.** into ns.svc.**
   134  					domainName = ns + "." + v
   135  				}
   136  				break
   137  			}
   138  		}
   139  		log.Printf("Determined Domain to be %s", domainName)
   140  
   141  	} else {
   142  		domainName = strings.Join([]string{ns, "svc", *domain}, ".")
   143  	}
   144  
   145  	if *svc == "" || domainName == "" || (*onChange == "" && *onStart == "") {
   146  		log.Fatalf("Incomplete args, require -on-change and/or -on-start, -service and -ns or an env var for POD_NAMESPACE.")
   147  	}
   148  
   149  	myName := strings.Join([]string{hostname, *svc, domainName}, ".")
   150  	script := *onStart
   151  	if script == "" {
   152  		script = *onChange
   153  		log.Printf("No on-start supplied, on-change %v will be applied on start.", script)
   154  	}
   155  	for peers := sets.NewString(); script != ""; time.Sleep(pollPeriod) {
   156  		newPeers, err := lookup(*svc)
   157  		if err != nil {
   158  			log.Printf("%v", err)
   159  			continue
   160  		}
   161  		if newPeers.Equal(peers) || !newPeers.Has(myName) {
   162  			log.Printf("Have not found myself in list yet.\nMy Hostname: %s\nHosts in list: %s", myName, strings.Join(newPeers.List(), ", "))
   163  			continue
   164  		}
   165  		peerList := newPeers.List()
   166  		sort.Strings(peerList)
   167  		log.Printf("Peer list updated\nwas %v\nnow %v", peers.List(), newPeers.List())
   168  		shellOut(strings.Join(peerList, "\n"), script)
   169  		peers = newPeers
   170  		script = *onChange
   171  	}
   172  	// TODO: Exit if there's no on-change?
   173  	log.Printf("Peer finder exiting")
   174  }