diff --git a/cmd/edgeboxctl/main.go b/cmd/edgeboxctl/main.go index ba58361..e8f5b22 100644 --- a/cmd/edgeboxctl/main.go +++ b/cmd/edgeboxctl/main.go @@ -73,7 +73,6 @@ func main() { log.Printf("System not ready. Next try will be executed in 60 seconds") time.Sleep(defaultNotReadySleepTime) } - } @@ -93,8 +92,8 @@ func printVersion() { func printDbDetails() { fmt.Printf( - "\n\nDatabase Connection Information:\n %s\n\n", - utils.GetMySQLDbConnectionDetails(), + "\n\nSQLite Database Location:\n %s\n\n", + utils.GetSQLiteDbConnectionDetails(), ) } @@ -112,11 +111,15 @@ func isDatabaseReady() bool { func systemIterator(name *string, tick int) { log.Printf("Tick is %d", tick) - + tasks.ExecuteSchedules(tick) nextTask := tasks.GetNextTask() if nextTask.Task != "" { - log.Printf("Executing task %s / Args: %s", nextTask.Task, nextTask.Args) + taskArguments := "No arguments" + if nextTask.Args.Valid { + taskArguments = nextTask.Args.String + } + log.Printf("Executing task %s / Args: %s", nextTask.Task, taskArguments) tasks.ExecuteTask(nextTask) } else { log.Printf("No tasks to execute.") diff --git a/go.mod b/go.mod index b739f0c..175fc69 100644 --- a/go.mod +++ b/go.mod @@ -3,7 +3,13 @@ module github.com/edgebox-iot/edgeboxctl go 1.15 require ( + github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 // indirect + github.com/go-ole/go-ole v1.2.5 // indirect github.com/go-sql-driver/mysql v1.5.0 github.com/joho/godotenv v1.3.0 + github.com/mattn/go-sqlite3 v1.14.7 // indirect + github.com/shirou/gopsutil v3.21.4+incompatible // indirect + github.com/tklauser/go-sysconf v0.3.6 // indirect + golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549 // indirect gopkg.in/yaml.v2 v2.4.0 ) diff --git a/go.sum b/go.sum index 445db8a..3fe5772 100644 --- a/go.sum +++ b/go.sum @@ -1,6 +1,10 @@ github.com/BurntSushi/toml v0.3.1/go.mod h1:xHWCNGjB5oqiDr8zfno3MHue2Ht5sIBksp03qcyfWMU= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46 h1:5sXbqlSomvdjlRbWyNqkPsJ3Fg+tQZCbgeX1VGljbQY= +github.com/StackExchange/wmi v0.0.0-20210224194228-fe8f1750fd46/go.mod h1:3eOhrUMpNV+6aFIbp5/iudMxNCF27Vw2OZgy4xEx0Fg= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/go-ole/go-ole v1.2.5 h1:t4MGB5xEDZvXI+0rMjjsfBsD7yAgp/s9ZDkL1JndXwY= +github.com/go-ole/go-ole v1.2.5/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-sql-driver/mysql v1.5.0 h1:ozyZYNQW3x3HtqT1jira07DN2PArx2v7/mN66gGcHOs= github.com/go-sql-driver/mysql v1.5.0/go.mod h1:DCzpHaOWr8IXmIStZouvnhqoel9Qv2LBy8hT2VhHyBg= github.com/google/renameio v0.1.0/go.mod h1:KWCgfxg9yswjAJkECMjeO8J8rahYeXnNhOm40UhjYkI= @@ -10,12 +14,20 @@ github.com/kisielk/gotool v1.0.0/go.mod h1:XhKaO+MFFWcvkIS/tQcRk01m1F5IRFswLeQ+o github.com/kr/pretty v0.1.0/go.mod h1:dAy3ld7l9f0ibDNOQOHHMYYIIbhfbHSm3C4ZsoJORNo= github.com/kr/pty v1.1.1/go.mod h1:pFQYn66WHrOpPYNljwOMqo10TkYh1fy3cYio2l3bCsQ= github.com/kr/text v0.1.0/go.mod h1:4Jbv+DJW3UT/LiOwJeYQe1efqtUx/iVham/4vfdArNI= +github.com/mattn/go-sqlite3 v1.14.7 h1:fxWBnXkxfM6sRiuH3bqJ4CfzZojMOLVc0UTsTglEghA= +github.com/mattn/go-sqlite3 v1.14.7/go.mod h1:NyWgC/yNuGj7Q9rpYnZvas74GogHl5/Z4A/KQRfk6bU= github.com/pkg/errors v0.8.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/rogpeppe/go-internal v1.3.0/go.mod h1:M8bDsm7K2OlrFYOpmOWEs/qY81heoFRclV5y23lUDJ4= +github.com/shirou/gopsutil v3.21.4+incompatible h1:fuHcTm5mX+wzo542cmYcV9RTGQLbnHLI5SyQ5ryTVck= +github.com/shirou/gopsutil v3.21.4+incompatible/go.mod h1:5b4v6he4MtMOwMlS0TUMTu2PcXUg8+E1lC7eC3UO/RA= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= github.com/stretchr/testify v1.3.0/go.mod h1:M5WIy9Dh21IEIfnGCwXGc5bZfKNJtfHm1UVUgZn+9EI= github.com/stretchr/testify v1.4.0/go.mod h1:j7eGeouHqKxXV5pUuKE4zz7dFj8WfuZ+81PSLYec5m4= +github.com/tklauser/go-sysconf v0.3.6 h1:oc1sJWvKkmvIxhDHeKWvZS4f6AW+YcoguSfRF2/Hmo4= +github.com/tklauser/go-sysconf v0.3.6/go.mod h1:MkWzOF4RMCshBAMXuhXJs64Rte09mITnppBXY/rYEFI= +github.com/tklauser/numcpus v0.2.2 h1:oyhllyrScuYI6g+h/zUvNXNp1wy7x8qQy3t/piefldA= +github.com/tklauser/numcpus v0.2.2/go.mod h1:x3qojaO3uyYt0i56EW/VUYs7uBvdl2fkfZFu0T9wgjM= go.uber.org/atomic v1.6.0 h1:Ezj3JGmsOnG1MoRWQkPBsKLe9DwWD9QeXzTRzzldNVk= go.uber.org/atomic v1.6.0/go.mod h1:sABNBOSYdrvTF6hTgEIbc7YasKWGhgEQZyfxyTvoXHQ= go.uber.org/atomic v1.7.0 h1:ADUqmZGgLDDfbSL9ZmPxKTybcoEYHgpYfELNoN+7hsw= @@ -37,6 +49,10 @@ golang.org/x/net v0.0.0-20190620200207-3b0461eec859/go.mod h1:z5CRVTTTmAJ677TzLL golang.org/x/sync v0.0.0-20190423024810-112230192c58/go.mod h1:RxMgew5VJxzue5/jJTE5uejpjVlOe/izrB70Jof72aM= golang.org/x/sys v0.0.0-20190215142949-d0b11bdaac8a/go.mod h1:STP8DvDyc/dI5b8T5hshtkjS+E42TnysNCUPdjciGhY= golang.org/x/sys v0.0.0-20190412213103-97732733099d/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210316164454-77fc1eacc6aa/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= +golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549 h1:OL5GcZ2XPkte3dpfuFQ9o884vrE3BZQhajdntNMruv4= +golang.org/x/sys v0.0.0-20210531080801-fdfd190a6549/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/text v0.3.0/go.mod h1:NqM8EUOU14njkJ3fqMW+pc6Ldnwhi/IjpwHt7yyuwOQ= golang.org/x/tools v0.0.0-20190311212946-11955173bddd/go.mod h1:LCzVGOaR6xXOjkQ3onu1FJEFr0SW1gC7cKk1uF8kGRs= golang.org/x/tools v0.0.0-20190621195816-6e04913cbbac/go.mod h1:/rFqwRUd4F7ZHNgwSSTFct+R/Kf4OFW1sUzUTQQTgfc= diff --git a/internal/system/system.go b/internal/system/system.go new file mode 100644 index 0000000..f8c2610 --- /dev/null +++ b/internal/system/system.go @@ -0,0 +1,23 @@ +package system + +import ( + "fmt" + "strconv" + + "github.com/shirou/gopsutil/host" +) + +func GetUptimeInSeconds() string { + uptime, _ := host.Uptime() + + return strconv.FormatUint(uptime, 10) +} + +func GetUptimeFormatted() string { + uptime, _ := host.Uptime() + + days := uptime / (60 * 60 * 24) + hours := (uptime - (days * 60 * 60 * 24)) / (60 * 60) + minutes := ((uptime - (days * 60 * 60 * 24)) - (hours * 60 * 60)) / 60 + return fmt.Sprintf("%d days, %d hours, %d minutes", days, hours, minutes) +} diff --git a/internal/tasks/tasks.go b/internal/tasks/tasks.go index 8e92617..18bd238 100644 --- a/internal/tasks/tasks.go +++ b/internal/tasks/tasks.go @@ -6,18 +6,21 @@ import ( "fmt" "log" "strconv" + "time" "github.com/edgebox-iot/edgeboxctl/internal/diagnostics" "github.com/edgebox-iot/edgeboxctl/internal/edgeapps" + "github.com/edgebox-iot/edgeboxctl/internal/system" "github.com/edgebox-iot/edgeboxctl/internal/utils" _ "github.com/go-sql-driver/mysql" // Mysql Driver + _ "github.com/mattn/go-sqlite3" // SQlite Driver ) // Task : Struct for Task type type Task struct { ID int `json:"id"` Task string `json:"task"` - Args string `json:"args"` + Args sql.NullString `json:"args"` // Database fields that can be null must use the sql.NullString type Status string `json:"status"` Result sql.NullString `json:"result"` // Database fields that can be null must use the sql.NullString type Created string `json:"created"` @@ -60,18 +63,14 @@ type taskDisableOnlineArgs struct { func GetNextTask() Task { // Will try to connect to API database, which should be running locally under WS. - db, err := sql.Open("mysql", utils.GetMySQLDbConnectionDetails()) + db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) // if there is an error opening the connection, handle it if err != nil { panic(err.Error()) } - // defer the close till after the main function has finished executing - defer db.Close() - - // perform a db.Query insert - results, err := db.Query("SELECT * FROM tasks WHERE status = 0 ORDER BY created ASC LIMIT 1;") + results, err := db.Query("SELECT * FROM task WHERE status = 0 ORDER BY created ASC LIMIT 1;") // if there is an error inserting, handle it if err != nil { @@ -89,8 +88,8 @@ func GetNextTask() Task { } } - // be careful deferring Queries if you are using transactions - defer results.Close() + results.Close() + db.Close() return task @@ -99,18 +98,22 @@ func GetNextTask() Task { // ExecuteTask : Performs execution of the given task, updating the task status as it goes, and publishing the task result func ExecuteTask(task Task) Task { - db, err := sql.Open("mysql", utils.GetMySQLDbConnectionDetails()) + db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) if err != nil { panic(err.Error()) } - defer db.Close() - - _, err = db.Query("UPDATE tasks SET status = 1 WHERE ID = " + strconv.Itoa(task.ID)) - + statement, err := db.Prepare("UPDATE task SET status = ?, updated = ? WHERE ID = ?;") // Prepare SQL Statement if err != nil { - panic(err.Error()) + log.Fatal(err.Error()) + } + + formatedDatetime := utils.GetSQLiteFormattedDateTime(time.Now()) + + _, err = statement.Exec(1, formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement + if err != nil { + log.Fatal(err.Error()) } if diagnostics.Version == "dev" { @@ -122,7 +125,7 @@ func ExecuteTask(task Task) Task { log.Println("Setting up bootnode connection...") var args taskSetupTunnelArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of setup_bootnode task: %s", err) } else { @@ -134,7 +137,7 @@ func ExecuteTask(task Task) Task { log.Println("Installing EdgeApp...") var args taskInstallEdgeAppArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of install_edgeapp task: %s", err) } else { @@ -144,10 +147,9 @@ func ExecuteTask(task Task) Task { case "remove_edgeapp": - log.Println("Removing EdgeApp...") var args taskRemoveEdgeAppArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of remove_edgeapp task: %s", err) } else { @@ -159,7 +161,7 @@ func ExecuteTask(task Task) Task { log.Println("Starting EdgeApp...") var args taskStartEdgeAppArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of start_edgeapp task: %s", err) } else { @@ -171,7 +173,7 @@ func ExecuteTask(task Task) Task { log.Println("Stopping EdgeApp...") var args taskStopEdgeAppArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of stop_edgeapp task: %s", err) } else { @@ -183,7 +185,7 @@ func ExecuteTask(task Task) Task { log.Println("Enabling online access to EdgeApp...") var args taskEnableOnlineArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of enable_online task: %s", err) } else { @@ -195,7 +197,7 @@ func ExecuteTask(task Task) Task { log.Println("Disabling online access to EdgeApp...") var args taskDisableOnlineArgs - err := json.Unmarshal([]byte(task.Args), &args) + err := json.Unmarshal([]byte(task.Args.String), &args) if err != nil { log.Printf("Error reading arguments of enable_online task: %s", err) } else { @@ -207,16 +209,35 @@ func ExecuteTask(task Task) Task { } + statement, err = db.Prepare("Update task SET status = ?, result = ?, updated = ? WHERE ID = ?;") // Prepare SQL Statement + if err != nil { + log.Fatal(err.Error()) + } + if err != nil { + log.Fatal(err.Error()) + } + + formatedDatetime = utils.GetSQLiteFormattedDateTime(time.Now()) + if task.Result.Valid { - db.Query("Update tasks SET status = 2, result = '" + task.Result.String + "' WHERE ID = " + strconv.Itoa(task.ID) + ";") + _, err = statement.Exec(2, task.Result.String, formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with result info + if err != nil { + log.Fatal(err.Error()) + } + } else { - db.Query("Update tasks SET status = 3, result = 'Error' WHERE ID = " + strconv.Itoa(task.ID) + ";") + _, err = statement.Exec(3, "Error", formatedDatetime, strconv.Itoa(task.ID)) // Execute SQL Statement with Error info + if err != nil { + log.Fatal(err.Error()) + } } if err != nil { panic(err.Error()) } + db.Close() + returnTask := task return returnTask @@ -227,8 +248,13 @@ func ExecuteTask(task Task) Task { func ExecuteSchedules(tick int) { if tick == 1 { + // Executing on startup (first tick). Schedules run before tasks in the SystemIterator + uptime := taskGetSystemUptime() + log.Println("Uptime is " + uptime + " seconds (" + system.GetUptimeFormatted() + ")") + log.Println(taskGetEdgeApps()) + } if tick%30 == 0 { @@ -276,7 +302,6 @@ func taskInstallEdgeApp(args taskInstallEdgeAppArgs) string { return string(resultJSON) - } func taskRemoveEdgeApp(args taskRemoveEdgeAppArgs) string { @@ -293,7 +318,6 @@ func taskRemoveEdgeApp(args taskRemoveEdgeAppArgs) string { return string(resultJSON) - } func taskStartEdgeApp(args taskStartEdgeAppArgs) string { @@ -359,20 +383,55 @@ func taskGetEdgeApps() string { edgeApps := edgeapps.GetEdgeApps() edgeAppsJSON, _ := json.Marshal(edgeApps) - db, err := sql.Open("mysql", utils.GetMySQLDbConnectionDetails()) + db, err := sql.Open("sqlite3", utils.GetSQLiteDbConnectionDetails()) if err != nil { - panic(err.Error()) + log.Fatal(err.Error()) } - defer db.Close() - - _, err = db.Query("REPLACE into options (name, value) VALUES ('EDGEAPPS_LIST','" + string(edgeAppsJSON) + "');") - + statement, err := db.Prepare("REPLACE into option (name, value, created, updated) VALUES (?, ?, ?, ?);") // Prepare SQL Statement if err != nil { - panic(err.Error()) + 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() + 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() + + return uptime + +} diff --git a/internal/utils/utils.go b/internal/utils/utils.go index 110f891..ce9d032 100644 --- a/internal/utils/utils.go +++ b/internal/utils/utils.go @@ -7,6 +7,7 @@ import ( "log" "os" "os/exec" + "time" "github.com/joho/godotenv" ) @@ -82,6 +83,29 @@ func GetMySQLDbConnectionDetails() string { } +// GetSQLiteDbConnectionDetails : Returns the necessary string as connection info for SQL.db() +func GetSQLiteDbConnectionDetails() string { + + var apiEnv map[string]string + apiEnv, err := godotenv.Read(GetPath("apiEnvFileLocation")) + + if err != nil { + log.Fatal("Error loading .env file") + } + + return apiEnv["SQLITE_DATABASE"] // Will read from api project edgebox.env file + +} + +// GetSQLiteFormattedDateTime: Given a Time, Returns a string that is formatted ready to be inserted into an SQLite Datetime field using sql.Prepare. +func GetSQLiteFormattedDateTime(t time.Time) string { + // This date is used to indicate the layout. + const datetimeLayout = "2006-01-02 15:04:05" + formatedDatetime := t.Format(datetimeLayout) + + return formatedDatetime +} + // GetPath : Returns either the hardcoded path, or a overwritten value via .env file at project root. Register paths here for seamless working code between dev and prod environments ;) func GetPath(pathKey string) string {