From 7f4ff9df07515ee024efcf47bbc402ee228b258a Mon Sep 17 00:00:00 2001 From: Aneesh Kumar Date: Thu, 2 Jul 2020 17:41:35 +0530 Subject: [PATCH 1/4] first draft --- README.md | 23 +++++++++++++++++++---- 1 file changed, 19 insertions(+), 4 deletions(-) diff --git a/README.md b/README.md index 7584317..facb905 100644 --- a/README.md +++ b/README.md @@ -1,22 +1,37 @@ # MetaMorph -Lifecycle your BareMetal +MetaMorph is a tool introduced to provision baremetal nodes in the kubernetes native way. MetaMorph uses native redfish APIs to provision the baremetal nodes thus eliminating complex traditional pre-requisties like DHCP, TFTP, PXE booting etc. ISO used to provision the OS will be mounted from an HTTP share using `VirtualMedia` feature of Redfish +## Features + +1. **Minimum Pre-requisties/Dependencies** The only Pre-requisties Metamorph has is the Redfish Protocal support on the node to be provisioned. +2. **Edge Node Deployment** Since Metamorph eliminates the complex pre-requisties like DHCP, TFTP, PXE booting etc, Its very easy and reliable to deploy edge nodes. +3. **Vendor Indipendent** Servers manufactored by any vendor can be deployed (provided it supports Redfish protocol) +4. **Boot Actions** Boot Actions are jobs that will be executed on the first boot of the deployed node. It can be used to deploy any kind of software on the target node. Boot Actions can be written in any languages. +5. **Plugin Support** Plugins can do variety of things. It can extend default features/functionlity, Add support for old hardware that doesn't support Redfish protocol (eg: HP iLO4 RAID Config), Integrate othe tools/services to MetaMorph \**coming soon*. + + + +## Setup Dev env + +1. `git clone https://github.com/bm-metamorph/MetaMorph.git` -## Setting up Development Environment Setup ENV variables +``` export METAMORPH_CONFIGPATH= export REDFISH_SLEEPTIME_SECS=10 //time duration in between subsequent redfish API calls. Default = 120 secs export METAMORPH_POWERCHANGE_TIMEOUT=300 //To handle Nodes that are powered off at the start of RAID Creation. export METAMORPH_LOG_LEVEL= 1 // default is DEBUG, 1 = INFO 2 = WARN 3 = ERROR export REDFISH_JOBCHECKTIMEOUT_MTS= 10 // default is 10 minutes. Job related to firmware updates takes more time. +cd MetaMorph +``` -1. To Run the Controller +2. To Run the Controller `go run main.go controller` -2. To Run the API +3. To Run the API `go run main.go api` From b5fb64c5ff56e125ffa7d87cb51672d70805d02b Mon Sep 17 00:00:00 2001 From: Aneesh Kumar Date: Thu, 2 Jul 2020 18:14:09 +0530 Subject: [PATCH 2/4] added links to the docs (#2) --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index facb905..637f664 100644 --- a/README.md +++ b/README.md @@ -34,4 +34,11 @@ cd MetaMorph 3. To Run the API `go run main.go api` +## Resources + +* [Usage Documentation](https://metamorph.readthedocs.io/en/latest/usageGuide) +* [API References](https://metamorph.readthedocs.io/en/latest/references) + + + From 3ea5d47014c6e7d9380e180fdf44968f95273b5e Mon Sep 17 00:00:00 2001 From: Venugopal Kanumalla Date: Tue, 4 Aug 2020 14:49:19 -0500 Subject: [PATCH 3/4] Update README.md --- README.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 637f664..b8d56cb 100644 --- a/README.md +++ b/README.md @@ -1,12 +1,12 @@ # MetaMorph -MetaMorph is a tool introduced to provision baremetal nodes in the kubernetes native way. MetaMorph uses native redfish APIs to provision the baremetal nodes thus eliminating complex traditional pre-requisties like DHCP, TFTP, PXE booting etc. ISO used to provision the OS will be mounted from an HTTP share using `VirtualMedia` feature of Redfish +MetaMorph is a tool introduced to provision baremetal nodes in the kubernetes native way. It is fully compliant with Kubernetes Cluster API, its a Baremetal Provisioner. MetaMorph uses native redfish APIs to provision the baremetal nodes thus eliminating complex traditional pre-requisties like DHCP, TFTP, PXE booting etc. ISO used to provision the OS will be mounted from an HTTP share using `VirtualMedia` feature of Redfish ## Features 1. **Minimum Pre-requisties/Dependencies** The only Pre-requisties Metamorph has is the Redfish Protocal support on the node to be provisioned. 2. **Edge Node Deployment** Since Metamorph eliminates the complex pre-requisties like DHCP, TFTP, PXE booting etc, Its very easy and reliable to deploy edge nodes. -3. **Vendor Indipendent** Servers manufactored by any vendor can be deployed (provided it supports Redfish protocol) +3. **Vendor Independent** Servers manufactored by any vendor can be deployed (provided it supports Redfish protocol) 4. **Boot Actions** Boot Actions are jobs that will be executed on the first boot of the deployed node. It can be used to deploy any kind of software on the target node. Boot Actions can be written in any languages. 5. **Plugin Support** Plugins can do variety of things. It can extend default features/functionlity, Add support for old hardware that doesn't support Redfish protocol (eg: HP iLO4 RAID Config), Integrate othe tools/services to MetaMorph \**coming soon*. From 87b4674a73f52a1c751a8b2ac18e67991a57e61e Mon Sep 17 00:00:00 2001 From: shashipratap Date: Tue, 29 Sep 2020 11:31:56 +0000 Subject: [PATCH 4/4] test cases addition --- pkg/agent/agent.go | 6 +- pkg/apis/main.go | 27 +- pkg/apis/main_test.go | 335 ++++++++++++++++++ pkg/controller/grpc/server.go | 96 +++-- pkg/controller/grpc/server_test.go | 166 +++++++++ .../statemachine/metamorph-statemachine.go | 155 ++++---- .../metamorph-statemachine_test.go | 2 +- pkg/controller/statemachine/mock_nodedb.go | 2 +- pkg/db/models/node/node_type.go | 230 ++++++------ pkg/db/models/node/nodes.go | 134 +++++-- pkg/db/models/node/nodes_test.go | 179 +++++++++- pkg/plugin/pluginClient.go | 192 ++++++++++ pkg/plugin/pluginClient_test.go | 33 ++ pkg/test/server.go | 34 ++ pkg/test/server_test.go | 23 ++ pkg/test/test1.go | 20 ++ 16 files changed, 1395 insertions(+), 239 deletions(-) create mode 100644 pkg/apis/main_test.go create mode 100644 pkg/controller/grpc/server_test.go create mode 100644 pkg/plugin/pluginClient.go create mode 100644 pkg/plugin/pluginClient_test.go create mode 100644 pkg/test/server.go create mode 100644 pkg/test/server_test.go create mode 100644 pkg/test/test1.go diff --git a/pkg/agent/agent.go b/pkg/agent/agent.go index 214db2a..5401544 100644 --- a/pkg/agent/agent.go +++ b/pkg/agent/agent.go @@ -3,9 +3,9 @@ package main import( "fmt" //"time" - "bitbucket.com/metamorph/proto" - "bitbucket.com/metamorph/pkg/config" - "bitbucket.com/metamorph/pkg/db/models/node" + "github.com/bm-metamorph/MetaMorph/proto" + "github.com/manojkva/metamorph-plugin/pkg/config" + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" "google.golang.org/grpc" "context" "encoding/json" diff --git a/pkg/apis/main.go b/pkg/apis/main.go index 4d83f28..e9ca29a 100644 --- a/pkg/apis/main.go +++ b/pkg/apis/main.go @@ -1,25 +1,29 @@ package api import ( - "bitbucket.com/metamorph/pkg/logger" - "bitbucket.com/metamorph/proto" + "github.com/manojkva/metamorph-plugin/pkg/logger" + "github.com/bm-metamorph/MetaMorph/proto" "fmt" "github.com/gin-contrib/zap" "github.com/gin-gonic/gin" + ctrlgRPCServer "github.com/bm-metamorph/MetaMorph/pkg/controller/grpc" "go.uber.org/zap" "google.golang.org/grpc" "net/http" "time" "os" ) +func run_controller() { + ctrlgRPCServer.Serve() +} func grpcClient() (proto.NodeServiceClient, *grpc.ClientConn) { grpcServer := "localhost" if gs := os.Getenv("METMORPH_CONTROLLER_HOST"); gs != "" { grpcServer = gs } - + logger.Log.Info("grpcClient()") conn, err := grpc.Dial(fmt.Sprintf("%s:4040", grpcServer), grpc.WithInsecure()) if err != nil { @@ -27,7 +31,6 @@ func grpcClient() (proto.NodeServiceClient, *grpc.ClientConn) { panic(err) } client := proto.NewNodeServiceClient(conn) - return client, conn } @@ -37,6 +40,7 @@ func createNode(ctx *gin.Context) { data, _ := ctx.GetRawData() req := &proto.Request{NodeSpec: data} if response, err := client.Create(ctx, req); err == nil { + fmt.Println(response) ctx.JSON(http.StatusOK, gin.H{ "result": fmt.Sprint(response.Result), }) @@ -54,7 +58,7 @@ func describeNode(ctx *gin.Context) { logger.Log.Info("describeNode()") client, conn := grpcClient() node_id := ctx.Param("node_id") - fmt.Println(node_id) + logger.Log.Debug("Node info from Request", zap.String("NodeID", string(node_id))) req := &proto.Request{NodeID: string(node_id)} if response, err := client.Describe(ctx, req); err == nil { ctx.Data(http.StatusOK, gin.MIMEJSON, response.Res) @@ -195,7 +199,7 @@ func updateNodeHWStatus(ctx *gin.Context) { } -func Serve() { +func Serve() *gin.Engine { logger.Log.Info("Serve()") r := gin.Default() @@ -214,10 +218,13 @@ func Serve() { node.DELETE("/:node_id", deleteNode) node.POST("/deploy/:node_id", deployNode) } + return r +} - if err := r.Run(":8080"); err != nil { - logger.Log.Fatal("Failed to Run Server", zap.Error(err)) - //log.Fatalf("Failed to Run server: %v ", err) - } +func api() { + if err := Serve().Run(":8080"); err != nil { + logger.Log.Fatal("Failed to Run Server", zap.Error(err)) + //log.Fatalf("Failed to Run server: %v ", err) + } } diff --git a/pkg/apis/main_test.go b/pkg/apis/main_test.go new file mode 100644 index 0000000..c2233cf --- /dev/null +++ b/pkg/apis/main_test.go @@ -0,0 +1,335 @@ +package api + +import ( +// "github.com/manojkva/metamorph-plugin/pkg/logger" +// "github.com/bm-metamorph/MetaMorph/proto" + "fmt" +// "github.com/gin-contrib/zap" +// "github.com/gin-gonic/gin" +// "go.uber.org/zap" +// "google.golang.org/grpc" +// "net/http" +// "time" +// "os" + "io/ioutil" + "encoding/json" + "testing" + "reflect" + "strings" + "time" + "net" +// "bytes" + "github.com/manojkva/metamorph-plugin/pkg/config" +// "github.com/gin-gonic/gin" + "net/http" +// ctrlgRPCServer "github.com/bm-metamorph/MetaMorph/pkg/controller/grpc" + "github.com/stretchr/testify/require" + "net/http/httptest" + "github.com/stretchr/testify/assert" +) + +var w http.ResponseWriter +var node_id string +func init(){ + + config.SetLoggerConfig("logger.apipath") + port := "4040" + ln, err := net.Listen("tcp", ":" + port) + ln.Close() + if err != nil { + fmt.Println("Controller already running on port ",port,":", err) + } else { + fmt.Println("running controller on port ", port) + go run_controller() + time.Sleep(5 * time.Second) + } +} + +func TestGrpcClient(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + client,conn:= grpcClient() + fmt.Println(reflect.TypeOf(client)) + fmt.Println(reflect.TypeOf(conn)) + if client==nil { + t.Error("got",client, "want", "proto.NodeServiceClient") + if conn==nil{ + t.Error("got",conn , "want", "*grpc.ClientConn") + } + } + +} + +func TestCreateNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/node/", r) + router.ServeHTTP(w, req) + type content struct { + Node_id string `json:"result"` + } + + var resp content + i:=w.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp.Node_id) + + assert.Equal(t, 200, w.Code) +} + +/* func TestGetUUID(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/uuid", r) + router.ServeHTTP(w, req) + + fmt.Println(w.Body.String()) + assert.Equal(t, 200, w.Code) +}*/ + +func TestDescribeNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") +// Creating a node and getting its node_id + + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/node/", r) + router.ServeHTTP(w, req) + type content struct { + Node_id string `json:"result"` + } + + var resp content + i:=w.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp.Node_id) + +// end of node creation + ts := httptest.NewServer(Serve()) + defer ts.Close() + res, err := http.Get(fmt.Sprintf("%s/node/%s", ts.URL,resp.Node_id)) + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if res.StatusCode != 200 { + t.Fatalf("Expected status code 200, got %v", res.StatusCode) + } +} + +func TestDeployNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") +// Creating a node and getting its node_id + + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/node/", r) + router.ServeHTTP(w, req) + type content struct { + Node_id string `json:"result"` + } + + var resp content + i:=w.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp.Node_id) + +// end of node creation + w = httptest.NewRecorder() + req, _ = http.NewRequest("POST", fmt.Sprintf("/node/deploy/%s", resp.Node_id), nil) + router.ServeHTTP(w, req) + assert.Equal(t, 200, w.Code) + +} + +func TestDeleteNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") +// Creating a node and getting its node_id + + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/node/", r) + router.ServeHTTP(w, req) + type content struct { + Node_id string `json:"result"` + } + + var resp content + i:=w.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp.Node_id) + +// end of node creation +// Deleting the node + ts := httptest.NewServer(Serve()) + defer ts.Close() + + client := &http.Client{} + + // Create request + request, err := http.NewRequest("DELETE", fmt.Sprintf("%s/node/%s", ts.URL,resp.Node_id), nil) + if err != nil { + fmt.Println(err) + return + } + + // Fetch Request + response, err := client.Do(request) + if err != nil { + fmt.Println(err) + return + } + defer response.Body.Close() + + // Read Response Body + respBody, err := ioutil.ReadAll(response.Body) + if err != nil { + fmt.Println(err) + return + } + + // Display Results + fmt.Println("response Status : ", response.Status) + fmt.Println("response Headers : ", response.Header) + fmt.Println("response Body : ", string(respBody)) + if response.StatusCode != 200 { + t.Fatalf("Expected status code 200, got %v", response.StatusCode) + } + + +} + +func TestUpdateNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") +// Creating a node and getting its node_id + + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + router := Serve() + w := httptest.NewRecorder() + + req, _ := http.NewRequest("POST", "/node/", r) + router.ServeHTTP(w, req) + type content struct { + Node_id string `json:"result"` + } + + var resp content + i:=w.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp.Node_id) + +// end of node creation +// Updating the node + ts := httptest.NewServer(Serve()) + defer ts.Close() + + client := &http.Client{} + id := fmt.Sprintf("%s",resp.Node_id) + + // Create request + //r = strings.NewReader(" { \"AllowFirwareUpgrade\": true,\"NodeID\": id }") + //r = strings.NewReader(fmt.Sprintf("{\"AllowFirwareUpgrade\": true,\"NodeID\": %s}",id)) + update_req := fmt.Sprintf("{\"AllowFirwareUpgrade\": true, \"NodeID\": \"%s\"}",id) + fmt.Println(update_req) + r = strings.NewReader(update_req) + router = Serve() + request, err := http.NewRequest("PUT", fmt.Sprintf("%s/node/%s", ts.URL,resp.Node_id), r) + if err != nil { + fmt.Println(err) + return + } + + // Fetch Request + response, err := client.Do(request) + if err != nil { + fmt.Println(err) + return + } + defer response.Body.Close() + + // Read Response Body + respBody, err := ioutil.ReadAll(response.Body) + if err != nil { + fmt.Println(err) + return + } + + // Display Results + fmt.Println("response Status : ", response.Status) + fmt.Println("response Headers : ", response.Header) + fmt.Println("response Body : ", string(respBody)) + if response.StatusCode != 200 { + t.Fatalf("Expected status code 200, got %v", response.StatusCode) + } + + +} +//func TestGetNodeHWStatus(t *testing.T) { +// config.SetLoggerConfig("logger.apipath") +// ts := httptest.NewServer(Serve()) +// defer ts.Close() +// resp, err := http.Get(fmt.Sprintf("%s/hwstatus/8c3a4c25-b352-4b82-9347-84257a3e5341", ts.URL)) + // if err != nil { + // t.Fatalf("Expected no error, got %v", err) + //} + + //if resp.StatusCode != 200 { + // t.Fatalf("Expected status code 200, got %v", resp.StatusCode) + // } +//} + +func TestListNodes(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + ts := httptest.NewServer(Serve()) + defer ts.Close() + fmt.Println(ts.URL) + res, err := http.Get(fmt.Sprintf("%s/nodes", ts.URL)) + fmt.Printf("%T",res) + fmt.Println("####List of nodes####") + fmt.Println(*res) + /* + fmt.Printf("%T",res.Body) + type content struct { + Node_id string `json:"result"` + } + + var resp content + + i:=res.Body.String() + json.Unmarshal([]byte(i), &resp) + fmt.Println(resp) + */ + if err != nil { + t.Fatalf("Expected no error, got %v", err) + } + + if res.StatusCode != 200 { + t.Fatalf("Expected status code 200, got %v", res.StatusCode) + } + +} + +func TestServe(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + // Inject the StartServer method into a test server + ts := httptest.NewServer(Serve()) + defer ts.Close() + + // Make a request to your server with the {base url}/nodes + resp, err := http.Get(fmt.Sprintf("%s/nodes", ts.URL)) + require.NoError(t, err, "Error querying nodes") + assert.Equal(t, http.StatusOK, resp.StatusCode) +} + + diff --git a/pkg/controller/grpc/server.go b/pkg/controller/grpc/server.go index ba278fe..9d301b8 100644 --- a/pkg/controller/grpc/server.go +++ b/pkg/controller/grpc/server.go @@ -7,12 +7,14 @@ import ( "fmt" "net" - "bitbucket.com/metamorph/pkg/db/models/node" - "bitbucket.com/metamorph/pkg/drivers/redfish" - "bitbucket.com/metamorph/proto" + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" + "github.com/bm-metamorph/MetaMorph/pkg/plugin" + "github.com/bm-metamorph/MetaMorph/proto" "github.com/google/uuid" "google.golang.org/grpc" "google.golang.org/grpc/reflection" + "github.com/manojkva/metamorph-plugin/pkg/logger" + "go.uber.org/zap" ) type server struct{} @@ -20,9 +22,11 @@ type server struct{} type agent struct{} func Serve() { + logger.Log.Info("Serve()") listner, err := net.Listen("tcp", ":4040") if err != nil { + logger.Log.Error("Failed to receive network connection for GRPC server ", zap.Error(err)) panic(err) } @@ -32,6 +36,7 @@ func Serve() { reflection.Register(srv) if e := srv.Serve(listner); e != nil { + logger.Log.Error("Failed to start GRPC Server", zap.Error(e)) panic(e) } @@ -39,9 +44,12 @@ func Serve() { func (a *agent) GetTasks(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("GetTasks()") + nodeId := request.GetNodeID() bootactions, err := node.GetBootActions(nodeId) if err != nil { + logger.Log.Error("Failed GetBootActions() from DB", zap.Error(err)) return &proto.Response{Res: nil}, err } return &proto.Response{Res: bootactions}, nil @@ -49,25 +57,30 @@ func (a *agent) GetTasks(ctx context.Context, request *proto.Request) (*proto.Re } func (a *agent) UpdateTaskStatus(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("UpdateTaskStatus()") data := request.GetTask() var task node.BootAction err := json.Unmarshal(data, &task) if err != nil { + logger.Log.Error("Failed to UnMarshal json", zap.Error(err)) fmt.Println(err) } err = node.UpdateTaskStatus(&task) if err != nil { + logger.Log.Error("Failed to update Task to DB", zap.Error(err)) return &proto.Response{Res: nil}, err } return &proto.Response{Result: "Task status updated"}, nil } func (s *agent) UpdateNodeState(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("UpdateNodeState()") nodeId := request.GetNodeID() state := request.GetNodeState() result, err := node.Describe(nodeId) if err != nil { + logger.Log.Error("Failed to retrieve node info ", zap.String("NodeId",nodeId), zap.Error(err)) return &proto.Response{Res: nil}, err } var Node node.Node @@ -75,15 +88,18 @@ func (s *agent) UpdateNodeState(ctx context.Context, request *proto.Request) (*p Node.State = state err = node.Update(&Node) if err != nil { + logger.Log.Error("Failed to update Node in DB", zap.Error(err)) return &proto.Response{Res: nil}, err } return &proto.Response{Result: "Node State Updated"}, nil } func (s *server) Describe(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("Describe()") nodeId := request.GetNodeID() result, err := node.Describe(nodeId) if err != nil { + logger.Log.Error("Failed to Describe Node", zap.String("NodeID", nodeId), zap.Error(err)) return &proto.Response{Res: nil}, err } return &proto.Response{Res: result}, nil @@ -91,6 +107,8 @@ func (s *server) Describe(ctx context.Context, request *proto.Request) (*proto.R func (s *server) Deploy(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("Deploy()") + nodeId := request.GetNodeID() fmt.Println(nodeId) result := nodeId @@ -99,24 +117,30 @@ func (s *server) Deploy(ctx context.Context, request *proto.Request) (*proto.Res func (s *server) Create(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("Create()") + NodeSpec := request.GetNodeSpec() fmt.Println(string(NodeSpec)) result := "Creating node" result, err := node.Create(NodeSpec) if err != nil { + logger.Log.Error("Failed to create Node", zap.Error(err)) return &proto.Response{Result: ""}, err } return &proto.Response{Result: result}, nil } func (s *server) Update(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("Update()") NodeSpec := request.GetNodeSpec() nodeId := request.GetNodeID() //fmt.Println(string(NodeSpec)) - fmt.Println(nodeId) + //fmt.Println(nodeId) + logger.Log.Debug("Node to be updated", zap.String("NodeID", nodeId)) var nodeUpdate node.Node err := json.Unmarshal(NodeSpec, &nodeUpdate) if err != nil { + logger.Log.Error("Update failed. Invalid JSON",zap.Error(err)) return &proto.Response{Result: "Update failed. Invalid JSON"}, err } @@ -126,17 +150,22 @@ func (s *server) Update(ctx context.Context, request *proto.Request) (*proto.Res if err == nil { return &proto.Response{Result: "Update successful"}, nil } + logger.Log.Error("Update failed", zap.Error(err)) return &proto.Response{Result: "Update failed"}, err } func (s *server) Delete(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("Delete()") + var result string nodeId := request.GetNodeID() - fmt.Println(nodeId) + //fmt.Println(nodeId) + logger.Log.Debug("Node to be deleted", zap.String("NodeID", nodeId)) err := node.Delete(nodeId) if err != nil { + logger.Log.Error("Failed to Delete Node",zap.String("NodeID", nodeId), zap.Error(err)) result = "Failed" } result = "Successful" @@ -145,6 +174,8 @@ func (s *server) Delete(ctx context.Context, request *proto.Request) (*proto.Res func (s *server) List(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("List()") + result := "List of nodes" return &proto.Response{Result: result}, nil @@ -152,80 +183,91 @@ func (s *server) List(ctx context.Context, request *proto.Request) (*proto.Respo func (s *server) GetNodeUUID(ctx context.Context, request *proto.Request) (*proto.Response, error) { - nodeInfo := new(struct { - IPMIIP string - IPMIUser string - IPMIPassword string - }) + logger.Log.Info("GetNodeUUID()") + + redfishClient := plugin.BMHNode{&node.Node{}} var result string data := request.GetNodeSpec() - err := json.Unmarshal(data, &nodeInfo) + err := json.Unmarshal(data, &redfishClient) if err == nil { - if uuid, _ := redfish.GetUUID(nodeInfo.IPMIIP, nodeInfo.IPMIUser, nodeInfo.IPMIPassword); uuid != "" { - result = uuid + if uuid, _ := redfishClient.DispenseClientRequest("getguuid"); uuid.(string) != "" { + result = uuid.(string) } else { - err = errors.New(fmt.Sprintf("Failed to retrieve UUID from node for IPMI IP : %v", nodeInfo.IPMIIP)) + errString := fmt.Sprintf("Failed to retrieve UUID from node for IPMI IP : %v", redfishClient.IPMIIP) + logger.Log.Error(errString) + err = errors.New(errString) + err = errors.New(fmt.Sprintf("Failed to retrieve UUID from node for IPMI IP : %v", redfishClient.IPMIIP)) result = "" } + }else { + logger.Log.Error("Failed to decode JSON object", zap.Error(err)) } + return &proto.Response{Result: result}, err } func (s *server) GetHWStatus(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("GetHWStatus()") var result string = "Off" nodeId := request.GetNodeID() data, err := node.Describe(nodeId) if err != nil { + logger.Log.Error("Failed to retrieve node info", zap.String("NodeID", nodeId),zap.Error(err)) return &proto.Response{Result: result}, err } var node node.Node err = json.Unmarshal(data, &node) if err != nil { + logger.Log.Error("Failed to decode JSON object", zap.Error(err)) return &proto.Response{Result: result}, err } - redfishClient := &redfish.BMHNode{&node} - status := redfishClient.GetPowerStatus() - if status == true { - result = "On" - } + redfishClient := &plugin.BMHNode{&node} + status, err := redfishClient.DispenseClientRequest("getpowerstatus") + + result = status.(string) return &proto.Response{Result: result}, nil } func (s *server) UpdateHWStatus(ctx context.Context, request *proto.Request) (*proto.Response, error) { + logger.Log.Info("UpdateHWStatus()") data := request.GetNodeSpec() nodeId := request.GetNodeID() nodeInfoBytes, err := node.Describe(nodeId) if err != nil { + logger.Log.Error("Failed to retrieve node info", zap.String("NodeID", nodeId),zap.Error(err)) return &proto.Response{Result: ""}, err } var node node.Node - var status bool err = json.Unmarshal(nodeInfoBytes, &node) if err != nil { + logger.Log.Error("Failed to decode JSON object", zap.Error(err)) return &proto.Response{Result: ""}, err } - redfishClient := &redfish.BMHNode{&node} + redfishClient := &plugin.BMHNode{&node} hwInfo := new(struct { PowerState string }) err = json.Unmarshal(data, &hwInfo) if err != nil { + logger.Log.Error("Failed to decode JSON object", zap.Error(err)) return &proto.Response{Result: ""}, err } if hwInfo.PowerState == "On" { - status = redfishClient.PowerOn() - if status == false { - return &proto.Response{Result: ""}, errors.New("Failed to Power On") + _, err = redfishClient.DispenseClientRequest("poweron") + if err != nil { + logger.Log.Error("PowerOn() API failed", zap.Error(err)) + return &proto.Response{Result: ""},err } } else if hwInfo.PowerState == "Off" { - status = redfishClient.PowerOff() - if status == false { - return &proto.Response{Result: ""}, errors.New("Failed to Power Off") + _, err = redfishClient.DispenseClientRequest("poweroff") + if err != nil { + logger.Log.Error("PowerOff() API failed", zap.Error(err)) + return &proto.Response{Result: ""}, err } } diff --git a/pkg/controller/grpc/server_test.go b/pkg/controller/grpc/server_test.go new file mode 100644 index 0000000..e8d1d08 --- /dev/null +++ b/pkg/controller/grpc/server_test.go @@ -0,0 +1,166 @@ +package controller + +import ( + "github.com/manojkva/metamorph-plugin/pkg/logger" + "github.com/bm-metamorph/MetaMorph/proto" + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" + "github.com/google/uuid" +// "google.golang.org/grpc/reflection" + "fmt" + // "github.com/gin-contrib/zap" +// "github.com/gin-gonic/gin" + "go.uber.org/zap" + "google.golang.org/grpc" +// "net" +// "time" + "os" + "encoding/json" + "testing" +// "reflect" + "strings" +// "bytes" + "github.com/manojkva/metamorph-plugin/pkg/config" +// "github.com/gin-gonic/gin" + "net/http" + // "github.com/stretchr/testify/require" + // "net/http/httptest" + // "github.com/stretchr/testify/assert" + "github.com/gin-gonic/gin" +) + + +func TestServe(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + go Serve() + grpcServer := "localhost" + if gs := os.Getenv("METMORPH_CONTROLLER_HOST"); gs != "" { + grpcServer = gs + } + + logger.Log.Info("grpcClient()") + conn, err := grpc.Dial(fmt.Sprintf("%s:4040", grpcServer), grpc.WithInsecure()) + if err != nil { + logger.Log.Error("Failed to connect to GRPC server", zap.Error(err)) + panic(err) + } + client := proto.NewNodeServiceClient(conn) + fmt.Println(client, conn) + +} + + +func TestCreate(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + req, _ := http.NewRequest("POST", "/node/", r) + ctx := &gin.Context{ + Request: req, + + } + data, _ := ctx.GetRawData() + request := &proto.Request{NodeSpec: data} + NodeSpec := request.GetNodeSpec() + fmt.Println(string(NodeSpec)) + result, err := node.Create(NodeSpec) + fmt.Println("printint uuid") + fmt.Println(result,err) + + + +} +func TestDescribe(t *testing.T) { + //Creating node + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + req, _ := http.NewRequest("POST", "/node/", r) + ctx := &gin.Context{ + Request: req, + + } + data, _ := ctx.GetRawData() + request := &proto.Request{NodeSpec: data} + NodeSpec := request.GetNodeSpec() + fmt.Println(string(NodeSpec)) + nodeId, err := node.Create(NodeSpec) + fmt.Println("printint uuid") + fmt.Println(nodeId,err) + + //Describing node + result, err := node.Describe(nodeId) + fmt.Println("Describing node") + fmt.Println(result,err) + +} + + +func TestDelete(t *testing.T) { + //Creating node + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + req, _ := http.NewRequest("POST", "/node/", r) + ctx := &gin.Context{ + Request: req, + + } + data, _ := ctx.GetRawData() + request := &proto.Request{NodeSpec: data} + NodeSpec := request.GetNodeSpec() + fmt.Println(string(NodeSpec)) + nodeId, err := node.Create(NodeSpec) + fmt.Println("printint uuid") + fmt.Println(nodeId,err) + + //Describing node + err = node.Delete(nodeId) + fmt.Println("Deleting node") + fmt.Println(err) + +} + +func TestUpdate(t *testing.T) { + //Creating node + config.SetLoggerConfig("logger.apipath") + r := strings.NewReader(" { \"AllowFirwareUpgrade\": false }") + req, _ := http.NewRequest("POST", "/node/", r) + ctx := &gin.Context{ + Request: req, + + } + data, _ := ctx.GetRawData() + request := &proto.Request{NodeSpec: data} + NodeSpec := request.GetNodeSpec() + fmt.Println(string(NodeSpec)) + nodeId, err := node.Create(NodeSpec) + fmt.Println("printint uuid") + fmt.Println(nodeId,err) + + //Describing node + + update_req := fmt.Sprintf("{\"AllowFirwareUpgrade\": true, \"NodeID\": \"%s\"}",nodeId) + r = strings.NewReader(update_req) + request_new, _ := http.NewRequest("PUT", fmt.Sprintf("/node/%s/", nodeId), r) + ctx = &gin.Context{ + Request: request_new, + + } + data, _ = ctx.GetRawData() + request = &proto.Request{NodeSpec: data} + NodeSpec = request.GetNodeSpec() + + + var nodeUpdate node.Node + err = json.Unmarshal(NodeSpec, &nodeUpdate) + if err != nil { + fmt.Println("Update failed. Invalid JSON 1") + + } + fmt.Printf("%+v", nodeUpdate) + nodeUpdate.NodeUUID, err = uuid.Parse(nodeId) + + err = node.Update(&nodeUpdate) + if err == nil { + fmt.Println("Update Successful") + } + +} + diff --git a/pkg/controller/statemachine/metamorph-statemachine.go b/pkg/controller/statemachine/metamorph-statemachine.go index f9362af..e8fe663 100644 --- a/pkg/controller/statemachine/metamorph-statemachine.go +++ b/pkg/controller/statemachine/metamorph-statemachine.go @@ -6,10 +6,9 @@ import ( "sync" "time" - "bitbucket.com/metamorph/pkg/db/models/node" - "bitbucket.com/metamorph/pkg/drivers/redfish" - "bitbucket.com/metamorph/pkg/logger" - "bitbucket.com/metamorph/pkg/util/isogen" + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" + "github.com/manojkva/metamorph-plugin/pkg/logger" + "github.com/bm-metamorph/MetaMorph/pkg/plugin" "github.com/google/uuid" "go.uber.org/zap" ) @@ -49,7 +48,7 @@ type NodeStatus struct { } func StartMetamorphFSM(runOnce bool) { - logger.Log.Info("Starting Metamorph FSM") + logger.Log.Info("StartMetamorphFSM()") dbHandler := new(DBHandler) dbHandler.startFSM(runOnce) @@ -83,7 +82,7 @@ func (h *DBHandler) startFSM(runOnce bool) { for _, bmnode := range nodelist { // What about nodes that are already in transistions.. should there be a transition state. - fmt.Printf("[%v] - Starting Processing\n", bmnode.Name) + logger.Log.Debug(fmt.Sprintf("[%v] - Starting Processing\n", bmnode.Name)) requestsChan <- BMNode{&bmnode} } @@ -153,46 +152,70 @@ func serviceRequest(requestsChan chan BMNode, nodeStatusChan chan<- NodeStatus, func ReadystateHandler(bmnode BMNode, nodeStatusChan chan<- NodeStatus, wg *sync.WaitGroup) { var err error var state string + var nodestatus NodeStatus + var node_uuid uuid.UUID // to be removed once UUID = Server UUID is planned. + var nodeuuidStringFromServer string + var redfishManagerID string + var redfishSystemID string + var redfishVersion string + var maphwInventory map[string]string + var hwInventory interface{} + redfishClient := &plugin.BMHNode{bmnode.Node} logger.Log.Info("ReadystateHandler()", zap.String("Node Name", bmnode.Name), zap.String("Node UUID", bmnode.NodeUUID.String())) //Update the DB Now - err = node.Update(&node.Node{State: INTRANSITION}) - var nodestatus NodeStatus + err = node.Update(&node.Node{State: INTRANSITION, NodeUUID: bmnode.NodeUUID}) + + if err != nil { + logger.Log.Error(" Failed to update DB.Setting Node to FAILED State", zap.String("Node Name", bmnode.Name)) + goto End + + } + // Check if we could extract UUID from the Node using Redfish //This call should satisfy the following requirements // - Network Connectivity // - working credentials // - Redfish API availability(though ver is not compared yet) - var node_uuid uuid.UUID // to be removed once UUID = Server UUID is planned. - redfishClient := &redfish.BMHNode{bmnode.Node} - redfishManagerID := redfishClient.GetManagerID() - redfishSystemID := redfishClient.GetSystemID() - redfishVersion := redfishClient.GetRedfishVersion() + err = redfishClient.ReadConfigFile() + if err != nil{ + logger.Log.Error("Failed to Read Plugin info from configuration file. Setting Node to FAILED State", zap.String("NodeName", bmnode.Name)) + goto End + } + hwInventory, err = redfishClient.DispenseClientRequest("gethwinventory") + maphwInventory = hwInventory.(map[string]string) - var res bool = false - var nodeuuidStringFromServer string - if redfishSystemID != "" { - nodeuuidStringFromServer, res = redfish.GetUUID(bmnode.Node.IPMIIP, bmnode.IPMIUser, bmnode.IPMIPassword) + if err != nil { + logger.Log.Error("Failed to retrieve HW Inventory using Redfish Protocol Setting Node to FAILED State", zap.String("IPMIIP", bmnode.Node.IPMIIP), zap.String("IPMIUser", bmnode.IPMIUser)) + goto End } - if res == true { - node_uuid, err = uuid.Parse(nodeuuidStringFromServer) + + redfishManagerID = maphwInventory["RedfishManagerID"] + redfishSystemID = maphwInventory["RedfishSystemID"] + redfishVersion = maphwInventory["RedfishVersion"] + + if redfishSystemID != "" { + uuidasIntf, err := redfishClient.DispenseClientRequest("getguuid") + if err != nil { + logger.Log.Error("Failed to retrieve Node GUUID using Redfish Protocol Setting Node to FAILED State", zap.String("IPMIIP", bmnode.Node.IPMIIP), zap.String("IPMIUser", bmnode.IPMIUser)) + goto End + } + nodeuuidStringFromServer = string(uuidasIntf.([]byte)) } - node_uuid = bmnode.NodeUUID // to be removed once UUID = Server UUID is planned. + node_uuid, err = uuid.Parse(nodeuuidStringFromServer) - if (err != nil) || (res == false) || (redfishSystemID == "") || (redfishManagerID == "") || (redfishVersion == "") { - logger.Log.Error("Failed to connect to Node using Redfish Protocol or Failed to update DB.Setting Node to FAILED State", zap.String("IPMIIP", bmnode.Node.IPMIIP), zap.String("IPMIUser", bmnode.IPMIUser)) - nodestatus = NodeStatus{NodeUUID: node_uuid, Status: false} - state = FAILED - } else { + node_uuid = bmnode.NodeUUID // to be removed once UUID = Server UUID is planned. - state = READYWAIT - nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} - } + state = READYWAIT + nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} //Update the DB Now err = node.Update(&node.Node{State: state, NodeUUID: node_uuid, RedfishManagerID: redfishManagerID, RedfishSystemID: redfishSystemID, RedfishVersion: redfishVersion}) + +End: if err != nil { logger.Log.Error("Failed to update Node to READYWAIT state", zap.String("Node Name", bmnode.Name)) + nodestatus = NodeStatus{NodeUUID: node_uuid, Status: false} state = FAILED node.Update(&node.Node{State: state, NodeUUID: node_uuid}) } @@ -205,40 +228,47 @@ func SetupreadyHandler(bmnode BMNode, nodeStatusChan chan<- NodeStatus, wg *sync logger.Log.Info("SetupreadyHandler()", zap.String("Node Name", bmnode.Name)) var nodestatus NodeStatus var err error + var state string + isogenClient := &plugin.BMHNode{bmnode.Node} //Update the DB Now nodeuuidString := bmnode.Node.NodeUUID.String() - err = node.Update(&node.Node{State: INTRANSITION}) + err = node.Update(&node.Node{State: INTRANSITION, NodeUUID: bmnode.NodeUUID}) + if err != nil { + goto End + } fmt.Println(nodeuuidString) //check for firmware upgrade - var res bool = true if bmnode.AllowFirmwareUpgrade { - redfishClient := &redfish.BMHNode{bmnode.Node} - res = redfishClient.UpgradeFirmwareList() + redfishClient := &plugin.BMHNode{bmnode.Node} + _, err = redfishClient.DispenseClientRequest("updatefirmware") + if err != nil { + logger.Log.Error("Failed to upgrade Firmware", zap.String("Node Name", bmnode.Name)) + goto End + } } - isogenClient := &isogen.BMHNode{bmnode.Node} //Create iSO - err = isogenClient.PrepareISO() + _, err = isogenClient.DispenseClientRequest("createiso") - var state string - if (err != nil) || (res == false) { - logger.Log.Error("Failed to create ISO file or Failed to upgrade Firmware", zap.String("Node Name", bmnode.Name)) - nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: false} - state = FAILED + if err != nil { + logger.Log.Error("Failed to create ISO file ", zap.String("Node Name", bmnode.Name)) + goto End + } - } else { - nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} - state = SETUPREADYWAIT - } - err = node.Update(&node.Node{State: state}) + nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} + state = SETUPREADYWAIT + + err = node.Update(&node.Node{State: state, NodeUUID: bmnode.NodeUUID}) +End: if err != nil { logger.Log.Error("Failed to update to SETUPREADYWAIT state", zap.String("Node Name", bmnode.Name)) + nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: false} state = FAILED - node.Update(&node.Node{State: state}) + node.Update(&node.Node{State: state, NodeUUID: bmnode.NodeUUID}) } nodeStatusChan <- nodestatus wg.Done() @@ -246,30 +276,35 @@ func SetupreadyHandler(bmnode BMNode, nodeStatusChan chan<- NodeStatus, wg *sync func DeployedHandler(bmnode BMNode, nodeStatusChan chan<- NodeStatus, wg *sync.WaitGroup) { logger.Log.Info("DeployedHandler()", zap.String("Node Name", bmnode.Name)) var nodestatus NodeStatus - var result bool + var state string + redfishClient := &plugin.BMHNode{bmnode.Node} //Update the DB Now - err := node.Update(&node.Node{State: INTRANSITION}) - fmt.Println(bmnode.NodeUUID) - redfishClient := &redfish.BMHNode{bmnode.Node} + err := node.Update(&node.Node{State: INTRANSITION, NodeUUID: bmnode.NodeUUID}) - result = redfishClient.DeployISO() + if err != nil { + logger.Log.Error("Failed to update to TRANSITION state", zap.String("Node Name", bmnode.Name)) + goto End + } - var state string + fmt.Println(bmnode.NodeUUID) - if (result == false) || (err != nil) { - logger.Log.Error("Failed to Deply ISO", zap.String("Node Name", bmnode.Name)) - nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: false} - state = FAILED - } else { + _, err = redfishClient.DispenseClientRequest("deployiso") - nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} - state = DEPLOYING + if err != nil { + logger.Log.Error("Failed to Deply ISO", zap.String("Node Name", bmnode.Name)) + goto End } - err = node.Update(&node.Node{State: state}) + + + nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: true} + state = DEPLOYING + err = node.Update(&node.Node{State: state, NodeUUID: bmnode.NodeUUID}) +End: if err != nil { logger.Log.Error("Failed to update to DEPLOYING state", zap.String("Node Name", bmnode.Name)) + nodestatus = NodeStatus{NodeUUID: bmnode.NodeUUID, Status: false} state = FAILED - node.Update(&node.Node{State: state}) + node.Update(&node.Node{State: state, NodeUUID: bmnode.NodeUUID}) } nodeStatusChan <- nodestatus wg.Done() diff --git a/pkg/controller/statemachine/metamorph-statemachine_test.go b/pkg/controller/statemachine/metamorph-statemachine_test.go index 999ed29..596f373 100644 --- a/pkg/controller/statemachine/metamorph-statemachine_test.go +++ b/pkg/controller/statemachine/metamorph-statemachine_test.go @@ -1,7 +1,7 @@ package controller import ( - "bitbucket.com/metamorph/pkg/db/models/node" + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" "fmt" "github.com/google/uuid" // "github.com/stretchr/testify/assert" diff --git a/pkg/controller/statemachine/mock_nodedb.go b/pkg/controller/statemachine/mock_nodedb.go index 36719e2..22e6e66 100644 --- a/pkg/controller/statemachine/mock_nodedb.go +++ b/pkg/controller/statemachine/mock_nodedb.go @@ -3,7 +3,7 @@ package controller import ( - node "bitbucket.com/metamorph/pkg/db/models/node" + node "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" mock "github.com/stretchr/testify/mock" ) diff --git a/pkg/db/models/node/node_type.go b/pkg/db/models/node/node_type.go index c2b56c1..0d1c736 100644 --- a/pkg/db/models/node/node_type.go +++ b/pkg/db/models/node/node_type.go @@ -1,133 +1,147 @@ package node import ( - _ "github.com/jinzhu/gorm/dialects/sqlite" - "github.com/jinzhu/gorm" "github.com/google/uuid" + "github.com/jinzhu/gorm" + _ "github.com/jinzhu/gorm/dialects/sqlite" ) - type Node struct { gorm.Model - NodeUUID uuid.UUID - Name string - ISOURL string - ISOChecksum string - ImageURL string - ChecksumURL string - DisableCertVerification bool - ImageReadilyAvailable bool - OamIP string - OamGateway string - NameServers []NameServer `json:"NameServers"` - OsDisk string - Partitions []Partition - GrubConfig string - KvmPolicy KvmPolicy - SSHPubKeys []SSHPubKey - BondInterfaces []BondInterface - BondParameters []BondParameter - IPMIIP string - IPMIUser string - IPMIPassword string - Vendor string - ServerModel string - VirtualDisks []VirtualDisk - State string `gorm:"DEFAULT:new"` - ProvisioningIP string - ProvisionerPort int - HTTPPort int - BootActions []BootAction - NetworkConfig string - RAID_reset bool - RedfishManagerID string - RedfishSystemID string - RedfishVersion string - Domain string - Firmwares []Firmware - AllowFirmwareUpgrade bool - } + NodeUUID uuid.UUID + Name string + ISOURL string + ISOChecksum string + ImageURL string + ChecksumURL string + DisableCertVerification bool + ImageReadilyAvailable bool + OamIP string + OamGateway string + NameServers []NameServer `json:"NameServers"` + OsDisk string + Partitions []Partition + GrubConfig string + KvmPolicy KvmPolicy + SSHPubKeys []SSHPubKey + BondInterfaces []BondInterface + BondParameters []BondParameter + IPMIIP string + IPMIUser string + IPMIPassword string + Vendor string + ServerModel string + VirtualDisks []VirtualDisk + State string `gorm:"DEFAULT:new"` + ProvisioningIP string + ProvisionerPort int + HTTPPort int + BootActions []BootAction + NetworkConfig string + RAID_reset bool + RedfishManagerID string + RedfishSystemID string + RedfishVersion string + Domain string + Firmwares []Firmware + AllowFirmwareUpgrade bool + Plugins Plugins + CloudInit string +} - type Firmware struct{ - gorm.Model - NodeID uint - Name string - Version string - URL string - } - type BootAction struct { - gorm.Model - NodeID uint - Name string - Location string - Priority uint - Control string - Args string - Status string `gorm:"DEFAULT:new"` - } +type Plugins struct { + gorm.Model + NodeID uint + APIs []API +} - type NameServer struct { +type API struct { gorm.Model - NodeID uint - NameServer string `json:"NameServer"` - } - - type Partition struct { + PluginsID uint + Name string + Plugin string +} + +type Firmware struct { gorm.Model - NodeID uint - Name string - Size string - Bootable bool - Primary bool - Filesystem Filesystem - } - - type Filesystem struct { + NodeID uint + Name string + Version string + URL string +} +type BootAction struct { gorm.Model - PartitionID uint - Mountpoint string - Fstype string - MountOptions string - } - - type KvmPolicy struct { + NodeID uint + Name string + Location string + Priority uint + Control string + Args string + Status string `gorm:"DEFAULT:new"` +} + +type NameServer struct { gorm.Model - NodeID uint - CpuAllocation string - CpuPinning string - CpuHyperthreading string - } - - type SSHPubKey struct { + NodeID uint + NameServer string `json:"NameServer"` +} + +type Partition struct { gorm.Model NodeID uint - SSHPubKey string - } - - type BondInterface struct { + Name string + Size string + Bootable bool + Primary bool + Filesystem Filesystem +} + +type Filesystem struct { gorm.Model - NodeID uint - BondInterface string - } - - type BondParameter struct { + PartitionID uint + Mountpoint string + Fstype string + MountOptions string +} + +type KvmPolicy struct { gorm.Model - NodeID uint - Key string - Value string - } - - type VirtualDisk struct { + NodeID uint + CpuAllocation string + CpuPinning string + CpuHyperthreading string +} + +type SSHPubKey struct { + gorm.Model + NodeID uint + SSHPubKey string +} + +type BondInterface struct { + gorm.Model + NodeID uint + BondInterface string +} + +type BondParameter struct { + gorm.Model + NodeID uint + Key string + Value string +} + +type VirtualDisk struct { gorm.Model NodeID uint DiskName string - RaidType int + RaidType int RaidController string - PhysicalDisks []PhysicalDisk - } - - type PhysicalDisk struct { + PhysicalDisks []PhysicalDisk +} + +type PhysicalDisk struct { gorm.Model VirtualDiskID uint PhysicalDisk string - } +} diff --git a/pkg/db/models/node/nodes.go b/pkg/db/models/node/nodes.go index 02fe441..7ebaf89 100644 --- a/pkg/db/models/node/nodes.go +++ b/pkg/db/models/node/nodes.go @@ -5,12 +5,15 @@ import ( "errors" "fmt" "io/ioutil" + "strings" - "bitbucket.com/metamorph/pkg/config" -// "bitbucket.com/metamorph/pkg/drivers/redfish" + "github.com/manojkva/metamorph-plugin/pkg/config" + "github.com/manojkva/metamorph-plugin/pkg/logger" + // "github.com/bm-metamorph/MetaMorph/pkg/drivers/redfish" "github.com/google/uuid" "github.com/jinzhu/gorm" _ "github.com/jinzhu/gorm/dialects/sqlite" + "go.uber.org/zap" ) func getDB() *gorm.DB { @@ -32,6 +35,8 @@ func getDB() *gorm.DB { &PhysicalDisk{}, &BootAction{}, &Firmware{}, + &Plugins{}, + &API{}, ) return db } @@ -41,6 +46,8 @@ func getDB() *gorm.DB { //About node. Including Credentials func GetNodes() ([]Node, error) { + logger.Log.Info("GetNodes()") + nodes := []Node{} db := getDB() defer db.Close() @@ -49,11 +56,13 @@ func GetNodes() ([]Node, error) { if len(nodes) > 0 { return nodes, nil } else { + logger.Log.Error("Nodes not found") return nil, errors.New("Nodes not found") } } func GetNameServers(node_uuid string) ([]NameServer, error) { + logger.Log.Info("GetNameServers()") node := Node{} nameservers := []NameServer{} db := getDB() @@ -64,11 +73,13 @@ func GetNameServers(node_uuid string) ([]NameServer, error) { if len(nameservers) > 0 { return nameservers, nil } else { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } } func GetPartitions(node_uuid string) ([]Partition, error) { + logger.Log.Info("GetPartitions()") node := Node{} partitions := []Partition{} db := getDB() @@ -79,11 +90,13 @@ func GetPartitions(node_uuid string) ([]Partition, error) { if len(partitions) > 0 { return partitions, nil } else { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } } func GetSSHPubKeys(node_uuid string) ([]SSHPubKey, error) { + logger.Log.Info("GetSSHPubKeys()") node := Node{} sshPubKeys := []SSHPubKey{} db := getDB() @@ -94,11 +107,13 @@ func GetSSHPubKeys(node_uuid string) ([]SSHPubKey, error) { if len(sshPubKeys) > 0 { return sshPubKeys, nil } else { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } } func GetBondInterfaces(node_uuid string) ([]BondInterface, error) { + logger.Log.Info("GetBondInterfaces()") node := Node{} bondInterfaces := []BondInterface{} db := getDB() @@ -109,10 +124,12 @@ func GetBondInterfaces(node_uuid string) ([]BondInterface, error) { if len(bondInterfaces) > 0 { return bondInterfaces, nil } else { + logger.Log.Error("No record found") return nil, errors.New("No record Found") } } func GetFirmwares(node_uuid string) ([]Firmware, error) { + logger.Log.Info("GetFirmwares()") node := Node{} firmwares := []Firmware{} db := getDB() @@ -123,13 +140,62 @@ func GetFirmwares(node_uuid string) ([]Firmware, error) { if len(firmwares) > 0 { return firmwares, nil } else { + logger.Log.Error("No record found") return nil, errors.New("No record Found") } } +//Plugins + +func GetPlugins(node_uuid string) (*Plugins, error) { + logger.Log.Info("GetPlugins()") + node := Node{} + plugins := Plugins{} + db := getDB() + defer db.Close() + node_uuid1, _ := uuid.Parse(node_uuid) + db.Where("node_uuid = ?", node_uuid1).First(&node) + err := db.Model(&node).Related(&plugins).Error + if err == nil { + return &plugins, err + } else { + logger.Log.Error("No record found", zap.Error(err)) + return nil, errors.New("No record Found") + } +} + +func GetPluginAPIs(pluginID uint) ([]API, error) { + logger.Log.Info("GetPluginAPIs()") + plugins := Plugins{} + apis := []API{} + db := getDB() + defer db.Close() + db.Where("id = ?", pluginID).First(&plugins) + db.Model(&plugins).Related(&apis) + if len(apis) > 0 { + return apis, nil + } else { + logger.Log.Error("No record found") + return nil, errors.New(" No record Found") + } + +} + +func GetPluginForAPI(apis []API, apiName string)string{ + logger.Log.Info("GetPluginForAPI()") + for _, api := range apis{ + if strings.ToLower(api.Name) == strings.ToLower(apiName) { + return api.Plugin + } + } + return "" + +} + //VirtualDisk func GetVirtualDisks(node_uuid string) ([]VirtualDisk, error) { + logger.Log.Info("GetVirtualDisks()") node := Node{} virtualdisks := []VirtualDisk{} db := getDB() @@ -140,29 +206,32 @@ func GetVirtualDisks(node_uuid string) ([]VirtualDisk, error) { if len(virtualdisks) > 0 { return virtualdisks, nil } else { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } } - func GetBootActions(node_uuid string) ([]byte, error) { + logger.Log.Info("GetBootActions()") node := Node{} bootactions := []BootAction{} db := getDB() defer db.Close() - node_uuid1,_ := uuid.Parse(node_uuid) + node_uuid1, _ := uuid.Parse(node_uuid) db.Where("node_uuid = ?", node_uuid1).First(&node) db.Model(&node).Where("status = ?", "new").Order("priority").Related(&bootactions) if len(bootactions) > 0 { res, _ := json.Marshal(bootactions) return res, nil } else { + logger.Log.Error("No record found") return nil, errors.New("No Record Found") } } func GetPhysicalDisks(virtualDiskID uint) ([]PhysicalDisk, error) { + logger.Log.Info("GetPhysicalDisks()") vdisk := VirtualDisk{} physcical_disks := []PhysicalDisk{} @@ -173,26 +242,30 @@ func GetPhysicalDisks(virtualDiskID uint) ([]PhysicalDisk, error) { if len(physcical_disks) > 0 { return physcical_disks, nil } else { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } } func GetBondParameters(node_uuid string) ([]BondParameter, error) { + logger.Log.Info("GetBondParameters") node := Node{} bondParameters := []BondParameter{} db := getDB() - defer db.Close() node_uuid1, _ := uuid.Parse(node_uuid) db.Where("node_uuid = ?", node_uuid1).First(&node) db.Model(&node).Related(&bondParameters) + defer db.Close() if len(bondParameters) == 0 { - return nil, errors.New(" No record Found") + logger.Log.Error("No record found") + return nil, errors.New(" No record Found") } else { - return bondParameters, nil + return bondParameters, nil } } func GetKvmPolicy(node_uuid string) (*KvmPolicy, error) { + logger.Log.Info("GetKvmPolicy()") node := Node{} kvmPolicy := KvmPolicy{} db := getDB() @@ -201,6 +274,7 @@ func GetKvmPolicy(node_uuid string) (*KvmPolicy, error) { db.Where("node_uuid = ?", node_uuid1).First(&node) db.Model(&node).Related(&kvmPolicy) if kvmPolicy == (KvmPolicy{}) { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } else { return &kvmPolicy, nil @@ -208,6 +282,7 @@ func GetKvmPolicy(node_uuid string) (*KvmPolicy, error) { } func GetFilesystem(partitionId uint) (*Filesystem, error) { + logger.Log.Info("GetFilesystem()") partition := Partition{} filesystem := Filesystem{} db := getDB() @@ -215,6 +290,7 @@ func GetFilesystem(partitionId uint) (*Filesystem, error) { db.Where("id = ?", partitionId).First(&partition) db.Model(&partition).Related(&filesystem) if filesystem == (Filesystem{}) { + logger.Log.Error("No record found") return nil, errors.New(" No record Found") } else { return &filesystem, nil @@ -222,6 +298,7 @@ func GetFilesystem(partitionId uint) (*Filesystem, error) { } func Describe(node_uuid string) ([]byte, error) { + logger.Log.Info("Describe()") node := Node{} db := getDB() defer db.Close() @@ -233,25 +310,26 @@ func Describe(node_uuid string) ([]byte, error) { res, _ := json.Marshal(node) return res, nil } else { + logger.Log.Error("Node not found", zap.String("NodeUUID",node_uuid)) return nil, errors.New("Node not found") } } -func Delete(node_uuid string) (error){ +func Delete(node_uuid string) error { + logger.Log.Info("Delete()") node := Node{} - db := getDB() + db := getDB() defer db.Close() node_uuid1, _ := uuid.Parse(node_uuid) err := db.Where("node_uuid = ?", node_uuid1).First(&node).Error - if err == nil{ + if err == nil { err = db.Delete(&node).Error } return err - - } + /* func Update(node *Node) error { db := getDB() @@ -271,7 +349,8 @@ func UpdateRaw(node_uuid string, data []byte )error{ } */ -func Update(updateNode *Node) error{ +func Update(updateNode *Node) error { + logger.Log.Info("Update()") node := Node{} db := getDB() defer db.Close() @@ -282,7 +361,8 @@ func Update(updateNode *Node) error{ } -func UpdateTaskStatus(task *BootAction) error{ +func UpdateTaskStatus(task *BootAction) error { + logger.Log.Info("UpdateTaskStatus()") db := getDB() defer db.Close() err := db.Save(task).Error @@ -290,6 +370,7 @@ func UpdateTaskStatus(task *BootAction) error{ } func Create(data []byte) (string, error) { + logger.Log.Info("Create()") db := getDB() defer db.Close() @@ -299,16 +380,16 @@ func Create(data []byte) (string, error) { UUID, err := uuid.NewRandom() err = json.Unmarshal(data, &node) /* - //Get UUID using Redfish Library. - err := json.Unmarshal(data, &node) - if err == nil { - uuidString, _ := redfish.GetUUID(node.IPMIIP, node.IPMIUser, node.IPMIPassword) - if uuidString == "" { - - err = errors.New(fmt.Sprintf("Failed to retreive Node UUID for nodename : %v", node.Name)) + //Get UUID using Redfish Library. + err := json.Unmarshal(data, &node) + if err == nil { + uuidString, _ := redfish.GetUUID(node.IPMIIP, node.IPMIUser, node.IPMIPassword) + if uuidString == "" { + + err = errors.New(fmt.Sprintf("Failed to retreive Node UUID for nodename : %v", node.Name)) + } } - } - UUID, err := uuid.Parse(uuidString) + UUID, err := uuid.Parse(uuidString) */ if err == nil { @@ -317,6 +398,7 @@ func Create(data []byte) (string, error) { err = db.Create(&node).Error } if err != nil { + logger.Log.Error("Failed to create Node", zap.Error(err)) return "", err } else { return node.NodeUUID.String(), nil @@ -326,13 +408,15 @@ func Create(data []byte) (string, error) { // For Testing purpose only func CreateTestNode() *Node { data, _ := ioutil.ReadFile(config.Get("testing.inputfile").(string)) - Create(data) + uuid, _ := Create(data) nodelist, err := GetNodes() if err != nil { return nil } for _, node := range nodelist { - return &node + if node.NodeUUID.String() == uuid { + return &node + } } return nil diff --git a/pkg/db/models/node/nodes_test.go b/pkg/db/models/node/nodes_test.go index 50d6125..66b55b1 100644 --- a/pkg/db/models/node/nodes_test.go +++ b/pkg/db/models/node/nodes_test.go @@ -3,28 +3,199 @@ package node import ( "fmt" "testing" - + "github.com/jinzhu/gorm" + "github.com/manojkva/metamorph-plugin/pkg/config" "github.com/stretchr/testify/assert" ) +func TestGetDB(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + db:=getDB() + fmt.Println(db) + fmt.Printf("%T",db) + assert.NotEqual(t, db, nil) + dbPath := config.Get("database.path") + db, err := gorm.Open("sqlite3", dbPath) + if err != nil { + fmt.Println("failed to connect database") + t.Error("got",err , "want", nil) + } +} + +func TestGetNodes(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node,err:=GetNodes() + if len(node)<=0 || err!=nil { + t.Error("got",len(node) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + + +func TestGetNameServers(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + nameserver,err:=GetNameServers(node.NodeUUID.String()) + + if len(nameserver)<=0 || err!=nil { + t.Error("got",len(nameserver) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + +func TestGetPartitions(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + partitions,err:=GetPartitions(node.NodeUUID.String()) + + if len(partitions)<=0 || err!=nil { + t.Error("got",len(partitions) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + +func TestGetSSHPubKeys(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + sshPubKeys,err:=GetSSHPubKeys(node.NodeUUID.String()) + + if len(sshPubKeys)<=0 || err!=nil { + t.Error("got",len(sshPubKeys) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + +func TestGetFirmwares(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + firmwares,err:=GetFirmwares(node.NodeUUID.String()) + + if len(firmwares)<=0 || err!=nil { + t.Error("got",len(firmwares) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} +func TestGetVirtualDisks(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + virtualdisks,err:=GetVirtualDisks(node.NodeUUID.String()) + fmt.Println("virtualdisks[0]") + fmt.Println(virtualdisks[0].ID) + fmt.Printf("%T",virtualdisks[0]) + + if len(virtualdisks)<=0 || err!=nil { + t.Error("got",len(virtualdisks) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} +func TestGetBootActions(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + bootactions,err:=GetBootActions(node.NodeUUID.String()) + + if len(bootactions)<=0 || err!=nil { + t.Error("got",len(bootactions) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + +func TestGetPhysicalDisks(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + virtualdisks,err:=GetVirtualDisks(node.NodeUUID.String()) + fmt.Println("virtualdisks[0]") + fmt.Println(virtualdisks[0].ID) + fmt.Printf("%T",virtualdisks[0]) + + physcical_disks,err:=GetPhysicalDisks(virtualdisks[0].ID) + + if len(physcical_disks)<=0 || err!=nil { + t.Error("got",len(physcical_disks) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + +func TestDescribe(t *testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + res,err:=Describe(node.NodeUUID.String()) + + if len(res)<=0 || err!=nil { + t.Error("got",len(res) , "want", "greater then 0") + t.Error("got",err , "want", nil) + } + + +} + + + +func init() { + config.SetLoggerConfig("logger.apipath") +} + func TestGetBondParameters(t *testing.T) { + config.SetLoggerConfig("logger.apipath") node := CreateTestNode() bondParameters, _ := GetBondParameters(node.NodeUUID.String()) - assert.Equal(t, len(bondParameters),6) + fmt.Println("length",len(bondParameters)) + if node != nil { + if len(bondParameters) != 6 && len(bondParameters) != 0 { + t.Error("got",len(bondParameters) , "want", "6 or 0") + } + } } func TestGetKvmPolicy(t *testing.T) { + config.SetLoggerConfig("logger.apipath") node := CreateTestNode() - kvmPolicy, _ := GetKvmPolicy(node.NodeUUID.String()) + kvmPolicy, err := GetKvmPolicy(node.NodeUUID.String()) + assert.Equal(t, err, nil) assert.Equal(t, kvmPolicy.CpuAllocation, "1:1") assert.Equal(t, kvmPolicy.CpuPinning, "enabled") assert.Equal(t, kvmPolicy.CpuHyperthreading, "enabled") } func TestGetFilesystem(t *testing.T) { + config.SetLoggerConfig("logger.apipath") node := CreateTestNode() - partitions, _ := GetPartitions(node.NodeUUID.String()) + partitions, err := GetPartitions(node.NodeUUID.String()) + assert.Equal(t, err, nil) for index, part := range partitions { filesystem, _ := GetFilesystem(part.ID) partitions[index].Filesystem = *filesystem } fmt.Printf("%v", partitions[0].Filesystem) } + +func TestGetPlugins(t * testing.T){ + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + plugins, err := GetPlugins(node.NodeUUID.String()) + assert.Equal(t, err, nil) + fmt.Println("PLUGINS") + fmt.Printf("%+v", plugins) + apis,err := GetPluginAPIs(plugins.ID) + assert.Equal(t, err, nil) + fmt.Println("APIS") + fmt.Printf("%+v", apis) +} +func TestCreateTestNode(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + node := CreateTestNode() + assert.NotEqual(t, node, nil) +} diff --git a/pkg/plugin/pluginClient.go b/pkg/plugin/pluginClient.go new file mode 100644 index 0000000..136df13 --- /dev/null +++ b/pkg/plugin/pluginClient.go @@ -0,0 +1,192 @@ +package plugin + +import ( + "encoding/base64" + "encoding/json" + "fmt" + "os" + "os/exec" + "strings" + + hclog "github.com/hashicorp/go-hclog" + + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" + "github.com/hashicorp/go-plugin" + "github.com/manojkva/metamorph-plugin/common/bmh" + "github.com/manojkva/metamorph-plugin/common/isogen" + config "github.com/manojkva/metamorph-plugin/pkg/config" + "github.com/manojkva/metamorph-plugin/pkg/logger" + "go.uber.org/zap" +) + +var HandShakeMap = map[string]plugin.HandshakeConfig { + "metamorph-redfish-plugin": bmh.Handshake, + "metamorph-isogen-plugin": isogen.Handshake, + +} +type BMHNode struct { + *node.Node +} + +func (bmhnode *BMHNode) ReadConfigFile() error { + + var err error + var plugins node.Plugins + var pluginskeyname string = "plugins" + var valueFromNode string + var errorString string + var apisNode []node.API + + logger.Log.Info("ReadConfigFile()") + + pluginsConfig := config.GetStringMapString(pluginskeyname) + + if pluginsConfig != nil { + + pluginsNode, err := node.GetPlugins(bmhnode.NodeUUID.String()) + if err == nil { + apisNode, err = node.GetPluginAPIs(pluginsNode.ID) + } else { + logger.Log.Info("Plugin details not found in input json file. Info in config files will be used") + } + + for key, value := range pluginsConfig { + var api node.API + api.Name = key + if len(apisNode) > 0 { + + valueFromNode = node.GetPluginForAPI(apisNode, key) + } + if valueFromNode == "" { + api.Plugin = value + } else { + logger.Log.Info(fmt.Sprintf("Value overridden for %v", key), zap.String("Old value", value), zap.String("New value", valueFromNode)) + api.Plugin = valueFromNode + } + + plugins.APIs = append(plugins.APIs, api) + err = node.Update(&node.Node{NodeUUID: bmhnode.NodeUUID, Plugins: plugins}) + + } + } else { + errorString = "Failed to retrieve Plugins information from config file" + logger.Log.Error(errorString, zap.String("NodeName", bmhnode.Name)) + err = fmt.Errorf(errorString) + } + + return err +} + +func (bmhnode *BMHNode) DispenseClientRequest(apiName string) (interface{}, error) { + + logger.Log.Info("DispenseClientRequest") + + var resultIntf interface{} + + pluginLocation := config.Get("pluginlocation").(string) + + if pluginLocation == "" { + logger.Log.Error("Failed to retrieve pluginlocation from config file") + return nil, fmt.Errorf("Failed to retrieve pluginlocation from config file") + } + fmt.Println(bmhnode.NodeUUID.String()) + pluginsNode, err := node.GetPlugins(bmhnode.NodeUUID.String()) + if err != nil { + fmt.Println(err) + logger.Log.Error("Failed to retreive Plugins information from config file") + return nil, err + } + apisNode, err := node.GetPluginAPIs(pluginsNode.ID) + if err != nil { + logger.Log.Error("Failed to retrieve supported APIS for plugin.", zap.String( "PluginID", fmt.Sprintf("%v",pluginsNode.ID))) + return nil, err + } + + pluginName := node.GetPluginForAPI(apisNode, apiName) + + data, err := json.Marshal(bmhnode) + + if err != nil { + logger.Log.Error("Failed to Marshal JSON info", zap.Error(err)) + fmt.Printf("Error %v\n", err) + return nil, err + } + inputConfig := base64.StdEncoding.EncodeToString(data) + + hclogger := hclog.New(&hclog.LoggerOptions{ + Name: "plugin", + Output: os.Stdout, + Level: hclog.Trace}) + client := plugin.NewClient(&plugin.ClientConfig{ + HandshakeConfig: HandShakeMap[pluginName], + Plugins: map[string]plugin.Plugin{ + "metamorph-redfish-plugin": &bmh.BmhPlugin{}, + "metamorph-isogen-plugin": &isogen.ISOgenPlugin{}}, + Cmd: exec.Command("sh", "-c", pluginLocation+"/"+pluginName+" "+string(inputConfig)), + AllowedProtocols: []plugin.Protocol{plugin.ProtocolGRPC}, + Logger: hclogger}) + defer client.Kill() + + rpcClient, err := client.Client() + + if err != nil { + logger.Log.Error("Failed to retrieve RPC Client", zap.Error(err)) + fmt.Printf("Error %v\n", err) + return nil, err + } + + raw, err := rpcClient.Dispense(pluginName) + if err != nil { + logger.Log.Error("Failed to Dispense plugin", zap.Error(err), zap.String("PluginName", pluginName)) + fmt.Printf("Error %v\n", err) + return nil, err + + } + switch apiNameLowerCase := strings.ToLower(apiName); apiNameLowerCase { + case "getguuid": + service := raw.(bmh.Bmh) + resultIntf, err = service.GetGUUID() + logger.Log.Debug("GetGUUID() ", zap.String("result",fmt.Sprintf("%v\n", resultIntf.([]byte)))) + fmt.Printf("%v\n", resultIntf.([]byte)) + case "deployiso": + service := raw.(bmh.Bmh) + err = service.DeployISO() + resultIntf = nil + case "updatefirmware": + service := raw.(bmh.Bmh) + err = service.UpdateFirmware() + resultIntf = nil + case "configureraid": + service := raw.(bmh.Bmh) + err = service.ConfigureRAID() + resultIntf = nil + case "createiso": + service := raw.(isogen.ISOgen) + err = service.CreateISO() + resultIntf = nil + case "gethwinventory": + service := raw.(bmh.Bmh) + resultIntf, err = service.GetHWInventory() + case "poweron": + service := raw.(bmh.Bmh) + err = service.PowerOn() + resultIntf = nil + case "poweroff": + service := raw.(bmh.Bmh) + err = service.PowerOff() + resultIntf = nil + case "getpowerstatus": + service := raw.(bmh.Bmh) + status, _ := service.GetPowerStatus() + if status == true { + resultIntf = "On" + } else { + resultIntf = "Off" + } + err = nil + default: + logger.Log.Error("Unsupported API", zap.String("API Name", apiName)) + err = fmt.Errorf("%v not supported.", apiName) + } + return resultIntf, err +} diff --git a/pkg/plugin/pluginClient_test.go b/pkg/plugin/pluginClient_test.go new file mode 100644 index 0000000..b134512 --- /dev/null +++ b/pkg/plugin/pluginClient_test.go @@ -0,0 +1,33 @@ +package plugin + +import ( + "github.com/bm-metamorph/MetaMorph/pkg/db/models/node" + "github.com/manojkva/metamorph-plugin/pkg/config" + // "github.com/stretchr/testify/assert" + "fmt" + "testing" +) + +func TestReadConfigFile(t *testing.T) { + bmhnode := &BMHNode{node.CreateTestNode()} + bmhnode.ReadConfigFile() + +} + +func TestDispenseClientRequest(t *testing.T) { + config.SetLoggerConfig("logger.apipath") + bmhnode := &BMHNode{node.CreateTestNode()} + err := bmhnode.ReadConfigFile() + if err == nil{ + x, err := bmhnode.DispenseClientRequest("gethwinventory") + if err == nil { + fmt.Printf("%+v\n", (x.(map[string]string))) + } else { + fmt.Printf("Error %v\n", err) + } +}else{ + fmt.Println("Failed to read Config file") +} + + +} diff --git a/pkg/test/server.go b/pkg/test/server.go new file mode 100644 index 0000000..2096ce6 --- /dev/null +++ b/pkg/test/server.go @@ -0,0 +1,34 @@ +package main + +import ( + "log" + "io/ioutil" + "bytes" + "github.com/gin-gonic/gin" +) + +func Logger() gin.HandlerFunc { + return func(c *gin.Context) { + var bodyBytes []byte + if c.Request.Body != nil { + bodyBytes, _ = ioutil.ReadAll(c.Request.Body) + } + c.Request.Body = ioutil.NopCloser(bytes.NewBuffer(bodyBytes)) + + log.Println("first", string(bodyBytes)) + } +} + +func main() { + r := gin.New() + r.Use(Logger()) + + r.POST("/test", func(c *gin.Context) { + // Body disappear on controller + NrawBody, _ := c.GetRawData() + log.Println("second", string(NrawBody)) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":81") +} diff --git a/pkg/test/server_test.go b/pkg/test/server_test.go new file mode 100644 index 0000000..ab68821 --- /dev/null +++ b/pkg/test/server_test.go @@ -0,0 +1,23 @@ +package main + +import ( + "fmt" + "net/http" + "net/http/httptest" + "testing" + + "github.com/stretchr/testify/assert" + "github.com/stretchr/testify/require" +) + +func TestNodes(t *testing.T) { + // Inject the StartServer method into a test server + ts := httptest.NewServer(StartServer()) + defer ts.Close() + + // Make a request to your server with the {base url}/nodes + resp, err := http.Get(fmt.Sprintf("%s/nodes", ts.URL)) + require.NoError(t, err, "Error querying nodes") + assert.Equal(t, http.StatusOK, resp.StatusCode) + // Do rest of your validations here +} diff --git a/pkg/test/test1.go b/pkg/test/test1.go new file mode 100644 index 0000000..09b31d6 --- /dev/null +++ b/pkg/test/test1.go @@ -0,0 +1,20 @@ +package main +import ( + "github.com/gin-gonic/gin" + "log" +// "fmt" +) + + +func main() { + r := gin.New() + + r.POST("/test", func(c *gin.Context) { + // Body disappear on controller + NrawBody, _ := c.GetRawData() + log.Println("second", string(NrawBody)) + }) + + // Listen and serve on 0.0.0.0:8080 + r.Run(":810") +}