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 }