Merge branch 'master' of /home/q3k/Projects/hscloud/go/src/code.hackerspace.pl/q3k/arista-proxy

master
Serge Bazanski 2018-10-25 12:06:17 +01:00
commit 5f5a0353c2
7 changed files with 250 additions and 0 deletions

2
.gitignore vendored Normal file
View File

@ -0,0 +1,2 @@
arista-proxy
*swp

49
arista-proxy/README.md Normal file
View File

@ -0,0 +1,49 @@
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.
PKI
---
This service uses [HSPKI](https://code.hackerspace.pl/q3k/hspki), you will need to generate development TLS certificates for local use.
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

31
arista-proxy/arista.proto Normal file
View File

@ -0,0 +1,31 @@
syntax = "proto3";
package 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);
};

67
arista-proxy/main.go Normal file
View File

@ -0,0 +1,67 @@
package main
import (
"flag"
"fmt"
"code.hackerspace.pl/q3k/mirko"
"github.com/golang/glog"
"github.com/ybbus/jsonrpc"
pb "code.hackerspace.pl/q3k/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 {}
}

1
arista-proxy/proto/.gitignore vendored Normal file
View File

@ -0,0 +1 @@
arista.pb.go

View File

@ -0,0 +1,3 @@
//go:generate protoc -I.. ../arista.proto --go_out=plugins=grpc:.
package proto

97
arista-proxy/service.go Normal file
View File

@ -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/q3k/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
}