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
+}