go/svc/(dc stuff) -> dc/
We want to start keeping codebases separated per 'team'/intent, to then
have simple OWNER files/trees to specify review rules.
This means dc/ stuff can all be OWNED by q3k, and review will only
involve a +1 for style/readability, instead of a +2 for approval.
Change-Id: I05afbc4e1018944b841ec0d88cd24cc95bec8bf1
diff --git a/dc/arista-proxy/BUILD.bazel b/dc/arista-proxy/BUILD.bazel
new file mode 100644
index 0000000..fb7ec6a
--- /dev/null
+++ b/dc/arista-proxy/BUILD.bazel
@@ -0,0 +1,25 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_binary", "go_library")
+
+go_library(
+ name = "go_default_library",
+ srcs = [
+ "main.go",
+ "service.go",
+ ],
+ importpath = "code.hackerspace.pl/hscloud/dc/arista-proxy",
+ visibility = ["//visibility:private"],
+ deps = [
+ "//dc/arista-proxy/proto:go_default_library",
+ "//go/mirko:go_default_library",
+ "@com_github_golang_glog//:go_default_library",
+ "@com_github_ybbus_jsonrpc//:go_default_library",
+ "@org_golang_google_grpc//codes:go_default_library",
+ "@org_golang_google_grpc//status:go_default_library",
+ ],
+)
+
+go_binary(
+ name = "arista-proxy",
+ embed = [":go_default_library"],
+ visibility = ["//visibility:public"],
+)
diff --git a/dc/arista-proxy/README.md b/dc/arista-proxy/README.md
new file mode 100644
index 0000000..60368dc
--- /dev/null
+++ b/dc/arista-proxy/README.md
@@ -0,0 +1,44 @@
+Old Shitty Arista eAPI/Capi <-> gRPC proxy
+==========================================
+
+Our Arista 7148S does not support gRPC/OpenConfig, so we have to make our own damn gRPC proxy.
+
+The schema is supposed to be 1:1 mapped to the JSON-RPC EAPI. This is just a dumb proxy.
+
+Getting and Building
+--------------------
+
+ go get -d -u code.hackerspace.pl/q3k/arista-proxy
+ go generate code.hackerspace.pl/q3k/arista-proxy/proto
+ go build code.hackerspace.pl/q3k/arista-proxy
+
+Debug Status Page
+-----------------
+
+The `debug_address` flag controls spawning an HTTP server useful for debugging. You can use it to inspect gRPC request and view general status information of the proxy.
+
+Flags
+-----
+
+ ./arista-proxy -help
+ Usage of ./arista-proxy:
+ -alsologtostderr
+ log to standard error as well as files
+ -arista_api string
+ Arista remote endpoint (default "http://admin:password@1.2.3.4:80/command-api")
+ -debug_address string
+ Debug HTTP listen address, or empty to disable (default "127.0.0.1:42000")
+ -listen_address string
+ gRPC listen address (default "127.0.0.1:43001")
+ -log_backtrace_at value
+ when logging hits line file:N, emit a stack trace
+ -log_dir string
+ If non-empty, write log files in this directory
+ -logtostderr
+ log to standard error instead of files
+ -stderrthreshold value
+ logs at or above this threshold go to stderr
+ -v value
+ log level for V logs
+ -vmodule value
+ comma-separated list of pattern=N settings for file-filtered logging
diff --git a/dc/arista-proxy/main.go b/dc/arista-proxy/main.go
new file mode 100644
index 0000000..ccd1046
--- /dev/null
+++ b/dc/arista-proxy/main.go
@@ -0,0 +1,67 @@
+package main
+
+import (
+ "flag"
+ "fmt"
+
+ "code.hackerspace.pl/hscloud/go/mirko"
+ "github.com/golang/glog"
+ "github.com/ybbus/jsonrpc"
+
+ pb "code.hackerspace.pl/hscloud/dc/arista-proxy/proto"
+)
+
+var (
+ flagAristaAPI string
+)
+
+type aristaClient struct {
+ rpc jsonrpc.RPCClient
+}
+
+func (c *aristaClient) structuredCall(res interface{}, command ...string) error {
+ cmd := struct {
+ Version int `json:"version"`
+ Cmds []string `json:"cmds"`
+ Format string `json:"format"`
+ }{
+ Version: 1,
+ Cmds: command,
+ Format: "json",
+ }
+
+ err := c.rpc.CallFor(res, "runCmds", cmd)
+ if err != nil {
+ return fmt.Errorf("could not execute structured call: %v", err)
+ }
+ return nil
+}
+
+type server struct {
+ arista *aristaClient
+}
+
+func main() {
+ flag.StringVar(&flagAristaAPI, "arista_api", "http://admin:password@1.2.3.4:80/command-api", "Arista remote endpoint")
+ flag.Parse()
+
+ arista := &aristaClient{
+ rpc: jsonrpc.NewClient(flagAristaAPI),
+ }
+
+ m := mirko.New()
+ if err := m.Listen(); err != nil {
+ glog.Exitf("Listen(): %v", err)
+ }
+
+ s := &server{
+ arista: arista,
+ }
+ pb.RegisterAristaProxyServer(m.GRPC(), s)
+
+ if err := m.Serve(); err != nil {
+ glog.Exitf("Serve(): %v", err)
+ }
+
+ select {}
+}
diff --git a/dc/arista-proxy/proto/.gitignore b/dc/arista-proxy/proto/.gitignore
new file mode 100644
index 0000000..46ddcab
--- /dev/null
+++ b/dc/arista-proxy/proto/.gitignore
@@ -0,0 +1 @@
+arista.pb.go
diff --git a/dc/arista-proxy/proto/BUILD.bazel b/dc/arista-proxy/proto/BUILD.bazel
new file mode 100644
index 0000000..2df4f58
--- /dev/null
+++ b/dc/arista-proxy/proto/BUILD.bazel
@@ -0,0 +1,23 @@
+load("@io_bazel_rules_go//go:def.bzl", "go_library")
+load("@io_bazel_rules_go//proto:def.bzl", "go_proto_library")
+
+proto_library(
+ name = "proto_proto",
+ srcs = ["arista.proto"],
+ visibility = ["//visibility:public"],
+)
+
+go_proto_library(
+ name = "proto_go_proto",
+ compilers = ["@io_bazel_rules_go//proto:go_grpc"],
+ importpath = "code.hackerspace.pl/hscloud/dc/arista-proxy/proto",
+ proto = ":proto_proto",
+ visibility = ["//visibility:public"],
+)
+
+go_library(
+ name = "go_default_library",
+ embed = [":proto_go_proto"],
+ importpath = "code.hackerspace.pl/hscloud/dc/arista-proxy/proto",
+ visibility = ["//visibility:public"],
+)
diff --git a/dc/arista-proxy/proto/arista.proto b/dc/arista-proxy/proto/arista.proto
new file mode 100644
index 0000000..2874f70
--- /dev/null
+++ b/dc/arista-proxy/proto/arista.proto
@@ -0,0 +1,31 @@
+syntax = "proto3";
+package proto;
+option go_package = "code.hackerspace.pl/hscloud/dc/arista-proxy/proto";
+
+message ShowVersionRequest {
+};
+
+message ShowVersionResponse {
+ string model_name = 1;
+ string internal_version = 2;
+ string system_mac_address = 3;
+ string serial_number = 4;
+ int64 mem_total = 5;
+ double bootup_timestamp = 6;
+ int64 mem_free = 7;
+ string version = 8;
+ string architecture = 9;
+ string internal_build_id = 10;
+ string hardware_revision = 11;
+};
+
+message ShowEnvironmentTemperatureRequest {
+};
+
+message ShowEnvironmentTemperatureResponse {
+};
+
+service AristaProxy {
+ rpc ShowVersion(ShowVersionRequest) returns (ShowVersionResponse);
+ rpc ShowEnvironmentTemperature(ShowEnvironmentTemperatureRequest) returns (ShowEnvironmentTemperatureResponse);
+};
diff --git a/dc/arista-proxy/service.go b/dc/arista-proxy/service.go
new file mode 100644
index 0000000..3144ff7
--- /dev/null
+++ b/dc/arista-proxy/service.go
@@ -0,0 +1,97 @@
+package main
+
+import (
+ "context"
+
+ "github.com/golang/glog"
+ "google.golang.org/grpc/codes"
+ "google.golang.org/grpc/status"
+
+ pb "code.hackerspace.pl/hscloud/dc/arista-proxy/proto"
+)
+
+func (s *server) ShowVersion(ctx context.Context, req *pb.ShowVersionRequest) (*pb.ShowVersionResponse, error) {
+ var version []struct {
+ ModelName string `json:"modelName"`
+ InternalVersion string `json:"internalVersion"`
+ SystemMacAddress string `json:"systemMacAddress"`
+ SerialNumber string `json:"serialNumber"`
+ MemTotal int64 `json:"memTotal"`
+ BootupTimestamp float64 `json:"bootupTimestamp"`
+ MemFree int64 `json:"memFree"`
+ Version string `json:"version"`
+ Architecture string `json:"architecture"`
+ InternalBuildId string `json:"internalBuildId"`
+ HardwareRevision string `json:"hardwareRevision"`
+ }
+
+ err := s.arista.structuredCall(&version, "show version")
+ if err != nil {
+ glog.Errorf("EOS Capi: show version: %v", err)
+ return nil, status.Error(codes.Unavailable, "EOS Capi call failed")
+ }
+
+ if len(version) != 1 {
+ glog.Errorf("Expected 1-length result, got %d", len(version))
+ return nil, status.Error(codes.Internal, "Internal error")
+ }
+
+ d := version[0]
+
+ return &pb.ShowVersionResponse{
+ ModelName: d.ModelName,
+ InternalVersion: d.InternalVersion,
+ SystemMacAddress: d.SystemMacAddress,
+ SerialNumber: d.SerialNumber,
+ MemTotal: d.MemTotal,
+ BootupTimestamp: d.BootupTimestamp,
+ MemFree: d.MemFree,
+ Version: d.Version,
+ Architecture: d.Architecture,
+ InternalBuildId: d.InternalBuildId,
+ HardwareRevision: d.HardwareRevision,
+ }, nil
+}
+
+type temperatureSensor struct {
+ InAlertState bool `json:"inAlertState"`
+ MaxTemperature float64 `json:"maxTemperature"`
+ RelPos int64 `json:"relPos"`
+ Description string `json:"description"`
+ Name string `json:"name"`
+ AlertCount int64 `json:"alertCount"`
+ CurrentTemperature float64 `json:"currentTemperature"`
+ OverheatThreshold float64 `json:"overheatThreshold"`
+ CriticalThreshold float64 `json:"criticalThreshold"`
+ HwStatus string `json:"hwStatus"`
+}
+
+func (s *server) ShowEnvironmentTemperature(ctx context.Context, req *pb.ShowEnvironmentTemperatureRequest) (*pb.ShowEnvironmentTemperatureResponse, error) {
+ var response []struct {
+ PowerSuppplySlots []struct {
+ TempSensors []temperatureSensor `json:"tempSensors"`
+ EntPhysicalClass string `json:"entPhysicalClass"`
+ RelPos int64 `json:"relPos"`
+ } `json:"powerSupplySlots"`
+
+ ShutdownOnOverheat bool `json:"shutdownOnOverheat"`
+ TempSensors []temperatureSensor `json:"tempSensors"`
+ SystemStatus string `json:"systemStatus"`
+ }
+
+ err := s.arista.structuredCall(&response, "show environment temperature")
+ if err != nil {
+ glog.Errorf("EOS Capi: show environment temperature: %v", err)
+ return nil, status.Error(codes.Unavailable, "EOS Capi call failed")
+ }
+
+ if len(response) != 1 {
+ glog.Errorf("Expected 1-length result, got %d", len(response))
+ return nil, status.Error(codes.Internal, "Internal error")
+ }
+
+ d := response[0]
+ glog.Infof("%+v", d)
+
+ return &pb.ShowEnvironmentTemperatureResponse{}, nil
+}