[WIP] Support for cloud version (#17)

* Added build-cloud to Makefile

* Fix env var key GOOS

* Added taskSetReleaseVersion for edgeboxctl version option write into DB

* taskGetSystemUptime to run every 5 ticks

* Added support for detecting main disk in cloud version

* Added missing import

* Finding main device in test

* Refactored GetDevices and test to use release_version

* Added utils.getIP function, set to run as a 60 tick scheduled task

* Added taskEnablePublicDashboard and taskDisablePublicDashboard

* Added utils.WriteOption and refactor, added enablePublicDashb0oard

* Finalizing online support for dashboard, cloud version support

* Added task SetupCloudOptions

* Added couple of func description comments

* Early return statement (thanks @inverse)

* Using IsPublicDashboard in DisablePublicDashboard func

* Added constants for release version and device types, added diagnostics.GetReleaseVersion

* Removed codecov from test action
pull/19/merge
Paulo Truta 2022-02-06 15:37:50 +01:00 committed by GitHub
parent 864d1e8f0f
commit 83d2fdcae1
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
10 changed files with 312 additions and 123 deletions

View File

@ -22,7 +22,7 @@ jobs:
run: make build
- name: Test
run: make test-with-coverage
- uses: codecov/codecov-action@v1
with:
token: ${{ secrets.CODECOV_TOKEN }}
files: coverage.out
# - uses: codecov/codecov-action@v1
# with:
# token: ${{ secrets.CODECOV_TOKEN }}
# files: coverage.out

View File

@ -12,6 +12,9 @@ build-all:
build-prod:
GOOS=linux GOARCH=arm RELEASE=prod make build
build-cloud:
GOOS=linux GOARCH=amd64 RELEASE=cloud make build
build:
@echo "Building ${GOOS}-${GOARCH}"
GOOS=${GOOS} GOARCH=${GOARCH} go build \

View File

@ -90,6 +90,7 @@ sudo apt-get install docker docker-compose
Check the following links for more info on [Docker](https://www.docker.com/) and [Docker Compose](https://docs.docker.com/compose/).
Aditionally, edgeboxctl needs the following bash commands available wherever it runs:
* `arm-linux-gnueabi-gcc` (`sudo apt-get install gcc-arm*`)
* `sh`
* `rm`
* `systemctl`

View File

@ -1,7 +1,18 @@
package diagnostics
type ReleaseVersion string
var Version string
var Commit string
var BuildDate string
const (
DEV_VERSION ReleaseVersion = "dev"
PROD_VERSION ReleaseVersion = "prod"
CLOUD_VERSION ReleaseVersion = "cloud"
OTHER_VERSION ReleaseVersion = "other"
)
func GetReleaseVersion() ReleaseVersion {
return ReleaseVersion(Version)
}

View File

@ -335,6 +335,37 @@ func DisableOnline(ID string) MaybeEdgeApp {
}
func EnablePublicDashboard(InternetURL string) bool {
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
env, _ := godotenv.Unmarshal("INTERNET_URL=" + InternetURL)
_ = godotenv.Write(env, envFilePath)
buildFrameworkContainers()
return true
}
func DisablePublicDashboard() bool {
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
if !IsPublicDashboard() {
log.Println("myedge.app environment file for the dashboard / api not found. No need to delete.")
return false
}
cmdArgs := []string{envFilePath}
utils.Exec(utils.GetPath("apiPath"), "rm", cmdArgs)
buildFrameworkContainers()
return true
}
func IsPublicDashboard() bool {
envFilePath := utils.GetPath("apiPath") + myEdgeAppServiceEnvFilename
_, err := godotenv.Read(envFilePath)
return err == nil
}
func buildFrameworkContainers() {
cmdArgs := []string{utils.GetPath("wsPath") + "ws", "--build"}

View File

@ -6,24 +6,25 @@ import (
"path/filepath"
"strings"
"github.com/edgebox-iot/edgeboxctl/internal/diagnostics"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
"github.com/shirou/gopsutil/disk"
)
// Device : Struct representing a storage device in the system
type Device struct {
ID string `json:"id"`
Name string `json:"name"`
Size string `json:"size"`
InUse bool `json:"in_use"`
MainDevice bool `json:"main_device"`
MAJ string `json:"maj"`
MIN string `json:"min"`
RM string `json:"rm"`
RO string `json:"ro"`
Partitions []Partition `json:"partitions"`
Status DeviceStatus `json:"status"`
UsageStat UsageStat `json:"usage_stat"`
ID DeviceIdentifier `json:"id"`
Name string `json:"name"`
Size string `json:"size"`
InUse bool `json:"in_use"`
MainDevice bool `json:"main_device"`
MAJ string `json:"maj"`
MIN string `json:"min"`
RM string `json:"rm"`
RO string `json:"ro"`
Partitions []Partition `json:"partitions"`
Status DeviceStatus `json:"status"`
UsageStat UsageStat `json:"usage_stat"`
}
// DeviceStatus : Struct representing possible storage device statuses (code + description)
@ -66,10 +67,27 @@ type Partition struct {
UsageStat UsageStat `json:"usage_stat"`
}
const mainDiskID = "mmcblk0"
type DeviceIdentifier string
const (
DISK_TYPE_SDA DeviceIdentifier = "sda"
DISK_TYPE_MCBLK DeviceIdentifier = "mmcblk0"
DISK_TYPE_VDA DeviceIdentifier = "vda"
)
func GetDeviceIdentifier(release_version diagnostics.ReleaseVersion) DeviceIdentifier {
switch release_version {
case diagnostics.CLOUD_VERSION:
return DISK_TYPE_VDA
case diagnostics.PROD_VERSION:
return DISK_TYPE_MCBLK
}
return DISK_TYPE_SDA
}
// GetDevices : Returns a list of all available sotrage devices in structs filled with information
func GetDevices() []Device {
func GetDevices(release_version diagnostics.ReleaseVersion) []Device {
var devices []Device
@ -82,6 +100,8 @@ func GetDevices() []Device {
firstDevice := true
currentDeviceInUseFlag := false
mainDiskID := GetDeviceIdentifier(release_version)
for scanner.Scan() {
// 1 Device is represented here. Extract words in order for filling a Device struct
// Example deviceRawInfo: "mmcblk0 179:0 0 29.7G 0 disk"
@ -122,7 +142,7 @@ func GetDevices() []Device {
mainDevice := false
device := Device{
ID: deviceRawInfo[0],
ID: DeviceIdentifier(deviceRawInfo[0]),
Name: deviceRawInfo[0],
Size: deviceRawInfo[3],
MainDevice: mainDevice,

View File

@ -7,25 +7,37 @@ import (
)
func TestGetDevices(t *testing.T) {
result := GetDevices()
if len(result) == 0 {
t.Log("Testing with release version dev")
assertGetDevices(GetDevices("dev"), t)
t.Log("Testing with release version prod")
assertGetDevices(GetDevices("prod"), t)
t.Log("Testing with release version cloud")
assertGetDevices(GetDevices("cloud"), t)
}
func assertGetDevices(devices []Device, t *testing.T) {
if len(devices) == 0 {
t.Log("Expecting at least 1 block device, 0 elements found in slice")
t.Fail()
}
foundDevice := false
t.Log("Looking for a mmcblk0 or sda device")
for _, device := range result {
if device.ID == "mmcblk0" || device.ID == "sda" {
t.Log("Looking for a mmcblk0, sda or dva device")
for _, device := range devices {
if device.ID == "mmcblk0" || device.ID == "sda" || device.ID == "vda" {
t.Log("Found target device", device.ID)
foundDevice = true
}
}
if !foundDevice {
t.Log("Expected to find device mmcblk0 but did not. Devices:", result)
t.Log("Expected to find device mmcblk0, sda or dva but did not. Devices:", devices)
t.Fail()
}
}

View File

@ -3,16 +3,23 @@ package system
import (
"fmt"
"strconv"
"strings"
"github.com/edgebox-iot/edgeboxctl/internal/utils"
"github.com/shirou/gopsutil/host"
"github.com/joho/godotenv"
)
// GetUptimeInSeconds: Returns a value (as string) of the total system uptime
func GetUptimeInSeconds() string {
uptime, _ := host.Uptime()
return strconv.FormatUint(uptime, 10)
}
// GetUptimeFormatted: Returns a humanized version that can be useful for logging
func GetUptimeFormatted() string {
uptime, _ := host.Uptime()
@ -21,3 +28,61 @@ func GetUptimeFormatted() string {
minutes := ((uptime - (days * 60 * 60 * 24)) - (hours * 60 * 60)) / 60
return fmt.Sprintf("%d days, %d hours, %d minutes", days, hours, minutes)
}
// GetIP: Returns the ip address of the instance
func GetIP() string {
ip := ""
// Trying to find a valid IP (For direct connection, not tunneled)
ethResult := utils.ExecAndGetLines("/", "ip", []string{"-o", "-4", "addr", "list", "eth0"})
for ethResult.Scan() {
adapterRawInfo := strings.Fields(ethResult.Text())
if ip == "" {
ip = strings.Split(adapterRawInfo[3], "/")[0]
}
}
// If no IP was found yet, try wlan0
if ip == "" {
wlanResult := utils.ExecAndGetLines("/", "ip", []string{"-o", "-4", "addr", "list", "wlan0"})
for wlanResult.Scan() {
adapterRawInfo := strings.Fields(wlanResult.Text())
if ip == "" {
ip = strings.Split(adapterRawInfo[3], "/")[0]
}
}
}
return ip
}
func GetHostname() string {
return utils.Exec("/", "hostname", []string{})
}
// SetupCloudOptions: Reads the designated env file looking for options to write into the options table. Meant to be used on initial setup. Deletes source env file after operation.
func SetupCloudOptions() {
var cloudEnv map[string]string
cloudEnv, err := godotenv.Read(utils.GetPath("cloudEnvFileLocation"))
if err != nil {
fmt.Println("Error loading .env file for cloud version setup")
}
if cloudEnv["NAME"] != "" {
utils.WriteOption("NAME", cloudEnv["NAME"])
}
if cloudEnv["EMAIL"] != "" {
utils.WriteOption("EMAIL", cloudEnv["EMAIL"])
}
if cloudEnv["EDGEBOXIO_API_TOKEN"] != "" {
utils.WriteOption("EDGEBOXIO_API_TOKEN", cloudEnv["EDGEBOXIO_API_TOKEN"])
}
// In the end of this operation takes place, remove the env file as to not overwrite any options once they are set.
utils.Exec("/", "rm", []string{utils.GetPath("cloudEnvFileLocation")})
}

View File

@ -60,6 +60,10 @@ type taskDisableOnlineArgs struct {
ID string `json:"id"`
}
type taskEnablePublicDashboardArgs struct {
InternetURL string `json:"internet_url"`
}
const STATUS_CREATED int = 0
const STATUS_EXECUTING int = 1
const STATUS_FINISHED int = 2
@ -122,10 +126,11 @@ func ExecuteTask(task Task) Task {
log.Fatal(err.Error())
}
if diagnostics.Version == "dev" {
if diagnostics.GetReleaseVersion() == diagnostics.DEV_VERSION {
log.Printf("Dev environemnt. Not executing tasks.")
} else {
log.Println("Task: " + task.Task)
log.Println("Args: " + task.Args.String)
switch task.Task {
case "setup_tunnel":
@ -211,6 +216,24 @@ func ExecuteTask(task Task) Task {
task.Result = sql.NullString{String: taskResult, Valid: true}
}
case "enable_public_dashboard":
log.Println("Enabling online access to Dashboard...")
var args taskEnablePublicDashboardArgs
err := json.Unmarshal([]byte(task.Args.String), &args)
if err != nil {
log.Printf("Error reading arguments of enable_public_dashboard task: %s", err)
} else {
taskResult := taskEnablePublicDashboard(args)
task.Result = sql.NullString{String: taskResult, Valid: true}
}
case "disable_public_dashboard":
log.Println("Disabling online access to Dashboard...")
taskResult := taskDisablePublicDashboard()
task.Result = sql.NullString{String: taskResult, Valid: true}
}
}
@ -230,7 +253,6 @@ func ExecuteTask(task Task) Task {
if err != nil {
log.Fatal(err.Error())
}
} else {
_, err = statement.Exec(STATUS_ERROR, "Error", formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with Error info
if err != nil {
@ -255,6 +277,26 @@ func ExecuteSchedules(tick int) {
if tick == 1 {
ip := taskGetSystemIP()
log.Println("System IP is: " + ip)
release := taskSetReleaseVersion()
log.Println("Setting api option flag for Edgeboxctl (" + release + " version)")
hostname := taskGetHostname()
log.Println("Hostname is " + hostname)
// if diagnostics.Version == "cloud" && !edgeapps.IsPublicDashboard() {
// taskEnablePublicDashboard(taskEnablePublicDashboardArgs{
// InternetURL: hostname + ".myedge.app",
// })
// }
if diagnostics.GetReleaseVersion() == diagnostics.CLOUD_VERSION {
log.Println("Setting up cloud version options (name, email, api token)")
taskSetupCloudOptions()
}
// Executing on startup (first tick). Schedules run before tasks in the SystemIterator
uptime := taskGetSystemUptime()
log.Println("Uptime is " + uptime + " seconds (" + system.GetUptimeFormatted() + ")")
@ -266,18 +308,18 @@ func ExecuteSchedules(tick int) {
if tick%5 == 0 {
// Executing every 5 ticks
taskGetSystemUptime()
log.Println(taskGetStorageDevices())
}
if tick%30 == 0 {
// Executing every 30 ticks
log.Println(taskGetEdgeApps())
}
if tick%60 == 0 {
// Every 60 ticks...
ip := taskGetSystemIP()
log.Println("System IP is: " + ip)
}
// Just add a schedule here if you need a custom one (every "tick hour", every "tick day", etc...)
@ -285,7 +327,6 @@ func ExecuteSchedules(tick int) {
}
func taskSetupTunnel(args taskSetupTunnelArgs) string {
fmt.Println("Executing taskSetupTunnel")
cmdargs := []string{"gen", "--name", args.NodeName, "--token", args.BootnodeToken, args.BootnodeAddress + ":8655", "--prefix", args.AssignedAddress}
@ -299,181 +340,145 @@ func taskSetupTunnel(args taskSetupTunnelArgs) string {
output := "OK" // Better check / logging of command execution result.
return output
}
func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string {
fmt.Println("Executing taskInstallEdgeApp for " + args.ID)
result := edgeapps.SetEdgeAppInstalled(args.ID)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps()
return string(resultJSON)
}
func taskRemoveEdgeApp(args taskRemoveEdgeAppArgs) string {
fmt.Println("Executing taskRemoveEdgeApp for " + args.ID)
// Making sure the application is stopped before setting it as removed.
edgeapps.StopEdgeApp(args.ID)
result := edgeapps.SetEdgeAppNotInstalled(args.ID)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps()
return string(resultJSON)
}
func taskStartEdgeApp(args taskStartEdgeAppArgs) string {
fmt.Println("Executing taskStartEdgeApp for " + args.ID)
result := edgeapps.RunEdgeApp(args.ID)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps() // This task will imediatelly update the entry in the api database.
return string(resultJSON)
}
func taskStopEdgeApp(args taskStopEdgeAppArgs) string {
fmt.Println("Executing taskStopEdgeApp for " + args.ID)
result := edgeapps.StopEdgeApp(args.ID)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps() // This task will imediatelly update the entry in the api database.
return string(resultJSON)
}
func taskEnableOnline(args taskEnableOnlineArgs) string {
fmt.Println("Executing taskEnableOnline for " + args.ID)
result := edgeapps.EnableOnline(args.ID, args.InternetURL)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps()
return string(resultJSON)
}
func taskDisableOnline(args taskDisableOnlineArgs) string {
fmt.Println("Executing taskDisableOnline for " + args.ID)
result := edgeapps.DisableOnline(args.ID)
resultJSON, _ := json.Marshal(result)
taskGetEdgeApps()
return string(resultJSON)
}
func taskEnablePublicDashboard(args taskEnablePublicDashboardArgs) string {
fmt.Println("Enabling taskEnablePublicDashboard")
result := edgeapps.EnablePublicDashboard(args.InternetURL)
if result {
utils.WriteOption("PUBLIC_DASHBOARD", args.InternetURL)
return "{result: true}"
}
return "{result: false}"
}
func taskDisablePublicDashboard() string {
fmt.Println("Executing taskDisablePublicDashboard")
result := edgeapps.DisablePublicDashboard()
utils.WriteOption("PUBLIC_DASHBOARD", "")
if result {
return "{result: true}"
}
return "{result: false}"
}
func taskSetReleaseVersion() string {
fmt.Println("Executing taskSetReleaseVersion")
utils.WriteOption("RELEASE_VERSION", diagnostics.Version)
return diagnostics.Version
}
func taskGetEdgeApps() string {
fmt.Println("Executing taskGetEdgeApps")
edgeApps := edgeapps.GetEdgeApps()
edgeAppsJSON, _ := json.Marshal(edgeApps)
db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement
if err != nil {
log.Fatal(err.Error())
}
formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now())
_, err = statement.Exec("EDGEAPPS_LIST", string(edgeAppsJSON), formatedDatetime, formatedDatetime) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
db.Close()
utils.WriteOption("EDGEAPPS_LIST", string(edgeAppsJSON))
return string(edgeAppsJSON)
}
func taskGetSystemUptime() string {
fmt.Println("Executing taskGetSystemUptime")
uptime := system.GetUptimeInSeconds()
db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement
if err != nil {
log.Fatal(err.Error())
}
formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now())
_, err = statement.Exec("SYSTEM_UPTIME", uptime, formatedDatetime, formatedDatetime) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
db.Close()
utils.WriteOption("SYSTEM_UPTIME", uptime)
return uptime
}
func taskGetStorageDevices() string {
fmt.Println("Executing taskGetStorageDevices")
devices := storage.GetDevices()
devices := storage.GetDevices(diagnostics.GetReleaseVersion())
devicesJSON, _ := json.Marshal(devices)
db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement
if err != nil {
log.Fatal(err.Error())
}
formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now())
_, err = statement.Exec("STORAGE_DEVICES_LIST", devicesJSON, formatedDatetime, formatedDatetime) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
db.Close()
utils.WriteOption("STORAGE_DEVICES_LIST", string(devicesJSON))
return string(devicesJSON)
}
func taskGetSystemIP() string {
fmt.Println("Executing taskGetStorageDevices")
ip := system.GetIP()
utils.WriteOption("IP_ADDRESS", ip)
return ip
}
func taskGetHostname() string {
fmt.Println("Executing taskGetHostname")
hostname := system.GetHostname()
utils.WriteOption("HOSTNAME", hostname)
return hostname
}
func taskSetupCloudOptions() {
fmt.Println("Executing taskSetupCloudOptions")
system.SetupCloudOptions()
}

View File

@ -3,6 +3,7 @@ package utils
import (
"bufio"
"bytes"
"database/sql"
"fmt"
"io"
"log"
@ -112,6 +113,14 @@ func GetPath(pathKey string) string {
}
switch pathKey {
case "cloudEnvFileLocation":
if env["CLOUD_ENV_FILE_LOCATION"] != "" {
targetPath = env["CLOUD_ENV_FILE_LOCATION"]
} else {
targetPath = "/home/system/components/edgeboxctl/cloud.env"
}
case "apiEnvFileLocation":
if env["API_ENV_FILE_LOCATION"] != "" {
@ -120,6 +129,14 @@ func GetPath(pathKey string) string {
targetPath = "/home/system/components/api/edgebox.env"
}
case "apiPath":
if env["API_PATH"] != "" {
targetPath = env["APT_PATH"]
} else {
targetPath = "/home/system/components/api/"
}
case "edgeAppsPath":
if env["EDGEAPPS_PATH"] != "" {
@ -145,3 +162,27 @@ func GetPath(pathKey string) string {
return targetPath
}
// WriteOption : Writes a key value pair option into the api shared database
func WriteOption(optionKey string, optionValue string) {
db, err := sql.Open("sqlite3", GetSQLiteDbConnectionDetails())
if err != nil {
log.Fatal(err.Error())
}
statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement
if err != nil {
log.Fatal(err.Error())
}
formatedDatetime := GetSQLiteFormattedDateTime(time.Now())
_, err = statement.Exec(optionKey, optionValue, formatedDatetime, formatedDatetime) // Execute SQL Statement
if err != nil {
log.Fatal(err.Error())
}
db.Close()
}