Skip to content

Commit be8e3a9

Browse files
committed
Add save and load route table feature to nats-client
1 parent 210f5ac commit be8e3a9

File tree

2 files changed

+195
-11
lines changed

2 files changed

+195
-11
lines changed
Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,3 @@
1+
gorouter.yml
2+
routes.json
3+
nats_client

src/routing_utils/nats_client/main.go

Lines changed: 192 additions & 11 deletions
Original file line numberDiff line numberDiff line change
@@ -3,9 +3,14 @@ package main
33
import (
44
"crypto/tls"
55
"crypto/x509"
6+
"encoding/json"
67
"fmt"
8+
"io"
9+
"net/http"
710
"net/url"
811
"os"
12+
"strconv"
13+
"strings"
914
"time"
1015

1116
"code.cloudfoundry.org/tlsconfig"
@@ -14,30 +19,41 @@ import (
1419
)
1520

1621
const USAGE = `Usage:
17-
/var/vcap/jobs/gorouter/bin/nats_client [COMMAND] [SUBJECT] [MESSAGE]
22+
/var/vcap/jobs/gorouter/bin/nats_client [COMMAND]
1823
1924
COMMANDS:
20-
subscribe (Default) Streams NATS messages from server with provided SUBJECT. Default SUBJECT is 'router.*'
21-
Example: /var/vcap/jobs/gorouter/bin/nats_client subscribe 'router.*'
25+
sub [SUBJECT] [MESSAGE]
26+
(Default) Streams NATS messages from server with provided SUBJECT. Default SUBJECT is 'router.*'
27+
Example: /var/vcap/jobs/gorouter/bin/nats_client sub 'router.*'
2228
23-
publish Publish the provided message JSON to SUBJECT subscription. SUBJECT and MESSAGE are required
24-
Example: /var/vcap/jobs/gorouter/bin/nats_client publish router.register '{"host":"172.217.6.68","port":80,"uris":["bar.example.com"]}'
29+
pub [SUBJECT] [MESSAGE]
30+
Publish the provided message JSON to SUBJECT subscription. SUBJECT and MESSAGE are required
31+
Example: /var/vcap/jobs/gorouter/bin/nats_client pub router.register '{"host":"172.217.6.68","port":80,"uris":["bar.example.com"]}'
32+
33+
save <FILE>
34+
Save this gorouter's route table to a json file.
35+
Example: /var/vcap/jobs/gorouter/bin/nats_client save routes.json'
36+
37+
load <FILE>
38+
Load routes from a json file into this gorouter.
39+
Example: /var/vcap/jobs/gorouter/bin/nats_client load routes.json'
2540
`
2641

2742
// Simple NATS client for debugging
2843
// Uses gorouter.yml for config
2944
func main() {
30-
if os.Args[len(os.Args)-1] == "--help" || os.Args[len(os.Args)-1] == "-h" || os.Args[len(os.Args)-1] == "help" {
45+
//TODO: use a proper arg parser here
46+
if len(os.Args) < 2 || os.Args[len(os.Args)-1] == "--help" || os.Args[len(os.Args)-1] == "-h" || os.Args[len(os.Args)-1] == "help" {
3147
fmt.Println(USAGE)
3248
os.Exit(1)
3349
}
3450

3551
configPath := os.Args[1]
36-
command := "subscribe"
52+
command := "sub"
3753
if len(os.Args) >= 3 {
3854
command = os.Args[2]
3955
}
40-
if command != "subscribe" && command != "publish" {
56+
if command != "sub" && command != "pub" && command != "save" && command != "load" {
4157
fmt.Println(USAGE)
4258
os.Exit(1)
4359
}
@@ -48,7 +64,7 @@ func main() {
4864
}
4965

5066
var message string
51-
if command == "publish" {
67+
if command == "pub" {
5268
if len(os.Args) >= 5 {
5369
message = os.Args[4]
5470
} else {
@@ -57,6 +73,16 @@ func main() {
5773
}
5874
}
5975

76+
var filename string
77+
if command == "save" || command == "load" {
78+
if len(os.Args) >= 4 {
79+
filename = os.Args[3]
80+
} else {
81+
fmt.Println(USAGE)
82+
os.Exit(1)
83+
}
84+
}
85+
6086
config, err := loadConfig(configPath)
6187
if err != nil {
6288
panic(err)
@@ -72,7 +98,7 @@ func main() {
7298
panic(err)
7399
}
74100

75-
if command == "publish" {
101+
if command == "pub" {
76102
fmt.Fprintf(os.Stderr, "Publishing message to %s\n", subject)
77103
err := natsConn.Publish(subject, []byte(message))
78104
if err != nil {
@@ -81,7 +107,7 @@ func main() {
81107
fmt.Fprintln(os.Stderr, "Done")
82108
}
83109

84-
if command == "subscribe" {
110+
if command == "sub" {
85111
fmt.Fprintf(os.Stderr, "Subscribing to %s\n", subject)
86112
subscription, err := natsConn.SubscribeSync(subject)
87113
if err != nil {
@@ -98,6 +124,25 @@ func main() {
98124
}
99125
}
100126
}
127+
128+
if command == "save" {
129+
fmt.Fprintf(os.Stderr, "Saving route table to %s\n", filename)
130+
err := dumpRoutes(config, filename)
131+
if err != nil {
132+
panic(err)
133+
}
134+
fmt.Fprintln(os.Stderr, "Done")
135+
}
136+
137+
if command == "load" {
138+
fmt.Fprintf(os.Stderr, "Loading route table from %s\n", filename)
139+
err := loadRoutes(config, filename)
140+
if err != nil {
141+
panic(err)
142+
}
143+
fmt.Fprintln(os.Stderr, "Done")
144+
}
145+
101146
}
102147

103148
// From code.cloudfoundry.org/gorouter/mbus/client.go
@@ -124,10 +169,18 @@ func natsOptions(c *Config) (nats.Options, error) {
124169

125170
// From src/code.cloudfoundry.org/gorouter/config/config.go
126171
type Config struct {
172+
Status StatusConfig `yaml:"status,omitempty"`
127173
Nats NatsConfig `yaml:"nats,omitempty"`
128174
NatsClientPingInterval time.Duration `yaml:"nats_client_ping_interval,omitempty"`
129175
}
130176

177+
type StatusConfig struct {
178+
Host string `yaml:"host"`
179+
Port uint16 `yaml:"port"`
180+
User string `yaml:"user"`
181+
Pass string `yaml:"pass"`
182+
}
183+
131184
type NatsConfig struct {
132185
Hosts []NatsHost `yaml:"hosts"`
133186
User string `yaml:"user"`
@@ -192,3 +245,131 @@ func (c *Config) NatsServers() []string {
192245

193246
return natsServers
194247
}
248+
249+
func dumpRoutes(config *Config, filename string) error {
250+
res, err := http.Get(fmt.Sprintf("http://%s:%s@%s:%d/routes", config.Status.User, config.Status.Pass, config.Status.Host, config.Status.Port))
251+
252+
if err != nil {
253+
return err
254+
}
255+
if res.StatusCode != http.StatusOK {
256+
return fmt.Errorf("unexpected status code from /routes: %s", res.Status)
257+
}
258+
file, err := os.Create(filename)
259+
if err != nil {
260+
return err
261+
}
262+
defer file.Close()
263+
264+
var jsonObject map[string]interface{}
265+
dataIn, err := io.ReadAll(res.Body)
266+
if err != nil {
267+
return err
268+
}
269+
err = json.Unmarshal(dataIn, &jsonObject)
270+
if err != nil {
271+
return err
272+
}
273+
// Pretty print json so that humans can change it.
274+
dataOut, err := json.MarshalIndent(jsonObject, "", " ")
275+
if err != nil {
276+
return err
277+
}
278+
_, err = file.Write(dataOut)
279+
if err != nil {
280+
return err
281+
}
282+
283+
return err
284+
}
285+
286+
// From src/code.cloudfoundry.org/gorouter/mbus/subscriber.go
287+
type RegistryMessage struct {
288+
Host string `json:"host"`
289+
Port int `json:"port"`
290+
Protocol string `json:"protocol"`
291+
TLSPort int `json:"tls_port"`
292+
Uris []string `json:"uris"`
293+
Tags map[string]string `json:"tags"`
294+
App string `json:"app"`
295+
StaleThresholdInSeconds int `json:"stale_threshold_in_seconds"`
296+
RouteServiceURL string `json:"route_service_url"`
297+
PrivateInstanceID string `json:"private_instance_id"`
298+
ServerCertDomainSAN string `json:"server_cert_domain_san"`
299+
PrivateInstanceIndex string `json:"private_instance_index"`
300+
IsolationSegment string `json:"isolation_segment"`
301+
EndpointUpdatedAtNs int64 `json:"endpoint_updated_at_ns"`
302+
}
303+
304+
// From src/code.cloudfoundry.org/gorouter/route/pool.go
305+
type RouteTableEntry struct {
306+
Address string `json:"address"`
307+
Protocol string `json:"protocol"`
308+
TLS bool `json:"tls"`
309+
TTL int `json:"ttl"`
310+
RouteServiceUrl string `json:"route_service_url,omitempty"`
311+
Tags map[string]string `json:"tags"`
312+
IsolationSegment string `json:"isolation_segment,omitempty"`
313+
PrivateInstanceId string `json:"private_instance_id,omitempty"`
314+
ServerCertDomainSAN string `json:"server_cert_domain_san,omitempty"`
315+
}
316+
317+
func loadRoutes(config *Config, filename string) error {
318+
natsOptions, err := natsOptions(config)
319+
if err != nil {
320+
return err
321+
}
322+
323+
natsConn, err := natsOptions.Connect()
324+
if err != nil {
325+
return err
326+
}
327+
328+
var routeTable map[string][]RouteTableEntry
329+
330+
routesFile, err := os.Open(filename)
331+
if err != nil {
332+
return err
333+
}
334+
data, err := io.ReadAll(routesFile)
335+
if err != nil {
336+
return err
337+
}
338+
err = json.Unmarshal(data, &routeTable)
339+
if err != nil {
340+
return err
341+
}
342+
for uri, routes := range routeTable {
343+
for _, route := range routes {
344+
host := strings.Split(route.Address, ":")[0]
345+
port, _ := strconv.Atoi(strings.Split(route.Address, ":")[1])
346+
tlsPort := 0
347+
if route.TLS {
348+
tlsPort = port
349+
}
350+
351+
msg := RegistryMessage{
352+
Host: host,
353+
Port: port,
354+
TLSPort: tlsPort,
355+
Protocol: route.Protocol,
356+
Uris: []string{uri},
357+
Tags: route.Tags,
358+
App: route.Tags["app_id"],
359+
StaleThresholdInSeconds: route.TTL,
360+
PrivateInstanceID: route.PrivateInstanceId,
361+
IsolationSegment: route.IsolationSegment,
362+
ServerCertDomainSAN: route.ServerCertDomainSAN,
363+
}
364+
msgData, err := json.Marshal(msg)
365+
if err != nil {
366+
return err
367+
}
368+
err = natsConn.Publish("router.register", msgData)
369+
if err != nil {
370+
return err
371+
}
372+
}
373+
}
374+
return nil
375+
}

0 commit comments

Comments
 (0)