Skip to content

Commit

Permalink
FS-1450: do not fail to retrieve inventory if a component's attribute…
Browse files Browse the repository at this point in the history
…s do not meet expectations (#94)
  • Loading branch information
DoctorVin authored Jul 25, 2024
1 parent 4b663d7 commit 1c33aa8
Show file tree
Hide file tree
Showing 3 changed files with 82 additions and 1 deletion.
1 change: 1 addition & 0 deletions cmd/root.go
Original file line number Diff line number Diff line change
Expand Up @@ -89,6 +89,7 @@ func initLogger() *zap.SugaredLogger {
}

log := zap.Must(logCfg.Build())
_ = zap.ReplaceGlobals(log) // make the logger accessible globally by zap.L() (sugared with zap.S())
return log.Sugar()
}

Expand Down
15 changes: 14 additions & 1 deletion internal/inventory/device_components.go
Original file line number Diff line number Diff line change
Expand Up @@ -3,13 +3,15 @@ package inventory
import (
"context"
"database/sql"
"encoding/json"

"github.com/bmc-toolbox/common"
rivets "github.com/metal-toolbox/rivets/types"
"github.com/pkg/errors"
"github.com/volatiletech/null/v8"
"github.com/volatiletech/sqlboiler/v4/boil"
"github.com/volatiletech/sqlboiler/v4/queries/qm"
"go.uber.org/zap"

"github.com/metal-toolbox/fleetdb/internal/dbtools"
"github.com/metal-toolbox/fleetdb/internal/metrics"
Expand Down Expand Up @@ -211,10 +213,21 @@ func componentsFromDatabase(ctx context.Context, exec boil.ContextExecutor,

var comps []*rivets.Component

var ute *json.UnmarshalTypeError
for _, rec := range records {
// attributes/firmware/status might not be stored because it was missing in the original data.
attr, err := retrieveComponentAttributes(ctx, exec, rec.ID, getAttributeNamespace(inband))
if err != nil {
switch {
case err == nil, errors.Is(err, sql.ErrNoRows):
case errors.As(err, &ute):
// attributes are a bit of the wild-west. if the JSON we stored doesn't deserialize
// cleanly into an attributes structure, just complain about it but don't stop.
zap.L().With(
zap.String("server.id", deviceID),
zap.String("component.id", rec.ID),
zap.String("component.type", rec.Name.String),
).Warn("bad json attributes")
default:
return nil, errors.Wrap(err, "retrieving "+rec.Name.String+"-"+rec.ID+" attributes"+":"+err.Error())
}

Expand Down
67 changes: 67 additions & 0 deletions internal/inventory/device_components_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -4,6 +4,7 @@ package inventory

import (
"context"
"encoding/json"
"testing"

"github.com/bmc-toolbox/common"
Expand Down Expand Up @@ -277,4 +278,70 @@ func TestComposeComponentRecords(t *testing.T) {
require.NoError(t, err)
require.Len(t, comps, 1)
})
t.Run("nonconforming existing data", func(t *testing.T) {
// write an attribute record that doesn't have rivets.ComponentAttribute as a basis
srvUUID := mustCreateServerRecord(t, db, "bad-attribute-json")

var inband bool
attributeNS := getAttributeNamespace(inband)

slug := common.SlugBIOS

orig := &rivets.Component{
// this can be any real slug, but *must* be a real slug, otherwise
// we will panic on the slug -> type-id lookup.
Name: slug,
Vendor: "the-vendor",
Serial: "some-serial-number",
Firmware: &common.Firmware{
Installed: "old-version",
},
Status: &common.Status{
State: "OK",
Health: "decent",
},
}

tx := db.MustBegin()
err := composeRecords(context.TODO(), tx, orig, inband, srvUUID.String())
require.NoError(t, err)
_ = tx.Commit()

// add the crazy attribute record
compRecs, err := models.ServerComponents(
models.ServerComponentWhere.Name.EQ(null.StringFrom(slug)),
models.ServerComponentWhere.ServerID.EQ(srvUUID.String()),
).All(context.TODO(), db)

require.NoError(t, err)
require.Len(t, compRecs, 1)
// get id and inject attributes
compID := compRecs[0].ID
badData := []map[string]string{
{
"msg": "this is not a rivets component attributes structure",
},
{
"msg": "this is also not a rivets component attributes structure",
},
}
payload, err := json.Marshal(badData)
require.NoError(t, err)

err = updateAnyAttribute(context.TODO(), db, false, compID, attributeNS, payload)
require.NoError(t, err)

// be pedantic and validate that there is an attribute record
attrs, err := models.Attributes(
models.AttributeWhere.ServerComponentID.EQ(null.StringFrom(compID)),
models.AttributeWhere.Namespace.EQ(attributeNS),
).All(context.TODO(), db)
require.NoError(t, err)
require.Len(t, attrs, 1)

// now ask for this component via the API
comps, err := componentsFromDatabase(context.TODO(), db, inband, srvUUID.String())
require.NoError(t, err, "received error with type %T", err)
require.Len(t, comps, 1)
})
}

0 comments on commit 1c33aa8

Please sign in to comment.