merging wrapup
commit
6ad458ff3d
@ -1 +0,0 @@
|
||||
b43ecff1fe53e18c4c9b756b32d38078
|
@ -1,3 +1,64 @@
|
||||
# DMAC
|
||||
|
||||
Distributed Monitoring and Control
|
||||
## Distributed Monitoring and Control
|
||||
|
||||
This branch will serve as the staging ground for adding unit tests and documentation in order to finalize v0.1.0-alpha
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Introduction](#introduction)
|
||||
* [Getting Started](#getting-started)
|
||||
* [Installation](#installation)
|
||||
* [Usage](#usage)
|
||||
* [Wiki](wiki/wiki.md)
|
||||
* [Overview](wiki/wiki.md#overview)
|
||||
* [Server](wiki/server.md)
|
||||
* [Reactor](wiki/reactor.md)
|
||||
* [Hardware](wiki/reactor.md#hardware)
|
||||
* [Networking](wiki/networking.md)
|
||||
* [GUI](wiki/gui.md)
|
||||
* [API](wiki/api.md)
|
||||
* [Future Work](wiki/future-work.md)
|
||||
|
||||
## Introduction
|
||||
|
||||
FRMS serves as both an internal framework for testing reactor designs as well as a scalable customer facing application for monitoring and control.
|
||||
The project makes heavy use of low-cost yet powerful embedded systems capable of running full Linux kernels.
|
||||
Examples include the [BeagleBone Black](https://beagleboard.org/black) which was heavily used in development of FRMS as well as the popular [Raspberry Pi 4](https://www.raspberrypi.com/products/raspberry-pi-4-model-b/).
|
||||
For more information about the hardware used in the reactors see [here](wiki/reactor.md#hardware).
|
||||
|
||||
In its current state, FRMS is very bare bones and exists mostly as a proof of concept.
|
||||
Quickly navigate to:
|
||||
- [Getting started](#getting-started)
|
||||
- [Improving the project](wiki/future-work.md)
|
||||
- [More information](wiki/wiki.md)
|
||||
- [Bugs/questions](https://github.com/fl-src/FRMS/issues/new)
|
||||
|
||||
## Getting Started
|
||||
|
||||
For specific information about decisions made in development see [here](wiki/wiki.md).
|
||||
|
||||
### Installation
|
||||
|
||||
The project uses a make alternative called [task](https://github.com/go-task/task) written in go for building and testing.
|
||||
After using `git clone git@github.com:fl-src/FRMS.git` to clone the repository, you can then build binaries of the two commands `server` and `reactor` for testing.
|
||||
The binaries will be put into the `bin/` folder and will be labeled with the platform and architecture they were built for.
|
||||
|
||||
**WARNING**: The reactor binary currently relies on the Linux [i2c-tools](https://archive.kernel.org/oldwiki/i2c.wiki.kernel.org/index.php/I2C_Tools.html) to interact with the i2c bus.
|
||||
This may cause undefined behavior when run on a device without the tools installed. More information about this design choice can be found [here](wiki/reactor.md#i2c)
|
||||
|
||||
### Usage
|
||||
|
||||
## Technical Information
|
||||
|
||||
### Overview
|
||||
|
||||
### Reactor
|
||||
|
||||
### Networking
|
||||
|
||||
### GUI
|
||||
|
||||
### API
|
||||
|
||||
### Future Work
|
||||
|
@ -0,0 +1,89 @@
|
||||
#!/usr/bin/env python
|
||||
import json
|
||||
import subprocess
|
||||
|
||||
class PackageTest:
|
||||
def __init__(self):
|
||||
self.status = ""
|
||||
self.tests = []
|
||||
self.totaltime = 0
|
||||
|
||||
res = {}
|
||||
|
||||
output = subprocess.run(["go","test","-count=1","-json","./..."], capture_output=True, text=True)
|
||||
|
||||
output = str(output.stdout)
|
||||
output = output.split('\n')
|
||||
|
||||
for line in output[:-1]:
|
||||
# parse the json
|
||||
parsed = json.loads(line)
|
||||
action = parsed["Action"]
|
||||
|
||||
# skip
|
||||
if action in ["start", "output", "run"]:
|
||||
continue
|
||||
|
||||
# create blank if doesn't exist
|
||||
if parsed["Package"] not in res:
|
||||
res[parsed["Package"]] = PackageTest()
|
||||
|
||||
pkg = res[parsed["Package"]]
|
||||
|
||||
if "Test" not in parsed:
|
||||
# top level package result
|
||||
pkg.status = action
|
||||
if "Elapsed" in parsed:
|
||||
pkg.totaltime = parsed["Elapsed"]
|
||||
else:
|
||||
# individual test
|
||||
pkg.tests.append((parsed["Test"],parsed["Action"],parsed["Elapsed"]))
|
||||
|
||||
totalRan = 0
|
||||
totalPassed = 0
|
||||
totalTime = 0
|
||||
# generating output from parsed json
|
||||
for name, info in res.items():
|
||||
pkgname = name.split('/')
|
||||
pkgname = '/'.join(name.split('/')[1:])
|
||||
if info.status == "skip":
|
||||
print("Skipped %s" % (pkgname))
|
||||
continue
|
||||
|
||||
print("\nTesting %s:" % (pkgname))
|
||||
|
||||
passed = 0
|
||||
total = 0
|
||||
|
||||
for test in info.tests:
|
||||
total += 1
|
||||
out = []
|
||||
if test[1] == "pass":
|
||||
passed += 1
|
||||
out = [" " + test[0] + ":",'\033[32mpass\033[0m ',str(test[2]) + 's']
|
||||
elif test[1] == "fail":
|
||||
out = [" " + test[0] + ":",'\033[31mfail\033[0m ',str(test[2]) + 's']
|
||||
|
||||
print(f"{out[0] : <30}{out[1] : >5}{out[2] : >8}")
|
||||
|
||||
result = ""
|
||||
if info.status == "pass":
|
||||
result = "\033[32mPASSED\033[0m"
|
||||
else:
|
||||
result = "\033[31mFAILED\033[0m"
|
||||
|
||||
# keep track of grand totals
|
||||
totalRan += total
|
||||
totalPassed += passed
|
||||
totalTime += info.totaltime
|
||||
|
||||
print(" %s %d/%d in %.3fs" % (result, passed, total, info.totaltime))
|
||||
|
||||
|
||||
# output overall test statistics
|
||||
if totalRan == totalPassed:
|
||||
result = "\033[32mPASSED\033[0m"
|
||||
else:
|
||||
result = "\033[31mFAILED\033[0m"
|
||||
|
||||
print("\nSUMMARY:\n\t%s %d/%d in %.3fs" % (result, totalPassed, totalRan, totalTime))
|
@ -1,153 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
# adding commands
|
||||
usage() {
|
||||
# how to use this build script
|
||||
cat <<EOF
|
||||
usage: $0 [-c][-l][-i s] s1 [s2....]
|
||||
s1, s2, etc. the systems to build for (see -l)
|
||||
Options:
|
||||
-c, --clean cleans the bin folder of any existing builds
|
||||
-f, --force same as clean but skips prompt
|
||||
-l, --list list available systems to build for
|
||||
-s, --scp will attempt to scp to aplicable devices
|
||||
-h, --help display this message
|
||||
EOF
|
||||
}
|
||||
|
||||
list_systems() {
|
||||
# list available systems to build for
|
||||
cat <<EOF
|
||||
Name (shorthand) SCP available? (y/n)
|
||||
$0 Name or $0 (shorthand) will build for the device
|
||||
|
||||
RaspberryPi (rpi) y
|
||||
BeagleBone (bb) y
|
||||
Desktop (d) n
|
||||
Server (s) n
|
||||
EOF
|
||||
}
|
||||
|
||||
clean_builds() {
|
||||
# cleans old builds
|
||||
if [[ "$FORCE"=true ]] ; then
|
||||
printf 'Cleaning old builds... \n'
|
||||
rm -v bin/* 2>/dev/null
|
||||
else
|
||||
read -p "Clean old builds?(y/n) " -n 1 -r
|
||||
if [[ $REPLY =~ ^[Yy]$ ]] ; then
|
||||
rm -v bin/* 2>/dev/null
|
||||
fi
|
||||
fi
|
||||
printf 'Clean!\n'
|
||||
}
|
||||
|
||||
create_build() {
|
||||
# create build for $1
|
||||
case $1 in
|
||||
'rpi' )
|
||||
echo "NOT IMPL">&2 && exit 1
|
||||
printf 'Building for Raspberry Pi!\n'
|
||||
GARCH="arm64"
|
||||
PLATFORM="reactor"
|
||||
;;
|
||||
'bb')
|
||||
echo "NOT IMPL">&2 && exit 1
|
||||
printf 'Building for BeagleBone!\n'
|
||||
GARCH="arm"
|
||||
GARM="GOARM=7"
|
||||
PLATFORM="reactor"
|
||||
;;
|
||||
's')
|
||||
printf 'Building for Server!\n'
|
||||
GARCH="amd64"
|
||||
PLATFORM="server"
|
||||
;;
|
||||
'd')
|
||||
printf 'Building for Desktop!\n'
|
||||
GARCH="amd64"
|
||||
PLATFORM="server"
|
||||
;;
|
||||
* )
|
||||
printf 'ERROR: %s type unrecognized!\n' "$1"
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
# setting up build
|
||||
OUTFILE=$(printf '%s_linux_%s' "$PLATFORM" "$GARCH")
|
||||
# building
|
||||
( cd server; env GOOS=linux GOARCH="$GARCH" $GARM go build -o "$OUTFILE")
|
||||
mv server/"$OUTFILE" bin/"$OUTFILE"
|
||||
|
||||
echo "Finished"
|
||||
|
||||
# scp
|
||||
if [[ -n "$SCP" ]] ; then
|
||||
printf 'Attempting to transfer to %s\n' "$2"
|
||||
if [[ "$1" == "bb" ]] ; then
|
||||
printf 'Copying to %s\n' "192.168.100.90"
|
||||
scp "$HOME/FRMS/bin/$OUTFILE" debian:~/
|
||||
else
|
||||
printf 'SCP Not available!\n'
|
||||
fi
|
||||
fi
|
||||
}
|
||||
|
||||
# handle long form
|
||||
for arg in "$@"; do
|
||||
shift
|
||||
case "$arg" in
|
||||
'--help') set -- "$@" "-h" ;;
|
||||
'--list') set -- "$@" "-l" ;;
|
||||
'--scp') set -- "$@" "-s" ;;
|
||||
'--clean') set -- "$@" "-c" ;;
|
||||
'--force') set -- "$@" "-f" ;;
|
||||
*) set -- "$@" "$arg" ;;
|
||||
esac
|
||||
done
|
||||
|
||||
# handle args
|
||||
while getopts "lcsfh" opt ; do
|
||||
case "$opt" in
|
||||
'h' )
|
||||
usage
|
||||
exit 0
|
||||
;;
|
||||
'c' )
|
||||
clean_builds
|
||||
;;
|
||||
'f' )
|
||||
FORCE=true
|
||||
clean_builds
|
||||
;;
|
||||
's' )
|
||||
SCP=true
|
||||
;;
|
||||
'l')
|
||||
list_systems
|
||||
;;
|
||||
'?' )
|
||||
usage
|
||||
exit 1
|
||||
;;
|
||||
esac
|
||||
done
|
||||
|
||||
shift $(($OPTIND - 1))
|
||||
|
||||
for dev in "$@"; do
|
||||
case "$dev" in
|
||||
'RaspberryPi') dev='rpi' ;;
|
||||
'BeagleBone') dev='bb' ;;
|
||||
'Server') dev='s' ;;
|
||||
'Desktop') dev='d' ;;
|
||||
esac
|
||||
create_build "$dev"
|
||||
done
|
||||
printf 'Nothing else to do!\n'
|
||||
|
||||
# tar -czf pireactor.tar.gz -C bin reactor_linux_arm64
|
||||
# tar -czf bbreactor.tar.gz -C bin reactor_linux_arm
|
||||
# tar -czf server.tar.gz -C bin server_linux_amd64
|
||||
# tar -czf tui.tar.gz -C bin tui_linux_amd64 tui_linux_arm tui_linux_arm64
|
@ -0,0 +1,72 @@
|
||||
package main
|
||||
|
||||
import (
|
||||
"FRMS/internal/pkg/config"
|
||||
"FRMS/internal/pkg/logging"
|
||||
"FRMS/internal/pkg/reactor"
|
||||
"fmt"
|
||||
"os"
|
||||
"syscall"
|
||||
|
||||
"os/signal"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
type reactorCoordinator interface {
|
||||
Start()
|
||||
}
|
||||
|
||||
func NewReactorCoordinator(
|
||||
config *viper.Viper,
|
||||
ch chan error,
|
||||
) (reactorCoordinator, error) {
|
||||
// allows interface checking as opposed to calling directly
|
||||
return reactor.NewCoordinator(config, ch)
|
||||
}
|
||||
|
||||
func main() {
|
||||
// shutdown
|
||||
gracefulShutdown := make(chan os.Signal, 1)
|
||||
signal.Notify(gracefulShutdown, syscall.SIGINT, syscall.SIGTERM)
|
||||
|
||||
// load any stored configs
|
||||
home, err := os.UserHomeDir()
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
configPath := fmt.Sprintf("%s/.config/FRMS", home)
|
||||
configFile := "reactor"
|
||||
configExt := "yaml"
|
||||
|
||||
conf, err := config.Load(configFile, configPath, configExt)
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
ch := make(chan error)
|
||||
rlc, err := NewReactorCoordinator(conf, ch) // passing conf and err
|
||||
if err != nil {
|
||||
panic(err)
|
||||
}
|
||||
|
||||
go rlc.Start()
|
||||
logging.Debug(logging.DStart, "Reactor Started")
|
||||
|
||||
// check for errors
|
||||
select {
|
||||
case err := <-ch:
|
||||
if err != nil {
|
||||
conf.WriteConfig() // save changes
|
||||
panic(err)
|
||||
}
|
||||
case <-gracefulShutdown:
|
||||
// sigint
|
||||
fmt.Printf("\nStoring config to %s\n", conf.ConfigFileUsed())
|
||||
if err := conf.WriteConfig(); err != nil {
|
||||
panic(err)
|
||||
}
|
||||
os.Exit(0)
|
||||
}
|
||||
}
|
@ -1,14 +0,0 @@
|
||||
#!/bin/bash
|
||||
|
||||
#DB_URL=$(cat "$INFLUX_CONFIGS_PATH" | awk '/url/ {print $3}' | head -n 1)
|
||||
DB_URL="frms-db-1:8086"
|
||||
|
||||
TOKEN=$(influx auth list --user ${DOCKER_INFLUXDB_INIT_USER_ID} --hide-headers | cut -f 3)
|
||||
ORG=$(influx org list | grep ${DOCKER_INFLUXDB_INIT_ORG_ID} | awk '{print $2}')
|
||||
# creating starting server YAML
|
||||
echo -e "server:\n db-url: ${DB_URL}\n db-org: ${ORG}\n db-token: ${TOKEN}" >/configs/server.yaml;
|
||||
|
||||
# creating grafana yaml
|
||||
influx user create -n grafana -o ${ORG}
|
||||
GRAFANA_TOKEN=$(influx auth list --user grafana --hide-headers | cut -f 3)
|
||||
echo -e "apiVersion: 1\n\ndeleteDatasources:\n\ndatasources:\n - name: INFLUXDB\n type: influxdb\n access: proxy\n url: ${DB_URL}\n jsonData:\n httpMode: GET\n httpHeaderName1: 'Authorization'\n secureJsonData:\n httpHeaderValue1: 'Token ${GRAFANA_TOKEN}'" >/grafana/datasources/datasource.yaml
|
@ -1,6 +0,0 @@
|
||||
----
|
||||
# ${gen_statement}
|
||||
server:
|
||||
db-url: "${db_url}"
|
||||
db-org: "${db_org}"
|
||||
db-token: "${db_token}"
|
@ -0,0 +1,51 @@
|
||||
// package Config wraps the viper library to setup/manage files for FRMS
|
||||
package config
|
||||
|
||||
import (
|
||||
"FRMS/internal/pkg/logging"
|
||||
"fmt"
|
||||
"os"
|
||||
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
// Load the file at path/file into a viper object.
|
||||
// Expects config file to be yaml.
|
||||
func Load(file, path, ext string) (*viper.Viper, error) {
|
||||
|
||||
logging.Debug(logging.DStart, "CON loading %s", file)
|
||||
|
||||
config := viper.New()
|
||||
|
||||
//configFile := fmt.Sprintf("%s/%s.%s", path, file, ext)
|
||||
|
||||
config.SetConfigName(file)
|
||||
config.AddConfigPath(path)
|
||||
config.SetConfigType(ext)
|
||||
|
||||
// Sets env vars
|
||||
config.AutomaticEnv()
|
||||
|
||||
// create config directory if it doesn't exist
|
||||
if err := os.MkdirAll(path, 0750); err != nil && !os.IsExist(err) {
|
||||
return config, err
|
||||
}
|
||||
|
||||
// attempt to create an empty config incase it doesn't exist
|
||||
// if err := config.SafeWriteConfigAs(configFile); err != nil {
|
||||
// // if error thrown because file exists, fine to ignore
|
||||
// if _, ok := err.(viper.ConfigFileAlreadyExistsError); !ok {
|
||||
// return config, err
|
||||
// }
|
||||
// }
|
||||
|
||||
if err := config.ReadInConfig(); err != nil {
|
||||
fmt.Printf("read error %v\n", config)
|
||||
return config, err
|
||||
}
|
||||
|
||||
logging.Debug(logging.DStart, "CON Loaded configs from %#V", config.ConfigFileUsed())
|
||||
|
||||
// returning config object
|
||||
return config, nil
|
||||
}
|
@ -0,0 +1,64 @@
|
||||
// package Database wraps some influx db methods to provide functionality.
|
||||
package database
|
||||
|
||||
import (
|
||||
"context"
|
||||
"errors"
|
||||
"fmt"
|
||||
|
||||
influx "github.com/influxdata/influxdb-client-go/v2"
|
||||
"github.com/spf13/viper"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrDBConnection = errors.New("connection to database failed")
|
||||
ErrNoURLFound = errors.New("database url not found")
|
||||
)
|
||||
|
||||
var db influx.Client
|
||||
|
||||
// Connect takes in a config and attempts to create a client for influxdb.
|
||||
// Will automatically write changes back to config for future attempts
|
||||
func Connect(config *viper.Viper) error {
|
||||
|
||||
url := config.GetString("db.url")
|
||||
token := config.GetString("db.token")
|
||||
|
||||
if url == "" {
|
||||
return ErrNoURLFound
|
||||
}
|
||||
|
||||
db = influx.NewClient(url, token)
|
||||
|
||||
if token == "" {
|
||||
// try setup
|
||||
fmt.Printf("attempting to setup database at %v\n", url)
|
||||
|
||||
user := config.GetString("db.username")
|
||||
password := config.GetString("db.password")
|
||||
org := config.GetString("db.org")
|
||||
bucket := config.GetString("db.bucket")
|
||||
|
||||
Setup(user, pass, org, bucket
|
||||
}
|
||||
|
||||
db = influx.NewClient(url, token)
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func Setup(user, pass, org, bucket string, ret int) (string, error) {
|
||||
|
||||
resp, err := db.Setup(context.Background(), user, pass, org, bucket, ret)
|
||||
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func GetBucket(id int) (string, error) {
|
||||
return "", nil
|
||||
}
|
||||
|
||||
func GetToken(id int) (string, error) {
|
||||
// bucket, err := client.BucketsAPI().FindBucketByName(context.Background(), id)
|
||||
return "", nil
|
||||
}
|
@ -0,0 +1,114 @@
|
||||
// package i2c wraps the [i2c package] to interact with the i2c
|
||||
// with devices on the bus
|
||||
//
|
||||
// [i2c package]: https://pkg.go.dev/periph.io/x/conn/v3/i2c#pkg-overview
|
||||
package i2c
|
||||
|
||||
import (
|
||||
"FRMS/internal/pkg/logging"
|
||||
"bytes"
|
||||
"fmt"
|
||||
_ "log"
|
||||
"os/exec"
|
||||
"strconv"
|
||||
"strings"
|
||||
)
|
||||
|
||||
// GetConnected returns a map of each device address and its current
|
||||
// connection status.
|
||||
func GetConnected(b int) (map[int]bool, error) {
|
||||
|
||||
bus := strconv.Itoa(b)
|
||||
devices := make(map[int]bool) // only keys
|
||||
|
||||
cmd := exec.Command("i2cdetect", "-y", "-r", bus)
|
||||
|
||||
var out bytes.Buffer
|
||||
var errs bytes.Buffer
|
||||
cmd.Stderr = &errs
|
||||
cmd.Stdout = &out
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
||||
logging.Debug(
|
||||
logging.DError,
|
||||
"I2C scan error %v",
|
||||
errs.String(),
|
||||
)
|
||||
|
||||
return devices, err
|
||||
}
|
||||
|
||||
// parsing the command output
|
||||
outString := out.String()
|
||||
split := strings.SplitAfter(outString, ":")
|
||||
|
||||
// 1st entry is reserved and ending is always \n##:
|
||||
split = split[1:]
|
||||
|
||||
// create empty slice for all the devices
|
||||
for i, v := range split {
|
||||
lastDevice := strings.Index(v, "\n")
|
||||
|
||||
trimmed := v[:lastDevice]
|
||||
trimmed = strings.Trim(trimmed, " ")
|
||||
|
||||
count := strings.Split(trimmed, " ")
|
||||
|
||||
for j, d := range count {
|
||||
// the first row has to be offset by 3 but after its just i*16 + j
|
||||
offset := j
|
||||
if i == 0 {
|
||||
offset += 3
|
||||
}
|
||||
|
||||
addr := i*16 + offset
|
||||
|
||||
if !strings.Contains(d, "--") && !strings.Contains(d, "UU") {
|
||||
devices[addr] = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return devices, nil
|
||||
}
|
||||
|
||||
// SendCmd sends an arbitrary command string to the device at addr on i2c bus b.
|
||||
// Command will be converted from a string to bytes before
|
||||
// attempting to be sent.
|
||||
func SendCmd(b, addr int, command string) (string, error) {
|
||||
|
||||
var cmd *exec.Cmd
|
||||
bus := strconv.Itoa(b)
|
||||
// default to an empty read
|
||||
operation := "r20"
|
||||
frmt_cmd := ""
|
||||
if command != "" {
|
||||
// command, do write
|
||||
operation = fmt.Sprintf("w%d", len(command)) // write
|
||||
// formatting cmd
|
||||
for _, char := range command {
|
||||
// loop over string
|
||||
frmt_cmd += fmt.Sprintf("0x%x", char)
|
||||
}
|
||||
cmd = exec.Command("i2ctransfer", "-y", bus, fmt.Sprintf("%s@0x%x", operation, addr), frmt_cmd)
|
||||
} else {
|
||||
// reading
|
||||
cmd = exec.Command("i2ctransfer", "-y", bus, fmt.Sprintf("%s@0x%x", operation, addr))
|
||||
}
|
||||
|
||||
// execute command
|
||||
var out bytes.Buffer
|
||||
var errs bytes.Buffer
|
||||
cmd.Stderr = &errs
|
||||
cmd.Stdout = &out
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
|
||||
logging.Debug(logging.DError, "I2C command error %v", err)
|
||||
|
||||
return "", err
|
||||
}
|
||||
|
||||
return out.String(), nil
|
||||
}
|
@ -1,118 +1,149 @@
|
||||
<<<<<<< HEAD:reactor/needs_port/system/hwinfo.go
|
||||
// package system uses linux commands to get hardware info for identifation
|
||||
=======
|
||||
// package system uses linux ip command to get hardware info from devices
|
||||
>>>>>>> origin/wrapup:internal/pkg/system/hwinfo.go
|
||||
package system
|
||||
|
||||
import (
|
||||
"bytes"
|
||||
"encoding/json"
|
||||
"errors"
|
||||
"fmt"
|
||||
"hash/fnv"
|
||||
"net"
|
||||
"os/exec"
|
||||
"strings"
|
||||
)
|
||||
|
||||
func GetId() (int, error) {
|
||||
// gets the mac address and hashes into consistent id
|
||||
maccmd := fmt.Sprintf("ifconfig %v | awk '/ether / {print $2}'", et)
|
||||
var stderr bytes.Buffer
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("bash", "-c", maccmd)
|
||||
cmd.Stdout = &out
|
||||
var (
|
||||
ErrBusNotFound = errors.New("bus not found for device")
|
||||
ErrNoNetworkInterface = errors.New("no default network found")
|
||||
)
|
||||
|
||||
var HardwareInfo = &hardwareInfo{}
|
||||
|
||||
type hardwareInfo struct {
|
||||
MAC string
|
||||
IP string
|
||||
ID int
|
||||
Model string
|
||||
}
|
||||
|
||||
// NetInterfaces is a struct to unmarshal the ip command json.
|
||||
type netInterfaces []network
|
||||
|
||||
// Networks holds relevant information for each network interface.
|
||||
// Used to unmarshal ip command json.
|
||||
type network struct {
|
||||
Subnets []netInfo `json:"addr_info"`
|
||||
Mac string `json:"address"`
|
||||
Group string `json:"group"`
|
||||
State string `json:"operstate"`
|
||||
}
|
||||
|
||||
type netInfo struct {
|
||||
Family string `json:"family"`
|
||||
Ip string `json:"local"`
|
||||
}
|
||||
|
||||
func getInfo() error {
|
||||
|
||||
var stderr, stdout bytes.Buffer
|
||||
cmd := exec.Command("ip", "-j", "a")
|
||||
cmd.Stdout = &stdout
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return 0, err
|
||||
return err
|
||||
}
|
||||
|
||||
var networks netInterfaces
|
||||
|
||||
if err := json.Unmarshal(stdout.Bytes(), &networks); err != nil {
|
||||
return err
|
||||
}
|
||||
|
||||
// loop over found networks finding first default, IPv4 network currently
|
||||
// UP (active).
|
||||
// Eventually need to look only at wg interface which simplifies
|
||||
// implementation.
|
||||
|
||||
for _, network := range networks {
|
||||
if network.Group == "default" && network.State == "UP" {
|
||||
|
||||
for _, subnet := range network.Subnets {
|
||||
if subnet.Family == "inet" {
|
||||
|
||||
hash := fnv.New32a()
|
||||
hash.Write(out.Bytes())
|
||||
hash.Write([]byte(network.Mac))
|
||||
id := hash.Sum32()
|
||||
return int(id), nil
|
||||
}
|
||||
|
||||
func GetIp() (string, error) {
|
||||
ipcmd := "ip route get 1 | sed 's/^.*src \([^ ]*\).*$/\1/;q'"
|
||||
var stderr bytes.Buffer
|
||||
var out bytes.Buffer
|
||||
cmd := exec.Command("bash", "-c", ipcmd)
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
HardwareInfo.MAC = network.Mac
|
||||
HardwareInfo.IP = subnet.Ip
|
||||
HardwareInfo.ID = int(id)
|
||||
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
ip := strings.Trim(out.String(), " \n")
|
||||
return ip, nil
|
||||
|
||||
return nil
|
||||
}
|
||||
|
||||
func GetPort() (int, error) {
|
||||
// obsolete
|
||||
if addr, err := net.ResolveTCPAddr("tcp", ":0"); err != nil {
|
||||
return 0, err
|
||||
} else if lis, err := net.ListenTCP("tcp", addr); err != nil {
|
||||
func GetID() (int, error) {
|
||||
|
||||
if HardwareInfo.ID == 0 {
|
||||
if err := getInfo(); err != nil {
|
||||
return 0, err
|
||||
} else {
|
||||
defer lis.Close()
|
||||
return lis.Addr().(*net.TCPAddr).Port, nil
|
||||
}
|
||||
}
|
||||
|
||||
func GetBus() (int, error) {
|
||||
// preset busses
|
||||
busList := map[string]int{"raspberrypi": 1, "beaglebone": 2}
|
||||
// vars
|
||||
var bus int
|
||||
var ok bool
|
||||
return HardwareInfo.ID, nil
|
||||
}
|
||||
|
||||
if name, err =: GetModel(); err != nil {
|
||||
return bus, err
|
||||
} else if bus, ok = busList[b]; !ok {
|
||||
return 0, errors.New(fmt.Sprintf("No bus for dev %s", b))
|
||||
func GetIP() (string, error) {
|
||||
|
||||
if HardwareInfo.IP == "" {
|
||||
if err := getInfo(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
}
|
||||
|
||||
// returns correct bus
|
||||
return bus, nil
|
||||
return HardwareInfo.IP, nil
|
||||
}
|
||||
|
||||
func GetModel() (string, error) {
|
||||
|
||||
if HardwareInfo.Model == "" {
|
||||
|
||||
var stderr, out bytes.Buffer
|
||||
cmd := exec.Command("bash", "-c", "hostname")
|
||||
cmd := exec.Command("uname", "-n")
|
||||
cmd.Stdout = &out
|
||||
cmd.Stderr = &stderr
|
||||
|
||||
if err := cmd.Run(); err != nil {
|
||||
return "", err
|
||||
}
|
||||
|
||||
b := out.String()
|
||||
b = strings.Trim(b, " \n")
|
||||
return b, nil
|
||||
HardwareInfo.Model = strings.Trim(b, " \n")
|
||||
}
|
||||
|
||||
func Get() error {
|
||||
// responsible for filling out struct
|
||||
//bus := map[string]int{"raspberrypi":1,"beaglebone":2} // eventually will replace this with a config file
|
||||
return HardwareInfo.Model, nil
|
||||
}
|
||||
|
||||
ipcmd := "ifconfig eth0 | awk '/inet / {print $2}'"
|
||||
maccmd := "ifconfig eth0 | awk '/ether / {print $2}'"
|
||||
devcmd := "lshw -C system 2>/dev/null | head -n 1"
|
||||
func GetBus() (int, error) {
|
||||
// preset busses
|
||||
busList := map[string]int{"raspberrypi": 1, "beaglebone": 2}
|
||||
// vars
|
||||
var bus int
|
||||
var ok bool
|
||||
|
||||
res := [3]bytes.Buffer{}
|
||||
var stderr bytes.Buffer
|
||||
cmds := []string{ipcmd, maccmd, devcmd}
|
||||
for i, c := range cmds {
|
||||
cmd := exec.Command("bash", "-c", c)
|
||||
cmd.Stdout = &res[i]
|
||||
cmd.Stderr = &stderr
|
||||
err := cmd.Run()
|
||||
if err != nil {
|
||||
return err
|
||||
}
|
||||
if name, err := GetModel(); err != nil {
|
||||
return bus, err
|
||||
} else if bus, ok = busList[name]; !ok {
|
||||
return 0, ErrBusNotFound
|
||||
}
|
||||
// formatting
|
||||
ip := res[0].String()
|
||||
ip = strings.Trim(ip, " \n")
|
||||
|
||||
hash := fnv.New32a()
|
||||
hash.Write(res[1].Bytes())
|
||||
|
||||
b := res[2].String()
|
||||
b = strings.Trim(b, " \n")
|
||||
return nil
|
||||
return bus, nil
|
||||
}
|
||||
|
@ -0,0 +1,41 @@
|
||||
package system
|
||||
|
||||
import (
|
||||
"testing"
|
||||
)
|
||||
|
||||
// TestGetInfo ensures that the array can be populated with device info.
|
||||
func TestGetInfo(t *testing.T) {
|
||||
if err := getInfo(); err != nil {
|
||||
t.Fatalf(`getInfo() failed %v`, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetIP tests that the IP is returned without error and not empty.
|
||||
func TestGetIP(t *testing.T) {
|
||||
if ip, err := GetIP(); err != nil || ip == "" {
|
||||
t.Fatalf(`GetIP() failed, got %s, err: %v`, ip, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetID tests that the ID is returned without error and not empty.
|
||||
func TestGetID(t *testing.T) {
|
||||
if id, err := GetID(); err != nil || id == 0 {
|
||||
t.Fatalf(`GetID() failed, got %d %v`, id, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetModel tests that the Model is returned without error and not empty.
|
||||
func TestGetModel(t *testing.T) {
|
||||
if model, err := GetModel(); err != nil || model == "" {
|
||||
t.Fatalf(`GetModel() failed, got %s %v`, model, err)
|
||||
}
|
||||
}
|
||||
|
||||
// TestGetModel tests that the correct error is thrown as the bus does not exist on the test rig.
|
||||
func TestGetBus(t *testing.T) {
|
||||
if bus, err := GetBus(); err != ErrBusNotFound {
|
||||
t.Fatalf(`GetBus() should fail, got %d %v`, bus, err)
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,260 @@
|
||||
// Code generated by protoc-gen-go. DO NOT EDIT.
|
||||
// versions:
|
||||
// protoc-gen-go v1.28.1
|
||||
// protoc v3.21.12
|
||||
// source: internal/pkg/grpc/handshake.proto
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
protoreflect "google.golang.org/protobuf/reflect/protoreflect"
|
||||
protoimpl "google.golang.org/protobuf/runtime/protoimpl"
|
||||
reflect "reflect"
|
||||
sync "sync"
|
||||
)
|
||||
|
||||
const (
|
||||
// Verify that this generated code is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(20 - protoimpl.MinVersion)
|
||||
// Verify that runtime/protoimpl is sufficiently up-to-date.
|
||||
_ = protoimpl.EnforceVersion(protoimpl.MaxVersion - 20)
|
||||
)
|
||||
|
||||
type ReactorClientRequest struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Port uint32 `protobuf:"varint,2,opt,name=port,proto3" json:"port,omitempty"` // client gRPC port
|
||||
}
|
||||
|
||||
func (x *ReactorClientRequest) Reset() {
|
||||
*x = ReactorClientRequest{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_pkg_grpc_handshake_proto_msgTypes[0]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReactorClientRequest) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReactorClientRequest) ProtoMessage() {}
|
||||
|
||||
func (x *ReactorClientRequest) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_pkg_grpc_handshake_proto_msgTypes[0]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ReactorClientRequest.ProtoReflect.Descriptor instead.
|
||||
func (*ReactorClientRequest) Descriptor() ([]byte, []int) {
|
||||
return file_internal_pkg_grpc_handshake_proto_rawDescGZIP(), []int{0}
|
||||
}
|
||||
|
||||
func (x *ReactorClientRequest) GetId() uint32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReactorClientRequest) GetPort() uint32 {
|
||||
if x != nil {
|
||||
return x.Port
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
type ReactorClientResponse struct {
|
||||
state protoimpl.MessageState
|
||||
sizeCache protoimpl.SizeCache
|
||||
unknownFields protoimpl.UnknownFields
|
||||
|
||||
Id uint32 `protobuf:"varint,1,opt,name=id,proto3" json:"id,omitempty"`
|
||||
Url string `protobuf:"bytes,2,opt,name=url,proto3" json:"url,omitempty"`
|
||||
Org string `protobuf:"bytes,3,opt,name=org,proto3" json:"org,omitempty"`
|
||||
Token string `protobuf:"bytes,4,opt,name=token,proto3" json:"token,omitempty"`
|
||||
Bucket string `protobuf:"bytes,5,opt,name=bucket,proto3" json:"bucket,omitempty"`
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) Reset() {
|
||||
*x = ReactorClientResponse{}
|
||||
if protoimpl.UnsafeEnabled {
|
||||
mi := &file_internal_pkg_grpc_handshake_proto_msgTypes[1]
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) String() string {
|
||||
return protoimpl.X.MessageStringOf(x)
|
||||
}
|
||||
|
||||
func (*ReactorClientResponse) ProtoMessage() {}
|
||||
|
||||
func (x *ReactorClientResponse) ProtoReflect() protoreflect.Message {
|
||||
mi := &file_internal_pkg_grpc_handshake_proto_msgTypes[1]
|
||||
if protoimpl.UnsafeEnabled && x != nil {
|
||||
ms := protoimpl.X.MessageStateOf(protoimpl.Pointer(x))
|
||||
if ms.LoadMessageInfo() == nil {
|
||||
ms.StoreMessageInfo(mi)
|
||||
}
|
||||
return ms
|
||||
}
|
||||
return mi.MessageOf(x)
|
||||
}
|
||||
|
||||
// Deprecated: Use ReactorClientResponse.ProtoReflect.Descriptor instead.
|
||||
func (*ReactorClientResponse) Descriptor() ([]byte, []int) {
|
||||
return file_internal_pkg_grpc_handshake_proto_rawDescGZIP(), []int{1}
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) GetId() uint32 {
|
||||
if x != nil {
|
||||
return x.Id
|
||||
}
|
||||
return 0
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) GetUrl() string {
|
||||
if x != nil {
|
||||
return x.Url
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) GetOrg() string {
|
||||
if x != nil {
|
||||
return x.Org
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) GetToken() string {
|
||||
if x != nil {
|
||||
return x.Token
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
func (x *ReactorClientResponse) GetBucket() string {
|
||||
if x != nil {
|
||||
return x.Bucket
|
||||
}
|
||||
return ""
|
||||
}
|
||||
|
||||
var File_internal_pkg_grpc_handshake_proto protoreflect.FileDescriptor
|
||||
|
||||
var file_internal_pkg_grpc_handshake_proto_rawDesc = []byte{
|
||||
0x0a, 0x21, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67,
|
||||
0x72, 0x70, 0x63, 0x2f, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x2e, 0x70, 0x72,
|
||||
0x6f, 0x74, 0x6f, 0x12, 0x04, 0x67, 0x72, 0x70, 0x63, 0x22, 0x3a, 0x0a, 0x14, 0x52, 0x65, 0x61,
|
||||
0x63, 0x74, 0x6f, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x12, 0x0e, 0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69,
|
||||
0x64, 0x12, 0x12, 0x0a, 0x04, 0x70, 0x6f, 0x72, 0x74, 0x18, 0x02, 0x20, 0x01, 0x28, 0x0d, 0x52,
|
||||
0x04, 0x70, 0x6f, 0x72, 0x74, 0x22, 0x79, 0x0a, 0x15, 0x52, 0x65, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x12, 0x0e,
|
||||
0x0a, 0x02, 0x69, 0x64, 0x18, 0x01, 0x20, 0x01, 0x28, 0x0d, 0x52, 0x02, 0x69, 0x64, 0x12, 0x10,
|
||||
0x0a, 0x03, 0x75, 0x72, 0x6c, 0x18, 0x02, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x75, 0x72, 0x6c,
|
||||
0x12, 0x10, 0x0a, 0x03, 0x6f, 0x72, 0x67, 0x18, 0x03, 0x20, 0x01, 0x28, 0x09, 0x52, 0x03, 0x6f,
|
||||
0x72, 0x67, 0x12, 0x14, 0x0a, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x18, 0x04, 0x20, 0x01, 0x28,
|
||||
0x09, 0x52, 0x05, 0x74, 0x6f, 0x6b, 0x65, 0x6e, 0x12, 0x16, 0x0a, 0x06, 0x62, 0x75, 0x63, 0x6b,
|
||||
0x65, 0x74, 0x18, 0x05, 0x20, 0x01, 0x28, 0x09, 0x52, 0x06, 0x62, 0x75, 0x63, 0x6b, 0x65, 0x74,
|
||||
0x32, 0x5c, 0x0a, 0x09, 0x68, 0x61, 0x6e, 0x64, 0x73, 0x68, 0x61, 0x6b, 0x65, 0x12, 0x4f, 0x0a,
|
||||
0x14, 0x52, 0x65, 0x61, 0x63, 0x74, 0x6f, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x48, 0x61,
|
||||
0x6e, 0x64, 0x6c, 0x65, 0x72, 0x12, 0x1a, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61,
|
||||
0x63, 0x74, 0x6f, 0x72, 0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x71, 0x75, 0x65, 0x73,
|
||||
0x74, 0x1a, 0x1b, 0x2e, 0x67, 0x72, 0x70, 0x63, 0x2e, 0x52, 0x65, 0x61, 0x63, 0x74, 0x6f, 0x72,
|
||||
0x43, 0x6c, 0x69, 0x65, 0x6e, 0x74, 0x52, 0x65, 0x73, 0x70, 0x6f, 0x6e, 0x73, 0x65, 0x42, 0x13,
|
||||
0x5a, 0x11, 0x69, 0x6e, 0x74, 0x65, 0x72, 0x6e, 0x61, 0x6c, 0x2f, 0x70, 0x6b, 0x67, 0x2f, 0x67,
|
||||
0x72, 0x70, 0x63, 0x62, 0x06, 0x70, 0x72, 0x6f, 0x74, 0x6f, 0x33,
|
||||
}
|
||||
|
||||
var (
|
||||
file_internal_pkg_grpc_handshake_proto_rawDescOnce sync.Once
|
||||
file_internal_pkg_grpc_handshake_proto_rawDescData = file_internal_pkg_grpc_handshake_proto_rawDesc
|
||||
)
|
||||
|
||||
func file_internal_pkg_grpc_handshake_proto_rawDescGZIP() []byte {
|
||||
file_internal_pkg_grpc_handshake_proto_rawDescOnce.Do(func() {
|
||||
file_internal_pkg_grpc_handshake_proto_rawDescData = protoimpl.X.CompressGZIP(file_internal_pkg_grpc_handshake_proto_rawDescData)
|
||||
})
|
||||
return file_internal_pkg_grpc_handshake_proto_rawDescData
|
||||
}
|
||||
|
||||
var file_internal_pkg_grpc_handshake_proto_msgTypes = make([]protoimpl.MessageInfo, 2)
|
||||
var file_internal_pkg_grpc_handshake_proto_goTypes = []interface{}{
|
||||
(*ReactorClientRequest)(nil), // 0: grpc.ReactorClientRequest
|
||||
(*ReactorClientResponse)(nil), // 1: grpc.ReactorClientResponse
|
||||
}
|
||||
var file_internal_pkg_grpc_handshake_proto_depIdxs = []int32{
|
||||
0, // 0: grpc.handshake.ReactorClientHandler:input_type -> grpc.ReactorClientRequest
|
||||
1, // 1: grpc.handshake.ReactorClientHandler:output_type -> grpc.ReactorClientResponse
|
||||
1, // [1:2] is the sub-list for method output_type
|
||||
0, // [0:1] is the sub-list for method input_type
|
||||
0, // [0:0] is the sub-list for extension type_name
|
||||
0, // [0:0] is the sub-list for extension extendee
|
||||
0, // [0:0] is the sub-list for field type_name
|
||||
}
|
||||
|
||||
func init() { file_internal_pkg_grpc_handshake_proto_init() }
|
||||
func file_internal_pkg_grpc_handshake_proto_init() {
|
||||
if File_internal_pkg_grpc_handshake_proto != nil {
|
||||
return
|
||||
}
|
||||
if !protoimpl.UnsafeEnabled {
|
||||
file_internal_pkg_grpc_handshake_proto_msgTypes[0].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReactorClientRequest); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
file_internal_pkg_grpc_handshake_proto_msgTypes[1].Exporter = func(v interface{}, i int) interface{} {
|
||||
switch v := v.(*ReactorClientResponse); i {
|
||||
case 0:
|
||||
return &v.state
|
||||
case 1:
|
||||
return &v.sizeCache
|
||||
case 2:
|
||||
return &v.unknownFields
|
||||
default:
|
||||
return nil
|
||||
}
|
||||
}
|
||||
}
|
||||
type x struct{}
|
||||
out := protoimpl.TypeBuilder{
|
||||
File: protoimpl.DescBuilder{
|
||||
GoPackagePath: reflect.TypeOf(x{}).PkgPath(),
|
||||
RawDescriptor: file_internal_pkg_grpc_handshake_proto_rawDesc,
|
||||
NumEnums: 0,
|
||||
NumMessages: 2,
|
||||
NumExtensions: 0,
|
||||
NumServices: 1,
|
||||
},
|
||||
GoTypes: file_internal_pkg_grpc_handshake_proto_goTypes,
|
||||
DependencyIndexes: file_internal_pkg_grpc_handshake_proto_depIdxs,
|
||||
MessageInfos: file_internal_pkg_grpc_handshake_proto_msgTypes,
|
||||
}.Build()
|
||||
File_internal_pkg_grpc_handshake_proto = out.File
|
||||
file_internal_pkg_grpc_handshake_proto_rawDesc = nil
|
||||
file_internal_pkg_grpc_handshake_proto_goTypes = nil
|
||||
file_internal_pkg_grpc_handshake_proto_depIdxs = nil
|
||||
}
|
@ -0,0 +1,21 @@
|
||||
syntax = "proto3";
|
||||
package grpc;
|
||||
|
||||
option go_package = "internal/pkg/grpc";
|
||||
|
||||
service handshake {
|
||||
rpc ReactorClientHandler(ReactorClientRequest) returns (ReactorClientResponse);
|
||||
}
|
||||
|
||||
message ReactorClientRequest {
|
||||
uint32 id = 1;
|
||||
uint32 port = 2; // client gRPC port
|
||||
}
|
||||
|
||||
message ReactorClientResponse {
|
||||
uint32 id = 1;
|
||||
string url = 2;
|
||||
string org = 3;
|
||||
string token = 4;
|
||||
string bucket = 5;
|
||||
}
|
@ -0,0 +1,105 @@
|
||||
// Code generated by protoc-gen-go-grpc. DO NOT EDIT.
|
||||
// versions:
|
||||
// - protoc-gen-go-grpc v1.2.0
|
||||
// - protoc v3.21.12
|
||||
// source: internal/pkg/grpc/handshake.proto
|
||||
|
||||
package grpc
|
||||
|
||||
import (
|
||||
context "context"
|
||||
grpc "google.golang.org/grpc"
|
||||
codes "google.golang.org/grpc/codes"
|
||||
status "google.golang.org/grpc/status"
|
||||
)
|
||||
|
||||
// This is a compile-time assertion to ensure that this generated file
|
||||
// is compatible with the grpc package it is being compiled against.
|
||||
// Requires gRPC-Go v1.32.0 or later.
|
||||
const _ = grpc.SupportPackageIsVersion7
|
||||
|
||||
// HandshakeClient is the client API for Handshake service.
|
||||
//
|
||||
// For semantics around ctx use and closing/ending streaming RPCs, please refer to https://pkg.go.dev/google.golang.org/grpc/?tab=doc#ClientConn.NewStream.
|
||||
type HandshakeClient interface {
|
||||
ReactorClientHandler(ctx context.Context, in *ReactorClientRequest, opts ...grpc.CallOption) (*ReactorClientResponse, error)
|
||||
}
|
||||
|
||||
type handshakeClient struct {
|
||||
cc grpc.ClientConnInterface
|
||||
}
|
||||
|
||||
func NewHandshakeClient(cc grpc.ClientConnInterface) HandshakeClient {
|
||||
return &handshakeClient{cc}
|
||||
}
|
||||
|
||||
func (c *handshakeClient) ReactorClientHandler(ctx context.Context, in *ReactorClientRequest, opts ...grpc.CallOption) (*ReactorClientResponse, error) {
|
||||
out := new(ReactorClientResponse)
|
||||
err := c.cc.Invoke(ctx, "/grpc.handshake/ReactorClientHandler", in, out, opts...)
|
||||
if err != nil {
|
||||
return nil, err
|
||||
}
|
||||
return out, nil
|
||||
}
|
||||
|
||||
// HandshakeServer is the server API for Handshake service.
|
||||
// All implementations must embed UnimplementedHandshakeServer
|
||||
// for forward compatibility
|
||||
type HandshakeServer interface {
|
||||
ReactorClientHandler(context.Context, *ReactorClientRequest) (*ReactorClientResponse, error)
|
||||
mustEmbedUnimplementedHandshakeServer()
|
||||
}
|
||||
|
||||
// UnimplementedHandshakeServer must be embedded to have forward compatible implementations.
|
||||
type UnimplementedHandshakeServer struct {
|
||||
}
|
||||
|
||||
func (UnimplementedHandshakeServer) ReactorClientHandler(context.Context, *ReactorClientRequest) (*ReactorClientResponse, error) {
|
||||
return nil, status.Errorf(codes.Unimplemented, "method ReactorClientHandler not implemented")
|
||||
}
|
||||
func (UnimplementedHandshakeServer) mustEmbedUnimplementedHandshakeServer() {}
|
||||
|
||||
// UnsafeHandshakeServer may be embedded to opt out of forward compatibility for this service.
|
||||
// Use of this interface is not recommended, as added methods to HandshakeServer will
|
||||
// result in compilation errors.
|
||||
type UnsafeHandshakeServer interface {
|
||||
mustEmbedUnimplementedHandshakeServer()
|
||||
}
|
||||
|
||||
func RegisterHandshakeServer(s grpc.ServiceRegistrar, srv HandshakeServer) {
|
||||
s.RegisterService(&Handshake_ServiceDesc, srv)
|
||||
}
|
||||
|
||||
func _Handshake_ReactorClientHandler_Handler(srv interface{}, ctx context.Context, dec func(interface{}) error, interceptor grpc.UnaryServerInterceptor) (interface{}, error) {
|
||||
in := new(ReactorClientRequest)
|
||||
if err := dec(in); err != nil {
|
||||
return nil, err
|
||||
}
|
||||
if interceptor == nil {
|
||||
return srv.(HandshakeServer).ReactorClientHandler(ctx, in)
|
||||
}
|
||||
info := &grpc.UnaryServerInfo{
|
||||
Server: srv,
|
||||
FullMethod: "/grpc.handshake/ReactorClientHandler",
|
||||
}
|
||||
handler := func(ctx context.Context, req interface{}) (interface{}, error) {
|
||||
return srv.(HandshakeServer).ReactorClientHandler(ctx, req.(*ReactorClientRequest))
|
||||
}
|
||||
return interceptor(ctx, in, info, handler)
|
||||
}
|
||||
|
||||
// Handshake_ServiceDesc is the grpc.ServiceDesc for Handshake service.
|
||||
// It's only intended for direct use with grpc.RegisterService,
|
||||
// and not to be introspected or modified (even as a copy)
|
||||
var Handshake_ServiceDesc = grpc.ServiceDesc{
|
||||
ServiceName: "grpc.handshake",
|
||||
HandlerType: (*HandshakeServer)(nil),
|
||||
Methods: []grpc.MethodDesc{
|
||||
{
|
||||
MethodName: "ReactorClientHandler",
|
||||
Handler: _Handshake_ReactorClientHandler_Handler,
|
||||
},
|
||||
},
|
||||
Streams: []grpc.StreamDesc{},
|
||||
Metadata: "internal/pkg/grpc/handshake.proto",
|
||||
}
|
@ -0,0 +1,206 @@
|
||||
package manager
|
||||
|
||||
import (
|
||||
"math/rand"
|
||||
"testing"
|
||||
"time"
|
||||
|
||||
"github.com/stretchr/testify/assert"
|
||||
)
|
||||
|
||||
// creating, starting and stopping tests
|
||||
|
||||
// newManager is a helper for creating new managers for tests
|
||||
func newManager(conn int, want error, t *testing.T) *Manager {
|
||||
var manager *Manager
|
||||
var err error
|
||||
assert := assert.New(t)
|
||||
|
||||
manager, err = New(conn)
|
||||
|
||||
if err != want {
|
||||
t.Fatalf(
|
||||
`New(%d) = %v, %v, %d max connections failed`,
|
||||
conn,
|
||||
manager,
|
||||
err,
|
||||
conn,
|
||||
)
|
||||
}
|
||||
|
||||
assert.Equal(manager.IsActive(), Inactive, "manager should start inactive")
|
||||
|
||||
return manager
|
||||
}
|
||||
|
||||
// TestEmptyManager creates a new manager with 0 max connections
|
||||
func TestEmptyManager(t *testing.T) {
|
||||
conn := 0
|
||||
newManager(conn, nil, t)
|
||||
}
|
||||
|
||||
// TestPostiveManager creates a new manager with valid max connections
|
||||
func TestPositiveManager(t *testing.T) {
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
newManager(conn, nil, t)
|
||||
}
|
||||
|
||||
// TestNegativeManager creates a new manager with negative max connections
|
||||
func TestNegativeManager(t *testing.T) {
|
||||
conn := -1 * rand.Intn(MaxConnAttempts)
|
||||
newManager(conn, ErrInvalidMaxConn, t)
|
||||
}
|
||||
|
||||
// TestInvalidManager creates a new manager with large max connections
|
||||
func TestInvalidManager(t *testing.T) {
|
||||
conn := MaxConnAttempts + 0xf
|
||||
newManager(conn, ErrInvalidMaxConn, t)
|
||||
}
|
||||
|
||||
// TestManagerLifeCycle tests that a manager can start and exit several times
|
||||
func TestManagerLifeCycle(t *testing.T) {
|
||||
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
cycles := 10
|
||||
|
||||
// starting and stopping sequentially
|
||||
for i := 0; i < cycles; i++ {
|
||||
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
assert.Equal(manager.IsActive(), Active, "manager inactive after start")
|
||||
|
||||
assert.NoError(manager.Stop(), "stopping manager failed")
|
||||
assert.Equal(manager.IsActive(), Inactive, "manager active after stop")
|
||||
}
|
||||
}
|
||||
|
||||
// TestManagerStopFail tests that stopping an inactive manager will error
|
||||
func TestManagerStopFail(t *testing.T) {
|
||||
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
|
||||
// stopping sequentially
|
||||
assert.NoError(manager.Stop(), "stopping manager failed")
|
||||
assert.Error(manager.Stop(), "stopping inactive manager should fail")
|
||||
}
|
||||
|
||||
// TestManagerStartFail tests that starting an active manager will error
|
||||
func TestManagerStartFail(t *testing.T) {
|
||||
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
// starting sequentially
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
assert.Error(manager.Start(), "starting active manager should fail")
|
||||
}
|
||||
|
||||
// auxiliary tests
|
||||
|
||||
// TestManagerTimeout checks that timeouts exponentially backoff
|
||||
func TestManagerTimeout(t *testing.T) {
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := 10
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
assert.Equal(manager.IsActive(), Active, "manager is inactive")
|
||||
|
||||
prevTimeout, err := manager.Timeout()
|
||||
|
||||
for i := 1; i <= conn; i++ {
|
||||
assert.NoError(err, "generating timeout failed")
|
||||
assert.True(prevTimeout > 0, "invalid timeout")
|
||||
|
||||
timeout, err := manager.Timeout()
|
||||
|
||||
if i == conn {
|
||||
assert.Error(err, "allowed exceeding max attempts")
|
||||
} else {
|
||||
assert.NoError(err, "generating timeout failed")
|
||||
assert.True(
|
||||
timeout == 2*prevTimeout,
|
||||
"incorrect timeout %d, expected %d",
|
||||
timeout, 2*prevTimeout,
|
||||
)
|
||||
}
|
||||
|
||||
prevTimeout = timeout
|
||||
}
|
||||
}
|
||||
|
||||
// TestManagerHB tests the heartbeat channel opens and closes
|
||||
func TestManagerHB(t *testing.T) {
|
||||
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
assert.Equal(manager.IsActive(), Active, "manager is inactive")
|
||||
|
||||
ch := make(chan struct{})
|
||||
|
||||
go manager.HeartBeat(ch, 10, 0, time.Millisecond)
|
||||
|
||||
for range ch {
|
||||
// close on first ping
|
||||
assert.NoError(manager.Stop(), "stopping manager failed")
|
||||
}
|
||||
|
||||
assert.Equal(manager.IsActive(), Inactive, "manager is active")
|
||||
}
|
||||
|
||||
// TestManagerHBTiming tests the heartbeat channel timing is correct
|
||||
func TestManagerHBTiming(t *testing.T) {
|
||||
|
||||
var manager *Manager
|
||||
assert := assert.New(t)
|
||||
|
||||
conn := rand.Intn(MaxConnAttempts)
|
||||
manager = newManager(conn, nil, t)
|
||||
|
||||
assert.NoError(manager.Start(), "starting manager failed")
|
||||
assert.Equal(manager.IsActive(), Active, "manager is inactive")
|
||||
|
||||
ch := make(chan struct{})
|
||||
hb := 100
|
||||
pings := 10
|
||||
|
||||
// expected time with tolerance for other logic and worst case rand timeout
|
||||
expected := time.Duration(pings*hb+15) * time.Millisecond
|
||||
|
||||
go manager.HeartBeat(ch, hb, 1, time.Millisecond)
|
||||
|
||||
iter := 0
|
||||
start := time.Now()
|
||||
for range ch {
|
||||
// close after 10 pings
|
||||
iter += 1
|
||||
if iter >= pings {
|
||||
assert.NoError(manager.Stop(), "stopping manager failed")
|
||||
}
|
||||
}
|
||||
end := time.Now()
|
||||
|
||||
assert.Equal(manager.IsActive(), Inactive, "manager is active")
|
||||
assert.WithinDuration(start, end, expected, "inaccurate heartbeat")
|
||||
}
|
@ -0,0 +1,13 @@
|
||||
package server
|
||||
|
||||
type dbinfo struct {
|
||||
url string
|
||||
org string
|
||||
token string
|
||||
bucket string
|
||||
}
|
||||
|
||||
func (c *Coordinator) getReactorDatabaseCred(id int) (*dbinfo, error) {
|
||||
|
||||
return &dbinfo{}, nil
|
||||
}
|
@ -0,0 +1,48 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
pb "FRMS/internal/pkg/grpc"
|
||||
"FRMS/internal/pkg/logging"
|
||||
"context"
|
||||
)
|
||||
|
||||
// ClientDiscoveryHandler implements the grpc method which can be called
|
||||
// by incoming clients to first make connection to the central
|
||||
// coordinator and receive database credentials.
|
||||
func (c *Coordinator) ReactorClientHandler(
|
||||
ctx context.Context,
|
||||
req *pb.ReactorClientRequest,
|
||||
) (*pb.ReactorClientResponse, error) {
|
||||
|
||||
id := int(req.GetId())
|
||||
|
||||
logging.Debug(
|
||||
logging.DClient,
|
||||
"LIS 00 reactor %v has connected\n",
|
||||
id,
|
||||
)
|
||||
|
||||
db, err := c.getReactorDatabaseCred(id)
|
||||
if err != nil {
|
||||
return &pb.ReactorClientResponse{}, err
|
||||
}
|
||||
|
||||
return &pb.ReactorClientResponse{
|
||||
Id: id,
|
||||
Url: db.url,
|
||||
Org: db.org,
|
||||
Token: db.token,
|
||||
Bucket: db.bucket,
|
||||
}, err
|
||||
}
|
||||
|
||||
// ReactorStatusHandler is a gRPC handler used to handle incoming
|
||||
// reactor requests containing information about said reactor.
|
||||
// It will get the associate reactor manager and pass the
|
||||
// request device information before returning an acknowledgement.
|
||||
func (c *Coordinator) ReactorStatusHandler(ctx context.Context, req *pb.ReactorStatusPing) (*pb.ReactorStatusResponse, error) {
|
||||
|
||||
// rm, err := c.LoadReactorManager(int(req.GetId()))
|
||||
|
||||
return &pb.ReactorStatusResponse{}, nil
|
||||
}
|
@ -0,0 +1,121 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
"FRMS/internal/pkg/logging"
|
||||
"FRMS/internal/pkg/manager"
|
||||
"errors"
|
||||
"time"
|
||||
)
|
||||
|
||||
var (
|
||||
ErrNoReactorManager = errors.New("no reactor manager found")
|
||||
)
|
||||
|
||||
// ReactorManager can be started/stopped as clients connect/disconnect.
|
||||
type ReactorManager struct {
|
||||
Manager // base manager interface
|
||||
}
|
||||
|
||||
// Manager is an interface requiring a structure that can be started
|
||||
// and stopped as well as provide timeouts in milliseconds.
|
||||
type Manager interface {
|
||||
Start() error // status checks
|
||||
Stop() error
|
||||
Timeout() (time.Duration, error) // TO Generator
|
||||
}
|
||||
|
||||
// NewManager returns a manager fulfilling the Manager interface as well as
|
||||
// any potential errors.
|
||||
func NewManager(max int) (Manager, error) {
|
||||
return manager.New(max)
|
||||
}
|
||||
|
||||
// GetReactorManager returns a reactor manager for passed id.
|
||||
// Throws error if manager not found for id.
|
||||
func (c *Coordinator) LoadReactorManager(id int) (*ReactorManager, error) {
|
||||
|
||||
c.managerMu.RLock()
|
||||
defer c.managerMu.RUnlock()
|
||||
|
||||
rm, exists := c.directory[id]
|
||||
|
||||
if !exists {
|
||||
logging.Debug(
|
||||
logging.DClient,
|
||||
"RCO 00 creating manager for %v",
|
||||
id,
|
||||
)
|
||||
|
||||
m, err := NewManager(0)
|
||||
|
||||
rm = &ReactorManager{
|
||||
Manager: m,
|
||||
}
|
||||
|
||||
if err = rm.Start(); err != nil {
|
||||
return rm, err
|
||||
}
|
||||
|
||||
c.directory[id] = rm
|
||||
}
|
||||
|
||||
return rm, nil
|
||||
}
|
||||
|
||||
// // NewReactorManager takes in a client, config and channel to pass errors on.
|
||||
// // Returns a new reactor manager as well as any errors that occured during
|
||||
// // creation.
|
||||
// // Uses MaxConnectionAttempts which defaults to 10 to prevent
|
||||
// // unnessecary network load and/or timeout lengths.
|
||||
// func NewReactorManager(
|
||||
// ) (*ReactorManager, error) {
|
||||
|
||||
// m, err := NewManager(MaxConnectionAttempts)
|
||||
|
||||
// if err != nil {
|
||||
// return &ReactorManager{}, err
|
||||
// }
|
||||
|
||||
// return r, err
|
||||
// }
|
||||
|
||||
// Start logs the start and calls start on the embedded manager.
|
||||
func (r *ReactorManager) Start() error {
|
||||
// logging.Debug(logging.DStart, "RMA starting", r.Id)
|
||||
return r.Manager.Start()
|
||||
}
|
||||
|
||||
// Stop logs the stop and calls stop on the embedded manager.
|
||||
func (r *ReactorManager) Stop() error {
|
||||
// logging.Debug(logging.DExit, "RMA %v stopping", r.Id)
|
||||
return r.Manager.Stop()
|
||||
}
|
||||
|
||||
// UpdateClient is used to change the underlying manager client if there
|
||||
// changes to its data.
|
||||
//
|
||||
// BUG(Keegan): Client is not protected by a lock and may lead to races
|
||||
// func (r *ReactorManager) UpdateClient(cl *Client) error {
|
||||
// logging.Debug(logging.DClient, "RMA %v updating client", r.Id)
|
||||
// r.Client = cl
|
||||
// return nil
|
||||
// }
|
||||
|
||||
// // ReactorDeviceHandler processes incoming device information and
|
||||
// // updates the manager accordingly.
|
||||
// func (r *ReactorManager) ReactorDeviceHandler(devs []*pb.Device) error {
|
||||
|
||||
// logging.Debug(logging.DClient, "CCO recieved ping from %v", r.Id)
|
||||
|
||||
// for _, dev := range devs {
|
||||
// logging.Debug(
|
||||
// logging.DClient,
|
||||
// "CCO %v device %v is %v",
|
||||
// r.Id,
|
||||
// dev.GetAddr(),
|
||||
// dev.GetStatus().String(),
|
||||
// )
|
||||
// }
|
||||
|
||||
// return nil
|
||||
// }
|
@ -0,0 +1,19 @@
|
||||
package server
|
||||
|
||||
import (
|
||||
pb "FRMS/internal/pkg/grpc"
|
||||
"FRMS/internal/pkg/logging"
|
||||
)
|
||||
|
||||
func (c *Coordinator) Register() error {
|
||||
// register services
|
||||
pb.RegisterHandshakeServer(c.grpcServer, c)
|
||||
|
||||
go c.grpcServer.Serve(c.listener)
|
||||
// testing
|
||||
pb.RegisterMonitoringServer(c.grpcServer, c)
|
||||
|
||||
logging.Debug(logging.DStart, "CCO 00 registered grpc")
|
||||
|
||||
return nil
|
||||
}
|
@ -0,0 +1,34 @@
|
||||
# TODO
|
||||
|
||||
### Documentation
|
||||
|
||||
- [ ] API
|
||||
- [ ] Config
|
||||
- [ ] Device
|
||||
- [ ] gRPC
|
||||
- [ ] I2C
|
||||
- [ ] DB
|
||||
- [ ] logging
|
||||
- [ ] reactor
|
||||
- [ ] server
|
||||
- [ ] system?
|
||||
|
||||
### Testing
|
||||
|
||||
- [ ] Config
|
||||
- [ ] Device
|
||||
- [ ] i2c
|
||||
- [ ] logging
|
||||
- [ ] reactor
|
||||
- [O] server
|
||||
- [ ] gRPC?
|
||||
- [ ] db?
|
||||
- [ ] system?
|
||||
|
||||
### Misc
|
||||
|
||||
- [ ] i2c -> lib
|
||||
- [ ] strip device pkg
|
||||
- [ ] future work writeup
|
||||
- [ ] strip reactor down
|
||||
- [ ] websocket -> web-gRPC
|
@ -1,2 +0,0 @@
|
||||
#!/bin/bash
|
||||
echo $(git ls-files | grep .go | awk '!/pb/' | xargs wc -l | tail -n 1)
|
@ -0,0 +1,3 @@
|
||||
# API
|
||||
|
||||
This will describe the idea for the API
|
@ -0,0 +1,3 @@
|
||||
# Future Work
|
||||
|
||||
This will describe where to take the project from here
|
@ -0,0 +1,3 @@
|
||||
# GUI
|
||||
|
||||
This will describe the basic outline for the front-end gui
|
@ -0,0 +1,3 @@
|
||||
# Networking
|
||||
|
||||
This will describe how the reactor/server/client talk to each other
|
@ -0,0 +1,10 @@
|
||||
# Reactor
|
||||
|
||||
This will talk about the reactor setup
|
||||
|
||||
## Hardware
|
||||
|
||||
This will describe the hardware used
|
||||
|
||||
## I2C
|
||||
|
@ -0,0 +1,3 @@
|
||||
# Server Information
|
||||
|
||||
## This is a sample
|
@ -0,0 +1,14 @@
|
||||
# Wiki
|
||||
|
||||
## Overview
|
||||
|
||||
This is an example of the overview section of the wiki
|
||||
|
||||
## Table of Contents
|
||||
|
||||
* [Server](server.md)
|
||||
* [Reactor](reactor.md)
|
||||
* [Networking](networking.md)
|
||||
* [GUI](gui.md)
|
||||
* [API](api.md)
|
||||
* [Future Work](future-work.md)
|
Loading…
Reference in New Issue