blob: 6942df3d3fa1ed0d8540f3078f71d8cf183b569b [file] [log] [blame]
Zack Williams41513bf2018-07-07 20:08:35 -07001/*
2 * Copyright 2017-present Open Networking Foundation
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 */
Sergio Slobodrianbe829272017-07-17 14:45:45 -040016package main // import "ciena.com/envoyd"
17
18import (
Richard Jankowski15274592017-12-12 15:52:37 -050019 "context"
Sergio Slobodrianbe829272017-07-17 14:45:45 -040020 "os"
21 "os/exec"
22 "fmt"
23 "log"
24 "strconv"
25 "time"
26 "net"
27 "io/ioutil"
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040028 "io"
Sergio Slobodrianbe829272017-07-17 14:45:45 -040029 "text/template"
30 "encoding/json"
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040031 "sync"
32 "flag"
33 "bufio"
Sergio Slobodrianbe829272017-07-17 14:45:45 -040034 consulapi "github.com/hashicorp/consul/api"
Richard Jankowski15274592017-12-12 15:52:37 -050035 etcdapi "github.com/coreos/etcd/clientv3"
Sergio Slobodrianbe829272017-07-17 14:45:45 -040036)
37
38// DATA STRUCTURES
39
Sergio Slobodrianbe829272017-07-17 14:45:45 -040040//Client provides an interface for getting data out of Consul
41type Client interface {
42// Get a Service from consulapi
43 Service(string, string) ([]string, error)
44// Register a service with local agent
45 Register(string, int) error
46// Deregister a service with local agent
47 DeRegister(string) error
48}
49
50type client struct {
51 consulapi *consulapi.Client
52}
53
Richard Jankowski15274592017-12-12 15:52:37 -050054type KvConnectFunc func(string, string) (error)
55type KvMonitorFunc func()
56
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040057type EnvoyConfigVars struct {
58 VolthaVip string
59 VolthaRR []string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040060 vcorePort string
61 HttpPort string
62 HttpsPort string
63 GrpcPort string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040064}
65
66type VolthaClusterEntry struct {
67 Prefix string
68 Id string
69 Host string
70}
71
Sergio Slobodrianbe829272017-07-17 14:45:45 -040072// This struct is not used yet
73// TODO: Update the daemon to use this structure to for a
74// more object oriented implementation
75type EnvoyControl struct {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040076 // Command line parameters
77 assignmentKey string
78 envoyConfigTemplate string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040079 envoyConfigTemplateBoth string
80 envoyConfigTemplateNoHttps string
81 envoyConfigTemplateNoHttp string
82 envoyHttpPort string
83 envoyHttpsPort string
84 httpDisabled bool
85 httpsDisabled bool
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040086 envoyConfig string
87 vcoreSvcName string
88 consulSvcName string
89 vcorePort string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040090 envoyGrpcPort string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040091 consulPort string
Richard Jankowski15274592017-12-12 15:52:37 -050092 kvStore string
93 kvSvcName string
94 kvPort string
95 kvConnect map[string]KvConnectFunc
96 kvMonitor map[string]KvMonitorFunc
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040097 retries int
Sergio Slobodrianbe829272017-07-17 14:45:45 -040098 waitTime int
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040099 // Runtime variables
100 consul * consulapi.Client
Richard Jankowski15274592017-12-12 15:52:37 -0500101 etcd * etcdapi.Client
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400102 vcoreHostIpName string
103 vcoreIdName string
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400104 vc []VolthaClusterEntry
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400105 ipAddrs map[string][]string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400106 restartEpoch int
107 reLock sync.Mutex // Exclusive access to the restartEpoch
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400108}
109
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400110
111func NewEnvoyControl() (ec * EnvoyControl) {
112 var envCtrl EnvoyControl = EnvoyControl { // Default values
113 // Command line parameters
114 assignmentKey: "service/voltha/data/core/assignment",
115 envoyConfigTemplate: "/envoy/voltha-grpc-proxy.template.json",
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400116 envoyConfigTemplateBoth: "/envoy/voltha-grpc-proxy.template.json",
117 envoyConfigTemplateNoHttps: "/envoy/voltha-grpc-proxy-no-https.template.json",
118 envoyConfigTemplateNoHttp: "/envoy/voltha-grpc-proxy-no-http.template.json",
119 envoyHttpsPort: "8443",
120 envoyHttpPort: "8882",
121 envoyGrpcPort: "50555",
122 httpDisabled: false,
123 httpsDisabled: false,
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400124 envoyConfig: "/envoy/voltha-grpc-proxy.json",
125 //envoyLogFile: "/envoy/voltha_access_log.log",
126 vcoreSvcName: "vcore",
127 consulSvcName: "consul",
128 vcorePort: "50556",
129 consulPort: "8500",
Richard Jankowski15274592017-12-12 15:52:37 -0500130 kvStore: "consul",
131 kvSvcName: "consul",
132 kvPort: "8500",
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400133 retries: 10,
134 waitTime: 2,
135 // Runtime variables
136 vcoreHostIpName: "host",
137 vcoreIdName: "id",
138 ipAddrs: make(map[string][]string),
139 restartEpoch: 0,
140 }
141 ec = &envCtrl
Richard Jankowski15274592017-12-12 15:52:37 -0500142 ec.kvConnect = make(map[string]KvConnectFunc)
143 ec.kvConnect["consul"] = ec.consulConnect
144 ec.kvConnect["etcd"] = ec.etcdConnect
145 ec.kvMonitor = make(map[string]KvMonitorFunc)
146 ec.kvMonitor["consul"] = ec.monitorConsulKey
147 ec.kvMonitor["etcd"] = ec.monitorEtcdKey
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400148 return
149}
150
151func (ec * EnvoyControl) resolveServiceAddress(serviceName string) (err error) {
152 for i := 0; i < ec.retries; i++ {
153 ec.ipAddrs[serviceName], err = net.LookupHost(serviceName)
154 if err != nil {
155 log.Printf("%s name resolution failed %d time(s) retrying...", serviceName, i+1)
156 } else {
157 //fmt.Printf("%s address = %s\n",serviceName, addrs[0])
158 break
159 }
160 time.Sleep(time.Duration(ec.waitTime) * time.Second)
161 }
162 if err != nil {
Richard Jankowskid4454382018-02-08 16:21:43 -0500163 log.Printf("%s name resolution failed %d times giving up", serviceName, ec.retries)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400164 }
165 return
166}
167
168func (ec * EnvoyControl) consulConnect(serviceName string, port string) (err error) {
169 // Fire up a consul client and get the kv store
170 cConfig := consulapi.DefaultNonPooledConfig()
171 cConfig.Address = ec.ipAddrs[serviceName][0] + ":" + port
172 ec.consul, err = consulapi.NewClient(cConfig)
173 if err != nil {
174 log.Fatal("error creating consul client aborting")
175 return
176 }
177 return
178}
179
Richard Jankowski15274592017-12-12 15:52:37 -0500180func (ec * EnvoyControl) etcdConnect(serviceName string, port string) (err error) {
181 // Fire up an etcd client to access the kv store
182 cfg := etcdapi.Config {
183 Endpoints: []string{serviceName + ":" + port},
184 DialTimeout: 5 * time.Second,
185 }
186 ec.etcd, err = etcdapi.New(cfg)
187 if err != nil {
188 log.Fatal("Failed to create etcd client, aborting...")
189 return
190 }
191 return
192}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400193
194//NewConsul returns a Client interface for given consulapi address
195func NewConsulClient(addr string) (*client, error) {
196 config := consulapi.DefaultConfig()
197 config.Address = addr
198 c, err := consulapi.NewClient(config)
199 if err != nil {
200 return nil, err
201 }
202 return &client{consulapi: c}, nil
203}
204
205// Register a service with consulapi local agent
206func (c *client) Register(name string, port int) error {
207 reg := &consulapi.AgentServiceRegistration{
208 ID: name,
209 Name: name,
210 Port: port,
211 }
212 return c.consulapi.Agent().ServiceRegister(reg)
213}
214
215// DeRegister a service with consulapi local agent
216func (c *client) DeRegister(id string) error {
217 return c.consulapi.Agent().ServiceDeregister(id)
218}
219
220// Service return a service
221func (c *client) Service(service, tag string) ([]*consulapi.ServiceEntry, *consulapi.QueryMeta, error) {
222 passingOnly := true
223 addrs, meta, err := c.consulapi.Health().Service(service, tag, passingOnly, nil)
224 if len(addrs) == 0 && err == nil {
225 return nil, nil, fmt.Errorf("service ( %s ) was not found", service)
226 }
227 if err != nil {
228 return nil, nil, err
229 }
230 return addrs, meta, nil
231}
232
233// Starts envoy with the current restartEpoch
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400234func (ec * EnvoyControl) startEnvoy() {
235 var curEpoch int
236 var err error
237 var count int
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400238
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400239 ec.reLock.Lock() // Make sure we've got exclusive access to the variable
240 cmd := exec.Command("/usr/local/bin/envoy", "--restart-epoch", strconv.Itoa(ec.restartEpoch),
241 "--config-path", ec.envoyConfig, "--parent-shutdown-time-s", "10")
242
243 curEpoch = ec.restartEpoch
244 ec.restartEpoch += 1
245 ec.reLock.Unlock() // Done, release the lock.
246
247 stderr, err := cmd.StderrPipe()
248 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400249 log.Fatal("Couldn't attach to stderr running envoy command: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400250 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400251 stdout, err := cmd.StdoutPipe()
252 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400253 log.Fatal("Couldn't attach to stdout running envoy command: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400254 }
255 so := bufio.NewReader(stdout)
256 se := bufio.NewReader(stderr)
257
258 if err = cmd.Start(); err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400259 log.Fatal("Error starting envoy: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400260 }
261 log.Printf("Envoy(%d) started", curEpoch)
262 soEof := false
263 seEof := false
264
265 data := make([]byte, 80)
266 log.Printf("Log forwarding for envoy(%d) started", curEpoch)
267 for {
268 data = make([]byte, 80)
269 count, err = so.Read(data)
270 log.Printf("ENVOY_LOG(%d): %s", curEpoch, string(data))
271 if err == io.EOF {
272 if seEof == true {
273 break
274 } else {
275 soEof = true
276 }
277 } else if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400278 log.Fatal("Attempt to read envoy standard out failed: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400279 } else if count > 0 {
280 log.Printf("ENVOY_LOG(%d)(%d): %s",curEpoch,count,string(data))
281 }
282 data = make([]byte, 80)
283 count, err = se.Read(data)
284 if err == io.EOF {
285 if soEof == true {
286 break
287 } else {
288 seEof = true
289 }
290 } else if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400291 log.Fatal("Attempt to read envoy standard err failed: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400292 } else if count > 0 {
293 log.Printf("ENVOY_LOG(%d)(%d): %s",curEpoch,count,string(data))
294 }
295 }
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400296 log.Printf("Waiting on envoy %d to exit", curEpoch)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400297 if err = cmd.Wait(); err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400298 log.Fatal("Envoy %d exited with an unexpected exit code: %s", curEpoch, err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400299 }
300 log.Printf("Envoy %d exited", curEpoch)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400301 // Check if this was the primary envoy, if so
302 // something went terribly wrong, panic to force
303 // forcefully exit.
304 ec.reLock.Lock()
305 if ec.restartEpoch == (curEpoch + 1) {
306 ec.reLock.Unlock()
307 log.Fatal("Last running envoy exited, aborting!")
308 panic("This should never happen")
309 }
310 ec.reLock.Unlock()
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400311
312}
313
314// This function will use the provided templete file to generate
315// the targetfile substituting
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400316func (ec * EnvoyControl) updateEnvoyConfig(ecv * EnvoyConfigVars) (err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400317 var firstRun bool = true
Sergio Slobodrian19628742017-09-05 19:52:54 -0400318 var firstRun2 bool = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400319 f := func() (bool) {
320 var rtrn bool = firstRun
321 firstRun = false
322 return rtrn
323 }
Sergio Slobodrian19628742017-09-05 19:52:54 -0400324 g := func() (bool) {
325 var rtrn bool = firstRun2
326 firstRun2 = false
327 return rtrn
328 }
329 var funcs = template.FuncMap{"isFirst": f, "isFirst2": g}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400330 // Slurp up the template file.
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400331 tplt, err := ioutil.ReadFile(ec.envoyConfigTemplate)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400332 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400333 log.Fatal("ERROR reading the template file, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400334 }
335 //fmt.Println(string(tplt))
336 configTemplate, err := template.New("config").Funcs(funcs).Parse(string(tplt));
337 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400338 log.Fatal("Unexpected error loading the Envoy template, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400339 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400340 outFile,err := os.Create(ec.envoyConfig)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400341 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400342 log.Fatal("Unexpected error opening the Envoy config file for write, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400343 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400344 if err = configTemplate.Execute(outFile, ecv); err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400345 log.Fatal("Unexpected error executing the Envoy config template, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400346 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400347 //cfgFile, err := ioutil.ReadFile(ec.envoyConfig)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400348 //if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400349 // log.Fatal("ERROR reading the config file, aborting: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400350 // panic(err)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400351 //}
352 //fmt.Println(string(cfgFile))
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400353 return
354}
355
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400356func (ec * EnvoyControl) parseAssignment(jsonString []byte) (vCluster []VolthaClusterEntry, err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400357 var f interface{}
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400358 var vc VolthaClusterEntry
359 //var isErr bool
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400360
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400361 log.Printf("Parsing %s", string(jsonString))
362 //err = json.Unmarshal(jsonString, &f)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400363 err = json.Unmarshal(jsonString, &f)
364 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400365 log.Fatal("Unable to parse json record %s : %s", jsonString, err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400366 } else {
367 m := f.(map[string]interface{})
368 for k, v := range m {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400369 isErr := false
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400370 vc.Prefix = k
371 //log.Printf("Processing key %s\n", k)
372 switch vv := v.(type) {
373 case map[string]interface{}:
374 for i, u := range vv {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400375 //log.Printf("Processing key %sn", i)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400376 switch uu := u.(type) {
377 case string:
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400378 if i == ec.vcoreHostIpName {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400379 vc.Host = uu
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400380 } else if i == ec.vcoreIdName {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400381 vc.Id = uu
382 } else {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400383 log.Printf("WARNING: unexpected descriptor,%s", i)
384 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400385 }
386 default:
387 log.Printf("WARNING: unexpected type, ")
388 log.Println(i, u)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400389 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400390 }
391 }
392 default:
393 log.Printf("WARNING: unexpected type, ")
394 log.Println(k, v)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400395 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400396 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400397 if ! isErr {
398 vCluster = append(vCluster, vc)
399 }
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400400 }
401 }
402 log.Println("Parsing complete")
403 return
404}
405
Richard Jankowski15274592017-12-12 15:52:37 -0500406func (ec * EnvoyControl) prepareEnvoyConfig(keyValue []byte, ecv * EnvoyConfigVars) (err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400407 var vCluster []VolthaClusterEntry
408
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400409 ecv.HttpPort = ec.envoyHttpPort
410 ecv.HttpsPort = ec.envoyHttpsPort
411 ecv.GrpcPort = ec.envoyGrpcPort
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400412 ecv.VolthaVip = ec.ipAddrs[ec.vcoreSvcName][0] + ":" + ec.vcorePort
413
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400414 // Extract all values from the KV record
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400415 // In the future, the values should all be compared to what we currently have
Richard Jankowski15274592017-12-12 15:52:37 -0500416 vCluster, err = ec.parseAssignment(keyValue)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400417 if err == nil {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400418 ec.vc = vCluster // For future use to determine if there's been a real change
419 //templateValues["VolthaRR"] = []string{}
420 ecv.VolthaRR = []string{}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400421 for i := range vCluster {
422 //log.Printf("Processing %s\n", vCluster[i].Host)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400423 //templateValues["VolthaRR"] = append(templateValues["VolthaRR"].([]string), vCluster[i].Host)
424 ecv.VolthaRR = append(ecv.VolthaRR, vCluster[i].Host + ":" + ec.vcorePort)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400425 }
426 } else {
Richard Jankowski15274592017-12-12 15:52:37 -0500427 log.Fatal("Couldn't parse the KV record %s: %s", string(keyValue), err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400428 }
429 return
430}
431
Richard Jankowski15274592017-12-12 15:52:37 -0500432func (ec * EnvoyControl) runEnvoy(keyValue []byte) {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400433 var err error
434 var ecv EnvoyConfigVars
435
Richard Jankowski15274592017-12-12 15:52:37 -0500436 if err = ec.prepareEnvoyConfig(keyValue, &ecv); err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400437 log.Fatal("Error preparing envoy config variables, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400438 }
439
440 // Now that we have the data loaded, update the envoy config and start envoy
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400441 ec.updateEnvoyConfig(&ecv)
442 go ec.startEnvoy()
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400443}
444
Richard Jankowski15274592017-12-12 15:52:37 -0500445func (ec * EnvoyControl) readConsulKey(key string, qo * consulapi.QueryOptions) (value []byte, meta * consulapi.QueryMeta, err error) {
446
447 var kvp *consulapi.KVPair
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400448
449 kv := ec.consul.KV()
450 // Get the initial values of the assignment key which contains individual
451 // voltha core IP addresses. This may be empty until voltha populates it
452 // so it must be checked
453 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
454 for i := 0; i < ec.retries; i++ {
455 if err != nil {
456 fmt.Println(err)
457 log.Printf("Unable to read assignment consul key, retry %d", i+1)
458 time.Sleep(time.Duration(ec.waitTime) * time.Second)
459 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
460 } else if kvp != nil && len(string(kvp.Value)) > 10 {
461 // A valid read, return
Richard Jankowski15274592017-12-12 15:52:37 -0500462 value = kvp.Value
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400463 break
464 } else {
465 log.Printf("Voltha assignment key invalid, retry %d", i+1)
466 time.Sleep(time.Duration(ec.waitTime) * time.Second)
467 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
468 }
469 if i == ec.retries {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400470 log.Fatal("Failed to read the assignment key after %d retries, aborting: %s", ec.retries, err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400471 }
472 }
473 return
474}
475
Richard Jankowski15274592017-12-12 15:52:37 -0500476func (ec * EnvoyControl) readEtcdKey(key string) (value []byte, index int64, err error) {
477 // Get the initial values of the assignment key which contains individual
478 // voltha core IP addresses. This may be empty until voltha populates it
479 // so it must be checked
480 resp, err := ec.etcd.Get(context.Background(), ec.assignmentKey)
481 for i := 0; i < ec.retries; i++ {
482 if err != nil {
483 fmt.Println(err)
484 log.Printf("Unable to read assignment etcd key, retry %d", i+1)
485 time.Sleep(time.Duration(ec.waitTime) * time.Second)
486 resp, err = ec.etcd.Get(context.Background(), ec.assignmentKey)
487 } else if resp != nil && len(resp.Kvs) > 0 && len(resp.Kvs[0].Value) > 10 {
488 // A valid read, return
489 kv := resp.Kvs[0]
490 value = kv.Value
491 index = kv.ModRevision
492 break
493 } else {
494 log.Printf("Voltha assignment key from etcd invalid, retry %d", i+1)
495 time.Sleep(time.Duration(ec.waitTime) * time.Second)
496 resp, err = ec.etcd.Get(context.Background(), ec.assignmentKey)
497 }
498 if i == ec.retries {
499 log.Fatal("Failed to read assignment key from etcd after %d retries, aborting: %s", ec.retries, err.Error())
500 }
501 }
502 return
503}
504
505func (ec * EnvoyControl) monitorConsulKey() {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400506 var err error
507 var qo consulapi.QueryOptions
508
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400509 // Get the initial values of the assignment key which contains individual
510 // voltha core IP addresses. This may be empty until voltha populates it
511 // so it must be checked
Richard Jankowski15274592017-12-12 15:52:37 -0500512 log.Printf("Monitoring consul key")
513 val, meta, err := ec.readConsulKey(ec.assignmentKey, nil)
514 log.Printf("Starting Envoy, initial index = %d", meta.LastIndex)
515 ec.runEnvoy(val)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400516
517 for {
518 qo.WaitIndex = meta.LastIndex
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400519 qo.RequireConsistent = true
Sergio Slobodrian19628742017-09-05 19:52:54 -0400520 //qo.AllowStale = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400521 for {
522 if qo.WaitIndex != meta.LastIndex {
523 break
524 }
Richard Jankowski15274592017-12-12 15:52:37 -0500525 val, meta, err = ec.readConsulKey(ec.assignmentKey, &qo)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400526 if err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400527 log.Fatal("Unable to read assignment consul key: %s\n", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400528 } else {
Richard Jankowski15274592017-12-12 15:52:37 -0500529 log.Println(string(val))
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400530 log.Printf("meta.LastIndex = %d", meta.LastIndex)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400531 }
532 }
533 // Fell through, the index has changed thus the key has changed
534
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400535 log.Printf("Starting Envoy")
Richard Jankowski15274592017-12-12 15:52:37 -0500536 ec.runEnvoy(val)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400537 log.Printf("meta.LastIndex = %d", meta.LastIndex)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400538 }
539}
540
Richard Jankowski15274592017-12-12 15:52:37 -0500541func (ec * EnvoyControl) monitorEtcdKey() {
542 var err error
543
Richard Jankowski15274592017-12-12 15:52:37 -0500544 // Get the initial values of the assignment key which contains individual
545 // voltha core IP addresses. This may be empty until voltha populates it
546 // so it must be checked
Richard Jankowski15274592017-12-12 15:52:37 -0500547
Richard Jankowski9fb5b082018-05-16 17:58:00 -0400548 log.Printf("Monitoring etcd key %s", ec.assignmentKey)
549 val, index, err := ec.readEtcdKey(ec.assignmentKey)
550 if err == nil {
551 lastIndex := index
552 log.Printf("Starting Envoy, initial index = %d", lastIndex)
Richard Jankowski15274592017-12-12 15:52:37 -0500553 ec.runEnvoy(val)
Richard Jankowski9fb5b082018-05-16 17:58:00 -0400554 }
555
556 rch := ec.etcd.Watch(context.Background(), ec.assignmentKey)
557 for resp := range rch {
558 for _, ev := range resp.Events {
559 val = ev.Kv.Value
560 log.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
561 if ev.Type == etcdapi.EventTypePut {
562 log.Printf("Starting Envoy")
563 ec.runEnvoy(val)
564 }
565 }
Richard Jankowski15274592017-12-12 15:52:37 -0500566 }
567}
568
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400569func (ec * EnvoyControl) ParseCommandArguments() {
570 flag.StringVar(&(ec.assignmentKey), "assignment-key", ec.assignmentKey,
571 "The key for the voltha assignment value in consul")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400572
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400573 flag.StringVar(&( ec.envoyConfigTemplate),"envoy-cfg-template", ec.envoyConfigTemplate,
574 "The path to envoy's configuration template")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400575
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400576 flag.StringVar(&( ec.envoyConfigTemplateBoth),"envoy-cfg-template-both", ec.envoyConfigTemplateBoth,
577 "The path to envoy's configuration template for both http and https")
578
579 flag.StringVar(&( ec.envoyConfigTemplateNoHttps),"envoy-cfg-template-no-https", ec.envoyConfigTemplateNoHttps,
580 "The path to envoy's configuration template with no https")
581
582 flag.StringVar(&( ec.envoyConfigTemplateNoHttp),"envoy-cfg-template-no-http", ec.envoyConfigTemplateNoHttp,
583 "The path to envoy's configuration template with no http")
584
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400585 flag.StringVar(&(ec.envoyConfig), "envoy-config", ec.envoyConfig,
586 "The path to envoy's configuration file" )
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400587
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400588 flag.StringVar(&(ec.vcoreSvcName), "vcore-svc-name", ec.vcoreSvcName,
589 "The service name of the voltha core service")
590
591 flag.StringVar(&(ec.consulSvcName),"consul-svc-nme", ec.consulSvcName,
592 "The service name of the consul service")
593
594 flag.StringVar(&(ec.vcorePort), "vcore-port", ec.vcorePort,
595 "The port where the vcore's GRPC service can be found")
596
597 flag.StringVar(&(ec.consulPort), "consul-port", ec.consulPort,
598 "The port where the consul service api can be found")
599
Richard Jankowski15274592017-12-12 15:52:37 -0500600 flag.StringVar(&(ec.kvStore), "kv", ec.kvStore,
601 "The KV store: consul or etcd")
602
603 flag.StringVar(&(ec.kvSvcName), "kv-svc-name", ec.kvSvcName,
604 "The name of the KV store service")
605
606 flag.StringVar(&(ec.kvPort), "kv-port", ec.kvPort,
607 "The port where the KV service api can be found")
608
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400609 flag.StringVar(&(ec.envoyHttpPort), "http-port", ec.envoyHttpPort,
610 "The port where the http front-end is served ")
611
612 flag.StringVar(&(ec.envoyHttpsPort), "https-port", ec.envoyHttpsPort,
613 "The port where the https front-end is served ")
614
615 flag.StringVar(&(ec.envoyGrpcPort), "grpc-port", ec.envoyGrpcPort,
616 "The port where the grpc front-end is served ")
617
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400618 flag.IntVar(&(ec.retries), "retries", ec.retries,
619 "The number of times to retry name lookups and connect requests before failing")
620
621 flag.IntVar(&(ec.waitTime), "wait-time", ec.waitTime,
622 "The number of seconds to wait between retries")
623
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400624 flag.BoolVar(&(ec.httpDisabled), "disable-http", ec.httpDisabled,
625 "Disables the http front-end")
626
627 flag.BoolVar(&(ec.httpsDisabled), "disable-https", ec.httpsDisabled,
628 "Disables ths https front-end")
629
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400630 flag.Parse()
631}
632
633func (ec * EnvoyControl) Initialize() (err error) {
Richard Jankowskid4454382018-02-08 16:21:43 -0500634 // Resolve KV store's virtual ip address
635 if err = ec.resolveServiceAddress(ec.kvSvcName); err != nil {
636 log.Fatal("Can't proceed without KV store's vIP address: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400637 }
638
639 // Resolve voltha's virtual ip address
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400640 if err = ec.resolveServiceAddress(ec.vcoreSvcName); err != nil {
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400641 log.Fatal("Can't proceed without voltha's vIP address: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400642 }
643
Richard Jankowski15274592017-12-12 15:52:37 -0500644 if err = ec.kvConnect[ec.kvStore](ec.kvSvcName, ec.kvPort); err != nil {
645 log.Fatal("Failed to create KV client, aborting: %s", err.Error())
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400646 }
647
648 if ec.httpDisabled == true && ec.httpsDisabled == true {
Richard Jankowskid4454382018-02-08 16:21:43 -0500649 log.Printf("Cowardly refusing to disable both http and https, leaving them both enabled\n")
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400650 } else if ec.httpDisabled == true {
651 log.Printf("Diasabling http\n")
652 ec.envoyConfigTemplate = ec.envoyConfigTemplateNoHttp
653 } else if ec.httpsDisabled == true {
654 log.Printf("Diasabling https\n")
655 ec.envoyConfigTemplate = ec.envoyConfigTemplateNoHttps
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400656 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400657
658 return
659}
660
661func main() {
662
663 var err error
664 var ec * EnvoyControl
665
666 ec = NewEnvoyControl()
667 ec.ParseCommandArguments()
Richard Jankowski15274592017-12-12 15:52:37 -0500668 if ec.kvStore != "etcd" {
669 ec.kvStore = "consul"
670 }
671 log.Printf("KV-store %s at %s:%s", ec.kvStore, ec.kvSvcName, ec.kvPort)
672
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400673 if err = ec.Initialize(); err != nil {
Richard Jankowskid4454382018-02-08 16:21:43 -0500674 log.Fatal("Envoy control initialization failed, aborting: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400675 }
676
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400677
678 // Start envoy and monitor changes to the KV store to reload
Richard Jankowski15274592017-12-12 15:52:37 -0500679 // consul's config. This never returns unless something crashes.
680 ec.kvMonitor[ec.kvStore]()
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400681 log.Fatal("Monitor returned, this shouldn't happen")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400682}