Skip to content
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
5 changes: 5 additions & 0 deletions in-band-manageability/configs/inbd_schema.json
Original file line number Diff line number Diff line change
Expand Up @@ -16,6 +16,11 @@
"proceedWithoutRollback": {
"type": "boolean",
"description": "Indicates whether to proceed with updates without rollback support."
},
"installIdleTimeoutSeconds": {
"type": "integer",
"minimum": 0,
"description": "Maximum idle duration without install output/progress, in seconds."
}
},
"required": ["trustedRepositories", "proceedWithoutRollback"],
Expand Down
3 changes: 2 additions & 1 deletion in-band-manageability/configs/intel_manageability.conf
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
{
"os_updater": {
"trustedRepositories": [],
"proceedWithoutRollback": true
"proceedWithoutRollback": true,
"installIdleTimeoutSeconds": 3600
}
}
3 changes: 1 addition & 2 deletions in-band-manageability/internal/inbc/commands/constants.go
Original file line number Diff line number Diff line change
Expand Up @@ -9,8 +9,7 @@ package commands
// Context Timeouts
const clientDialTimeoutInSeconds = 5
const sourceTimeoutInSeconds = 15
const emtSoftwareUpdateTimerInSeconds = 1200
const defaultSoftwareUpdateTimerInSeconds = 660
const emtSoftwareUpdateTimerInSeconds = 1800
const configTimeoutInSeconds = 15
const firmwareUpdateTimerInSeconds = 90
const queryTimeoutInSeconds = 15
11 changes: 5 additions & 6 deletions in-band-manageability/internal/inbc/commands/sota.go
Original file line number Diff line number Diff line change
Expand Up @@ -122,15 +122,14 @@ func handleSOTA(
return fmt.Errorf("error detected OS type: %v", err)
}

var timeout time.Duration = defaultSoftwareUpdateTimerInSeconds
updateCtx := context.Background()
if os == "EMT" {
timeout = emtSoftwareUpdateTimerInSeconds
var updateCancel context.CancelFunc
updateCtx, updateCancel = context.WithTimeout(context.Background(), emtSoftwareUpdateTimerInSeconds*time.Second)
defer updateCancel()
}

ctx, cancel = context.WithTimeout(context.Background(), timeout*time.Second)
defer cancel()

resp, err := client.UpdateSystemSoftware(ctx, request)
resp, err := client.UpdateSystemSoftware(updateCtx, request)
if err != nil {
return fmt.Errorf("error updating system software: %v", err)
}
Expand Down
50 changes: 50 additions & 0 deletions in-band-manageability/internal/inbc/commands/sota_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@ import (
"context"
"errors"
"testing"
"time"

pb "github.com/open-edge-platform/edge-node-agents/in-band-manageability/pkg/api/inbd/v1"
"github.com/spf13/cobra"
Expand Down Expand Up @@ -180,4 +181,53 @@ func TestHandleSOTA(t *testing.T) {
err := handleSOTA(&socket, &url, &releaseDate, &mode, &reboot, &packageList, &signature, detectOS, dialer)(cmd, args)
assert.NoError(t, err, "handleSOTA should not return an error even if Close fails")
})

t.Run("SOTA request context has no deadline", func(t *testing.T) {
mockClient := new(MockInbServiceClient)
mockClient.On("UpdateSystemSoftware", mock.Anything, mock.Anything, mock.Anything).
Run(func(args mock.Arguments) {
ctxArg, ok := args.Get(0).(context.Context)
assert.True(t, ok)
_, hasDeadline := ctxArg.Deadline()
assert.False(t, hasDeadline)
}).
Return(&pb.UpdateResponse{StatusCode: 200, Error: ""}, nil)

dialer := func(ctx context.Context, socket string) (pb.InbServiceClient, grpc.ClientConnInterface, error) {
return MockDialer(ctx, socket, mockClient, false)
}
detectOS := func() (string, error) {
return "ubuntu", nil
}

err := handleSOTA(&socket, &url, &releaseDate, &mode, &reboot, &packageList, &signature, detectOS, dialer)(cmd, args)
assert.NoError(t, err)
mockClient.AssertExpectations(t)
})

t.Run("SOTA request context has EMT deadline", func(t *testing.T) {
mockClient := new(MockInbServiceClient)
mockClient.On("UpdateSystemSoftware", mock.Anything, mock.Anything, mock.Anything).
Run(func(args mock.Arguments) {
ctxArg, ok := args.Get(0).(context.Context)
assert.True(t, ok)
deadline, hasDeadline := ctxArg.Deadline()
assert.True(t, hasDeadline)
remaining := time.Until(deadline)
assert.Greater(t, remaining, 0*time.Second)
assert.LessOrEqual(t, remaining, emtSoftwareUpdateTimerInSeconds*time.Second)
}).
Return(&pb.UpdateResponse{StatusCode: 200, Error: ""}, nil)

dialer := func(ctx context.Context, socket string) (pb.InbServiceClient, grpc.ClientConnInterface, error) {
return MockDialer(ctx, socket, mockClient, false)
}
detectOS := func() (string, error) {
return "EMT", nil
}

err := handleSOTA(&socket, &url, &releaseDate, &mode, &reboot, &packageList, &signature, detectOS, dialer)(cmd, args)
assert.NoError(t, err)
mockClient.AssertExpectations(t)
})
}
31 changes: 26 additions & 5 deletions in-band-manageability/internal/inbd/utils/config.go
Original file line number Diff line number Diff line change
Expand Up @@ -11,18 +11,25 @@ import (
"fmt"
"log"
"strings"
"time"

"github.com/spf13/afero"
)

// Configurations represents the structure of the XML configuration file
// OSUpdaterConfig represents the OS updater section of the config file.
type OSUpdaterConfig struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
InstallIdleTimeoutSeconds int `json:"installIdleTimeoutSeconds"`
}

// Configurations represents the structure of the JSON configuration file.
type Configurations struct {
OSUpdater struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
} `json:"os_updater"`
OSUpdater OSUpdaterConfig `json:"os_updater"`
}

const defaultInstallIdleTimeout = 60 * time.Minute

// LoadConfig loads the XML configuration file
func LoadConfig(fs afero.Fs, filePath string) (*Configurations, error) {
var content []byte
Expand Down Expand Up @@ -66,3 +73,17 @@ func IsProceedWithoutRollback(config *Configurations) bool {
log.Printf("Checking if rollback is allowed")
return config.OSUpdater.ProceedWithoutRollback
}

// GetInstallIdleTimeout converts os_updater.installIdleTimeoutSeconds into a duration.
// Empty value falls back to the default idle watchdog timeout.
func GetInstallIdleTimeout(config *Configurations) (time.Duration, error) {
if config == nil || config.OSUpdater.InstallIdleTimeoutSeconds == 0 {
return defaultInstallIdleTimeout, nil
}

if config.OSUpdater.InstallIdleTimeoutSeconds < 0 {
return 0, fmt.Errorf("invalid os_updater.installIdleTimeoutSeconds: must be >= 0")
}

return time.Duration(config.OSUpdater.InstallIdleTimeoutSeconds) * time.Second, nil
}
44 changes: 32 additions & 12 deletions in-band-manageability/internal/inbd/utils/config_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,7 @@ package utils

import (
"testing"
"time"

"github.com/spf13/afero"
"github.com/stretchr/testify/assert"
Expand Down Expand Up @@ -61,10 +62,7 @@ func TestLoadConfig(t *testing.T) {

func TestIsTrustedRepository(t *testing.T) {
config := &Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: OSUpdaterConfig{
TrustedRepositories: []string{
"https://example.com/repo1",
"https://example.com/repo2"},
Expand Down Expand Up @@ -93,10 +91,7 @@ func TestIsTrustedRepository(t *testing.T) {
func TestIsProceedWithoutRollback_True(t *testing.T) {
// Create a configuration where ProceedWithoutRollback is true
config := &Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: OSUpdaterConfig{
ProceedWithoutRollback: true,
},
}
Expand All @@ -111,10 +106,7 @@ func TestIsProceedWithoutRollback_True(t *testing.T) {
func TestIsProceedWithoutRollback_False(t *testing.T) {
// Create a configuration where ProceedWithoutRollback is false
config := &Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: OSUpdaterConfig{
ProceedWithoutRollback: false,
},
}
Expand All @@ -125,3 +117,31 @@ func TestIsProceedWithoutRollback_False(t *testing.T) {
// Assertions
assert.False(t, result, "Expected rollback to be disallowed")
}

func TestGetInstallIdleTimeout(t *testing.T) {
t.Run("empty value disabled", func(t *testing.T) {
cfg := &Configurations{OSUpdater: OSUpdaterConfig{}}
idleTimeout, err := GetInstallIdleTimeout(cfg)
assert.NoError(t, err)
assert.Equal(t, 60*time.Minute, idleTimeout)
})

t.Run("valid duration", func(t *testing.T) {
cfg := &Configurations{OSUpdater: OSUpdaterConfig{InstallIdleTimeoutSeconds: 2700}}
idleTimeout, err := GetInstallIdleTimeout(cfg)
assert.NoError(t, err)
assert.Equal(t, 45*time.Minute, idleTimeout)
})

t.Run("invalid duration", func(t *testing.T) {
cfg := &Configurations{OSUpdater: OSUpdaterConfig{InstallIdleTimeoutSeconds: -1}}
_, err := GetInstallIdleTimeout(cfg)
assert.ErrorContains(t, err, "invalid os_updater.installIdleTimeoutSeconds")
})

t.Run("negative duration", func(t *testing.T) {
cfg := &Configurations{OSUpdater: OSUpdaterConfig{InstallIdleTimeoutSeconds: -1}}
_, err := GetInstallIdleTimeout(cfg)
assert.ErrorContains(t, err, "must be >= 0")
})
}
Original file line number Diff line number Diff line change
Expand Up @@ -37,10 +37,7 @@ func TestAdd(t *testing.T) {
},
loadConfigFunc: func(afero.Fs, string) (*utils.Configurations, error) {
return &utils.Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: utils.OSUpdaterConfig{
TrustedRepositories: []string{
"https://example.com/repo1",
"https://example.com/repo2",
Expand Down Expand Up @@ -86,10 +83,7 @@ func TestAdd(t *testing.T) {
adder: &Adder{
loadConfigFunc: func(afero.Fs, string) (*utils.Configurations, error) {
return &utils.Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: utils.OSUpdaterConfig{
TrustedRepositories: []string{
"https://example.com/repo1",
"https://example.com/repo2",
Expand All @@ -115,10 +109,7 @@ func TestAdd(t *testing.T) {
adder: &Adder{
loadConfigFunc: func(afero.Fs, string) (*utils.Configurations, error) {
return &utils.Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: utils.OSUpdaterConfig{
TrustedRepositories: []string{
"https://example.com/repo1",
"https://example.com/repo2",
Expand Down Expand Up @@ -150,10 +141,7 @@ func TestAdd(t *testing.T) {
},
loadConfigFunc: func(fs afero.Fs, path string) (*utils.Configurations, error) {
return &utils.Configurations{
OSUpdater: struct {
TrustedRepositories []string `json:"trustedRepositories"`
ProceedWithoutRollback bool `json:"proceedWithoutRollback"`
}{
OSUpdater: utils.OSUpdaterConfig{
TrustedRepositories: []string{
"https://example.com/repo1",
"https://example.com/repo2",
Expand Down
Loading