Skip to content

Commit 8f6352e

Browse files
SushilLakrajkossak
andauthored
Added changes for support of popultaing existing_cves and fixed_cves in OS Resource and Instance Resource for immutable OS (#119)
Co-authored-by: joanna kossakowska <joanna.kossakowska@intel.com>
1 parent 957e06c commit 8f6352e

11 files changed

Lines changed: 1837 additions & 11 deletions

File tree

maintenance/pkg/invclient/invclient.go

Lines changed: 39 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -213,6 +213,7 @@ func UpdateInstance(
213213
updateStatus inv_status.ResourceStatus,
214214
updateStatusDetail string,
215215
newOSResID string,
216+
newExistingCves string,
216217
) error {
217218
zlog.Debug().Msgf("UpdateInstanceStatus: tenantID=%s, InstanceID=%s, NewUpdateStatus=%v, LastUpdateDetail=%s",
218219
tenantID, instanceID, updateStatus, updateStatusDetail)
@@ -245,6 +246,11 @@ func UpdateInstance(
245246
fields = append(fields, computev1.InstanceResourceEdgeCurrentOs)
246247
}
247248

249+
if newExistingCves != "" {
250+
instRes.ExistingCves = newExistingCves
251+
fields = append(fields, computev1.InstanceResourceFieldExistingCves)
252+
}
253+
248254
fieldMask, err := fieldmaskpb.New(instRes, fields...)
249255
if err != nil {
250256
// This should never happen
@@ -366,6 +372,39 @@ func GetLatestImmutableOSByProfile(
366372
return latestOS, nil
367373
}
368374

375+
func GetOSResourceByID(
376+
ctx context.Context,
377+
c inv_client.TenantAwareInventoryClient,
378+
tenantID, osResourceID string,
379+
) (*os_v1.OperatingSystemResource, error) {
380+
zlog.Debug().Msgf("GetOSResourceByID: tenantID=%s, osResourceID=%s", tenantID, osResourceID)
381+
382+
childCtx, cancel := context.WithTimeout(ctx, *inventoryTimeout)
383+
defer cancel()
384+
385+
resp, err := c.Get(childCtx, tenantID, osResourceID)
386+
if err != nil {
387+
zlog.InfraErr(err).Msgf("Failed to get OS resource: tenantID=%s, osResourceID=%s", tenantID, osResourceID)
388+
return nil, err
389+
}
390+
391+
if err = validator.ValidateMessage(resp); err != nil {
392+
zlog.InfraSec().InfraErr(err).Msg("")
393+
return nil, errors.Wrap(err)
394+
}
395+
396+
osResource, err := util.UnwrapResource[*os_v1.OperatingSystemResource](resp.GetResource())
397+
if err != nil {
398+
zlog.InfraSec().InfraErr(err).Msgf("Failed to unwrap OS resource: %s", resp.GetResource())
399+
return nil, err
400+
}
401+
402+
zlog.Debug().Msgf("Successfully retrieved OS resource: %s (Profile: %s, Version: %s)",
403+
osResource.GetResourceId(), osResource.GetProfileName(), osResource.GetImageId())
404+
405+
return osResource, nil
406+
}
407+
369408
func CreateOSUpdateRun(
370409
ctx context.Context, c inv_client.TenantAwareInventoryClient, tenantID string, osUpRun *computev1.OSUpdateRunResource,
371410
) error {

maintenance/pkg/invclient/invclient_test.go

Lines changed: 148 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -26,6 +26,10 @@ import (
2626
inv_utils "github.com/open-edge-platform/infra-managers/maintenance/pkg/utils"
2727
)
2828

29+
const (
30+
semverVersion100 = "1.0.0"
31+
)
32+
2933
func TestMain(m *testing.M) {
3034
wd, err := os.Getwd()
3135
if err != nil {
@@ -292,7 +296,7 @@ func TestInvClient_UpdateInstance(t *testing.T) {
292296
// Error - non-existent Instance
293297
t.Run("ErrorNoInst", func(t *testing.T) {
294298
err := invclient.UpdateInstance(ctx, client, mm_testing.Tenant1, "inst-12345678",
295-
mm_status.UpdateStatusUpToDate, "", newOSRes.GetResourceId())
299+
mm_status.UpdateStatusUpToDate, "", newOSRes.GetResourceId(), "")
296300
require.Error(t, err)
297301
sts, _ := status.FromError(err)
298302
assert.Equal(t, codes.NotFound, sts.Code())
@@ -303,7 +307,7 @@ func TestInvClient_UpdateInstance(t *testing.T) {
303307
t.Run("UpdateInstStatusNotCurrentOS", func(t *testing.T) {
304308
timeBeforeUpdate := time.Now().Unix()
305309
err := invclient.UpdateInstance(ctx, client, mm_testing.Tenant1, inst.ResourceId,
306-
mm_status.UpdateStatusInProgress, "", "")
310+
mm_status.UpdateStatusInProgress, "", "", "")
307311

308312
require.NoError(t, err)
309313
updatedInst, err := client.Get(ctx, mm_testing.Tenant1, inst.ResourceId)
@@ -323,7 +327,7 @@ func TestInvClient_UpdateInstance(t *testing.T) {
323327
require.NoError(t, err)
324328
assert.NotEqual(t, newOSRes.GetSha256(), beforeUpdateInst.GetResource().GetInstance().GetCurrentOs().GetSha256())
325329
err = invclient.UpdateInstance(ctx, client, mm_testing.Tenant1, inst.ResourceId,
326-
mm_status.UpdateStatusDone, "some update status detail", newOSRes.GetResourceId())
330+
mm_status.UpdateStatusDone, "some update status detail", newOSRes.GetResourceId(), "")
327331
require.NoError(t, err)
328332
updatedInst, err := client.Get(ctx, mm_testing.Tenant1, inst.ResourceId)
329333
require.NoError(t, err)
@@ -338,11 +342,11 @@ func TestInvClient_UpdateInstance(t *testing.T) {
338342
t.Run("UpdateUpdateStatusToRunning", func(t *testing.T) {
339343
// initial setup of instance status to running and update status to unknown
340344
err := invclient.UpdateInstance(ctx, client, mm_testing.Tenant1, inst.ResourceId,
341-
mm_status.UpdateStatusUnknown, "some update status detail", newOSRes.GetResourceId())
345+
mm_status.UpdateStatusUnknown, "some update status detail", newOSRes.GetResourceId(), "")
342346
require.NoError(t, err)
343347
// setup only the update status as instance status is already set to running
344348
err = invclient.UpdateInstance(ctx, client, mm_testing.Tenant1, inst.ResourceId,
345-
mm_status.UpdateStatusDone, "some update status detail", newOSRes.GetResourceId())
349+
mm_status.UpdateStatusDone, "some update status detail", newOSRes.GetResourceId(), "")
346350
require.NoError(t, err)
347351
updatedInst, err := client.Get(ctx, mm_testing.Tenant1, inst.ResourceId)
348352
require.NoError(t, err)
@@ -427,13 +431,12 @@ func TestInvClient_GetLatestImmutableOSByProfile(t *testing.T) {
427431
dao := inv_testing.NewInvResourceDAOOrFail(t)
428432
ctx := t.Context()
429433
client := inv_testing.TestClients[inv_testing.RMClient].GetTenantAwareInventoryClient()
430-
semver100 := "1.0.0"
431434

432435
osRes1 := dao.CreateOsWithOpts(t, mm_testing.Tenant1, true, func(os *os_v1.OperatingSystemResource) {
433436
os.Sha256 = inv_testing.GenerateRandomSha256()
434437
os.Name = "OS Resource 1"
435438
os.ProfileName = "profile name 1"
436-
os.ProfileVersion = semver100
439+
os.ProfileVersion = semverVersion100
437440
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_NONE
438441
os.OsType = os_v1.OsType_OS_TYPE_IMMUTABLE
439442
})
@@ -442,7 +445,7 @@ func TestInvClient_GetLatestImmutableOSByProfile(t *testing.T) {
442445
os.Sha256 = inv_testing.GenerateRandomSha256()
443446
os.Name = "OS Resource 2"
444447
os.ProfileName = "profile name 2"
445-
os.ProfileVersion = semver100
448+
os.ProfileVersion = semverVersion100
446449
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_NONE
447450
os.OsType = os_v1.OsType_OS_TYPE_IMMUTABLE
448451
})
@@ -460,7 +463,7 @@ func TestInvClient_GetLatestImmutableOSByProfile(t *testing.T) {
460463
os.Sha256 = inv_testing.GenerateRandomSha256()
461464
os.Name = "OS Resource 3"
462465
os.ProfileName = "profile name 3"
463-
os.ProfileVersion = semver100
466+
os.ProfileVersion = semverVersion100
464467
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_NONE
465468
os.OsType = os_v1.OsType_OS_TYPE_IMMUTABLE
466469
})
@@ -543,3 +546,139 @@ func TestInvClient_GetLatestImmutableOSByProfile(t *testing.T) {
543546
})
544547
}
545548
}
549+
550+
// createOSResourceValidator creates a validation function for OS resource comparison.
551+
func createOSResourceValidator(expected *os_v1.OperatingSystemResource) func(*testing.T, *os_v1.OperatingSystemResource) {
552+
return func(t *testing.T, osRes *os_v1.OperatingSystemResource) {
553+
t.Helper()
554+
assert.Equal(t, expected.GetResourceId(), osRes.GetResourceId())
555+
assert.Equal(t, expected.GetName(), osRes.GetName())
556+
assert.Equal(t, expected.GetProfileName(), osRes.GetProfileName())
557+
assert.Equal(t, expected.GetImageId(), osRes.GetImageId())
558+
assert.Equal(t, expected.GetProfileVersion(), osRes.GetProfileVersion())
559+
assert.Equal(t, expected.GetSha256(), osRes.GetSha256())
560+
assert.Equal(t, expected.GetSecurityFeature(), osRes.GetSecurityFeature())
561+
assert.Equal(t, expected.GetOsType(), osRes.GetOsType())
562+
}
563+
}
564+
565+
func TestInvClient_GetOSResourceByID(t *testing.T) {
566+
dao := inv_testing.NewInvResourceDAOOrFail(t)
567+
ctx := context.TODO()
568+
client := inv_testing.TestClients[inv_testing.RMClient].GetTenantAwareInventoryClient()
569+
570+
// Create test OS resources
571+
osRes1 := dao.CreateOsWithOpts(t, mm_testing.Tenant1, true, func(os *os_v1.OperatingSystemResource) {
572+
os.Sha256 = inv_testing.GenerateRandomSha256()
573+
os.Name = "Test OS Resource 1"
574+
os.ProfileName = "test-profile-1"
575+
os.ImageId = "test-image-1.0.0"
576+
os.ProfileVersion = "1.0.0"
577+
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_SECURE_BOOT_AND_FULL_DISK_ENCRYPTION
578+
os.OsType = os_v1.OsType_OS_TYPE_IMMUTABLE
579+
})
580+
581+
osRes2 := dao.CreateOsWithOpts(t, mm_testing.Tenant1, true, func(os *os_v1.OperatingSystemResource) {
582+
os.Sha256 = inv_testing.GenerateRandomSha256()
583+
os.Name = "Test OS Resource 2"
584+
os.ProfileName = "test-profile-2"
585+
os.ImageId = "test-image-2.0.0"
586+
os.ProfileVersion = "2.0.0"
587+
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_NONE
588+
os.OsType = os_v1.OsType_OS_TYPE_MUTABLE
589+
})
590+
591+
tests := []struct {
592+
name string
593+
osResourceID string
594+
expectError bool
595+
expectedCode codes.Code
596+
validateFunc func(*testing.T, *os_v1.OperatingSystemResource)
597+
}{
598+
{
599+
name: "SuccessGetImmutableOS",
600+
osResourceID: osRes1.GetResourceId(),
601+
expectError: false,
602+
validateFunc: createOSResourceValidator(osRes1),
603+
},
604+
{
605+
name: "SuccessGetMutableOS",
606+
osResourceID: osRes2.GetResourceId(),
607+
expectError: false,
608+
validateFunc: createOSResourceValidator(osRes2),
609+
},
610+
{
611+
name: "ErrorNonExistentOSResource",
612+
osResourceID: "os-nonexistent-12345678",
613+
expectError: true,
614+
expectedCode: codes.NotFound,
615+
},
616+
{
617+
name: "ErrorEmptyOSResourceID",
618+
osResourceID: "",
619+
expectError: true,
620+
expectedCode: codes.InvalidArgument,
621+
},
622+
{
623+
name: "ErrorInvalidOSResourceID",
624+
osResourceID: "invalid-resource-id",
625+
expectError: true,
626+
expectedCode: codes.InvalidArgument,
627+
},
628+
}
629+
630+
for _, tt := range tests {
631+
t.Run(tt.name, func(t *testing.T) {
632+
osRes, err := invclient.GetOSResourceByID(ctx, client, mm_testing.Tenant1, tt.osResourceID)
633+
634+
if tt.expectError {
635+
require.Error(t, err)
636+
assert.Equal(t, tt.expectedCode, status.Code(err))
637+
assert.Nil(t, osRes)
638+
} else {
639+
require.NoError(t, err)
640+
require.NotNil(t, osRes)
641+
if tt.validateFunc != nil {
642+
tt.validateFunc(t, osRes)
643+
}
644+
}
645+
})
646+
}
647+
}
648+
649+
func TestInvClient_GetOSResourceByID_ErrorCases(t *testing.T) {
650+
dao := inv_testing.NewInvResourceDAOOrFail(t)
651+
ctx := context.TODO()
652+
client := inv_testing.TestClients[inv_testing.RMClient].GetTenantAwareInventoryClient()
653+
654+
// Create a test OS resource for some tests
655+
osRes1 := dao.CreateOsWithOpts(t, mm_testing.Tenant1, true, func(os *os_v1.OperatingSystemResource) {
656+
os.Sha256 = inv_testing.GenerateRandomSha256()
657+
os.Name = "Test OS Resource 1"
658+
os.ProfileName = "test-profile-1"
659+
os.ImageId = "test-image-1.0.0"
660+
os.ProfileVersion = "1.0.0"
661+
os.SecurityFeature = os_v1.SecurityFeature_SECURITY_FEATURE_SECURE_BOOT_AND_FULL_DISK_ENCRYPTION
662+
os.OsType = os_v1.OsType_OS_TYPE_IMMUTABLE
663+
})
664+
665+
// Test with different tenant
666+
t.Run("ErrorWrongTenant", func(t *testing.T) {
667+
_, err := invclient.GetOSResourceByID(ctx, client, "wrong-tenant-id", osRes1.GetResourceId())
668+
require.Error(t, err)
669+
sts, _ := status.FromError(err)
670+
assert.Equal(t, codes.NotFound, sts.Code())
671+
})
672+
673+
// Test context timeout
674+
t.Run("ContextTimeout", func(t *testing.T) {
675+
timeoutCtx, cancel := context.WithTimeout(context.Background(), 1*time.Nanosecond)
676+
defer cancel()
677+
time.Sleep(1 * time.Millisecond) // Ensure context is expired
678+
679+
_, err := invclient.GetOSResourceByID(timeoutCtx, client, mm_testing.Tenant1, osRes1.GetResourceId())
680+
require.Error(t, err)
681+
sts, _ := status.FromError(err)
682+
assert.Equal(t, codes.DeadlineExceeded, sts.Code())
683+
})
684+
}

maintenance/pkg/maintmgr/southbound_handler.go

Lines changed: 44 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -12,8 +12,10 @@ import (
1212

1313
computev1 "github.com/open-edge-platform/infra-core/inventory/v2/pkg/api/compute/v1"
1414
os_v1 "github.com/open-edge-platform/infra-core/inventory/v2/pkg/api/os/v1"
15+
statusv1 "github.com/open-edge-platform/infra-core/inventory/v2/pkg/api/status/v1"
1516
inv_client "github.com/open-edge-platform/infra-core/inventory/v2/pkg/client"
1617
"github.com/open-edge-platform/infra-core/inventory/v2/pkg/errors"
18+
inv_status "github.com/open-edge-platform/infra-core/inventory/v2/pkg/status"
1719
pb "github.com/open-edge-platform/infra-managers/maintenance/pkg/api/maintmgr/v1"
1820
"github.com/open-edge-platform/infra-managers/maintenance/pkg/invclient"
1921
maintgmr_util "github.com/open-edge-platform/infra-managers/maintenance/pkg/utils"
@@ -43,8 +45,17 @@ func updateInstanceInInv(
4345
return
4446
}
4547

48+
zlog.Debug().Msgf("New OS Resource ID: %s", newOSResID)
49+
50+
newExistingCVEs, err := GetNewExistingCVEs(ctx, client, tenantID, newOSResID, instRes, newInstUpStatus)
51+
if err != nil {
52+
// Return and continue in case of errors
53+
zlog.InfraSec().Warn().Err(err).Msgf("Failed to get new existing CVEs")
54+
return
55+
}
56+
4657
err = invclient.UpdateInstance(ctx, client, tenantID, instRes.GetResourceId(),
47-
*newInstUpStatus, newUpdateStatusDetail, newOSResID)
58+
*newInstUpStatus, newUpdateStatusDetail, newOSResID, newExistingCVEs)
4859
if err != nil {
4960
// Return and continue in case of errors
5061
zlog.InfraSec().Warn().Err(err).Msgf("Failed to update Instance Status")
@@ -234,3 +245,35 @@ func updateOSUpdateRun(
234245
newUpdateStatus, maintgmr_util.GetUpdateStatusFromInstance(instRes))
235246
}
236247
}
248+
249+
// GetNewExistingCVEs retrieves the new existing CVEs based on the new OS Resource ID.
250+
// or from the current OS resource if no new OS Resource ID is provided.
251+
func GetNewExistingCVEs(
252+
ctx context.Context,
253+
client inv_client.TenantAwareInventoryClient,
254+
tenantID string,
255+
newOSResID string,
256+
instRes *computev1.InstanceResource,
257+
newInstUpStatus *inv_status.ResourceStatus,
258+
) (string, error) {
259+
var newExistingCVEs string
260+
261+
if newOSResID == "" {
262+
// If newOSResID is os zero length, it means there is no new OS Resource update and
263+
// also if Instance Status is getting updated from UnSpecified to Idle then only
264+
// copy existing CVEs from existing OS resource
265+
if instRes.GetUpdateStatusIndicator() == statusv1.StatusIndication_STATUS_INDICATION_UNSPECIFIED &&
266+
newInstUpStatus.StatusIndicator == statusv1.StatusIndication_STATUS_INDICATION_IDLE {
267+
newExistingCVEs = instRes.GetOs().GetExistingCves()
268+
}
269+
} else {
270+
// Fetch the new existing CVEs after fetching the new OS Resource based on the newOSResID
271+
newOSResource, osErr := invclient.GetOSResourceByID(ctx, client, tenantID, newOSResID)
272+
if osErr != nil {
273+
return "", osErr
274+
}
275+
newExistingCVEs = newOSResource.GetExistingCves()
276+
}
277+
278+
return newExistingCVEs, nil
279+
}

0 commit comments

Comments
 (0)