blob: 1a688cbb889435737a3d2ef062a8c7d79640cb3c [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"
khenaidood891eb62018-08-30 08:31:53 -040035// etcdapi "github.com/coreos/etcd/clientv3"
36 etcdapi "go.etcd.io/etcd/clientv3"
Sergio Slobodrianbe829272017-07-17 14:45:45 -040037)
38
39// DATA STRUCTURES
40
Sergio Slobodrianbe829272017-07-17 14:45:45 -040041//Client provides an interface for getting data out of Consul
42type Client interface {
43// Get a Service from consulapi
44 Service(string, string) ([]string, error)
45// Register a service with local agent
46 Register(string, int) error
47// Deregister a service with local agent
48 DeRegister(string) error
49}
50
51type client struct {
52 consulapi *consulapi.Client
53}
54
Richard Jankowski15274592017-12-12 15:52:37 -050055type KvConnectFunc func(string, string) (error)
56type KvMonitorFunc func()
57
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040058type EnvoyConfigVars struct {
59 VolthaVip string
60 VolthaRR []string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040061 vcorePort string
62 HttpPort string
63 HttpsPort string
64 GrpcPort string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040065}
66
67type VolthaClusterEntry struct {
68 Prefix string
69 Id string
70 Host string
71}
72
Sergio Slobodrianbe829272017-07-17 14:45:45 -040073// This struct is not used yet
74// TODO: Update the daemon to use this structure to for a
75// more object oriented implementation
76type EnvoyControl struct {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040077 // Command line parameters
78 assignmentKey string
79 envoyConfigTemplate string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040080 envoyConfigTemplateBoth string
81 envoyConfigTemplateNoHttps string
82 envoyConfigTemplateNoHttp string
83 envoyHttpPort string
84 envoyHttpsPort string
85 httpDisabled bool
86 httpsDisabled bool
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040087 envoyConfig string
88 vcoreSvcName string
89 consulSvcName string
90 vcorePort string
Sergio Slobodrian6570c742017-08-07 23:11:33 -040091 envoyGrpcPort string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040092 consulPort string
Richard Jankowski15274592017-12-12 15:52:37 -050093 kvStore string
94 kvSvcName string
95 kvPort string
96 kvConnect map[string]KvConnectFunc
97 kvMonitor map[string]KvMonitorFunc
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -040098 retries int
Sergio Slobodrianbe829272017-07-17 14:45:45 -040099 waitTime int
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400100 // Runtime variables
101 consul * consulapi.Client
Richard Jankowski15274592017-12-12 15:52:37 -0500102 etcd * etcdapi.Client
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400103 vcoreHostIpName string
104 vcoreIdName string
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400105 vc []VolthaClusterEntry
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400106 ipAddrs map[string][]string
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400107 restartEpoch int
108 reLock sync.Mutex // Exclusive access to the restartEpoch
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400109}
110
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400111
112func NewEnvoyControl() (ec * EnvoyControl) {
113 var envCtrl EnvoyControl = EnvoyControl { // Default values
114 // Command line parameters
115 assignmentKey: "service/voltha/data/core/assignment",
116 envoyConfigTemplate: "/envoy/voltha-grpc-proxy.template.json",
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400117 envoyConfigTemplateBoth: "/envoy/voltha-grpc-proxy.template.json",
118 envoyConfigTemplateNoHttps: "/envoy/voltha-grpc-proxy-no-https.template.json",
119 envoyConfigTemplateNoHttp: "/envoy/voltha-grpc-proxy-no-http.template.json",
120 envoyHttpsPort: "8443",
121 envoyHttpPort: "8882",
122 envoyGrpcPort: "50555",
123 httpDisabled: false,
124 httpsDisabled: false,
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400125 envoyConfig: "/envoy/voltha-grpc-proxy.json",
126 //envoyLogFile: "/envoy/voltha_access_log.log",
127 vcoreSvcName: "vcore",
128 consulSvcName: "consul",
129 vcorePort: "50556",
130 consulPort: "8500",
Richard Jankowski15274592017-12-12 15:52:37 -0500131 kvStore: "consul",
132 kvSvcName: "consul",
133 kvPort: "8500",
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400134 retries: 10,
135 waitTime: 2,
136 // Runtime variables
137 vcoreHostIpName: "host",
138 vcoreIdName: "id",
139 ipAddrs: make(map[string][]string),
140 restartEpoch: 0,
141 }
142 ec = &envCtrl
Richard Jankowski15274592017-12-12 15:52:37 -0500143 ec.kvConnect = make(map[string]KvConnectFunc)
144 ec.kvConnect["consul"] = ec.consulConnect
145 ec.kvConnect["etcd"] = ec.etcdConnect
146 ec.kvMonitor = make(map[string]KvMonitorFunc)
147 ec.kvMonitor["consul"] = ec.monitorConsulKey
148 ec.kvMonitor["etcd"] = ec.monitorEtcdKey
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400149 return
150}
151
152func (ec * EnvoyControl) resolveServiceAddress(serviceName string) (err error) {
153 for i := 0; i < ec.retries; i++ {
154 ec.ipAddrs[serviceName], err = net.LookupHost(serviceName)
155 if err != nil {
156 log.Printf("%s name resolution failed %d time(s) retrying...", serviceName, i+1)
157 } else {
158 //fmt.Printf("%s address = %s\n",serviceName, addrs[0])
159 break
160 }
161 time.Sleep(time.Duration(ec.waitTime) * time.Second)
162 }
163 if err != nil {
Richard Jankowskid4454382018-02-08 16:21:43 -0500164 log.Printf("%s name resolution failed %d times giving up", serviceName, ec.retries)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400165 }
166 return
167}
168
169func (ec * EnvoyControl) consulConnect(serviceName string, port string) (err error) {
170 // Fire up a consul client and get the kv store
171 cConfig := consulapi.DefaultNonPooledConfig()
172 cConfig.Address = ec.ipAddrs[serviceName][0] + ":" + port
173 ec.consul, err = consulapi.NewClient(cConfig)
174 if err != nil {
175 log.Fatal("error creating consul client aborting")
176 return
177 }
178 return
179}
180
Richard Jankowski15274592017-12-12 15:52:37 -0500181func (ec * EnvoyControl) etcdConnect(serviceName string, port string) (err error) {
182 // Fire up an etcd client to access the kv store
183 cfg := etcdapi.Config {
184 Endpoints: []string{serviceName + ":" + port},
185 DialTimeout: 5 * time.Second,
186 }
187 ec.etcd, err = etcdapi.New(cfg)
188 if err != nil {
189 log.Fatal("Failed to create etcd client, aborting...")
190 return
191 }
192 return
193}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400194
195//NewConsul returns a Client interface for given consulapi address
196func NewConsulClient(addr string) (*client, error) {
197 config := consulapi.DefaultConfig()
198 config.Address = addr
199 c, err := consulapi.NewClient(config)
200 if err != nil {
201 return nil, err
202 }
203 return &client{consulapi: c}, nil
204}
205
206// Register a service with consulapi local agent
207func (c *client) Register(name string, port int) error {
208 reg := &consulapi.AgentServiceRegistration{
209 ID: name,
210 Name: name,
211 Port: port,
212 }
213 return c.consulapi.Agent().ServiceRegister(reg)
214}
215
216// DeRegister a service with consulapi local agent
217func (c *client) DeRegister(id string) error {
218 return c.consulapi.Agent().ServiceDeregister(id)
219}
220
Zack Williams18357ed2018-11-14 10:41:08 -0700221// Service return a service
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400222func (c *client) Service(service, tag string) ([]*consulapi.ServiceEntry, *consulapi.QueryMeta, error) {
223 passingOnly := true
224 addrs, meta, err := c.consulapi.Health().Service(service, tag, passingOnly, nil)
225 if len(addrs) == 0 && err == nil {
226 return nil, nil, fmt.Errorf("service ( %s ) was not found", service)
227 }
228 if err != nil {
229 return nil, nil, err
230 }
231 return addrs, meta, nil
232}
233
234// Starts envoy with the current restartEpoch
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400235func (ec * EnvoyControl) startEnvoy() {
236 var curEpoch int
237 var err error
238 var count int
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400239
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400240 ec.reLock.Lock() // Make sure we've got exclusive access to the variable
241 cmd := exec.Command("/usr/local/bin/envoy", "--restart-epoch", strconv.Itoa(ec.restartEpoch),
242 "--config-path", ec.envoyConfig, "--parent-shutdown-time-s", "10")
243
244 curEpoch = ec.restartEpoch
245 ec.restartEpoch += 1
246 ec.reLock.Unlock() // Done, release the lock.
247
248 stderr, err := cmd.StderrPipe()
249 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700250 log.Fatalf("Couldn't attach to stderr running envoy command: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400251 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400252 stdout, err := cmd.StdoutPipe()
253 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700254 log.Fatalf("Couldn't attach to stdout running envoy command: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400255 }
256 so := bufio.NewReader(stdout)
257 se := bufio.NewReader(stderr)
258
259 if err = cmd.Start(); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700260 log.Fatalf("Error starting envoy: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400261 }
262 log.Printf("Envoy(%d) started", curEpoch)
263 soEof := false
264 seEof := false
265
266 data := make([]byte, 80)
267 log.Printf("Log forwarding for envoy(%d) started", curEpoch)
268 for {
269 data = make([]byte, 80)
270 count, err = so.Read(data)
271 log.Printf("ENVOY_LOG(%d): %s", curEpoch, string(data))
272 if err == io.EOF {
273 if seEof == true {
274 break
275 } else {
276 soEof = true
277 }
278 } else if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700279 log.Fatalf("Attempt to read envoy standard out failed: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400280 } else if count > 0 {
281 log.Printf("ENVOY_LOG(%d)(%d): %s",curEpoch,count,string(data))
282 }
283 data = make([]byte, 80)
284 count, err = se.Read(data)
285 if err == io.EOF {
286 if soEof == true {
287 break
288 } else {
289 seEof = true
290 }
291 } else if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700292 log.Fatalf("Attempt to read envoy standard err failed: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400293 } else if count > 0 {
294 log.Printf("ENVOY_LOG(%d)(%d): %s",curEpoch,count,string(data))
295 }
296 }
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400297 log.Printf("Waiting on envoy %d to exit", curEpoch)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400298 if err = cmd.Wait(); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700299 log.Fatalf("Envoy %d exited with an unexpected exit code: %s", curEpoch, err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400300 }
301 log.Printf("Envoy %d exited", curEpoch)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400302 // Check if this was the primary envoy, if so
303 // something went terribly wrong, panic to force
304 // forcefully exit.
305 ec.reLock.Lock()
306 if ec.restartEpoch == (curEpoch + 1) {
307 ec.reLock.Unlock()
308 log.Fatal("Last running envoy exited, aborting!")
309 panic("This should never happen")
310 }
311 ec.reLock.Unlock()
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400312
313}
314
315// This function will use the provided templete file to generate
Zack Williams18357ed2018-11-14 10:41:08 -0700316// the targetfile substituting
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400317func (ec * EnvoyControl) updateEnvoyConfig(ecv * EnvoyConfigVars) (err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400318 var firstRun bool = true
Sergio Slobodrian19628742017-09-05 19:52:54 -0400319 var firstRun2 bool = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400320 f := func() (bool) {
321 var rtrn bool = firstRun
322 firstRun = false
323 return rtrn
324 }
Sergio Slobodrian19628742017-09-05 19:52:54 -0400325 g := func() (bool) {
326 var rtrn bool = firstRun2
327 firstRun2 = false
328 return rtrn
329 }
330 var funcs = template.FuncMap{"isFirst": f, "isFirst2": g}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400331 // Slurp up the template file.
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400332 tplt, err := ioutil.ReadFile(ec.envoyConfigTemplate)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400333 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700334 log.Fatalf("ERROR reading the template file, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400335 }
336 //fmt.Println(string(tplt))
337 configTemplate, err := template.New("config").Funcs(funcs).Parse(string(tplt));
338 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700339 log.Fatalf("Unexpected error loading the Envoy template, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400340 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400341 outFile,err := os.Create(ec.envoyConfig)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400342 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700343 log.Fatalf("Unexpected error opening the Envoy config file for write, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400344 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400345 if err = configTemplate.Execute(outFile, ecv); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700346 log.Fatalf("Unexpected error executing the Envoy config template, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400347 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400348 //cfgFile, err := ioutil.ReadFile(ec.envoyConfig)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400349 //if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700350 // log.Fatalf("ERROR reading the config file, aborting: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400351 // panic(err)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400352 //}
353 //fmt.Println(string(cfgFile))
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400354 return
355}
356
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400357func (ec * EnvoyControl) parseAssignment(jsonString []byte) (vCluster []VolthaClusterEntry, err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400358 var f interface{}
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400359 var vc VolthaClusterEntry
360 //var isErr bool
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400361
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400362 log.Printf("Parsing %s", string(jsonString))
363 //err = json.Unmarshal(jsonString, &f)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400364 err = json.Unmarshal(jsonString, &f)
365 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700366 log.Fatalf("Unable to parse json record %s : %s", jsonString, err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400367 } else {
368 m := f.(map[string]interface{})
369 for k, v := range m {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400370 isErr := false
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400371 vc.Prefix = k
372 //log.Printf("Processing key %s\n", k)
373 switch vv := v.(type) {
374 case map[string]interface{}:
375 for i, u := range vv {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400376 //log.Printf("Processing key %sn", i)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400377 switch uu := u.(type) {
378 case string:
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400379 if i == ec.vcoreHostIpName {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400380 vc.Host = uu
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400381 } else if i == ec.vcoreIdName {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400382 vc.Id = uu
383 } else {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400384 log.Printf("WARNING: unexpected descriptor,%s", i)
385 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400386 }
387 default:
388 log.Printf("WARNING: unexpected type, ")
389 log.Println(i, u)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400390 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400391 }
392 }
393 default:
394 log.Printf("WARNING: unexpected type, ")
395 log.Println(k, v)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400396 isErr = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400397 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400398 if ! isErr {
399 vCluster = append(vCluster, vc)
400 }
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400401 }
402 }
403 log.Println("Parsing complete")
404 return
405}
406
Richard Jankowski15274592017-12-12 15:52:37 -0500407func (ec * EnvoyControl) prepareEnvoyConfig(keyValue []byte, ecv * EnvoyConfigVars) (err error) {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400408 var vCluster []VolthaClusterEntry
409
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400410 ecv.HttpPort = ec.envoyHttpPort
411 ecv.HttpsPort = ec.envoyHttpsPort
412 ecv.GrpcPort = ec.envoyGrpcPort
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400413 ecv.VolthaVip = ec.ipAddrs[ec.vcoreSvcName][0] + ":" + ec.vcorePort
414
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400415 // Extract all values from the KV record
Zack Williams18357ed2018-11-14 10:41:08 -0700416 // In the future, the values should all be compared to what we currently have
Richard Jankowski15274592017-12-12 15:52:37 -0500417 vCluster, err = ec.parseAssignment(keyValue)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400418 if err == nil {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400419 ec.vc = vCluster // For future use to determine if there's been a real change
420 //templateValues["VolthaRR"] = []string{}
421 ecv.VolthaRR = []string{}
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400422 for i := range vCluster {
423 //log.Printf("Processing %s\n", vCluster[i].Host)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400424 //templateValues["VolthaRR"] = append(templateValues["VolthaRR"].([]string), vCluster[i].Host)
425 ecv.VolthaRR = append(ecv.VolthaRR, vCluster[i].Host + ":" + ec.vcorePort)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400426 }
427 } else {
Zack Williams18357ed2018-11-14 10:41:08 -0700428 log.Fatalf("Couldn't parse the KV record %s: %s", string(keyValue), err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400429 }
430 return
431}
432
Richard Jankowski15274592017-12-12 15:52:37 -0500433func (ec * EnvoyControl) runEnvoy(keyValue []byte) {
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400434 var err error
435 var ecv EnvoyConfigVars
436
Richard Jankowski15274592017-12-12 15:52:37 -0500437 if err = ec.prepareEnvoyConfig(keyValue, &ecv); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700438 log.Fatalf("Error preparing envoy config variables, aborting: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400439 }
440
441 // Now that we have the data loaded, update the envoy config and start envoy
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400442 ec.updateEnvoyConfig(&ecv)
443 go ec.startEnvoy()
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400444}
445
Richard Jankowski15274592017-12-12 15:52:37 -0500446func (ec * EnvoyControl) readConsulKey(key string, qo * consulapi.QueryOptions) (value []byte, meta * consulapi.QueryMeta, err error) {
447
448 var kvp *consulapi.KVPair
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400449
450 kv := ec.consul.KV()
451 // Get the initial values of the assignment key which contains individual
452 // voltha core IP addresses. This may be empty until voltha populates it
453 // so it must be checked
454 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
455 for i := 0; i < ec.retries; i++ {
456 if err != nil {
457 fmt.Println(err)
458 log.Printf("Unable to read assignment consul key, retry %d", i+1)
459 time.Sleep(time.Duration(ec.waitTime) * time.Second)
460 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
461 } else if kvp != nil && len(string(kvp.Value)) > 10 {
462 // A valid read, return
Richard Jankowski15274592017-12-12 15:52:37 -0500463 value = kvp.Value
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400464 break
465 } else {
466 log.Printf("Voltha assignment key invalid, retry %d", i+1)
467 time.Sleep(time.Duration(ec.waitTime) * time.Second)
468 kvp, meta, err = kv.Get(ec.assignmentKey, qo)
469 }
470 if i == ec.retries {
Zack Williams18357ed2018-11-14 10:41:08 -0700471 log.Fatalf("Failed to read the assignment key after %d retries, aborting: %s", ec.retries, err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400472 }
473 }
474 return
475}
476
Richard Jankowski15274592017-12-12 15:52:37 -0500477func (ec * EnvoyControl) readEtcdKey(key string) (value []byte, index int64, err error) {
478 // Get the initial values of the assignment key which contains individual
479 // voltha core IP addresses. This may be empty until voltha populates it
480 // so it must be checked
481 resp, err := ec.etcd.Get(context.Background(), ec.assignmentKey)
482 for i := 0; i < ec.retries; i++ {
483 if err != nil {
484 fmt.Println(err)
485 log.Printf("Unable to read assignment etcd key, retry %d", i+1)
486 time.Sleep(time.Duration(ec.waitTime) * time.Second)
487 resp, err = ec.etcd.Get(context.Background(), ec.assignmentKey)
488 } else if resp != nil && len(resp.Kvs) > 0 && len(resp.Kvs[0].Value) > 10 {
489 // A valid read, return
490 kv := resp.Kvs[0]
491 value = kv.Value
492 index = kv.ModRevision
493 break
494 } else {
495 log.Printf("Voltha assignment key from etcd invalid, retry %d", i+1)
496 time.Sleep(time.Duration(ec.waitTime) * time.Second)
497 resp, err = ec.etcd.Get(context.Background(), ec.assignmentKey)
498 }
499 if i == ec.retries {
Zack Williams18357ed2018-11-14 10:41:08 -0700500 log.Fatalf("Failed to read assignment key from etcd after %d retries, aborting: %s", ec.retries, err.Error())
Richard Jankowski15274592017-12-12 15:52:37 -0500501 }
502 }
503 return
504}
505
506func (ec * EnvoyControl) monitorConsulKey() {
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400507 var err error
508 var qo consulapi.QueryOptions
509
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400510 // Get the initial values of the assignment key which contains individual
511 // voltha core IP addresses. This may be empty until voltha populates it
512 // so it must be checked
Richard Jankowski15274592017-12-12 15:52:37 -0500513 log.Printf("Monitoring consul key")
514 val, meta, err := ec.readConsulKey(ec.assignmentKey, nil)
515 log.Printf("Starting Envoy, initial index = %d", meta.LastIndex)
516 ec.runEnvoy(val)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400517
518 for {
519 qo.WaitIndex = meta.LastIndex
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400520 qo.RequireConsistent = true
Sergio Slobodrian19628742017-09-05 19:52:54 -0400521 //qo.AllowStale = true
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400522 for {
523 if qo.WaitIndex != meta.LastIndex {
524 break
525 }
Richard Jankowski15274592017-12-12 15:52:37 -0500526 val, meta, err = ec.readConsulKey(ec.assignmentKey, &qo)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400527 if err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700528 log.Fatalf("Unable to read assignment consul key: %s\n", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400529 } else {
Richard Jankowski15274592017-12-12 15:52:37 -0500530 log.Println(string(val))
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400531 log.Printf("meta.LastIndex = %d", meta.LastIndex)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400532 }
533 }
534 // Fell through, the index has changed thus the key has changed
535
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400536 log.Printf("Starting Envoy")
Richard Jankowski15274592017-12-12 15:52:37 -0500537 ec.runEnvoy(val)
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400538 log.Printf("meta.LastIndex = %d", meta.LastIndex)
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400539 }
540}
541
Richard Jankowski15274592017-12-12 15:52:37 -0500542func (ec * EnvoyControl) monitorEtcdKey() {
543 var err error
544
Richard Jankowski15274592017-12-12 15:52:37 -0500545 // Get the initial values of the assignment key which contains individual
546 // voltha core IP addresses. This may be empty until voltha populates it
547 // so it must be checked
Richard Jankowski15274592017-12-12 15:52:37 -0500548
Richard Jankowski9fb5b082018-05-16 17:58:00 -0400549 log.Printf("Monitoring etcd key %s", ec.assignmentKey)
550 val, index, err := ec.readEtcdKey(ec.assignmentKey)
551 if err == nil {
552 lastIndex := index
553 log.Printf("Starting Envoy, initial index = %d", lastIndex)
Richard Jankowski15274592017-12-12 15:52:37 -0500554 ec.runEnvoy(val)
Richard Jankowski9fb5b082018-05-16 17:58:00 -0400555 }
556
557 rch := ec.etcd.Watch(context.Background(), ec.assignmentKey)
558 for resp := range rch {
559 for _, ev := range resp.Events {
560 val = ev.Kv.Value
561 log.Printf("%s %q : %q\n", ev.Type, ev.Kv.Key, ev.Kv.Value)
562 if ev.Type == etcdapi.EventTypePut {
563 log.Printf("Starting Envoy")
564 ec.runEnvoy(val)
565 }
566 }
Richard Jankowski15274592017-12-12 15:52:37 -0500567 }
568}
569
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400570func (ec * EnvoyControl) ParseCommandArguments() {
571 flag.StringVar(&(ec.assignmentKey), "assignment-key", ec.assignmentKey,
572 "The key for the voltha assignment value in consul")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400573
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400574 flag.StringVar(&( ec.envoyConfigTemplate),"envoy-cfg-template", ec.envoyConfigTemplate,
575 "The path to envoy's configuration template")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400576
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400577 flag.StringVar(&( ec.envoyConfigTemplateBoth),"envoy-cfg-template-both", ec.envoyConfigTemplateBoth,
578 "The path to envoy's configuration template for both http and https")
579
580 flag.StringVar(&( ec.envoyConfigTemplateNoHttps),"envoy-cfg-template-no-https", ec.envoyConfigTemplateNoHttps,
581 "The path to envoy's configuration template with no https")
582
583 flag.StringVar(&( ec.envoyConfigTemplateNoHttp),"envoy-cfg-template-no-http", ec.envoyConfigTemplateNoHttp,
584 "The path to envoy's configuration template with no http")
585
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400586 flag.StringVar(&(ec.envoyConfig), "envoy-config", ec.envoyConfig,
587 "The path to envoy's configuration file" )
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400588
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400589 flag.StringVar(&(ec.vcoreSvcName), "vcore-svc-name", ec.vcoreSvcName,
590 "The service name of the voltha core service")
591
592 flag.StringVar(&(ec.consulSvcName),"consul-svc-nme", ec.consulSvcName,
593 "The service name of the consul service")
594
595 flag.StringVar(&(ec.vcorePort), "vcore-port", ec.vcorePort,
596 "The port where the vcore's GRPC service can be found")
597
598 flag.StringVar(&(ec.consulPort), "consul-port", ec.consulPort,
599 "The port where the consul service api can be found")
600
Richard Jankowski15274592017-12-12 15:52:37 -0500601 flag.StringVar(&(ec.kvStore), "kv", ec.kvStore,
602 "The KV store: consul or etcd")
603
604 flag.StringVar(&(ec.kvSvcName), "kv-svc-name", ec.kvSvcName,
605 "The name of the KV store service")
606
607 flag.StringVar(&(ec.kvPort), "kv-port", ec.kvPort,
608 "The port where the KV service api can be found")
609
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400610 flag.StringVar(&(ec.envoyHttpPort), "http-port", ec.envoyHttpPort,
611 "The port where the http front-end is served ")
612
613 flag.StringVar(&(ec.envoyHttpsPort), "https-port", ec.envoyHttpsPort,
614 "The port where the https front-end is served ")
615
616 flag.StringVar(&(ec.envoyGrpcPort), "grpc-port", ec.envoyGrpcPort,
617 "The port where the grpc front-end is served ")
618
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400619 flag.IntVar(&(ec.retries), "retries", ec.retries,
620 "The number of times to retry name lookups and connect requests before failing")
621
622 flag.IntVar(&(ec.waitTime), "wait-time", ec.waitTime,
623 "The number of seconds to wait between retries")
624
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400625 flag.BoolVar(&(ec.httpDisabled), "disable-http", ec.httpDisabled,
626 "Disables the http front-end")
627
628 flag.BoolVar(&(ec.httpsDisabled), "disable-https", ec.httpsDisabled,
629 "Disables ths https front-end")
630
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400631 flag.Parse()
632}
633
634func (ec * EnvoyControl) Initialize() (err error) {
Richard Jankowskid4454382018-02-08 16:21:43 -0500635 // Resolve KV store's virtual ip address
636 if err = ec.resolveServiceAddress(ec.kvSvcName); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700637 log.Fatalf("Can't proceed without KV store's vIP address: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400638 }
639
640 // Resolve voltha's virtual ip address
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400641 if err = ec.resolveServiceAddress(ec.vcoreSvcName); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700642 log.Fatalf("Can't proceed without voltha's vIP address: %s", err.Error())
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400643 }
644
Richard Jankowski15274592017-12-12 15:52:37 -0500645 if err = ec.kvConnect[ec.kvStore](ec.kvSvcName, ec.kvPort); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700646 log.Fatalf("Failed to create KV client, aborting: %s", err.Error())
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400647 }
648
649 if ec.httpDisabled == true && ec.httpsDisabled == true {
Richard Jankowskid4454382018-02-08 16:21:43 -0500650 log.Printf("Cowardly refusing to disable both http and https, leaving them both enabled\n")
Sergio Slobodrian6570c742017-08-07 23:11:33 -0400651 } else if ec.httpDisabled == true {
652 log.Printf("Diasabling http\n")
653 ec.envoyConfigTemplate = ec.envoyConfigTemplateNoHttp
654 } else if ec.httpsDisabled == true {
655 log.Printf("Diasabling https\n")
656 ec.envoyConfigTemplate = ec.envoyConfigTemplateNoHttps
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400657 }
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400658
659 return
660}
661
662func main() {
663
664 var err error
665 var ec * EnvoyControl
666
667 ec = NewEnvoyControl()
668 ec.ParseCommandArguments()
Richard Jankowski15274592017-12-12 15:52:37 -0500669 if ec.kvStore != "etcd" {
670 ec.kvStore = "consul"
671 }
672 log.Printf("KV-store %s at %s:%s", ec.kvStore, ec.kvSvcName, ec.kvPort)
673
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400674 if err = ec.Initialize(); err != nil {
Zack Williams18357ed2018-11-14 10:41:08 -0700675 log.Fatalf("Envoy control initialization failed, aborting: %s", err.Error())
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400676 }
677
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400678
679 // Start envoy and monitor changes to the KV store to reload
Richard Jankowski15274592017-12-12 15:52:37 -0500680 // consul's config. This never returns unless something crashes.
681 ec.kvMonitor[ec.kvStore]()
Sergio Slobodrian8dec8de2017-07-20 12:45:07 -0400682 log.Fatal("Monitor returned, this shouldn't happen")
Sergio Slobodrianbe829272017-07-17 14:45:45 -0400683}