From ba3e9df078fc86e450ce6bcc8e4d79b0c185d2c2 Mon Sep 17 00:00:00 2001 From: roos Date: Wed, 15 Apr 2020 19:07:56 +0200 Subject: [PATCH] json output --- go/pkg/showpaths/BUILD.bazel | 4 +- go/pkg/showpaths/config.go | 54 +++++++++++++++++++++++ go/pkg/showpaths/options.go | 81 ----------------------------------- go/pkg/showpaths/showpaths.go | 74 ++++++++++++++++++++++++++------ go/scion/scion.go | 2 +- go/scion/showpaths.go | 29 ++++++++----- go/tools/showpaths/paths.go | 23 +++++----- 7 files changed, 147 insertions(+), 120 deletions(-) create mode 100644 go/pkg/showpaths/config.go delete mode 100644 go/pkg/showpaths/options.go diff --git a/go/pkg/showpaths/BUILD.bazel b/go/pkg/showpaths/BUILD.bazel index f308d7d5b9..c4dc91fdb0 100644 --- a/go/pkg/showpaths/BUILD.bazel +++ b/go/pkg/showpaths/BUILD.bazel @@ -3,16 +3,18 @@ load("@io_bazel_rules_go//go:def.bzl", "go_library") go_library( name = "go_default_library", srcs = [ - "options.go", + "config.go", "showpaths.go", ], importpath = "github.com/scionproto/scion/go/pkg/showpaths", visibility = ["//visibility:public"], deps = [ "//go/lib/addr:go_default_library", + "//go/lib/common:go_default_library", "//go/lib/sciond:go_default_library", "//go/lib/sciond/pathprobe:go_default_library", "//go/lib/serrors:go_default_library", + "//go/lib/snet:go_default_library", "//go/lib/snet/addrutil:go_default_library", ], ) diff --git a/go/pkg/showpaths/config.go b/go/pkg/showpaths/config.go new file mode 100644 index 0000000000..ecc715cec7 --- /dev/null +++ b/go/pkg/showpaths/config.go @@ -0,0 +1,54 @@ +// Copyright 2020 Anapaya Systems +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// http://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. + +package showpaths + +import ( + "net" + + "github.com/scionproto/scion/go/lib/sciond" +) + +// DefaultMaxPaths is the maximum number of paths that are displayed by default. +const DefaultMaxPaths = 10 + +// Config configures the showpath run. +type Config struct { + // Local configures the local IP address to use. If this option is not provided, + // a local IP that can reach SCION hosts is selected with the help of the kernel. + Local net.IP + // SCIOND configures a specific SCION Deamon address. + SCIOND string + // MaxPaths configures the maximum number of displayed paths. If this option is + // not provided, the DefaultMaxPaths is used. + MaxPaths int + // ShowExpiration configures whether the expiration is displayed. + ShowExpiration bool + // Refresh configures whether sciond is queried with the refresh flag. + Refresh bool + // Probe configures whether the path status is probed and displayed. + Probe bool + // JSON configures whether the output is written as json. + JSON bool +} + +// InitDefaults initializes the default values. +func (cfg *Config) InitDefaults() { + if cfg.SCIOND == "" { + cfg.SCIOND = sciond.DefaultSCIONDAddress + } + if cfg.MaxPaths == 0 { + cfg.MaxPaths = DefaultMaxPaths + } +} diff --git a/go/pkg/showpaths/options.go b/go/pkg/showpaths/options.go deleted file mode 100644 index c129c645c7..0000000000 --- a/go/pkg/showpaths/options.go +++ /dev/null @@ -1,81 +0,0 @@ -// Copyright 2020 Anapaya Systems -// -// Licensed under the Apache License, Version 2.0 (the "License"); -// you may not use this file except in compliance with the License. -// You may obtain a copy of the License at -// -// http://www.apache.org/licenses/LICENSE-2.0 -// -// Unless required by applicable law or agreed to in writing, software -// distributed under the License is distributed on an "AS IS" BASIS, -// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -// See the License for the specific language governing permissions and -// limitations under the License. - -package showpaths - -import ( - "net" - - "github.com/scionproto/scion/go/lib/sciond" -) - -// DefaultMaxPaths is the maximum number of paths that are displayed by default. -const DefaultMaxPaths = 10 - -// Option configures the showpath run. -type Option func(*options) - -type options struct { - local net.IP - sciond string - maxPaths int - expiration bool - refresh bool - probe bool -} - -func invokeOptions(opts []Option) options { - o := options{ - sciond: sciond.DefaultSCIONDAddress, - maxPaths: DefaultMaxPaths, - } - for _, opt := range opts { - opt(&o) - } - return o -} - -// SCIOND configures a specific SCION Deamon address. -func SCIOND(sciond string) Option { - return func(opts *options) { opts.sciond = sciond } -} - -// MaxPaths configures the maximum number of displayed paths. If this option is -// not provided, the DefaultMaxPaths is used. -func MaxPaths(maxPaths int) Option { - return func(opts *options) { opts.maxPaths = maxPaths } -} - -// ShowExpiration configures whether the expiration is displayed. -func ShowExpiration(show bool) Option { - return func(opts *options) { opts.expiration = show } -} - -// Refresh configures whether sciond is queried with the refresh flag. -func Refresh(refresh bool) Option { - return func(opts *options) { opts.refresh = refresh } - -} - -// Probe configures whether the path status is probed and displayed. -func Probe(probe bool) Option { - return func(opts *options) { opts.probe = probe } - -} - -// Local configures the local IP address to use. If this option is not provided, -// a local IP that can reach SCION hosts is selected with the help of the kernel. -func Local(local net.IP) Option { - return func(opts *options) { opts.local = local } -} diff --git a/go/pkg/showpaths/showpaths.go b/go/pkg/showpaths/showpaths.go index 150f02c1de..bf8a1035e4 100644 --- a/go/pkg/showpaths/showpaths.go +++ b/go/pkg/showpaths/showpaths.go @@ -16,22 +16,25 @@ package showpaths import ( "context" + "encoding/json" "fmt" "net" + "os" + "strings" "time" "github.com/scionproto/scion/go/lib/addr" + "github.com/scionproto/scion/go/lib/common" "github.com/scionproto/scion/go/lib/sciond" "github.com/scionproto/scion/go/lib/sciond/pathprobe" "github.com/scionproto/scion/go/lib/serrors" + "github.com/scionproto/scion/go/lib/snet" "github.com/scionproto/scion/go/lib/snet/addrutil" ) // Run lists the paths to the specified ISD-AS to stdout. -func Run(ctx context.Context, dst addr.IA, options ...Option) error { - opts := invokeOptions(options) - - sdConn, err := sciond.NewService(opts.sciond).Connect(ctx) +func Run(ctx context.Context, dst addr.IA, cfg Config) error { + sdConn, err := sciond.NewService(cfg.SCIOND).Connect(ctx) if err != nil { return serrors.WrapStr("error connecting to SCIOND", err) } @@ -44,22 +47,21 @@ func Run(ctx context.Context, dst addr.IA, options ...Option) error { // possibility to have the same functionality, i.e. refresh, fetch all paths. // https://github.com/scionproto/scion/issues/3348 paths, err := sdConn.Paths(ctx, dst, addr.IA{}, - sciond.PathReqFlags{Refresh: opts.refresh, PathCount: uint16(opts.maxPaths)}) + sciond.PathReqFlags{Refresh: cfg.Refresh, PathCount: uint16(cfg.MaxPaths)}) if err != nil { return serrors.WrapStr("failed to retrieve paths from SCIOND", err) } - fmt.Println("Available paths to", dst) - var pathStatuses map[string]pathprobe.Status - if opts.probe { - localIP := opts.local + var statuses map[string]pathprobe.Status + if cfg.Probe { + localIP := cfg.Local if localIP == nil { localIP, err = findDefaultLocalIP(ctx, sdConn) if err != nil { return serrors.WrapStr("failed to determine local IP", err) } } - pathStatuses, err = pathprobe.Prober{ + statuses, err = pathprobe.Prober{ DstIA: dst, LocalIA: localIA, LocalIP: localIP, @@ -68,18 +70,62 @@ func Run(ctx context.Context, dst addr.IA, options ...Option) error { serrors.WrapStr("failed to get status", err) } } + if cfg.JSON { + return machine(paths, statuses, cfg.ShowExpiration) + } + fmt.Println("Available paths to", dst) + human(paths, statuses, cfg.ShowExpiration) + return nil +} + +func human(paths []snet.Path, statuses map[string]pathprobe.Status, showExpiration bool) { for i, path := range paths { fmt.Printf("[%2d] %s", i, fmt.Sprintf("%s", path)) - if opts.expiration { + if showExpiration { fmt.Printf(" Expires: %s (%s)", path.Expiry(), time.Until(path.Expiry()).Truncate(time.Second)) } - if pathStatuses != nil { - fmt.Printf(" Status: %s", pathStatuses[pathprobe.PathKey(path)]) + if statuses != nil { + fmt.Printf(" Status: %s", statuses[pathprobe.PathKey(path)]) } fmt.Printf("\n") } - return nil +} + +func machine(paths []snet.Path, statuses map[string]pathprobe.Status, showExpiration bool) error { + type Hop struct { + IfID common.IFIDType `json:"ifid"` + IA addr.IA `json:"isd_as"` + } + type Path struct { + Fingerprint string `json:"fingerprint"` + Hops []Hop `json:"hops"` + NextHop string `json:"next_hop"` + Expiry time.Time `json:"expiry"` + MTU uint16 `json:"mtu"` + Status string `json:"status,omitempty"` + StatusInfo string `json:"status_info,omitempty"` + } + jpaths := make([]Path, 0, len(paths)) + for _, path := range paths { + jpath := Path{ + Fingerprint: path.Fingerprint().String()[:16], + NextHop: path.OverlayNextHop().String(), + Expiry: path.Expiry(), + MTU: path.MTU(), + } + for _, hop := range path.Interfaces() { + jpath.Hops = append(jpath.Hops, Hop{IA: hop.IA(), IfID: hop.ID()}) + } + if status, ok := statuses[pathprobe.PathKey(path)]; ok { + jpath.Status = strings.ToLower(string(status.Status)) + jpath.StatusInfo = status.AdditionalInfo + } + jpaths = append(jpaths, jpath) + } + enc := json.NewEncoder(os.Stdout) + enc.SetIndent("", " ") + return enc.Encode(jpaths) } // TODO(matzf): this is a simple, hopefully temporary, workaround to not having diff --git a/go/scion/scion.go b/go/scion/scion.go index 529833d056..6bcb700527 100644 --- a/go/scion/scion.go +++ b/go/scion/scion.go @@ -23,7 +23,7 @@ import ( func main() { if err := rootCmd.Execute(); err != nil { - fmt.Fprintln(os.Stderr, err) + fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) } } diff --git a/go/scion/showpaths.go b/go/scion/showpaths.go index e6349baa85..733d8f06af 100644 --- a/go/scion/showpaths.go +++ b/go/scion/showpaths.go @@ -34,19 +34,25 @@ var showpathsFlags struct { expiration bool refresh bool probe bool + json bool local net.IP } var showpathsCmd = &cobra.Command{ Use: "showpaths", - Short: "A clean-slate internet architecture", + Short: "Display paths to a SCION AS", Args: cobra.ExactArgs(1), + Example: ` scion showpaths 1-ff00:0:110 --probe --expiration + scion showpaths 1-ff00:0:110 --probe --json + scion showpaths 1-ff00:0:110 --local 127.0.0.55`, Long: `'showpaths' lists available paths between the local and the specified SCION ASe a. By default, the paths are not probed. As paths might be served from the SCION Deamon's cache, they might not forward traffic successfully (e.g. if a network link went down). To list the paths with their health statuses, specify that the paths should be probed through the flag. + +'showpaths' can be instructed to output the paths as json using the the --json flag. `, RunE: func(cmd *cobra.Command, args []string) error { dst, err := addr.IAFromString(args[0]) @@ -62,17 +68,16 @@ through the flag. ctx, cancel := context.WithTimeout(context.Background(), showpathsFlags.timeout) defer cancel() - opts := []showpaths.Option{ - showpaths.SCIOND(showpathsFlags.sciond), - showpaths.MaxPaths(showpathsFlags.maxPaths), - showpaths.ShowExpiration(showpathsFlags.expiration), - showpaths.Refresh(showpathsFlags.refresh), - showpaths.Probe(showpathsFlags.probe), - } - if showpathsFlags.local != nil { - opts = append(opts, showpaths.Local(showpathsFlags.local)) + cfg := showpaths.Config{ + Local: showpathsFlags.local, + SCIOND: showpathsFlags.sciond, + MaxPaths: showpathsFlags.maxPaths, + ShowExpiration: showpathsFlags.expiration, + Refresh: showpathsFlags.refresh, + Probe: showpathsFlags.probe, + JSON: showpathsFlags.json, } - if err := showpaths.Run(ctx, dst, opts...); err != nil { + if err := showpaths.Run(ctx, dst, cfg); err != nil { return err } return nil @@ -92,6 +97,8 @@ func init() { "Set refresh flag for SCION Deamon path request") showpathsCmd.Flags().BoolVarP(&showpathsFlags.probe, "probe", "p", false, "Probe the paths and print the health status") + showpathsCmd.Flags().BoolVarP(&showpathsFlags.probe, "json", "j", false, + "Write the output as machine readable json") showpathsCmd.Flags().IPVarP(&showpathsFlags.local, "local", "l", nil, "Optional local IP address to use for probing health checks") } diff --git a/go/tools/showpaths/paths.go b/go/tools/showpaths/paths.go index 6a741d67d4..820adc23a4 100644 --- a/go/tools/showpaths/paths.go +++ b/go/tools/showpaths/paths.go @@ -37,6 +37,7 @@ var ( maxPaths = flag.Int("maxpaths", 10, "Maximum number of paths") expiration = flag.Bool("expiration", false, "Show path expiration timestamps") refresh = flag.Bool("refresh", false, "Set refresh flag for SCIOND path request") + json = flag.Bool("json", false, "Write output as machine readable json") status = flag.Bool("p", false, "Probe the paths and print out the statuses") localIPStr = flag.String("local", "", "(Optional) local IP address to use for health checks") version = flag.Bool("version", false, "Output version information and exit.") @@ -66,19 +67,17 @@ func main() { ctx, cancelF := context.WithTimeout(context.Background(), *timeout) defer cancelF() - - opts := []showpaths.Option{ - showpaths.SCIOND(*sciondAddr), - showpaths.MaxPaths(*maxPaths), - showpaths.ShowExpiration(*expiration), - showpaths.Refresh(*refresh), - showpaths.Probe(*status), - } - if localIP != nil { - opts = append(opts, showpaths.Local(localIP)) + cfg := showpaths.Config{ + Local: localIP, + SCIOND: *sciondAddr, + MaxPaths: *maxPaths, + ShowExpiration: *expiration, + Refresh: *refresh, + Probe: *status, + JSON: *json, } - if err := showpaths.Run(ctx, dstIA, opts...); err != nil { - fmt.Fprintf(os.Stderr, "Error: %s", err) + if err := showpaths.Run(ctx, dstIA, cfg); err != nil { + fmt.Fprintf(os.Stderr, "Error: %s\n", err) os.Exit(1) } }