From 9089164ea19a188d3e5eb79d56892e23d9b2f121 Mon Sep 17 00:00:00 2001 From: Alva Zhang <140438385+Alva8756@users.noreply.github.com> Date: Fri, 8 Sep 2023 13:31:50 -0700 Subject: [PATCH] FS-899 Add bom router handlers and client APIs to fetch bom by bmcMacAddree (#244) Add bom router handlers and client APIs to fetch bom by bmcMacAddree --- pkg/api/v1/bom.go | 26 ++++++ pkg/api/v1/router.go | 6 +- pkg/api/v1/router_bom.go | 41 +++++++++ pkg/api/v1/router_bom_test.go | 147 ++++++++++++++++++++++++++++++ pkg/api/v1/server_service.go | 15 +++ pkg/api/v1/server_service_test.go | 16 ++++ 6 files changed, 250 insertions(+), 1 deletion(-) diff --git a/pkg/api/v1/bom.go b/pkg/api/v1/bom.go index 9fcb7b6..d1fd595 100644 --- a/pkg/api/v1/bom.go +++ b/pkg/api/v1/bom.go @@ -27,6 +27,12 @@ type AocMacAddressBom struct { SerialNum string `json:"serial_num"` } +// BmcMacAddressBom provides a struct to map the bmc_mac_address table. +type BmcMacAddressBom struct { + BmcMacAddress string `json:"bmc_mac_address"` + SerialNum string `json:"serial_num"` +} + // toDBModel converts Bom to BomInfo. func (b *Bom) toDBModel() (*models.BomInfo, error) { if b.SerialNum == "" { @@ -76,3 +82,23 @@ func (b *Bom) toAocMacAddressDBModels() ([]*models.AocMacAddress, error) { return dbAs, nil } + +// toBmcMacAddressDBModels converts Bom to one or multiple BmcMacAddress. +func (b *Bom) toBmcMacAddressDBModels() ([]*models.BMCMacAddress, error) { + if b.BmcMacAddress == "" { + return nil, errors.Errorf("the primary key bmc-mac-address can not be blank") + } + + dbBs := []*models.BMCMacAddress{} + + BmcMacAddrs := strings.Split(b.BmcMacAddress, ",") + for _, bmcMacAddr := range BmcMacAddrs { + dbB := &models.BMCMacAddress{ + SerialNum: b.SerialNum, + BMCMacAddress: bmcMacAddr, + } + dbBs = append(dbBs, dbB) + } + + return dbBs, nil +} diff --git a/pkg/api/v1/router.go b/pkg/api/v1/router.go index b2f3379..d058782 100644 --- a/pkg/api/v1/router.go +++ b/pkg/api/v1/router.go @@ -137,7 +137,11 @@ func (r *Router) Routes(rg *gin.RouterGroup) { srvBomByAocMacAddress.GET("/:aoc_mac_address", amw.RequiredScopes(readScopes("aoc-mac-address")), r.getBomFromAocMacAddress) } - // TODO: support query by bmc-mac-address + // /bill-of-materials/bmc-mac-address + srvBomByBmcMacAddress := srvBoms.Group("/bmc-mac-address") + { + srvBomByBmcMacAddress.GET("/:bmc_mac_address", amw.RequiredScopes(readScopes("bmc-mac-address")), r.getBomFromBmcMacAddress) + } } } diff --git a/pkg/api/v1/router_bom.go b/pkg/api/v1/router_bom.go index 942b91c..dd72c07 100644 --- a/pkg/api/v1/router_bom.go +++ b/pkg/api/v1/router_bom.go @@ -39,6 +39,17 @@ func (r *Router) bomsUpload(c *gin.Context) { return err } } + + dbBmcMacAddrsBoms, err := (bom).toBmcMacAddressDBModels() + if err != nil { + return err + } + + for _, dbBmcMacAddrsBom := range dbBmcMacAddrsBoms { + if err := dbBmcMacAddrsBom.Insert(c.Request.Context(), r.DB, boil.Infer()); err != nil { + return err + } + } } return nil }) @@ -79,3 +90,33 @@ func (r *Router) getBomFromAocMacAddress(c *gin.Context) { itemResponse(c, bom) } + +func (r *Router) getBomFromBmcMacAddress(c *gin.Context) { + mods := []qm.QueryMod{ + qm.Where("bmc_mac_address=?", c.Param("bmc_mac_address")), + } + + bmcMacAddr, err := models.BMCMacAddresses(mods...).One(c.Request.Context(), r.DB) + if err != nil { + dbErrorResponse(c, err) + return + } + + mods = []qm.QueryMod{ + qm.Where("serial_num=?", bmcMacAddr.SerialNum), + } + + bomInfo, err := models.BomInfos(mods...).One(c.Request.Context(), r.DB) + if err != nil { + dbErrorResponse(c, err) + return + } + + bom := Bom{} + if err = bom.fromDBModel(bomInfo); err != nil { + dbErrorResponse(c, err) + return + } + + itemResponse(c, bom) +} diff --git a/pkg/api/v1/router_bom_test.go b/pkg/api/v1/router_bom_test.go index f1c68b3..83ae579 100644 --- a/pkg/api/v1/router_bom_test.go +++ b/pkg/api/v1/router_bom_test.go @@ -110,6 +110,31 @@ func TestIntegrationBomUpload(t *testing.T) { aocMacAddress: "fakeAocMacAddress3", expectedAocMacAddressError: false, }, + { + testName: "upload duplicate BmcMacAddress", + uploadBoms: []serverservice.Bom{ + { + SerialNum: "fakeSerialNum1", + AocMacAddress: "fakeAocMacAddress1,fakeAocMacAddress2", + BmcMacAddress: "fakeBmcMacAddress1,fakeBmcMacAddress2", + NumDefiPmi: "fakeNumDefipmi1", + NumDefPWD: "fakeNumDefpwd1", + Metro: "fakeMetro1", + }, + { + SerialNum: "fakeSerialNum2", + AocMacAddress: "fakeAocMacAddress3,fakeAocMacAddress4", + BmcMacAddress: "fakeBmcMacAddress1,fakeBmcMacAddress3", + NumDefiPmi: "fakeNumDefipmi2", + NumDefPWD: "fakeNumDefpwd2", + Metro: "fakeMetro2", + }, + }, + expectedUploadErr: true, + expectedUploadErrorMsg: "unable to insert into bmc_mac_address: pq: duplicate key value violates unique constraint", + aocMacAddress: "fakeBmcMacAddress3", + expectedAocMacAddressError: false, + }, { testName: "upload empty serial number", uploadBoms: []serverservice.Bom{ @@ -144,6 +169,23 @@ func TestIntegrationBomUpload(t *testing.T) { aocMacAddress: "fakeAocMacAddress3", expectedAocMacAddressError: false, }, + { + testName: "upload empty BmcMacAddress", + uploadBoms: []serverservice.Bom{ + { + SerialNum: "fakeSerialNum1", + AocMacAddress: "fakeAocMacAddress1,fakeAocMacAddress2", + BmcMacAddress: "", + NumDefiPmi: "fakeNumDefipmi1", + NumDefPWD: "fakeNumDefpwd1", + Metro: "fakeMetro1", + }, + }, + expectedUploadErr: true, + expectedUploadErrorMsg: "the primary key bmc-mac-address can not be blank", + aocMacAddress: "fakeAocMacAddress3", + expectedAocMacAddressError: false, + }, } for _, tc := range testCases { @@ -325,3 +367,108 @@ func TestIntegrationGetBomByAocMacAddr(t *testing.T) { }) } } + +func TestIntegrationGetBomByBmcMacAddr(t *testing.T) { + s := serverTest(t) + + uploadBoms := []serverservice.Bom{ + { + SerialNum: "fakeSerialNum1", + AocMacAddress: "fakeAocMacAddress1,fakeAocMacAddress2", + BmcMacAddress: "fakeBmcMacAddress1,fakeBmcMacAddress2", + NumDefiPmi: "fakeNumDefipmi1", + NumDefPWD: "fakeNumDefpwd1", + Metro: "fakeMetro1", + }, + { + SerialNum: "fakeSerialNum2", + AocMacAddress: "fakeAocMacAddress3,fakeAocMacAddress4", + BmcMacAddress: "fakeBmcMacAddress3,fakeBmcMacAddress4", + NumDefiPmi: "fakeNumDefipmi2", + NumDefPWD: "fakeNumDefpwd2", + Metro: "fakeMetro2", + }, + } + authToken := validToken(adminScopes) + s.Client.SetToken(authToken) + + _, err := s.Client.BillOfMaterialsBatchUpload(context.TODO(), uploadBoms) + if err != nil { + t.Fatalf("s.Client.BillOfMaterialsBatchUpload(%v) failed to upload, err %v", uploadBoms, err) + return + } + + var testCases = []struct { + testName string + bmcMacAddress string + expectedBom serverservice.Bom + expectedBmcMacAddressError bool + expectedBmcMacAddressErrorMsg string + }{ + { + testName: "get first bom by first bmc mac address", + bmcMacAddress: "fakeBmcMacAddress1", + expectedBom: uploadBoms[0], + expectedBmcMacAddressError: false, + expectedBmcMacAddressErrorMsg: "", + }, + { + testName: "get first bom by second bmc mac address", + bmcMacAddress: "fakeBmcMacAddress2", + expectedBom: uploadBoms[0], + expectedBmcMacAddressError: false, + expectedBmcMacAddressErrorMsg: "", + }, + { + testName: "get second bom by first bmc mac address", + bmcMacAddress: "fakeBmcMacAddress3", + expectedBom: uploadBoms[1], + expectedBmcMacAddressError: false, + expectedBmcMacAddressErrorMsg: "", + }, + { + testName: "get second bom by second bmc mac address", + bmcMacAddress: "fakeBmcMacAddress3", + expectedBom: uploadBoms[1], + expectedBmcMacAddressError: false, + expectedBmcMacAddressErrorMsg: "", + }, + { + testName: "non-exist bmc mac address", + bmcMacAddress: "random", + expectedBom: uploadBoms[1], + expectedBmcMacAddressError: true, + expectedBmcMacAddressErrorMsg: "sql: no rows in result set", + }, + { + testName: "empty bmc mac address", + bmcMacAddress: "", + expectedBom: uploadBoms[1], + expectedBmcMacAddressError: true, + expectedBmcMacAddressErrorMsg: "route not found", + }, + } + + for _, tc := range testCases { + t.Run(tc.testName, func(t *testing.T) { + bom, _, err := s.Client.GetBomInfoByBMCMacAddr(context.TODO(), tc.bmcMacAddress) + if tc.expectedBmcMacAddressError { + if err == nil { + t.Fatalf("GetBomInfoByBMCMacAddr(%v) expect error, got nil", tc.bmcMacAddress) + } + if !strings.Contains(err.Error(), tc.expectedBmcMacAddressErrorMsg) { + t.Fatalf("GetBomInfoByBMCMacAddr(%v) expect error %v, got %v", tc.bmcMacAddress, tc.expectedBmcMacAddressErrorMsg, err) + } + return + } + if err != nil { + t.Fatalf("GetBomInfoByBMCMacAddr(%v) failed to upload, err %v", tc.bmcMacAddress, err) + return + } + + if !reflect.DeepEqual(bom, &tc.expectedBom) { + t.Fatalf("got incorrect bom %v, expect %v", bom, tc.expectedBom) + } + }) + } +} diff --git a/pkg/api/v1/server_service.go b/pkg/api/v1/server_service.go index f2a1dc1..1c2bf07 100644 --- a/pkg/api/v1/server_service.go +++ b/pkg/api/v1/server_service.go @@ -21,6 +21,7 @@ const ( bomInfoEndpoint = "bill-of-materials" uploadFileEndpoint = "batch-upload" bomByMacAOCAddressEndpoint = "aoc-mac-address" + bomByMacBMCAddressEndpoint = "bmc-mac-address" ) // ClientInterface provides an interface for the expected calls to interact with a server service api @@ -59,6 +60,7 @@ type ClientInterface interface { ListServerCredentialTypes(context.Context) (*ServerResponse, error) BillOfMaterialsBatchUpload(context.Context, []Bom) (*ServerResponse, error) GetBomInfoByAOCMacAddr(context.Context, string) (*Bom, *ServerResponse, error) + GetBomInfoByBMCMacAddr(context.Context, string) (*Bom, *ServerResponse, error) } // Create will attempt to create a server in Hollow and return the new server's UUID @@ -412,3 +414,16 @@ func (c *Client) GetBomInfoByAOCMacAddr(ctx context.Context, aocMacAddr string) return bom, &r, nil } + +// GetBomInfoByBMCMacAddr will return the bom info object by the bmc mac address. +func (c *Client) GetBomInfoByBMCMacAddr(ctx context.Context, bmcMacAddr string) (*Bom, *ServerResponse, error) { + path := fmt.Sprintf("%s/%s/%s", bomInfoEndpoint, bomByMacBMCAddressEndpoint, bmcMacAddr) + bom := &Bom{} + r := ServerResponse{Record: bom} + + if err := c.get(ctx, path, &r); err != nil { + return nil, nil, err + } + + return bom, &r, nil +} diff --git a/pkg/api/v1/server_service_test.go b/pkg/api/v1/server_service_test.go index 117635e..a467934 100644 --- a/pkg/api/v1/server_service_test.go +++ b/pkg/api/v1/server_service_test.go @@ -381,3 +381,19 @@ func TestGetBomInfoByAOCMacAddr(t *testing.T) { return err }) } + +func TestGetBomInfoByBMCMacAddr(t *testing.T) { + mockClientTests(t, func(ctx context.Context, respCode int, expectError bool) error { + bom := hollow.Bom{SerialNum: "fakeSerialNum1", AocMacAddress: "fakeAocMacAddress1", BmcMacAddress: "fakeBmcMacAddress1"} + jsonResponse, err := json.Marshal(hollow.ServerResponse{Record: bom}) + require.Nil(t, err) + + c := mockClient(string(jsonResponse), respCode) + respBom, _, err := c.GetBomInfoByBMCMacAddr(ctx, "fakeBmcMacAddress1") + if !expectError { + assert.Equal(t, &bom, respBom) + } + + return err + }) +}