merging wrapup
commit
6ad458ff3d
@ -1 +0,0 @@
|
|||||||
b43ecff1fe53e18c4c9b756b32d38078
|
|
@ -1,3 +1,64 @@
|
|||||||
# DMAC
|
# 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 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
|
package system
|
||||||
|
|
||||||
import (
|
import (
|
||||||
"bytes"
|
"bytes"
|
||||||
|
"encoding/json"
|
||||||
"errors"
|
"errors"
|
||||||
"fmt"
|
|
||||||
"hash/fnv"
|
"hash/fnv"
|
||||||
"net"
|
|
||||||
"os/exec"
|
"os/exec"
|
||||||
"strings"
|
"strings"
|
||||||
)
|
)
|
||||||
|
|
||||||
func GetId() (int, error) {
|
var (
|
||||||
// gets the mac address and hashes into consistent id
|
ErrBusNotFound = errors.New("bus not found for device")
|
||||||
maccmd := fmt.Sprintf("ifconfig %v | awk '/ether / {print $2}'", et)
|
ErrNoNetworkInterface = errors.New("no default network found")
|
||||||
var stderr bytes.Buffer
|
)
|
||||||
var out bytes.Buffer
|
|
||||||
cmd := exec.Command("bash", "-c", maccmd)
|
var HardwareInfo = &hardwareInfo{}
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
type hardwareInfo struct {
|
||||||
if err := cmd.Run(); err != nil {
|
MAC string
|
||||||
return 0, err
|
IP string
|
||||||
}
|
ID int
|
||||||
hash := fnv.New32a()
|
Model string
|
||||||
hash.Write(out.Bytes())
|
}
|
||||||
id := hash.Sum32()
|
|
||||||
return int(id), nil
|
// 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 GetIp() (string, error) {
|
func getInfo() error {
|
||||||
ipcmd := "ip route get 1 | sed 's/^.*src \([^ ]*\).*$/\1/;q'"
|
|
||||||
var stderr bytes.Buffer
|
var stderr, stdout bytes.Buffer
|
||||||
var out bytes.Buffer
|
cmd := exec.Command("ip", "-j", "a")
|
||||||
cmd := exec.Command("bash", "-c", ipcmd)
|
cmd.Stdout = &stdout
|
||||||
cmd.Stdout = &out
|
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
|
|
||||||
if err := cmd.Run(); err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return "", err
|
return err
|
||||||
}
|
}
|
||||||
ip := strings.Trim(out.String(), " \n")
|
|
||||||
return ip, nil
|
|
||||||
|
|
||||||
}
|
var networks netInterfaces
|
||||||
|
|
||||||
func GetPort() (int, error) {
|
if err := json.Unmarshal(stdout.Bytes(), &networks); err != nil {
|
||||||
// obsolete
|
return err
|
||||||
if addr, err := net.ResolveTCPAddr("tcp", ":0"); err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else if lis, err := net.ListenTCP("tcp", addr); err != nil {
|
|
||||||
return 0, err
|
|
||||||
} else {
|
|
||||||
defer lis.Close()
|
|
||||||
return lis.Addr().(*net.TCPAddr).Port, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// 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([]byte(network.Mac))
|
||||||
|
id := hash.Sum32()
|
||||||
|
|
||||||
|
HardwareInfo.MAC = network.Mac
|
||||||
|
HardwareInfo.IP = subnet.Ip
|
||||||
|
HardwareInfo.ID = int(id)
|
||||||
|
|
||||||
|
return nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetBus() (int, error) {
|
func GetID() (int, error) {
|
||||||
// preset busses
|
|
||||||
busList := map[string]int{"raspberrypi": 1, "beaglebone": 2}
|
|
||||||
// vars
|
|
||||||
var bus int
|
|
||||||
var ok bool
|
|
||||||
|
|
||||||
if name, err =: GetModel(); err != nil {
|
if HardwareInfo.ID == 0 {
|
||||||
return bus, err
|
if err := getInfo(); err != nil {
|
||||||
} else if bus, ok = busList[b]; !ok {
|
return 0, err
|
||||||
return 0, errors.New(fmt.Sprintf("No bus for dev %s", b))
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// returns correct bus
|
return HardwareInfo.ID, nil
|
||||||
return bus, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func GetModel() (string, error) {
|
func GetIP() (string, error) {
|
||||||
var stderr, out bytes.Buffer
|
|
||||||
cmd := exec.Command("bash", "-c", "hostname")
|
if HardwareInfo.IP == "" {
|
||||||
cmd.Stdout = &out
|
if err := getInfo(); err != nil {
|
||||||
cmd.Stderr = &stderr
|
return "", err
|
||||||
if err := cmd.Run(); err != nil {
|
}
|
||||||
return "", err
|
|
||||||
}
|
}
|
||||||
b := out.String()
|
|
||||||
b = strings.Trim(b, " \n")
|
return HardwareInfo.IP, nil
|
||||||
return b, nil
|
|
||||||
}
|
}
|
||||||
|
|
||||||
func Get() error {
|
func GetModel() (string, error) {
|
||||||
// responsible for filling out struct
|
|
||||||
//bus := map[string]int{"raspberrypi":1,"beaglebone":2} // eventually will replace this with a config file
|
|
||||||
|
|
||||||
ipcmd := "ifconfig eth0 | awk '/inet / {print $2}'"
|
if HardwareInfo.Model == "" {
|
||||||
maccmd := "ifconfig eth0 | awk '/ether / {print $2}'"
|
|
||||||
devcmd := "lshw -C system 2>/dev/null | head -n 1"
|
|
||||||
|
|
||||||
res := [3]bytes.Buffer{}
|
var stderr, out bytes.Buffer
|
||||||
var stderr bytes.Buffer
|
cmd := exec.Command("uname", "-n")
|
||||||
cmds := []string{ipcmd, maccmd, devcmd}
|
cmd.Stdout = &out
|
||||||
for i, c := range cmds {
|
|
||||||
cmd := exec.Command("bash", "-c", c)
|
|
||||||
cmd.Stdout = &res[i]
|
|
||||||
cmd.Stderr = &stderr
|
cmd.Stderr = &stderr
|
||||||
err := cmd.Run()
|
|
||||||
if err != nil {
|
if err := cmd.Run(); err != nil {
|
||||||
return err
|
return "", err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
b := out.String()
|
||||||
|
HardwareInfo.Model = strings.Trim(b, " \n")
|
||||||
}
|
}
|
||||||
// formatting
|
|
||||||
ip := res[0].String()
|
|
||||||
ip = strings.Trim(ip, " \n")
|
|
||||||
|
|
||||||
hash := fnv.New32a()
|
return HardwareInfo.Model, nil
|
||||||
hash.Write(res[1].Bytes())
|
}
|
||||||
|
|
||||||
b := res[2].String()
|
func GetBus() (int, error) {
|
||||||
b = strings.Trim(b, " \n")
|
// preset busses
|
||||||
return nil
|
busList := map[string]int{"raspberrypi": 1, "beaglebone": 2}
|
||||||
|
// vars
|
||||||
|
var bus int
|
||||||
|
var ok bool
|
||||||
|
|
||||||
|
if name, err := GetModel(); err != nil {
|
||||||
|
return bus, err
|
||||||
|
} else if bus, ok = busList[name]; !ok {
|
||||||
|
return 0, ErrBusNotFound
|
||||||
|
}
|
||||||
|
|
||||||
|
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