github.com/deemoprobe/k8s-first-commit@v0.0.0-20230430165612-a541f1982be3/pkg/proxy/config/etcd.go (about)

     1  /*
     2  Copyright 2014 Google Inc. All rights reserved.
     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  // Watches etcd and gets the full configuration on preset intervals.
    18  // Expects the list of exposed services to live under:
    19  // registry/services
    20  // which in etcd is exposed like so:
    21  // http://<etcd server>/v2/keys/registry/services
    22  //
    23  // The port that proxy needs to listen in for each service is a value in:
    24  // registry/services/<service>
    25  //
    26  // The endpoints for each of the services found is a json string
    27  // representing that service at:
    28  // /registry/services/<service>/endpoint
    29  // and the format is:
    30  // '[ { "machine": <host>, "name": <name", "port": <port> },
    31  //    { "machine": <host2>, "name": <name2", "port": <port2> }
    32  //  ]',
    33  //
    34  package config
    35  
    36  import (
    37  	"encoding/json"
    38  	"fmt"
    39  	"log"
    40  	"strings"
    41  	"time"
    42  
    43  	"github.com/GoogleCloudPlatform/kubernetes/pkg/api"
    44  	"github.com/coreos/go-etcd/etcd"
    45  )
    46  
    47  const RegistryRoot = "registry/services"
    48  
    49  type ConfigSourceEtcd struct {
    50  	client           *etcd.Client
    51  	serviceChannel   chan ServiceUpdate
    52  	endpointsChannel chan EndpointsUpdate
    53  }
    54  
    55  func NewConfigSourceEtcd(client *etcd.Client, serviceChannel chan ServiceUpdate, endpointsChannel chan EndpointsUpdate) ConfigSourceEtcd {
    56  	config := ConfigSourceEtcd{
    57  		client:           client,
    58  		serviceChannel:   serviceChannel,
    59  		endpointsChannel: endpointsChannel,
    60  	}
    61  	go config.Run()
    62  	return config
    63  }
    64  
    65  func (impl ConfigSourceEtcd) Run() {
    66  	// Initially, just wait for the etcd to come up before doing anything more complicated.
    67  	var services []api.Service
    68  	var endpoints []api.Endpoints
    69  	var err error
    70  	for {
    71  		services, endpoints, err = impl.GetServices()
    72  		if err == nil {
    73  			break
    74  		}
    75  		log.Printf("Failed to get any services: %v", err)
    76  		time.Sleep(2 * time.Second)
    77  	}
    78  
    79  	if len(services) > 0 {
    80  		serviceUpdate := ServiceUpdate{Op: SET, Services: services}
    81  		impl.serviceChannel <- serviceUpdate
    82  	}
    83  	if len(endpoints) > 0 {
    84  		endpointsUpdate := EndpointsUpdate{Op: SET, Endpoints: endpoints}
    85  		impl.endpointsChannel <- endpointsUpdate
    86  	}
    87  
    88  	// Ok, so we got something back from etcd. Let's set up a watch for new services, and
    89  	// their endpoints
    90  	go impl.WatchForChanges()
    91  
    92  	for {
    93  		services, endpoints, err = impl.GetServices()
    94  		if err != nil {
    95  			log.Printf("ConfigSourceEtcd: Failed to get services: %v", err)
    96  		} else {
    97  			if len(services) > 0 {
    98  				serviceUpdate := ServiceUpdate{Op: SET, Services: services}
    99  				impl.serviceChannel <- serviceUpdate
   100  			}
   101  			if len(endpoints) > 0 {
   102  				endpointsUpdate := EndpointsUpdate{Op: SET, Endpoints: endpoints}
   103  				impl.endpointsChannel <- endpointsUpdate
   104  			}
   105  		}
   106  		time.Sleep(30 * time.Second)
   107  	}
   108  }
   109  
   110  // Finds the list of services and their endpoints from etcd.
   111  // This operation is akin to a set a known good at regular intervals.
   112  func (impl ConfigSourceEtcd) GetServices() ([]api.Service, []api.Endpoints, error) {
   113  	response, err := impl.client.Get(RegistryRoot+"/specs", true, false)
   114  	if err != nil {
   115  		log.Printf("Failed to get the key %s: %v", RegistryRoot, err)
   116  		return make([]api.Service, 0), make([]api.Endpoints, 0), err
   117  	}
   118  	if response.Node.Dir == true {
   119  		retServices := make([]api.Service, len(response.Node.Nodes))
   120  		retEndpoints := make([]api.Endpoints, len(response.Node.Nodes))
   121  		// Ok, so we have directories, this list should be the list
   122  		// of services. Find the local port to listen on and remote endpoints
   123  		// and create a Service entry for it.
   124  		for i, node := range response.Node.Nodes {
   125  			var svc api.Service
   126  			err = json.Unmarshal([]byte(node.Value), &svc)
   127  			if err != nil {
   128  				log.Printf("Failed to load Service: %s (%#v)", node.Value, err)
   129  				continue
   130  			}
   131  			retServices[i] = svc
   132  			endpoints, err := impl.GetEndpoints(svc.ID)
   133  			if err != nil {
   134  				log.Printf("Couldn't get endpoints for %s : %v skipping", svc.ID, err)
   135  			}
   136  			log.Printf("Got service: %s on localport %d mapping to: %s", svc.ID, svc.Port, endpoints)
   137  			retEndpoints[i] = endpoints
   138  		}
   139  		return retServices, retEndpoints, err
   140  	}
   141  	return nil, nil, fmt.Errorf("did not get the root of the registry %s", RegistryRoot)
   142  }
   143  
   144  func (impl ConfigSourceEtcd) GetEndpoints(service string) (api.Endpoints, error) {
   145  	key := fmt.Sprintf(RegistryRoot + "/endpoints/" + service)
   146  	response, err := impl.client.Get(key, true, false)
   147  	if err != nil {
   148  		log.Printf("Failed to get the key: %s %v", key, err)
   149  		return api.Endpoints{}, err
   150  	}
   151  	// Parse all the endpoint specifications in this value.
   152  	return ParseEndpoints(response.Node.Value)
   153  }
   154  
   155  // EtcdResponseToServiceAndLocalport takes an etcd response and pulls it apart to find
   156  // service
   157  func EtcdResponseToService(response *etcd.Response) (*api.Service, error) {
   158  	if response.Node == nil {
   159  		return nil, fmt.Errorf("invalid response from etcd: %#v", response)
   160  	}
   161  	var svc api.Service
   162  	err := json.Unmarshal([]byte(response.Node.Value), &svc)
   163  	if err != nil {
   164  		return nil, err
   165  	}
   166  	return &svc, err
   167  }
   168  
   169  func ParseEndpoints(jsonString string) (api.Endpoints, error) {
   170  	var e api.Endpoints
   171  	err := json.Unmarshal([]byte(jsonString), &e)
   172  	return e, err
   173  }
   174  
   175  func (impl ConfigSourceEtcd) WatchForChanges() {
   176  	log.Print("Setting up a watch for new services")
   177  	watchChannel := make(chan *etcd.Response)
   178  	go impl.client.Watch("/registry/services/", 0, true, watchChannel, nil)
   179  	for {
   180  		watchResponse := <-watchChannel
   181  		impl.ProcessChange(watchResponse)
   182  	}
   183  }
   184  
   185  func (impl ConfigSourceEtcd) ProcessChange(response *etcd.Response) {
   186  	log.Printf("Processing a change in service configuration... %s", *response)
   187  
   188  	// If it's a new service being added (signified by a localport being added)
   189  	// then process it as such
   190  	if strings.Contains(response.Node.Key, "/endpoints/") {
   191  		impl.ProcessEndpointResponse(response)
   192  	} else if response.Action == "set" {
   193  		service, err := EtcdResponseToService(response)
   194  		if err != nil {
   195  			log.Printf("Failed to parse %s Port: %s", response, err)
   196  			return
   197  		}
   198  
   199  		log.Printf("New service added/updated: %#v", service)
   200  		serviceUpdate := ServiceUpdate{Op: ADD, Services: []api.Service{*service}}
   201  		impl.serviceChannel <- serviceUpdate
   202  		return
   203  	}
   204  	if response.Action == "delete" {
   205  		parts := strings.Split(response.Node.Key[1:], "/")
   206  		if len(parts) == 4 {
   207  			log.Printf("Deleting service: %s", parts[3])
   208  			serviceUpdate := ServiceUpdate{Op: REMOVE, Services: []api.Service{api.Service{JSONBase: api.JSONBase{ID: parts[3]}}}}
   209  			impl.serviceChannel <- serviceUpdate
   210  			return
   211  		} else {
   212  			log.Printf("Unknown service delete: %#v", parts)
   213  		}
   214  	}
   215  }
   216  
   217  func (impl ConfigSourceEtcd) ProcessEndpointResponse(response *etcd.Response) {
   218  	log.Printf("Processing a change in endpoint configuration... %s", *response)
   219  	var endpoints api.Endpoints
   220  	err := json.Unmarshal([]byte(response.Node.Value), &endpoints)
   221  	if err != nil {
   222  		log.Printf("Failed to parse service out of etcd key: %v : %+v", response.Node.Value, err)
   223  		return
   224  	}
   225  	endpointsUpdate := EndpointsUpdate{Op: ADD, Endpoints: []api.Endpoints{endpoints}}
   226  	impl.endpointsChannel <- endpointsUpdate
   227  }