From f8b1e66c0f73ea6a649305968879dd3b69e980d0 Mon Sep 17 00:00:00 2001 From: Abhijith Dadaga Arkakeerthy Date: Fri, 1 Nov 2024 14:22:51 -0500 Subject: [PATCH] Capture run time of plugins; Allow overriding default log values via env --- README.md | 56 +++++++--- cmd/pm/integ_test.go | 29 ++--- config/config.go | 10 ++ config/config_test.go | 13 ++- graph.go => graph/graph.go | 68 ++++++------ graph_test.go => graph/graph_test.go | 24 ++-- plugin.go | 160 +++++++++++++-------------- plugin_test.go | 9 +- sample/plugins-preupgrade.yaml | 2 +- types/runtime/runtime.go | 12 ++ types/status/status.go | 15 +++ types/types.go | 24 ++++ utils/log/log.go | 41 ++++--- 13 files changed, 286 insertions(+), 177 deletions(-) rename graph.go => graph/graph.go (77%) rename graph_test.go => graph/graph_test.go (89%) create mode 100644 types/runtime/runtime.go create mode 100644 types/status/status.go create mode 100644 types/types.go diff --git a/README.md b/README.md index dd728e3..c748482 100644 --- a/README.md +++ b/README.md @@ -399,19 +399,49 @@ $ ``` ```yaml -$ cat a.yaml -type: preupgrade +# cat a.yaml +name: preupgrade +description: "" +requiredby: [] +requires: [] +execstart: "" plugins: -- description: Checking for "D" settings... - name: D/d.preupgrade - execstart: $PM_LIBRARY/D/preupgrade.sh - requiredby: - - A/a.preupgrade - requires: [] - status: Failed - stdouterr: "Running preupgrade.sh (path: sample/library//D/preupgrade.sh) with - status(1)...\nDisplaying Plugin Manager (PM) Config file path: \nFail(1)\n" + - name: A/a.preupgrade + description: Checking for "A" settings + requiredby: [] + requires: + - D/d.preupgrade + execstart: /bin/echo "Checking A..." + plugins: [] + library: "" + runtime: + starttime: 2024-10-28T18:21:17.289968946-05:00 + endtime: 2024-10-28T18:21:17.337773824-05:00 + duration: 47.804888ms + status: Skipped + stdouterr: [] + - name: D/d.preupgrade + description: Checking for "D" settings... + requiredby: [] + requires: [] + execstart: $PM_LIBRARY/D/preupgrade.sh + plugins: [] + library: "" + runtime: + starttime: 2024-10-28T18:21:17.220368224-05:00 + endtime: 2024-10-28T18:21:17.289945583-05:00 + duration: 69.577293ms + status: Failed + stdouterr: + - 'Running preupgrade.sh (path: sample/library//D/preupgrade.sh) with status(1)...' + - 'Displaying Plugin Manager (PM) Config file path: ' + - Fail(1) +library: "" +runtime: + starttime: 2024-10-28T18:21:17.185365352-05:00 + endtime: 2024-10-28T18:21:17.337805574-05:00 + duration: 152.440222ms status: Failed -stdouterr: 'Running preupgrade plugins: Failed' -$ +stdouterr: + - 'Running preupgrade plugins: Failed' ``` diff --git a/cmd/pm/integ_test.go b/cmd/pm/integ_test.go index 11cdef3..7537dd2 100644 --- a/cmd/pm/integ_test.go +++ b/cmd/pm/integ_test.go @@ -14,6 +14,7 @@ import ( "testing" "github.com/VeritasOS/plugin-manager/config" + "github.com/VeritasOS/plugin-manager/types/status" logger "github.com/VeritasOS/plugin-manager/utils/log" yaml "gopkg.in/yaml.v3" @@ -30,14 +31,6 @@ type Config struct { } } -// Status of plugin execution used for displaying to user on console. -const ( - dStatusFail = "Failed" - dStatusOk = "Succeeded" - dStatusSkip = "Skipped" - dStatusStart = "Starting" -) - func saveConfig(newConfig Config, configFile string) error { logger.Info.Println("Entering saveConfig") defer logger.Info.Println("Exiting saveConfig") @@ -144,11 +137,11 @@ func integTest(t *testing.T, pmBinary, tDir string) { pluginType: "preupgrade", }, want: []string{ - "Checking for \"D\" settings...: " + dStatusStart, - "Checking for \"D\" settings...: " + dStatusOk, - "Checking for \"A\" settings: " + dStatusStart, - "Checking for \"A\" settings: " + dStatusOk, - "Running preupgrade plugins: " + dStatusOk, + "Checking for \"D\" settings...: " + status.Start, + "Checking for \"D\" settings...: " + status.Ok, + "Checking for \"A\" settings: " + status.Start, + "Checking for \"A\" settings: " + status.Ok, + "Running preupgrade plugins: " + status.Ok, }, wantErr: false, }, @@ -159,11 +152,11 @@ func integTest(t *testing.T, pmBinary, tDir string) { testPluginExitStatus: 1, }, want: []string{ - "Checking for \"D\" settings...: " + dStatusStart, - "Checking for \"D\" settings...: " + dStatusFail, - "Checking for \"A\" settings: " + dStatusStart, - "Checking for \"A\" settings: " + dStatusSkip, - "Running preupgrade plugins: " + dStatusFail, + "Checking for \"D\" settings...: " + status.Start, + "Checking for \"D\" settings...: " + status.Fail, + "Checking for \"A\" settings: " + status.Start, + "Checking for \"A\" settings: " + status.Skip, + "Running preupgrade plugins: " + status.Fail, "", }, wantErr: true, diff --git a/config/config.go b/config/config.go index 6e27baf..80fd202 100644 --- a/config/config.go +++ b/config/config.go @@ -92,6 +92,16 @@ func Load() error { logger.Debug.Printf("config file: %s", myConfigFile) var err error myConfig, err = readConfigFile(myConfigFile) + // Set default values when it's not specified in config file. + if myConfig.PluginManager.LogDir == "" { + myConfig.PluginManager.LogDir = logger.DefaultLogDir + } + if myConfig.PluginManager.LogFile == "" { + myConfig.PluginManager.LogFile = logger.DefaultLogFile + } + if myConfig.PluginManager.LogLevel == "" { + myConfig.PluginManager.LogLevel = logger.DefaultLogLevel + } logger.Debug.Printf("Plugin Manager Config: %+v", myConfig) return err } diff --git a/config/config_test.go b/config/config_test.go index c0b8e6c..a894291 100644 --- a/config/config_test.go +++ b/config/config_test.go @@ -85,7 +85,18 @@ func Test_Load(t *testing.T) { args: args{ EnvConfFile: "non-existing/pm.config.yaml", }, - want: Config{}, + want: Config{ + PluginManager: struct { + Library string "yaml:\"library\"" + LogDir string "yaml:\"log dir\"" + LogFile string "yaml:\"log file\"" + LogLevel string "yaml:\"log level\"" + }{ + LogDir: logger.DefaultLogDir, + LogFile: logger.DefaultLogFile, + LogLevel: logger.DefaultLogLevel, + }, + }, }, } diff --git a/graph.go b/graph/graph.go similarity index 77% rename from graph.go rename to graph/graph.go index 949dacf..ca4e1dc 100644 --- a/graph.go +++ b/graph/graph.go @@ -1,18 +1,19 @@ // Copyright (c) 2024 Veritas Technologies LLC. All rights reserved. IP63-2828-7171-04-15-9 -// Package pm graph is used for generating the graph image. -package pm +// Package graph is used for generating the graph image. +package graph import ( "os" "path/filepath" - "sort" "strconv" "strings" "sync" "time" "github.com/VeritasOS/plugin-manager/config" + "github.com/VeritasOS/plugin-manager/types" + "github.com/VeritasOS/plugin-manager/types/status" logger "github.com/VeritasOS/plugin-manager/utils/log" osutils "github.com/VeritasOS/plugin-manager/utils/os" ) @@ -32,7 +33,14 @@ type graph struct { var g graph var dotCmdPresent = true -func initGraphConfig(imgNamePrefix string) { +// Plugin is of type types.Plugin +type Plugin = types.Plugin + +// Plugins is of type types.Plugins +type Plugins = types.Plugins + +// InitGraphConfig initliazes output file names. +func InitGraphConfig(imgNamePrefix string) { // Initialization should be done only once. if g.fileNoExt == "" { // Remove imgNamePrefix if it's end with ".log" @@ -41,39 +49,28 @@ func initGraphConfig(imgNamePrefix string) { } } -func getImagePath() string { +// GetImagePath gets the path of the image file. +func GetImagePath() string { return config.GetPMLogDir() + g.fileNoExt + ".svg" } -func getDotFilePath() string { +// GetDotFilePath gets the path of the dot file. +func GetDotFilePath() string { return config.GetPMLogDir() + g.fileNoExt + ".dot" } -// initGraph initliazes the graph data structure and invokes generateGraph. -func initGraph(pluginType string, pluginsInfo Plugins) error { - initGraphConfig(config.GetPMLogFile()) +// InitGraph initliazes the graph data structure and invokes generateGraph. +func InitGraph(pluginType string, pluginsInfo Plugins) error { + InitGraphConfig(config.GetPMLogFile()) // DOT guide: https://graphviz.gitlab.io/_pages/pdf/dotguide.pdf - // INFO: Sort the plugins so that list of dependencies generated - // (used by documentation) doesn't change. - // NOTE: If not sorted, then even without addition of any new plugin, - // the dependency file generated will keep changing and appears in - // git staged list. - orderedPluginsList := []string{} - pluginsIdx := map[string]int{} for pIdx, p := range pluginsInfo { - orderedPluginsList = append(orderedPluginsList, p.Name) - pluginsIdx[p.Name] = pIdx - } - sort.Strings(orderedPluginsList) - for _, pName := range orderedPluginsList { - pIdx := pluginsIdx[pName] - pFileString := "\"" + pName + "\"" + pFileString := "\"" + p.Name + "\"" absLogPath, _ := filepath.Abs(config.GetPMLogDir()) absLibraryPath, _ := filepath.Abs(config.GetPluginsLibrary()) relPath, _ := filepath.Rel(absLogPath, absLibraryPath) - pURL := "\"" + filepath.FromSlash(relPath+string(os.PathSeparator)+pName) + "\"" + pURL := "\"" + filepath.FromSlash(relPath+string(os.PathSeparator)+p.Name) + "\"" rows := []string{} rowsInterface, ok := g.subgraph.Load(pluginType) if ok { @@ -82,10 +79,10 @@ func initGraph(pluginType string, pluginsInfo Plugins) error { rows = append(rows, pFileString+" [label=\""+ strings.Replace(pluginsInfo[pIdx].Description, "\"", `\"`, -1)+ "\",style=filled,fillcolor=lightgrey,URL="+pURL+"]") - rows = append(rows, "\""+pName+"\"") + rows = append(rows, "\""+p.Name+"\"") rbyLen := len(pluginsInfo[pIdx].RequiredBy) if rbyLen != 0 { - graphRow := "\"" + pName + "\" -> " + graphRow := "\"" + p.Name + "\" -> " for rby := range pluginsInfo[pIdx].RequiredBy { graphRow += "\"" + pluginsInfo[pIdx].RequiredBy[rby] + "\"" if rby != rbyLen-1 { @@ -103,7 +100,7 @@ func initGraph(pluginType string, pluginsInfo Plugins) error { graphRow += ", " } } - graphRow += " -> \"" + pName + "\"" + graphRow += " -> \"" + p.Name + "\"" rows = append(rows, graphRow) } g.subgraph.Store(pluginType, rows) @@ -115,8 +112,8 @@ func initGraph(pluginType string, pluginsInfo Plugins) error { // generateGraph generates an input `.dot` file based on the fileNoExt name, // and then generates an `.svg` image output file as fileNoExt.svg. func generateGraph() error { - dotFile := getDotFilePath() - svgFile := getImagePath() + dotFile := GetDotFilePath() + svgFile := GetImagePath() fhDigraph, openerr := osutils.OsOpenFile(dotFile, os.O_RDWR|os.O_CREATE|os.O_TRUNC, 0666) if openerr != nil { @@ -168,20 +165,21 @@ func generateGraph() error { } // getStatusColor returns the color for a given result status. -func getStatusColor(status string) string { +func getStatusColor(myStatus string) string { // Node color - ncolor := "blue" // dStatusStart by default - if status == dStatusFail { + ncolor := "blue" // status.Start by default + if myStatus == status.Fail { ncolor = "red" - } else if status == dStatusOk { + } else if myStatus == status.Ok { ncolor = "green" - } else if status == dStatusSkip { + } else if myStatus == status.Skip { ncolor = "yellow" } return ncolor } -func updateGraph(subgraphName, plugin, status, url string) error { +// UpdateGraph updates the plugin node with the status and url. +func UpdateGraph(subgraphName, plugin, status, url string) error { ncolor := getStatusColor(status) gContents := []string{} gContentsInterface, ok := g.subgraph.Load(subgraphName) diff --git a/graph_test.go b/graph/graph_test.go similarity index 89% rename from graph_test.go rename to graph/graph_test.go index 49e696f..03596ce 100644 --- a/graph_test.go +++ b/graph/graph_test.go @@ -1,11 +1,13 @@ // Copyright (c) 2024 Veritas Technologies LLC. All rights reserved. IP63-2828-7171-04-15-9 -package pm +package graph import ( "os" "reflect" "sort" "testing" + + "github.com/VeritasOS/plugin-manager/types/status" ) func Test_getStatusColor(t *testing.T) { @@ -24,22 +26,22 @@ func Test_getStatusColor(t *testing.T) { }{ { name: "Start", - args: args{status: dStatusStart}, + args: args{status: status.Start}, want: "blue", }, { name: "Ok/Pass", - args: args{status: dStatusOk}, + args: args{status: status.Ok}, want: "green", }, { name: "Fail", - args: args{status: dStatusFail}, + args: args{status: status.Fail}, want: "red", }, { name: "Skip", - args: args{status: dStatusSkip}, + args: args{status: status.Skip}, want: "yellow", }, } @@ -52,7 +54,7 @@ func Test_getStatusColor(t *testing.T) { } } -func Test_updateGraph(t *testing.T) { +func Test_UpdateGraph(t *testing.T) { if os.Getenv("INTEGRATION_TEST") == "RUNNING" { t.Skip("Not applicable while running integration tests.") return @@ -77,7 +79,7 @@ func Test_updateGraph(t *testing.T) { name: "Append a row", args: args{ plugin: "A/a.test", - status: dStatusOk, + status: status.Ok, url: "url/A/a.test", }, wantErr: false, @@ -88,10 +90,10 @@ func Test_updateGraph(t *testing.T) { } for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := updateGraph(getPluginType(tt.args.plugin), tt.args.plugin, tt.args.status, tt.args.url); (err != nil) != tt.wantErr { + if err := UpdateGraph("test", tt.args.plugin, tt.args.status, tt.args.url); (err != nil) != tt.wantErr { t.Errorf("updateGraph() error = %v, wantErr %v", err, tt.wantErr) } - rowsInterface, _ := g.subgraph.Load(getPluginType(tt.args.plugin)) + rowsInterface, _ := g.subgraph.Load("test") rows := rowsInterface.([]string) if !reflect.DeepEqual(rows, tt.wants.rows) { t.Errorf("updateGraph() g.rows = %v, wants.rows %v", rows, tt.wants.rows) @@ -100,7 +102,7 @@ func Test_updateGraph(t *testing.T) { } } -func Test_initGraph(t *testing.T) { +func Test_InitGraph(t *testing.T) { type args struct { pluginType string pluginsInfo Plugins @@ -200,7 +202,7 @@ func Test_initGraph(t *testing.T) { for _, tt := range tests { t.Run(tt.name, func(t *testing.T) { - if err := initGraph(tt.args.pluginType, tt.args.pluginsInfo); (err != nil) != tt.wantErr { + if err := InitGraph(tt.args.pluginType, tt.args.pluginsInfo); (err != nil) != tt.wantErr { t.Errorf("initGraph() error = %v, wantErr %v", err, tt.wantErr) } rowsI, _ := g.subgraph.Load(tt.args.pluginType) diff --git a/plugin.go b/plugin.go index a1e83aa..27df116 100644 --- a/plugin.go +++ b/plugin.go @@ -22,6 +22,9 @@ import ( "time" "github.com/VeritasOS/plugin-manager/config" + "github.com/VeritasOS/plugin-manager/graph" + "github.com/VeritasOS/plugin-manager/types" + "github.com/VeritasOS/plugin-manager/types/status" logger "github.com/VeritasOS/plugin-manager/utils/log" osutils "github.com/VeritasOS/plugin-manager/utils/os" "github.com/VeritasOS/plugin-manager/utils/output" @@ -30,40 +33,14 @@ import ( var ( // Version of the Plugin Manager (PM). - version = "4.9" + version = "5.0" ) -// Status of plugin execution used for displaying to user on console. -const ( - dStatusFail = "Failed" - dStatusOk = "Succeeded" - dStatusSkip = "Skipped" - dStatusStart = "Starting" -) - -// Plugin is plugin's info: name, description, cmd to run, status, stdouterr. -type Plugin struct { - Name string - Description string - ExecStart string - RequiredBy []string - Requires []string - Status string - StdOutErr []string -} +// Plugin is of type types.Plugin +type Plugin = types.Plugin -// Plugins is a list of plugins' info. -type Plugins []Plugin - -// RunStatus is the pm run status. -type RunStatus struct { - Type string - Library string - // TODO: Add Percentage to get no. of pending vs. completed run of plugins. - Plugins Plugins `yaml:",omitempty"` - Status string - StdOutErr string -} +// Plugins is of type types.Plugins +type Plugins = types.Plugins // getPluginFiles retrieves the plugin files under each component matching // the specified pluginType. @@ -122,9 +99,9 @@ func getPluginType(file string) string { return strings.Replace(path.Ext(file), ".", ``, -1) } -func getPluginsInfoFromJSONStrOrFile(strOrFile string) (RunStatus, error) { +func getPluginsInfoFromJSONStrOrFile(strOrFile string) (Plugin, error) { var err error - var pluginsInfo RunStatus + var pluginsInfo Plugin rawData := strOrFile jsonFormat := true @@ -160,9 +137,9 @@ func getPluginsInfoFromJSONStrOrFile(strOrFile string) (RunStatus, error) { jsonFormat = false } } - // INFO: Use RunStatus to unmarshal to keep input consistent with current + // INFO: Use Plugin to unmarshal to keep input consistent with current // output json, so that rerun failed could be done using result json. - var pluginsData RunStatus + var pluginsData Plugin if jsonFormat { err = json.Unmarshal([]byte(rawData), &pluginsData) } else { @@ -198,7 +175,7 @@ func getPluginsInfoFromLibrary(pluginType, library string) (Plugins, error) { } logger.Info.Printf("Plugin %s info: %+v", pluginFiles[file], pInfo) pInfo.Name = pluginFiles[file] - pluginsInfo = append(pluginsInfo, pInfo) + pluginsInfo = append(pluginsInfo, &pInfo) } return pluginsInfo, nil } @@ -211,10 +188,12 @@ func normalizePluginsInfo(pluginsInfo Plugins) Plugins { pluginIndexes := make(map[string]int, len(pluginsInfo)) for pIdx, pInfo := range pluginsInfo { pluginIndexes[pInfo.Name] = pIdx - nPInfo[pIdx] = Plugin{ + nPInfo[pIdx] = &Plugin{ Name: pInfo.Name, Description: pInfo.Description, ExecStart: pInfo.ExecStart, + Plugins: pInfo.Plugins, + Library: pInfo.Library, } nPInfo[pIdx].RequiredBy = append(nPInfo[pIdx].Requires, pInfo.RequiredBy...) nPInfo[pIdx].Requires = append(nPInfo[pIdx].Requires, pInfo.Requires...) @@ -413,8 +392,8 @@ func validateDependencies(nPInfo Plugins) ([]string, error) { func executePluginCmd(statusCh chan<- map[string]*Plugin, pInfo Plugin, failedDependency bool, env map[string]string) { p := pInfo.Name logger.Debug.Printf("Channel: Plugin %s info: \n%+v", p, pInfo) - updateGraph(getPluginType(p), p, dStatusStart, "") - logger.ConsoleInfo.Printf("%s: %s", pInfo.Description, dStatusStart) + graph.UpdateGraph(getPluginType(p), p, status.Start, "") + logger.ConsoleInfo.Printf("%s: %s", pInfo.Description, status.Start) pluginLogFile := "" var chLog *log.Logger if !logger.IsFileLogger() { @@ -454,16 +433,26 @@ func executePluginCmd(statusCh chan<- map[string]*Plugin, pInfo Plugin, failedDe myStatusMsg := "" if failedDependency { myStatusMsg = "Skipping as its dependency failed." - myStatus = dStatusSkip + myStatus = status.Skip + } else if len(pInfo.Plugins) != 0 { + execStatus := executePlugins(&pInfo.Plugins, false, env) + if !execStatus { + myStatus = status.Fail + graph.UpdateGraph(getPluginType(p), p, myStatus, "") + err := fmt.Errorf("Running %s plugins: %s", p, myStatus) + statusCh <- map[string]*Plugin{p: {Status: myStatus, StdOutErr: []string{err.Error()}}} + return + } + myStatus = status.Ok } else if pInfo.ExecStart == "" { myStatusMsg = "Passing as ExecStart value is empty!" - myStatus = dStatusOk + myStatus = status.Ok } if myStatus != "" { chLog.Println("INFO: ", myStatusMsg) logger.Info.Printf("Plugin(%s): %s", p, myStatusMsg) - updateGraph(getPluginType(p), p, myStatus, "") + graph.UpdateGraph(getPluginType(p), p, myStatus, "") logger.ConsoleInfo.Printf("%s: %s", pInfo.Description, myStatus) statusCh <- map[string]*Plugin{p: {Status: myStatus}} return @@ -502,7 +491,7 @@ func executePluginCmd(statusCh chan<- map[string]*Plugin, pInfo Plugin, failedDe cmd.Env = envList iostdout, err := cmd.StdoutPipe() if err != nil { - pInfo.Status = dStatusFail + pInfo.Status = status.Fail logger.Error.Printf("Failed to execute plugin %s. Error: %s\n", pInfo.Name, err.Error()) pInfo.StdOutErr = []string{err.Error()} logger.ConsoleInfo.Printf("%s: %s\n", pInfo.Description, pInfo.Status) @@ -530,24 +519,24 @@ func executePluginCmd(statusCh chan<- map[string]*Plugin, pInfo Plugin, failedDe chLog.Printf("INFO: Plugin(%s): Executing command: %s", p, pInfo.ExecStart) if err != nil { chLog.Printf("ERROR: Plugin(%s): Failed to execute command, err=%s", p, err.Error()) - updateGraph(getPluginType(p), p, dStatusFail, pluginLogFile) + graph.UpdateGraph(getPluginType(p), p, status.Fail, pluginLogFile) } else { chLog.Printf("INFO: Plugin(%s): Stdout & Stderr: %v", p, stdOutErr) - updateGraph(getPluginType(p), p, dStatusOk, pluginLogFile) + graph.UpdateGraph(getPluginType(p), p, status.Ok, pluginLogFile) } }() logger.Debug.Println("Stdout & Stderr:", stdOutErr) pStatus := Plugin{StdOutErr: stdOutErr} if err != nil { - pStatus.Status = dStatusFail + pStatus.Status = status.Fail logger.Error.Printf("Failed to execute plugin %s. err=%s\n", p, err.Error()) - logger.ConsoleError.Printf("%s: %s\n", pInfo.Description, dStatusFail) + logger.ConsoleError.Printf("%s: %s\n", pInfo.Description, status.Fail) statusCh <- map[string]*Plugin{p: &pStatus} return } - pStatus.Status = dStatusOk - logger.ConsoleInfo.Printf("%s: %s\n", pInfo.Description, dStatusOk) + pStatus.Status = status.Ok + logger.ConsoleInfo.Printf("%s: %s\n", pInfo.Description, status.Ok) statusCh <- map[string]*Plugin{p: &pStatus} } @@ -591,8 +580,11 @@ func executePlugins(psStatus *Plugins, sequential bool, env map[string]string) b (sequential == true && executingCnt == 0)) { logger.Info.Printf("Plugin %s is ready for execution: %v.", p, pInfo) waitCount[p]-- + pIdx := pluginIndexes[p] + ps := *psStatus + ps[pIdx].RunTime.StartTime = time.Now() - go executePluginCmd(exeCh, pInfo, failedDependency[p], env) + go executePluginCmd(exeCh, *pInfo, failedDependency[p], env) executingCnt++ } } @@ -603,15 +595,17 @@ func executePlugins(psStatus *Plugins, sequential bool, env map[string]string) b logger.Info.Printf("%s status: %v", plugin, pStatus.Status) pIdx := pluginIndexes[plugin] ps := *psStatus + ps[pIdx].RunTime.EndTime = time.Now() + ps[pIdx].RunTime.Duration = ps[pIdx].RunTime.EndTime.Sub(ps[pIdx].RunTime.StartTime) ps[pIdx].Status = pStatus.Status ps[pIdx].StdOutErr = pStatus.StdOutErr - if pStatus.Status == dStatusFail { + if pStatus.Status == status.Fail { retStatus = false } for _, rby := range nPInfo[pIdx].RequiredBy { - if pStatus.Status == dStatusFail || - pStatus.Status == dStatusSkip { + if pStatus.Status == status.Fail || + pStatus.Status == status.Skip { // TODO: When "Wants" and "WantedBy" options are supported similar to // "Requires" and "RequiredBy", the failedDependency flag should be // checked in conjunction with if its required dependency is failed, @@ -695,12 +689,12 @@ func list(pluginsInfo Plugins, listOptions ListOptions) error { var err error - err = initGraph(pluginType, pluginsInfo) + err = graph.InitGraph(pluginType, pluginsInfo) if err != nil { return err } - logger.ConsoleInfo.Printf("The list of plugins are mapped in %s", getImagePath()) + logger.ConsoleInfo.Printf("The list of plugins are mapped in %s", graph.GetImagePath()) return nil } @@ -781,21 +775,21 @@ func RegisterCommandOptions(progname string) { // RunFromJSONStrOrFile runs the plugins based on dependencies specified in a // json string or a json/yaml file. -func RunFromJSONStrOrFile(result *RunStatus, jsonStrOrFile string, runOptions RunOptions) error { +func RunFromJSONStrOrFile(result *Plugin, jsonStrOrFile string, runOptions RunOptions) error { pluginsInfo, err := getPluginsInfoFromJSONStrOrFile(jsonStrOrFile) if err != nil { - result.Status = dStatusFail - result.StdOutErr = err.Error() + result.Status = status.Fail + result.StdOutErr = append(result.StdOutErr, err.Error()) return err } - result.Type = pluginsInfo.Type + result.Name = pluginsInfo.Name result.Library = pluginsInfo.Library result.Plugins = pluginsInfo.Plugins // INFO: Override values of json/file with explicitly passed cmdline parameter. Else, set runOptions type from json/file. if runOptions.Type != "" { - result.Type = runOptions.Type + result.Name = runOptions.Type } else { - runOptions.Type = pluginsInfo.Type + runOptions.Type = pluginsInfo.Name } if runOptions.Library != "" { result.Library = runOptions.Library @@ -806,13 +800,13 @@ func RunFromJSONStrOrFile(result *RunStatus, jsonStrOrFile string, runOptions Ru } // RunFromLibrary runs the specified plugin type plugins from the library. -func RunFromLibrary(result *RunStatus, pluginType string, runOptions RunOptions) error { - result.Type = pluginType +func RunFromLibrary(result *Plugin, pluginType string, runOptions RunOptions) error { + result.Name = pluginType var pluginsInfo, err = getPluginsInfoFromLibrary(pluginType, runOptions.Library) if err != nil { - result.Status = dStatusFail - result.StdOutErr = err.Error() + result.Status = status.Fail + result.StdOutErr = append(result.StdOutErr, err.Error()) return err } result.Plugins = pluginsInfo @@ -822,37 +816,43 @@ func RunFromLibrary(result *RunStatus, pluginType string, runOptions RunOptions) } // run the specified plugins. -func run(result *RunStatus, runOptions RunOptions) error { +func run(result *Plugin, runOptions RunOptions) error { logger.Debug.Printf("Entering run(%+v, %+v)...", result, runOptions) defer logger.Debug.Println("Exiting run") pluginType := runOptions.Type sequential := runOptions.Sequential + result.RunTime.StartTime = time.Now() + defer func() { + result.RunTime.EndTime = time.Now() + result.RunTime.Duration = result.RunTime.EndTime.Sub(result.RunTime.StartTime) + }() + if err := osutils.OsMkdirAll(config.GetPluginsLogDir(), 0755); nil != err { err = logger.ConsoleError.PrintNReturnError( "Failed to create the plugins logs directory: %s. "+ "Error: %s", config.GetPluginsLogDir(), err.Error()) - result.Status = dStatusFail - result.StdOutErr = err.Error() + result.Status = status.Fail + result.StdOutErr = append(result.StdOutErr, err.Error()) return err } - initGraph(pluginType, result.Plugins) + graph.InitGraph(pluginType, result.Plugins) env := map[string]string{} if runOptions.Library != "" { env["PM_LIBRARY"] = runOptions.Library } - status := executePlugins(&result.Plugins, sequential, env) - if status != true { - result.Status = dStatusFail - err := fmt.Errorf("Running %s plugins: %s", pluginType, dStatusFail) - result.StdOutErr = err.Error() + execStatus := executePlugins(&result.Plugins, sequential, env) + if execStatus != true { + result.Status = status.Fail + err := fmt.Errorf("Running %s plugins: %s", pluginType, status.Fail) + result.StdOutErr = append(result.StdOutErr, err.Error()) logger.ConsoleError.Printf("%s\n", err.Error()) return err } - result.Status = dStatusOk - logger.ConsoleInfo.Printf("Running %s plugins: %s\n", pluginType, dStatusOk) + result.Status = status.Ok + logger.ConsoleInfo.Printf("Running %s plugins: %s\n", pluginType, status.Ok) return nil } @@ -914,7 +914,7 @@ func ScanCommandOptions(options map[string]interface{}) error { if *CmdOptions.libraryPtr != "" { config.SetPluginsLibrary(*CmdOptions.libraryPtr) } - myLogFile := "./" + myLogFile := logger.DefaultLogDir if logger.GetLogDir() != "" { config.SetPMLogDir(logger.GetLogDir()) myLogFile = config.GetPMLogDir() @@ -932,7 +932,7 @@ func ScanCommandOptions(options map[string]interface{}) error { os.Exit(-1) } } else { - tLogFile := progname + tLogFile := logger.DefaultLogFile if logger.GetLogFile() != "" { tLogFile = logger.GetLogFile() } @@ -968,7 +968,7 @@ func ScanCommandOptions(options map[string]interface{}) error { ListOptions{Type: pluginType}) case "run": - pmstatus := RunStatus{} + pmstatus := Plugin{} runOptions := RunOptions{ Type: pluginType, Sequential: *CmdOptions.sequential, @@ -990,7 +990,7 @@ func ScanCommandOptions(options map[string]interface{}) error { err = ListFromLibrary(pluginType, config.GetPluginsLibrary()) case "run": - pmstatus := RunStatus{} + pmstatus := Plugin{} err = RunFromLibrary(&pmstatus, pluginType, RunOptions{Library: config.GetPluginsLibrary(), Sequential: *CmdOptions.sequential}) diff --git a/plugin_test.go b/plugin_test.go index f1b2b90..8f0adb2 100644 --- a/plugin_test.go +++ b/plugin_test.go @@ -10,6 +10,7 @@ import ( "testing" "github.com/VeritasOS/plugin-manager/config" + "github.com/VeritasOS/plugin-manager/graph" logger "github.com/VeritasOS/plugin-manager/utils/log" ) @@ -783,7 +784,7 @@ func Test_executePlugins(t *testing.T) { }, } - initGraphConfig(config.GetPMLogFile()) + graph.InitGraphConfig(config.GetPMLogFile()) for _, tt := range tests { // Test Sequential as well as sequential execution for _, tt.sequential = range []bool{false, true} { @@ -830,7 +831,7 @@ func Test_getPluginsInfoFromJSONStrOrFile(t *testing.T) { tests := []struct { name string args args - want RunStatus + want Plugin wantErr bool }{ { @@ -860,7 +861,7 @@ func Test_getPluginsInfoFromJSONStrOrFile(t *testing.T) { ] }`, }, - want: RunStatus{ + want: Plugin{ Plugins: Plugins{ { Name: "plugin1", @@ -885,7 +886,7 @@ func Test_getPluginsInfoFromJSONStrOrFile(t *testing.T) { { name: "Plugins in JSON file", args: args{jsonStrOrFile: "./sample/plugins-prereboot.json"}, - want: RunStatus{ + want: Plugin{ Plugins: Plugins{ { Name: "A/a.prereboot", diff --git a/sample/plugins-preupgrade.yaml b/sample/plugins-preupgrade.yaml index 9151838..d8bda75 100755 --- a/sample/plugins-preupgrade.yaml +++ b/sample/plugins-preupgrade.yaml @@ -1,4 +1,4 @@ -type: preupgrade +name: preupgrade plugins: - name: A/a.preupgrade description: Checking for "A" settings diff --git a/types/runtime/runtime.go b/types/runtime/runtime.go new file mode 100644 index 0000000..5dfb774 --- /dev/null +++ b/types/runtime/runtime.go @@ -0,0 +1,12 @@ +// Copyright (c) 2024 Veritas Technologies LLC. All rights reserved. IP63-2828-7171-04-15-9 + +package runtime + +import "time" + +// RunTime to hold start time, end time and duration of a plugin and/or plugin manager execution. +type RunTime struct { + StartTime time.Time + EndTime time.Time + Duration time.Duration +} diff --git a/types/status/status.go b/types/status/status.go new file mode 100644 index 0000000..dabd34a --- /dev/null +++ b/types/status/status.go @@ -0,0 +1,15 @@ +// Copyright (c) 2024 Veritas Technologies LLC. All rights reserved. IP63-2828-7171-04-15-9 + +package status + +// Status of plugin execution used for displaying to user on console. +const ( + // Fail + Fail = "Failed" + // Ok means success state. + Ok = "Succeeded" + // Skip + Skip = "Skipped" + // Start indicate process started / is running. + Start = "Starting" +) diff --git a/types/types.go b/types/types.go new file mode 100644 index 0000000..1270608 --- /dev/null +++ b/types/types.go @@ -0,0 +1,24 @@ +// Copyright (c) 2024 Veritas Technologies LLC. All rights reserved. IP63-2828-7171-04-15-9 + +// Package types defines new plugin manager types. +package types + +import "github.com/VeritasOS/plugin-manager/types/runtime" + +// Plugin is plugin's info: name, description, cmd to run, status, stdouterr. +type Plugin struct { + Name string + Description string + RequiredBy []string + Requires []string + ExecStart string + Plugins Plugins + Library string + // TODO: Add Percentage to get no. of pending vs. completed run of plugins. + RunTime runtime.RunTime + Status string + StdOutErr []string +} + +// Plugins is a list of plugins' info. +type Plugins []*Plugin diff --git a/utils/log/log.go b/utils/log/log.go index 667fc35..cc89b91 100644 --- a/utils/log/log.go +++ b/utils/log/log.go @@ -41,16 +41,19 @@ const ( SysLog ) -// DefaultLogLevel used in case if it's not specified in config or cmdline. -const DefaultLogLevel = "INFO" - var progname = filepath.Base(os.Args[0]) -var defaultLogDir = "/var/log/asum/" -var defaultLogFile = progname + ".log" +// DefaultLogDir used before reading conf file or cmdline params, and could be overridden by conf file or cmdline params. +var DefaultLogDir = "/var/log/asum/" + +// DefaultLogFile used before reading conf file or cmdline params, and could be overriden by conf file or cmdline params +var DefaultLogFile = progname + ".log" // DefaultLogPath used in case if path to log file is not specified in config or cmdline. -var DefaultLogPath = defaultLogDir + defaultLogFile +var DefaultLogPath = DefaultLogDir + DefaultLogFile + +// DefaultLogLevel used in case if it's not specified in config or cmdline. +var DefaultLogLevel = "INFO" const ( syslogConfig = "/etc/rsyslog.d/10-vxos-asum.conf" @@ -356,16 +359,26 @@ func InitLogging() { // NOTE: while running tests, the path of binary would be in `/tmp/`, // so, using relative logging path w.r.t. binary wouldn't be accessible on Jenkins. // So, use absolute path which also has write permissions (like current source directory). + + // Update default values with specified env values. logDir := os.Getenv("PM_LOG_DIR") - if logDir == "" { - logDir = defaultLogDir + if logDir != "" { + DefaultLogDir = filepath.Clean(logDir) } logFile := os.Getenv("PM_LOG_FILE") - if logFile == "" { - logFile = defaultLogFile + if logFile != "" { + DefaultLogFile = filepath.Clean(logFile) + } + // INFO: Update DefaultLogPath in case if values are passed via env, + // so that comparison at a later path succeeds. + DefaultLogPath = filepath.Clean(DefaultLogDir + string(os.PathSeparator) + DefaultLogFile) + + logLevel := os.Getenv("PM_LOG_LEVEL") + if logLevel != "" { + DefaultLogLevel = logLevel } - logPath := logDir + string(os.PathSeparator) + logFile - err := InitFileLogger(logPath, DefaultLogLevel) + + err := InitFileLogger(DefaultLogPath, DefaultLogLevel) if err != nil { fmt.Printf("Failed to initialize file logger [%#v].\n", err) os.Exit(1) @@ -484,11 +497,11 @@ func DeInitLogger() []error { func RegisterCommandOptions(f *flag.FlagSet, defaultParams map[string]string) { defaultLogDir, ok := defaultParams["log-dir"] if !ok { - defaultLogDir = "" + defaultLogDir = DefaultLogDir } defaultLogFile, ok := defaultParams["log-file"] if !ok { - defaultLogFile = "" + defaultLogFile = DefaultLogFile } defaultLogLevel, ok := defaultParams["log-level"] if !ok {