From 2d1b94df75873da55f07392765feefd684c7c546 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 17 Jul 2025 10:01:52 +0000 Subject: [PATCH 01/40] Implement GRPC Connection --- .../platform-manageability-agent.go | 8 ++ platform-manageability-agent/go.mod | 11 ++- platform-manageability-agent/go.sum | 18 +++- .../internal/comms/comms.go | 83 +++++++++++++++++++ 4 files changed, 116 insertions(+), 4 deletions(-) create mode 100644 platform-manageability-agent/internal/comms/comms.go diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 43c6160a..a9bf2370 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -15,7 +15,9 @@ import ( "time" "github.com/open-edge-platform/edge-node-agents/common/pkg/metrics" + auth "github.com/open-edge-platform/edge-node-agents/common/pkg/utils" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/info" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/comms" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" "github.com/sirupsen/logrus" ) @@ -117,6 +119,12 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err log.Info("Platform Manageability Agent started successfully") + tlsConfig, err := auth.GetAuthConfig(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), nil) + if err != nil { + log.Fatalf("TLS configuration creation failed! Error: %v", err) + } + dmManager := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) + // Main agent loop using context-aware ticker ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() diff --git a/platform-manageability-agent/go.mod b/platform-manageability-agent/go.mod index 4bcd85cb..6e84896a 100644 --- a/platform-manageability-agent/go.mod +++ b/platform-manageability-agent/go.mod @@ -1,14 +1,21 @@ module github.com/open-edge-platform/edge-node-agents/platform-manageability-agent -go 1.24.1 +go 1.24.4 + +toolchain go1.24.5 require ( + github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 github.com/open-edge-platform/edge-node-agents/common v1.7.1 + github.com/open-edge-platform/infra-external/dm-manager v0.2.2 github.com/sirupsen/logrus v1.9.3 + go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 + google.golang.org/grpc v1.73.0 gopkg.in/yaml.v3 v3.0.1 ) require ( + cloud.google.com/go/compute/metadata v0.6.0 // indirect github.com/cenkalti/backoff/v5 v5.0.2 // indirect github.com/ebitengine/purego v0.8.4 // indirect github.com/go-logr/logr v1.4.3 // indirect @@ -32,10 +39,10 @@ require ( go.opentelemetry.io/otel/trace v1.37.0 // indirect go.opentelemetry.io/proto/otlp v1.7.0 // indirect golang.org/x/net v0.41.0 // indirect + golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect - google.golang.org/grpc v1.73.0 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/platform-manageability-agent/go.sum b/platform-manageability-agent/go.sum index 52e5e59b..cea462f3 100644 --- a/platform-manageability-agent/go.sum +++ b/platform-manageability-agent/go.sum @@ -1,8 +1,11 @@ +cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= +cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= -github.com/davecgh/go-spew v1.1.1 h1:vj9j/u1bqnvCEfJOwUhtlOARqs3+rkHYY13jYWTU97c= github.com/davecgh/go-spew v1.1.1/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc h1:U9qPSI2PIWSS1VwoXQT9A3Wy9MM3WgvqSxFWenqJduM= +github.com/davecgh/go-spew v1.1.2-0.20180830191138-d8f796af33cc/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= github.com/ebitengine/purego v0.8.4 h1:CF7LEKg5FFOsASUj0+QwaXf8Ht6TlFxg09+S9wz0omw= github.com/ebitengine/purego v0.8.4/go.mod h1:iIjxzd6CiRiOG0UyXP+V1+jWqUXVjPKLAI0mRfJZTmQ= github.com/go-logr/logr v1.2.2/go.mod h1:jdQByPbusPIv2/zmleS9BjJVeZ6kBagPoEUsqbVz/1A= @@ -19,6 +22,8 @@ github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= github.com/google/go-cmp v0.7.0/go.mod h1:pXiqmnSA92OHEEa9HXL2W4E7lf9JzCmGVUdgjX3N/iU= github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= +github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= @@ -29,8 +34,11 @@ github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 h1:PpXWgLPs+Fqr32 github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35/go.mod h1:autxFIvghDt3jPTLoqZ9OZ7s9qTGNAWmYCjVFWPX/zg= github.com/open-edge-platform/edge-node-agents/common v1.7.1 h1:6BrEqf5w9rTk3a4XBuWyAuFnGr0/NW5+8mxjZyA4lfs= github.com/open-edge-platform/edge-node-agents/common v1.7.1/go.mod h1:MbjzFRp5qIY7MxhN2vfC0CE4d/xGwPgfzcBS6ABsHX4= -github.com/pmezard/go-difflib v1.0.0 h1:4DBwDE0NGyQoBHbLQYPwSUPoCMWR5BEzIk/f1lZbAQM= +github.com/open-edge-platform/infra-external/dm-manager v0.2.2 h1:hy7/jTqzgrVPcyeo+QH+pVX44iffN+HoxCT2Z0lxFzw= +github.com/open-edge-platform/infra-external/dm-manager v0.2.2/go.mod h1:9PoYl/Z+994IeWTD1D4WGdFc/Feje9afT3iw3FBbqiQ= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= +github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 h1:o4JXh1EVt9k/+g42oCprj/FisM4qX9L3sZB3upGN2ZU= github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55/go.mod h1:OmDBASR4679mdNQnz2pUhc2G8CO2JrUAVFDRBDP/hJE= github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= @@ -51,6 +59,8 @@ github.com/yusufpapurcu/wmi v1.2.4 h1:zFUKzehAFReQwLys1b/iSMl+JQGSCSjtVqQn9bBrPo github.com/yusufpapurcu/wmi v1.2.4/go.mod h1:SBZ9tNy3G9/m5Oi98Zks0QjeHVDvuK0qfxQmPyzfmi0= go.opentelemetry.io/auto/sdk v1.1.0 h1:cH53jehLUN6UFLY71z+NDOiNJqDdPRaXzTel0sJySYA= go.opentelemetry.io/auto/sdk v1.1.0/go.mod h1:3wSPjt5PWp2RhlCcmmOial7AvC4DQqZb7a7wCow3W8A= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0 h1:rbRJ8BBoVMsQShESYZ0FkvcITu8X8QNwJogcLUmDNNw= +go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc v0.62.0/go.mod h1:ru6KHrNtNHxM4nD/vd6QrLVWgKhxPYgblq4VAtNawTQ= go.opentelemetry.io/contrib/instrumentation/host v0.62.0 h1:hAVkLihKCrIkiX/cUvY0qn6yi0uMdr1/zWpb7lEjdYY= go.opentelemetry.io/contrib/instrumentation/host v0.62.0/go.mod h1:GiuKDIEAJPhz+D9gApgUxthEVmwC29T73Eg158qBT2g= go.opentelemetry.io/otel v1.37.0 h1:9zhNfelUvx0KBfu/gb+ZgeAfAgtWrfHJZcAqFC228wQ= @@ -67,8 +77,12 @@ go.opentelemetry.io/otel/trace v1.37.0 h1:HLdcFNbRQBE2imdSEgm/kwqmQj1Or1l/7bW6mx go.opentelemetry.io/otel/trace v1.37.0/go.mod h1:TlgrlQ+PtQO5XFerSPUYG0JSgGyryXewPGyayAWSBS0= go.opentelemetry.io/proto/otlp v1.7.0 h1:jX1VolD6nHuFzOYso2E73H85i92Mv8JQYk0K9vz09os= go.opentelemetry.io/proto/otlp v1.7.0/go.mod h1:fSKjH6YJ7HDlwzltzyMj036AJ3ejJLCgCSHGj4efDDo= +go.uber.org/goleak v1.3.0 h1:2K3zAYmnTNqV73imy9J1T3WC+gmCePx2hEGkimedGto= +go.uber.org/goleak v1.3.0/go.mod h1:CoHD4mav9JJNrW/WLlf7HGZPjdw8EucARQHekz1X6bE= golang.org/x/net v0.41.0 h1:vBTly1HeNPEn3wtREYfy4GZ/NECgw2Cnl+nK6Nz3uvw= golang.org/x/net v0.41.0/go.mod h1:B/K4NNqkfmg07DQYrbwvSluqCJOOXwUjeb/5lOisjbA= +golang.org/x/oauth2 v0.30.0 h1:dnDm7JmhM45NNpd8FDDeLhK6FwqbOf4MLCM9zb1BOHI= +golang.org/x/oauth2 v0.30.0/go.mod h1:B++QgG3ZKulg6sRPGD/mqlHQs5rB3Ml9erfeDY7xKlU= golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go new file mode 100644 index 00000000..c784a971 --- /dev/null +++ b/platform-manageability-agent/internal/comms/comms.go @@ -0,0 +1,83 @@ +package comms + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "time" + + "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout" + "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + + log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" + dm "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" +) + +const retryInterval = 10 * time.Second +const tickerInterval = 500 * time.Millisecond +const connTimeout = 5 * time.Second + +type Client struct { + DMMgrServiceAddr string + Dialer grpc.DialOption + Transport grpc.DialOption + GrpcConn *grpc.ClientConn + DMMgrClient dm.DeviceManagementClient + RetryInterval time.Duration +} + +func WithNetworkDialer(serviceAddr string) func(*Client) { + return func(s *Client) { + s.Dialer = grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { + return net.Dial("tcp", serviceAddr) + }) + } +} + +func NewClient(serviceURL string, tlsConfig *tls.Config) *Client { + cli := &Client{} + cli.DMMgrServiceAddr = serviceURL + cli.RetryInterval = retryInterval + cli.Transport = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) + + WithNetworkDialer(cli.DMMgrServiceAddr)(cli) + return cli +} + +func (cli *Client) Connect(ctx context.Context) (err error) { + cli.GrpcConn, err = grpc.DialContext(ctx, cli.DMMgrServiceAddr, cli.Transport, cli.Dialer, //nolint:staticcheck + grpc.WithUnaryInterceptor(timeout.UnaryClientInterceptor(connTimeout)), + grpc.WithStatsHandler(otelgrpc.NewClientHandler())) + if err != nil { + return fmt.Errorf("connection to %v failed: %v", cli.DMMgrServiceAddr, err) + } + cli.DMMgrClient = dm.NewDeviceManagementClient(cli.GrpcConn) + return nil +} + +func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls.Config) *Client { + dmMgr := NewClient(serviceAddr, tlsConfig) + + cyclicalTicker := time.NewTicker(tickerInterval) + defer cyclicalTicker.Stop() + + for { + select { + case <-ctx.Done(): + log.Logger.Info("Connecting to DM Manager has been cancelled") + return nil + case <-cyclicalTicker.C: + err := dmMgr.Connect(ctx) + if err != nil { + log.Logger.Warnf("Can't connect to DM Manager, retrying: %v", err) + time.Sleep(dmMgr.RetryInterval) + continue + } + log.Logger.Info("Successfully connected to DM Manager") + return dmMgr + } + } +} From 4a7c2559761bf1422f8d89580c48390f20991a9d Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Jul 2025 08:00:41 +0000 Subject: [PATCH 02/40] Add API implementation --- .../platform-manageability-agent.go | 4 +- .../internal/comms/comms.go | 106 +++++++++++++++++- .../internal/config/config.go | 1 + 3 files changed, 107 insertions(+), 4 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index a9bf2370..b0c477de 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -123,7 +123,9 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err if err != nil { log.Fatalf("TLS configuration creation failed! Error: %v", err) } - dmManager := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) + + // TODO: Call the APIs using the client created here. + _ = comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) // Main agent loop using context-aware ticker ticker := time.NewTicker(30 * time.Second) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index c784a971..b3c19e0e 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -1,10 +1,13 @@ package comms import ( + "bufio" "context" "crypto/tls" "fmt" "net" + "os/exec" + "strings" "time" "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout" @@ -12,8 +15,9 @@ import ( "google.golang.org/grpc" "google.golang.org/grpc/credentials" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" - dm "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" + pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) const retryInterval = 10 * time.Second @@ -25,7 +29,7 @@ type Client struct { Dialer grpc.DialOption Transport grpc.DialOption GrpcConn *grpc.ClientConn - DMMgrClient dm.DeviceManagementClient + DMMgrClient pb.DeviceManagementClient RetryInterval time.Duration } @@ -54,7 +58,7 @@ func (cli *Client) Connect(ctx context.Context) (err error) { if err != nil { return fmt.Errorf("connection to %v failed: %v", cli.DMMgrServiceAddr, err) } - cli.DMMgrClient = dm.NewDeviceManagementClient(cli.GrpcConn) + cli.DMMgrClient = pb.NewDeviceManagementClient(cli.GrpcConn) return nil } @@ -81,3 +85,99 @@ func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls. } } } + +// Open Questions: +// 1. I'm setting UUID in the hostID field, is this the correct approach or should we use a different identifier? +// 2. If Version is empty, should we set it to a default value or leave it empty? +// 3. If Version is not found in the output, should we simply set status to Disabled? +// 4. Should we handle the case where rpc amtinfo command fails and send the error message to dm-manager? + +// ParseAMTInfo parses the output of the `rpc amtinfo` command and populates the AMTStatusRequest. +func ParseAMTInfo(output string) (*pb.AMTStatusRequest, error) { + var ( + status = pb.AMTStatus_DISABLED + version string + ) + cmd := exec.Command("sudo", ".dmidecode", "-s", "system-uuid") + uuid, err := cmd.Output() + if err != nil { + return nil, fmt.Errorf("failed to retrieve UUID: %v", err) + } + scanner := bufio.NewScanner(strings.NewReader(output)) + for scanner.Scan() { + line := scanner.Text() + + if strings.HasPrefix(line, "Version") { + parts := strings.Split(line, ":") + if len(parts) > 1 { + version = strings.TrimSpace(parts[1]) + status = pb.AMTStatus_ENABLED + } + } + } + req := &pb.AMTStatusRequest{ + HostId: string(uuid), + Status: status, + Version: version, + } + return req, nil +} + +// ReportAMTStatus executes the `rpc amtinfo` command, parses the output, and sends the AMT status to the server. +func (cli *Client) ReportAMTStatus(ctx context.Context) error { + cmd := exec.Command("sudo", "./rpc", "amtinfo") + output, err := cmd.Output() + if err != nil { + errMsg := fmt.Sprintf("Failed to execute `rpc amtinfo` command: %v", err) + cli.ReportErrorToDMManager(ctx, errMsg) + return err + } + req, err := ParseAMTInfo(string(output)) + if err != nil { + errMsg := fmt.Sprintf("Failed to parse `rpc amtinfo` output: %v", err) + cli.ReportErrorToDMManager(ctx, errMsg) + return err + } + _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) + if err != nil { + return fmt.Errorf("failed to report AMT status: %v", err) + } + log.Logger.Info("Successfully reported AMT status") + return nil +} + +func (cli *Client) ReportErrorToDMManager(ctx context.Context, errMsg string) { + req := &pb.AMTStatusRequest{ + // Add field to send error message. + } + _, err := cli.DMMgrClient.ReportAMTStatus(ctx, req) + if err != nil { + log.Logger.Errorf("Failed to report error to DM Manager: %v", err) + } +} + +// RetrieveActivationDetails retrieves activation details and executes the activation command if required. +func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, conf *config.Config) error { + req := &pb.ActivationRequest{ + HostId: hostID, + } + resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) + if err != nil { + return fmt.Errorf("Failed to retrieve activation details: %v", err) + } + + log.Logger.Infof("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", + resp.HostId, resp.Operation, resp.ProfileName) + + if resp.Operation == pb.OperationType_ACTIVATE { + rpsAddress := fmt.Sprintf("wss://%s/activate", conf.RPSAddress) + cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName) + + output, err := cmd.CombinedOutput() + if err != nil { + return fmt.Errorf("Failed to execute activation command: %v, Output: %s", err, string(output)) + } + // TODO: Parse the output and send the activation result back to DM Manager. + } + return nil +} diff --git a/platform-manageability-agent/internal/config/config.go b/platform-manageability-agent/internal/config/config.go index aa7369d3..4116ad84 100644 --- a/platform-manageability-agent/internal/config/config.go +++ b/platform-manageability-agent/internal/config/config.go @@ -52,6 +52,7 @@ type Config struct { MetricsEndpoint string `yaml:"metricsEndpoint"` MetricsInterval time.Duration `yaml:"metricsInterval"` Auth ConfigAuth `yaml:"auth"` + RPSAddress string `yaml:"rpsAddress"` } func New(configPath string) (*Config, error) { From 30746f44a2afb151eb034442302f6108e31d94f0 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Jul 2025 10:05:54 +0000 Subject: [PATCH 03/40] Fix review comments --- .../internal/comms/comms.go | 65 +++++++++---------- .../internal/utils/utils.go | 42 ++++++++++++ 2 files changed, 71 insertions(+), 36 deletions(-) create mode 100644 platform-manageability-agent/internal/utils/utils.go diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index b3c19e0e..809e62a0 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -17,6 +17,7 @@ import ( "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/utils" pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) @@ -86,24 +87,14 @@ func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls. } } -// Open Questions: -// 1. I'm setting UUID in the hostID field, is this the correct approach or should we use a different identifier? -// 2. If Version is empty, should we set it to a default value or leave it empty? -// 3. If Version is not found in the output, should we simply set status to Disabled? -// 4. Should we handle the case where rpc amtinfo command fails and send the error message to dm-manager? - // ParseAMTInfo parses the output of the `rpc amtinfo` command and populates the AMTStatusRequest. -func ParseAMTInfo(output string) (*pb.AMTStatusRequest, error) { +func ParseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { var ( status = pb.AMTStatus_DISABLED version string ) - cmd := exec.Command("sudo", ".dmidecode", "-s", "system-uuid") - uuid, err := cmd.Output() - if err != nil { - return nil, fmt.Errorf("failed to retrieve UUID: %v", err) - } - scanner := bufio.NewScanner(strings.NewReader(output)) + + scanner := bufio.NewScanner(strings.NewReader(string(output))) for scanner.Scan() { line := scanner.Text() @@ -115,8 +106,9 @@ func ParseAMTInfo(output string) (*pb.AMTStatusRequest, error) { } } } + req := &pb.AMTStatusRequest{ - HostId: string(uuid), + HostId: strings.TrimSpace(string(uuid)), Status: status, Version: version, } @@ -125,35 +117,36 @@ func ParseAMTInfo(output string) (*pb.AMTStatusRequest, error) { // ReportAMTStatus executes the `rpc amtinfo` command, parses the output, and sends the AMT status to the server. func (cli *Client) ReportAMTStatus(ctx context.Context) error { - cmd := exec.Command("sudo", "./rpc", "amtinfo") - output, err := cmd.Output() + uuid, err := utils.GetSystemUUID() if err != nil { - errMsg := fmt.Sprintf("Failed to execute `rpc amtinfo` command: %v", err) - cli.ReportErrorToDMManager(ctx, errMsg) - return err + return fmt.Errorf("failed to retrieve UUID: %w", err) } - req, err := ParseAMTInfo(string(output)) + + output, err := utils.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) if err != nil { - errMsg := fmt.Sprintf("Failed to parse `rpc amtinfo` output: %v", err) - cli.ReportErrorToDMManager(ctx, errMsg) - return err + req := &pb.AMTStatusRequest{ + HostId: uuid, + Status: pb.AMTStatus_DISABLED, + Version: "", + } + _, reportErr := cli.DMMgrClient.ReportAMTStatus(ctx, req) + if reportErr != nil { + return fmt.Errorf("failed to report AMTStatus to DM Manager: %w", reportErr) + } + return fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) } - _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) + + req, err := ParseAMTInfo(uuid, output) if err != nil { - return fmt.Errorf("failed to report AMT status: %v", err) + return fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) } - log.Logger.Info("Successfully reported AMT status") - return nil -} -func (cli *Client) ReportErrorToDMManager(ctx context.Context, errMsg string) { - req := &pb.AMTStatusRequest{ - // Add field to send error message. - } - _, err := cli.DMMgrClient.ReportAMTStatus(ctx, req) + _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) if err != nil { - log.Logger.Errorf("Failed to report error to DM Manager: %v", err) + return fmt.Errorf("failed to report AMT status: %w", err) } + log.Logger.Info("Successfully reported AMT status") + return nil } // RetrieveActivationDetails retrieves activation details and executes the activation command if required. @@ -163,7 +156,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { - return fmt.Errorf("Failed to retrieve activation details: %v", err) + return fmt.Errorf("Failed to retrieve activation details: %w", err) } log.Logger.Infof("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", @@ -175,7 +168,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, output, err := cmd.CombinedOutput() if err != nil { - return fmt.Errorf("Failed to execute activation command: %v, Output: %s", err, string(output)) + return fmt.Errorf("failed to execute activation command: %w, Output: %s", err, string(output)) } // TODO: Parse the output and send the activation result back to DM Manager. } diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go new file mode 100644 index 00000000..96d4824c --- /dev/null +++ b/platform-manageability-agent/internal/utils/utils.go @@ -0,0 +1,42 @@ +package utils + +import ( + "fmt" + "os/exec" + "strings" + "time" + + log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" +) + +const ( + maxRetries = 3 + retryInterval = 5 * time.Second +) + +func ExecuteWithRetries(command string, args []string) ([]byte, error) { + var output []byte + var err error + + for i := 1; i <= maxRetries; i++ { + cmd := exec.Command(command, args...) + output, err = cmd.Output() + if err == nil { + return output, nil + } + log.Logger.Warnf("Failed to execute `%s` command (attempt %d/%d): %v", command, i, maxRetries, err) + if i < maxRetries { + time.Sleep(retryInterval) + } + } + return nil, fmt.Errorf("command `%s` failed after %d retries: %v", command, maxRetries, err) +} + +func GetSystemUUID() (string, error) { + cmd := exec.Command("sudo", "dmidecode", "-s", "system-uuid") + uuid, err := cmd.Output() + if err != nil { + return "", fmt.Errorf("failed to retrieve UUID: %v", err) + } + return strings.TrimSpace(string(uuid)), nil +} From 0b2cb7265ef6125b2e19fc47c8249d3dd4e3adba Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Jul 2025 10:10:53 +0000 Subject: [PATCH 04/40] Fix review comments --- platform-manageability-agent/internal/comms/comms.go | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 809e62a0..d3b1c913 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -87,8 +87,8 @@ func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls. } } -// ParseAMTInfo parses the output of the `rpc amtinfo` command and populates the AMTStatusRequest. -func ParseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { +// parseAMTInfo parses the output of the `rpc amtinfo` command and populates the AMTStatusRequest. +func parseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { var ( status = pb.AMTStatus_DISABLED version string @@ -136,7 +136,7 @@ func (cli *Client) ReportAMTStatus(ctx context.Context) error { return fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) } - req, err := ParseAMTInfo(uuid, output) + req, err := parseAMTInfo(uuid, output) if err != nil { return fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) } From c1c300d1b85b29df60041765bf237bfe2bf3c16c Mon Sep 17 00:00:00 2001 From: user Date: Fri, 18 Jul 2025 12:17:07 +0000 Subject: [PATCH 05/40] Add ticker for periodic activation check --- .../platform-manageability-agent.go | 51 +++++++++++++++++-- platform-manageability-agent/go.mod | 1 + platform-manageability-agent/go.sum | 2 + .../internal/comms/comms.go | 12 ++++- .../internal/utils/utils.go | 12 ++--- 5 files changed, 65 insertions(+), 13 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index b0c477de..e5c4c1ff 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -11,9 +11,11 @@ import ( "fmt" "os" "os/signal" + "sync/atomic" "syscall" "time" + "github.com/cenkalti/backoff/v4" "github.com/open-edge-platform/edge-node-agents/common/pkg/metrics" auth "github.com/open-edge-platform/edge-node-agents/common/pkg/utils" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/info" @@ -22,7 +24,10 @@ import ( "github.com/sirupsen/logrus" ) -const AGENT_NAME = "platform-manageability-agent" +const ( + AGENT_NAME = "platform-manageability-agent" + MAX_RETRIES = 3 +) func main() { if len(os.Args) == 2 && os.Args[1] == "version" { @@ -124,8 +129,39 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err log.Fatalf("TLS configuration creation failed! Error: %v", err) } - // TODO: Call the APIs using the client created here. - _ = comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) + dmMgrClient := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) + + // Open Questions: + // 1. Should we report AMTStatus periodically or just once at startup? + // 2. If perodic, then what if the system is never AMT enabled? Should there be max retries and exit if AMT is never enabled? + // 3. If there is an error in reporting AMT status, should we fatal after max retries? + err = dmMgrClient.ReportAMTStatus(ctx) + if err != nil { + log.Fatalf("AMT status report failed! Error: %v", err) + } + + var ( + lastActivationCheckTimestamp int64 + activationCheckInterval = 30 * time.Second // TODO: Make this configurable. + ) + + activationTicker := time.NewTicker(activationCheckInterval) + go func() { + op := func() error { + // TODO: Retrieve HostID. + return dmMgrClient.RetrieveActivationDetails(ctx, "", confs) + } + for { + select { + case <-ctx.Done(): + return + case <-activationTicker.C: + activationTicker.Stop() + updateWithRetry(ctx, log, op, &lastActivationCheckTimestamp) + } + activationTicker.Reset(activationCheckInterval) + } + }() // Main agent loop using context-aware ticker ticker := time.NewTicker(30 * time.Second) @@ -142,3 +178,12 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err } } } + +func updateWithRetry(ctx context.Context, log *logrus.Logger, op func() error, lastUpdateTimestamp *int64) { + err := backoff.Retry(op, backoff.WithMaxRetries(backoff.WithContext(backoff.NewExponentialBackOff(), ctx), MAX_RETRIES)) + if err != nil { + log.Errorf("Retry error: %v", err) + } else { + atomic.StoreInt64(lastUpdateTimestamp, time.Now().Unix()) + } +} diff --git a/platform-manageability-agent/go.mod b/platform-manageability-agent/go.mod index 6e84896a..82258476 100644 --- a/platform-manageability-agent/go.mod +++ b/platform-manageability-agent/go.mod @@ -5,6 +5,7 @@ go 1.24.4 toolchain go1.24.5 require ( + github.com/cenkalti/backoff/v4 v4.3.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 github.com/open-edge-platform/edge-node-agents/common v1.7.1 github.com/open-edge-platform/infra-external/dm-manager v0.2.2 diff --git a/platform-manageability-agent/go.sum b/platform-manageability-agent/go.sum index cea462f3..0bcf4e4d 100644 --- a/platform-manageability-agent/go.sum +++ b/platform-manageability-agent/go.sum @@ -1,5 +1,7 @@ cloud.google.com/go/compute/metadata v0.6.0 h1:A6hENjEsCDtC1k8byVsgwvVcioamEHvZ4j01OwKxG9I= cloud.google.com/go/compute/metadata v0.6.0/go.mod h1:FjyFAW1MW0C203CEOMDTu3Dk1FlqW3Rga40jzHL4hfg= +github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK343L8= +github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cenkalti/backoff/v5 v5.0.2 h1:rIfFVxEf1QsI7E1ZHfp/B4DF/6QBAUhmgkxc0H7Zss8= github.com/cenkalti/backoff/v5 v5.0.2/go.mod h1:rkhZdG3JZukswDf7f0cwqPNk4K0sa+F97BxZthm/crw= github.com/davecgh/go-spew v1.1.0/go.mod h1:J7Y8YcW2NihsgmVo/mv3lAwl/skON4iLHjSsI+c5H38= diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index d3b1c913..f4dda41a 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -57,7 +57,7 @@ func (cli *Client) Connect(ctx context.Context) (err error) { grpc.WithUnaryInterceptor(timeout.UnaryClientInterceptor(connTimeout)), grpc.WithStatsHandler(otelgrpc.NewClientHandler())) if err != nil { - return fmt.Errorf("connection to %v failed: %v", cli.DMMgrServiceAddr, err) + return fmt.Errorf("connection to %v failed: %w", cli.DMMgrServiceAddr, err) } cli.DMMgrClient = pb.NewDeviceManagementClient(cli.GrpcConn) return nil @@ -149,6 +149,14 @@ func (cli *Client) ReportAMTStatus(ctx context.Context) error { return nil } +// TODO: +// 1. Immplement polling logic to periodically query the API for +// operations that need to be performed once PMA is started. +// a. When operationType is activate, PMA should execute the activation command. +// b. When operationType is not activate, PMA should continue polling for the next operation. + +// 2. Implement logic to handle the activation command and report back the results to DM Manager. + // RetrieveActivationDetails retrieves activation details and executes the activation command if required. func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, conf *config.Config) error { req := &pb.ActivationRequest{ @@ -159,7 +167,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, return fmt.Errorf("Failed to retrieve activation details: %w", err) } - log.Logger.Infof("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", + log.Logger.Debugf("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", resp.HostId, resp.Operation, resp.ProfileName) if resp.Operation == pb.OperationType_ACTIVATE { diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index 96d4824c..6adb89e4 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -9,18 +9,14 @@ import ( log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" ) -const ( - maxRetries = 3 - retryInterval = 5 * time.Second -) - func ExecuteWithRetries(command string, args []string) ([]byte, error) { - var output []byte - var err error + maxRetries := 3 + retryInterval := 5 * time.Second + var err error for i := 1; i <= maxRetries; i++ { cmd := exec.Command(command, args...) - output, err = cmd.Output() + output, err := cmd.Output() if err == nil { return output, nil } From b03045a3fa503defec892760bad6b17708238f05 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 21 Jul 2025 08:23:36 +0000 Subject: [PATCH 06/40] Fix review comments --- .../platform-manageability-agent.go | 61 +++++++++++++++---- .../internal/comms/comms.go | 19 +++--- 2 files changed, 61 insertions(+), 19 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index e5c4c1ff..a45984a1 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -16,12 +16,15 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "github.com/sirupsen/logrus" + "github.com/open-edge-platform/edge-node-agents/common/pkg/metrics" auth "github.com/open-edge-platform/edge-node-agents/common/pkg/utils" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/info" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/comms" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" - "github.com/sirupsen/logrus" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/utils" + pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) const ( @@ -131,14 +134,36 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err dmMgrClient := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.Auth.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) - // Open Questions: - // 1. Should we report AMTStatus periodically or just once at startup? - // 2. If perodic, then what if the system is never AMT enabled? Should there be max retries and exit if AMT is never enabled? - // 3. If there is an error in reporting AMT status, should we fatal after max retries? - err = dmMgrClient.ReportAMTStatus(ctx) - if err != nil { - log.Fatalf("AMT status report failed! Error: %v", err) - } + var ( + isAMTEnabled = false + amtStatusCheckInterval = 30 * time.Second // TODO: Make this configurable. + lastAMTStatusCheckTimestamp int64 + ) + + amtStatusTicker := time.NewTicker(amtStatusCheckInterval) + go func() { + op := func() error { + status, err := dmMgrClient.ReportAMTStatus(ctx) + if err != nil || status == pb.AMTStatus_DISABLED { + log.Errorf("Failed to report AMT status: %v", err) + isAMTEnabled = false + return err + } + log.Info("Successfully reported AMT status") + isAMTEnabled = true + return nil + } + for { + select { + case <-ctx.Done(): + return + case <-amtStatusTicker.C: + amtStatusTicker.Stop() + updateWithRetry(ctx, log, op, &lastAMTStatusCheckTimestamp) + } + amtStatusTicker.Reset(amtStatusCheckInterval) + } + }() var ( lastActivationCheckTimestamp int64 @@ -148,8 +173,22 @@ func runAgent(ctx context.Context, log *logrus.Logger, confs *config.Config) err activationTicker := time.NewTicker(activationCheckInterval) go func() { op := func() error { - // TODO: Retrieve HostID. - return dmMgrClient.RetrieveActivationDetails(ctx, "", confs) + if !isAMTEnabled { + log.Info("Skipping activation check because AMT is not enabled") + return nil + } + uuid, err := utils.GetSystemUUID() + if err != nil { + log.Errorf("Failed to retrieve UUID: %v", err) + return err + } + err = dmMgrClient.RetrieveActivationDetails(ctx, uuid, confs) + if err != nil { + log.Errorf("Failed to retrieve activation details: %v", err) + return err + } + log.Info("Successfully retrieved activation details") + return nil } for { select { diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index f4dda41a..c95849df 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -116,37 +116,40 @@ func parseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { } // ReportAMTStatus executes the `rpc amtinfo` command, parses the output, and sends the AMT status to the server. -func (cli *Client) ReportAMTStatus(ctx context.Context) error { +func (cli *Client) ReportAMTStatus(ctx context.Context) (pb.AMTStatus, error) { + defaultStatus := pb.AMTStatus_DISABLED + uuid, err := utils.GetSystemUUID() if err != nil { - return fmt.Errorf("failed to retrieve UUID: %w", err) + return defaultStatus, fmt.Errorf("failed to retrieve UUID: %w", err) } + // TODO: RPC location need to be checked. output, err := utils.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) if err != nil { req := &pb.AMTStatusRequest{ HostId: uuid, - Status: pb.AMTStatus_DISABLED, + Status: defaultStatus, Version: "", } _, reportErr := cli.DMMgrClient.ReportAMTStatus(ctx, req) if reportErr != nil { - return fmt.Errorf("failed to report AMTStatus to DM Manager: %w", reportErr) + return defaultStatus, fmt.Errorf("failed to report AMTStatus to DM Manager: %w", reportErr) } - return fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) + return defaultStatus, fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) } req, err := parseAMTInfo(uuid, output) if err != nil { - return fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) + return defaultStatus, fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) } _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) if err != nil { - return fmt.Errorf("failed to report AMT status: %w", err) + return defaultStatus, fmt.Errorf("failed to report AMT status: %w", err) } log.Logger.Info("Successfully reported AMT status") - return nil + return req.Status, nil } // TODO: From 02bf4fc82c632e7ae69a241dc5a094fc308b00b8 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 21 Jul 2025 12:03:45 +0000 Subject: [PATCH 07/40] Send activation results back to dm-manager --- .../platform-manageability-agent.go | 23 ++++---- .../internal/comms/comms.go | 55 +++++++++++++++---- 2 files changed, 54 insertions(+), 24 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 66764fc8..1c82f89f 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -26,7 +26,6 @@ import ( "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/comms" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" - "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/utils" pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) @@ -115,13 +114,17 @@ func main() { dmMgrClient := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) var ( + wg sync.WaitGroup + isAMTEnabled = false amtStatusCheckInterval = 30 * time.Second // TODO: Make this configurable. lastAMTStatusCheckTimestamp int64 ) - - amtStatusTicker := time.NewTicker(amtStatusCheckInterval) go func() { + defer wg.Done() + amtStatusTicker := time.NewTicker(amtStatusCheckInterval) + defer amtStatusTicker.Stop() + op := func() error { status, err := dmMgrClient.ReportAMTStatus(ctx) if err != nil || status == pb.AMTStatus_DISABLED { @@ -149,20 +152,17 @@ func main() { lastActivationCheckTimestamp int64 activationCheckInterval = 30 * time.Second // TODO: Make this configurable. ) - - activationTicker := time.NewTicker(activationCheckInterval) go func() { + defer wg.Done() + activationTicker := time.NewTicker(activationCheckInterval) + defer activationTicker.Stop() + op := func() error { if !isAMTEnabled { log.Info("Skipping activation check because AMT is not enabled") return nil } - uuid, err := utils.GetSystemUUID() - if err != nil { - log.Errorf("Failed to retrieve UUID: %v", err) - return err - } - err = dmMgrClient.RetrieveActivationDetails(ctx, uuid, confs) + err = dmMgrClient.RetrieveActivationDetails(ctx, confs) if err != nil { log.Errorf("Failed to retrieve activation details: %v", err) return err @@ -183,7 +183,6 @@ func main() { }() // Main agent loop using context-aware ticker - var wg sync.WaitGroup var lastUpdateTimestamp int64 ticker := time.NewTicker(30 * time.Second) defer ticker.Stop() diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index c95849df..12bf07d5 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -152,18 +152,15 @@ func (cli *Client) ReportAMTStatus(ctx context.Context) (pb.AMTStatus, error) { return req.Status, nil } -// TODO: -// 1. Immplement polling logic to periodically query the API for -// operations that need to be performed once PMA is started. -// a. When operationType is activate, PMA should execute the activation command. -// b. When operationType is not activate, PMA should continue polling for the next operation. - -// 2. Implement logic to handle the activation command and report back the results to DM Manager. - // RetrieveActivationDetails retrieves activation details and executes the activation command if required. -func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, conf *config.Config) error { +func (cli *Client) RetrieveActivationDetails(ctx context.Context, conf *config.Config) error { + uuid, err := utils.GetSystemUUID() + if err != nil { + return fmt.Errorf("Failed to retrieve activation details: %w", err) + } + req := &pb.ActivationRequest{ - HostId: hostID, + HostId: uuid, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { @@ -175,13 +172,47 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, if resp.Operation == pb.OperationType_ACTIVATE { rpsAddress := fmt.Sprintf("wss://%s/activate", conf.RPSAddress) - cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName) + // TODO: + // This is a placeholder, replace with actual logic to fetch the password. + // Need to check how to fetch the password from dm-manager, hardcoded for now. + password := "P@ssw0rd" + cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName, "-password", password) output, err := cmd.CombinedOutput() if err != nil { return fmt.Errorf("failed to execute activation command: %w, Output: %s", err, string(output)) } - // TODO: Parse the output and send the activation result back to DM Manager. + + var req *pb.ActivationResultRequest + if isProvisioned(string(output)) { + req = &pb.ActivationResultRequest{ + HostId: uuid, + ActivationStatus: pb.ActivationStatus_PROVISIONED, + } + log.Logger.Info("Provisioning successful: CIRA is configured") + } else { + req = &pb.ActivationResultRequest{ + HostId: uuid, + ActivationStatus: pb.ActivationStatus_FAILED, + } + log.Logger.Warn("Provisioning failed: CIRA is not configured") + } + _, err = cli.DMMgrClient.ReportActivationResults(ctx, req) + if err != nil { + return fmt.Errorf("failed to report Activation results: %w", err) + } } return nil } + +// isProvisioned checks if the output contains the line indicating provisioning success +func isProvisioned(output string) bool { + scanner := bufio.NewScanner(strings.NewReader(output)) + for scanner.Scan() { + line := scanner.Text() + if strings.Contains(line, `msg="CIRA: Configured"`) { + return true + } + } + return false +} From d6359cbb23b3fd5dcdd4e861810f7b9f41dd6a44 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 21 Jul 2025 12:06:29 +0000 Subject: [PATCH 08/40] Add license check --- platform-manageability-agent/internal/comms/comms.go | 3 +++ platform-manageability-agent/internal/utils/utils.go | 3 +++ 2 files changed, 6 insertions(+) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 12bf07d5..385a3208 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + package comms import ( diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index 6adb89e4..43c43ca8 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -1,3 +1,6 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + package utils import ( From 7ab4d1db42d35e683ad01f5beefcea00429e4b03 Mon Sep 17 00:00:00 2001 From: punam biswal Date: Mon, 21 Jul 2025 21:25:44 +0530 Subject: [PATCH 09/40] Load mei_me kernel module Signed-off-by: punam biswal --- .../platform-manageability-agent.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 1c82f89f..991d95a0 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -10,6 +10,7 @@ import ( "flag" "fmt" "os" + "os/exec" "os/signal" "sync" "sync/atomic" @@ -17,6 +18,7 @@ import ( "time" "github.com/cenkalti/backoff/v4" + "github.com/labstack/gommon/log" "github.com/sirupsen/logrus" "github.com/open-edge-platform/edge-node-agents/common/pkg/metrics" @@ -134,6 +136,11 @@ func main() { } log.Info("Successfully reported AMT status") isAMTEnabled = true + if err := loadModule("mei_me"); err != nil { + log.Errorf("Error while loading module:", err) + } else { + log.Info("Module mei_me loaded successfully") + } return nil } for { @@ -242,6 +249,15 @@ func main() { log.Infof("Platform Manageability Agent finished") } +func loadModule(module string) error { + cmd := exec.Command("sudo", "modprobe", module) + output, err := cmd.CombinedOutput() + if err != nil { + return log.Errorf("failed to load module %s: %v, output: %s", module, err, output) + } + return nil +} + func initStatusClientAndTicker(ctx context.Context, cancel context.CancelFunc, log *logrus.Entry, statusServer string) (*status.StatusClient, time.Duration) { statusClient, err := status.InitClient(statusServer) if err != nil { From a206fd1a5a8970840e2a79fa45c66430779d20c1 Mon Sep 17 00:00:00 2001 From: punam biswal Date: Mon, 21 Jul 2025 22:39:03 +0530 Subject: [PATCH 10/40] Enable LMS service Signed-off-by: punam biswal --- .../platform-manageability-agent.go | 16 ++++++++++++++++ 1 file changed, 16 insertions(+) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 991d95a0..200af434 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -141,6 +141,13 @@ func main() { } else { log.Info("Module mei_me loaded successfully") } + service := "lms.service" + for _, action := range []string{"enable", "start"} { + log.Info("%sing %s...\n", action, service) + if err := enableService(action, service); err != nil { + log.Errorf("Error while enabling service:", err) + } + } return nil } for { @@ -249,6 +256,15 @@ func main() { log.Infof("Platform Manageability Agent finished") } +func enableService(action, service string) error { + cmd := exec.Command("sudo", "systemctl", action, service) + output, err := cmd.CombinedOutput() + if err != nil { + return log.Errorf("failed to %s %s: %v, output: %s", action, service, err, output) + } + return nil +} + func loadModule(module string) error { cmd := exec.Command("sudo", "modprobe", module) output, err := cmd.CombinedOutput() From f96818ef5674cdbd70b90e1af13197d88b497861 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 22 Jul 2025 04:26:55 +0000 Subject: [PATCH 11/40] Fix review comments --- .../platform-manageability-agent.go | 38 ++----------------- 1 file changed, 4 insertions(+), 34 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 200af434..32f2288f 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -10,7 +10,6 @@ import ( "flag" "fmt" "os" - "os/exec" "os/signal" "sync" "sync/atomic" @@ -18,7 +17,6 @@ import ( "time" "github.com/cenkalti/backoff/v4" - "github.com/labstack/gommon/log" "github.com/sirupsen/logrus" "github.com/open-edge-platform/edge-node-agents/common/pkg/metrics" @@ -119,9 +117,10 @@ func main() { wg sync.WaitGroup isAMTEnabled = false - amtStatusCheckInterval = 30 * time.Second // TODO: Make this configurable. + amtStatusCheckInterval = confs.Manageability.HeartbeatInterval lastAMTStatusCheckTimestamp int64 ) + wg.Add(1) go func() { defer wg.Done() amtStatusTicker := time.NewTicker(amtStatusCheckInterval) @@ -136,18 +135,6 @@ func main() { } log.Info("Successfully reported AMT status") isAMTEnabled = true - if err := loadModule("mei_me"); err != nil { - log.Errorf("Error while loading module:", err) - } else { - log.Info("Module mei_me loaded successfully") - } - service := "lms.service" - for _, action := range []string{"enable", "start"} { - log.Info("%sing %s...\n", action, service) - if err := enableService(action, service); err != nil { - log.Errorf("Error while enabling service:", err) - } - } return nil } for { @@ -164,8 +151,9 @@ func main() { var ( lastActivationCheckTimestamp int64 - activationCheckInterval = 30 * time.Second // TODO: Make this configurable. + activationCheckInterval = confs.Manageability.HeartbeatInterval ) + wg.Add(1) go func() { defer wg.Done() activationTicker := time.NewTicker(activationCheckInterval) @@ -256,24 +244,6 @@ func main() { log.Infof("Platform Manageability Agent finished") } -func enableService(action, service string) error { - cmd := exec.Command("sudo", "systemctl", action, service) - output, err := cmd.CombinedOutput() - if err != nil { - return log.Errorf("failed to %s %s: %v, output: %s", action, service, err, output) - } - return nil -} - -func loadModule(module string) error { - cmd := exec.Command("sudo", "modprobe", module) - output, err := cmd.CombinedOutput() - if err != nil { - return log.Errorf("failed to load module %s: %v, output: %s", module, err, output) - } - return nil -} - func initStatusClientAndTicker(ctx context.Context, cancel context.CancelFunc, log *logrus.Entry, statusServer string) (*status.StatusClient, time.Duration) { statusClient, err := status.InitClient(statusServer) if err != nil { From d289ff6e18fa34e28b10c7e094f417d039f24a24 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 22 Jul 2025 04:51:27 +0000 Subject: [PATCH 12/40] Fix review comments --- .../platform-manageability-agent.go | 19 +++++++++------ .../internal/comms/comms.go | 24 ++++++------------- 2 files changed, 19 insertions(+), 24 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 32f2288f..b60e454c 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -26,6 +26,7 @@ import ( "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/comms" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/utils" pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) @@ -113,12 +114,16 @@ func main() { dmMgrClient := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) + hostID, err := utils.GetSystemUUID() + if err != nil { + log.Fatalf("Failed to retrieve system UUID with an error: %v", err) + } var ( wg sync.WaitGroup - isAMTEnabled = false amtStatusCheckInterval = confs.Manageability.HeartbeatInterval lastAMTStatusCheckTimestamp int64 + isAMTEnabled int32 ) wg.Add(1) go func() { @@ -127,14 +132,14 @@ func main() { defer amtStatusTicker.Stop() op := func() error { - status, err := dmMgrClient.ReportAMTStatus(ctx) + status, err := dmMgrClient.ReportAMTStatus(ctx, hostID) if err != nil || status == pb.AMTStatus_DISABLED { log.Errorf("Failed to report AMT status: %v", err) - isAMTEnabled = false + atomic.StoreInt32(&isAMTEnabled, 0) return err } log.Info("Successfully reported AMT status") - isAMTEnabled = true + atomic.StoreInt32(&isAMTEnabled, 1) return nil } for { @@ -150,8 +155,8 @@ func main() { }() var ( - lastActivationCheckTimestamp int64 activationCheckInterval = confs.Manageability.HeartbeatInterval + lastActivationCheckTimestamp int64 ) wg.Add(1) go func() { @@ -160,11 +165,11 @@ func main() { defer activationTicker.Stop() op := func() error { - if !isAMTEnabled { + if atomic.LoadInt32(&isAMTEnabled) == 0 { log.Info("Skipping activation check because AMT is not enabled") return nil } - err = dmMgrClient.RetrieveActivationDetails(ctx, confs) + err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { log.Errorf("Failed to retrieve activation details: %v", err) return err diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 385a3208..0abeda44 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -119,19 +119,14 @@ func parseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { } // ReportAMTStatus executes the `rpc amtinfo` command, parses the output, and sends the AMT status to the server. -func (cli *Client) ReportAMTStatus(ctx context.Context) (pb.AMTStatus, error) { +func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTStatus, error) { defaultStatus := pb.AMTStatus_DISABLED - uuid, err := utils.GetSystemUUID() - if err != nil { - return defaultStatus, fmt.Errorf("failed to retrieve UUID: %w", err) - } - // TODO: RPC location need to be checked. output, err := utils.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) if err != nil { req := &pb.AMTStatusRequest{ - HostId: uuid, + HostId: hostID, Status: defaultStatus, Version: "", } @@ -142,7 +137,7 @@ func (cli *Client) ReportAMTStatus(ctx context.Context) (pb.AMTStatus, error) { return defaultStatus, fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) } - req, err := parseAMTInfo(uuid, output) + req, err := parseAMTInfo(hostID, output) if err != nil { return defaultStatus, fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) } @@ -156,14 +151,9 @@ func (cli *Client) ReportAMTStatus(ctx context.Context) (pb.AMTStatus, error) { } // RetrieveActivationDetails retrieves activation details and executes the activation command if required. -func (cli *Client) RetrieveActivationDetails(ctx context.Context, conf *config.Config) error { - uuid, err := utils.GetSystemUUID() - if err != nil { - return fmt.Errorf("Failed to retrieve activation details: %w", err) - } - +func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, conf *config.Config) error { req := &pb.ActivationRequest{ - HostId: uuid, + HostId: hostID, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { @@ -189,13 +179,13 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, conf *config.C var req *pb.ActivationResultRequest if isProvisioned(string(output)) { req = &pb.ActivationResultRequest{ - HostId: uuid, + HostId: hostID, ActivationStatus: pb.ActivationStatus_PROVISIONED, } log.Logger.Info("Provisioning successful: CIRA is configured") } else { req = &pb.ActivationResultRequest{ - HostId: uuid, + HostId: hostID, ActivationStatus: pb.ActivationStatus_FAILED, } log.Logger.Warn("Provisioning failed: CIRA is not configured") From 22268cabb1afd176b0071eb609956fd2e0960249 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 03:54:50 +0000 Subject: [PATCH 13/40] Fix minor issues --- .../platform-manageability-agent/platform-manageability-agent.go | 1 + 1 file changed, 1 insertion(+) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index b60e454c..93c8632b 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -246,6 +246,7 @@ func main() { } }() + wg.Wait() log.Infof("Platform Manageability Agent finished") } From c09b02e69037e3b708634fb5acc52661151d1531 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 05:31:57 +0000 Subject: [PATCH 14/40] Add test files --- .../internal/comms/comms.go | 16 +- .../internal/comms/comms_test.go | 261 ++++++++++++++++++ .../internal/utils/utils.go | 16 +- .../test/server-cert.pem | 30 ++ .../test/server-key.pem | 52 ++++ 5 files changed, 368 insertions(+), 7 deletions(-) create mode 100644 platform-manageability-agent/internal/comms/comms_test.go create mode 100644 platform-manageability-agent/test/server-cert.pem create mode 100644 platform-manageability-agent/test/server-key.pem diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 0abeda44..54de3659 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -9,7 +9,6 @@ import ( "crypto/tls" "fmt" "net" - "os/exec" "strings" "time" @@ -35,6 +34,7 @@ type Client struct { GrpcConn *grpc.ClientConn DMMgrClient pb.DeviceManagementClient RetryInterval time.Duration + Executor utils.CommandExecutor } func WithNetworkDialer(serviceAddr string) func(*Client) { @@ -45,13 +45,19 @@ func WithNetworkDialer(serviceAddr string) func(*Client) { } } -func NewClient(serviceURL string, tlsConfig *tls.Config) *Client { +func NewClient(serviceURL string, tlsConfig *tls.Config, options ...func(*Client)) *Client { cli := &Client{} cli.DMMgrServiceAddr = serviceURL cli.RetryInterval = retryInterval cli.Transport = grpc.WithTransportCredentials(credentials.NewTLS(tlsConfig)) + cli.Executor = &utils.RealCommandExecutor{} WithNetworkDialer(cli.DMMgrServiceAddr)(cli) + + // options can be used to override default values, e.g. from unit tests + for _, o := range options { + o(cli) + } return cli } @@ -123,7 +129,7 @@ func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTSt defaultStatus := pb.AMTStatus_DISABLED // TODO: RPC location need to be checked. - output, err := utils.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) + output, err := cli.Executor.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) if err != nil { req := &pb.AMTStatusRequest{ HostId: hostID, @@ -169,9 +175,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, // This is a placeholder, replace with actual logic to fetch the password. // Need to check how to fetch the password from dm-manager, hardcoded for now. password := "P@ssw0rd" - cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName, "-password", password) - - output, err := cmd.CombinedOutput() + output, err := cli.Executor.ExecuteCommand("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName, "-password", password) if err != nil { return fmt.Errorf("failed to execute activation command: %w, Output: %s", err, string(output)) } diff --git a/platform-manageability-agent/internal/comms/comms_test.go b/platform-manageability-agent/internal/comms/comms_test.go new file mode 100644 index 00000000..b19a09db --- /dev/null +++ b/platform-manageability-agent/internal/comms/comms_test.go @@ -0,0 +1,261 @@ +// SPDX-FileCopyrightText: (C) 2025 Intel Corporation +// SPDX-License-Identifier: Apache-2.0 + +package comms_test + +import ( + "context" + "crypto/tls" + "fmt" + "net" + "testing" + + "github.com/stretchr/testify/assert" + "google.golang.org/grpc" + "google.golang.org/grpc/credentials" + "google.golang.org/grpc/test/bufconn" + + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/comms" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" + log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" + "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/utils" + pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" +) + +type mockDeviceManagementServer struct { + pb.UnimplementedDeviceManagementServer + operationType pb.OperationType + onReportActivationResults func(context.Context, *pb.ActivationResultRequest) (*pb.ActivationResultResponse, error) +} + +type mockCommandExecutor struct { + amtInfoOutput []byte + amtInfoError error + activationOutput []byte + activationError error +} + +func (m *mockCommandExecutor) ExecuteWithRetries(command string, args []string) ([]byte, error) { + if command == "sudo" && len(args) >= 2 && args[0] == "./rpc" && args[1] == "amtinfo" { + return m.amtInfoOutput, m.amtInfoError + } + return nil, fmt.Errorf("unexpected command: %s %v", command, args) +} + +func (m *mockCommandExecutor) ExecuteCommand(name string, args ...string) ([]byte, error) { + if name == "sudo" && len(args) >= 2 && args[0] == "rpc" && args[1] == "activate" { + return m.activationOutput, m.activationError + } + return nil, fmt.Errorf("unexpected command: %s %v", name, args) +} + +func (m *mockDeviceManagementServer) ReportAMTStatus(ctx context.Context, req *pb.AMTStatusRequest) (*pb.AMTStatusResponse, error) { + log.Logger.Infof("Received ReportAMTStatus request: HostID=%s, Status=%v, Version=%s", req.HostId, req.Status, req.Version) + return &pb.AMTStatusResponse{}, nil +} + +func (m *mockDeviceManagementServer) RetrieveActivationDetails(ctx context.Context, req *pb.ActivationRequest) (*pb.ActivationDetailsResponse, error) { + log.Logger.Infof("Received RetrieveActivationDetails request: %v", req) + return &pb.ActivationDetailsResponse{ + HostId: req.HostId, + Operation: m.operationType, + ProfileName: "mock-profile", + }, nil +} + +func (m *mockDeviceManagementServer) ReportActivationResults(ctx context.Context, req *pb.ActivationResultRequest) (*pb.ActivationResultResponse, error) { + if m.onReportActivationResults != nil { + return m.onReportActivationResults(ctx, req) + } + log.Logger.Infof("Received ReportActivationResults request: %v", req) + return &pb.ActivationResultResponse{}, nil +} + +func runMockServer(server pb.DeviceManagementServer) (*bufconn.Listener, *grpc.Server) { + lis := bufconn.Listen(1024 * 1024) + creds, err := credentials.NewServerTLSFromFile("../../test/server-cert.pem", "../../test/server-key.pem") + if err != nil { + log.Logger.Fatalf("Failed to load TLS credentials: %v", err) + } + s := grpc.NewServer(grpc.Creds(creds)) + pb.RegisterDeviceManagementServer(s, server) + go func() { + if err := s.Serve(lis); err != nil { + log.Logger.Infof("Mock server stopped: %v", err) + } + }() + return lis, s +} + +func WithBufconnDialer(lis *bufconn.Listener) func(*comms.Client) { + return func(cli *comms.Client) { + cli.Dialer = grpc.WithContextDialer(func(ctx context.Context, s string) (net.Conn, error) { + return lis.Dial() + }) + } +} + +func WithMockExecutor(executor utils.CommandExecutor) func(*comms.Client) { + return func(c *comms.Client) { + c.Executor = executor + } +} + +func TestRetrieveActivationDetails_DeactivateOperation(t *testing.T) { + lis, server := runMockServer(&mockDeviceManagementServer{ + operationType: pb.OperationType_DEACTIVATE, + }) + defer func() { + server.GracefulStop() + lis.Close() + }() + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + client := comms.NewClient("mock-service", tlsConfig, WithBufconnDialer(lis)) + + err := client.Connect(context.Background()) + assert.NoError(t, err, "Client should connect successfully") + + err = client.RetrieveActivationDetails(context.Background(), "host-id", &config.Config{ + RPSAddress: "mock-service", + }) + assert.NoError(t, err, "RetrieveActivationDetails for deactivate should not process") +} + +func TestRetrieveActivationDetails_Success(t *testing.T) { + var capturedRequest *pb.ActivationResultRequest + + mockServer := &mockDeviceManagementServer{ + operationType: pb.OperationType_ACTIVATE, + onReportActivationResults: func(ctx context.Context, req *pb.ActivationResultRequest) (*pb.ActivationResultResponse, error) { + capturedRequest = req // Capture the request to verify later. + log.Logger.Infof("Received ReportActivationResults request: %v", req) + return &pb.ActivationResultResponse{}, nil + }, + } + + lis, server := runMockServer(mockServer) + defer func() { + server.GracefulStop() + lis.Close() + }() + + // Create mock executor with successful activation output. + mockExecutor := &mockCommandExecutor{ + activationOutput: []byte(`msg="CIRA: Configured"`), + activationError: nil, + } + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + client := comms.NewClient("mock-service", tlsConfig, + WithBufconnDialer(lis), + WithMockExecutor(mockExecutor)) + + err := client.Connect(context.Background()) + assert.NoError(t, err, "Client should connect successfully") + + err = client.RetrieveActivationDetails(context.Background(), "host-id", &config.Config{ + RPSAddress: "mock-service", + }) + assert.NoError(t, err, "RetrieveActivationDetails should succeed") + + // Verify that the activation result was reported with PROVISIONED status. + assert.NotNil(t, capturedRequest, "Activation result should have been reported") + assert.Equal(t, "host-id", capturedRequest.HostId, "Host ID should match") + assert.Equal(t, pb.ActivationStatus_PROVISIONED, capturedRequest.ActivationStatus, + "Activation status should be PROVISIONED when CIRA is configured") +} + +func TestRetrieveActivationDetails_Failed(t *testing.T) { + var capturedRequest *pb.ActivationResultRequest + + mockServer := &mockDeviceManagementServer{ + operationType: pb.OperationType_ACTIVATE, + onReportActivationResults: func(ctx context.Context, req *pb.ActivationResultRequest) (*pb.ActivationResultResponse, error) { + capturedRequest = req // Capture the request to verify later. + log.Logger.Infof("Received ReportActivationResults request: %v", req) + return &pb.ActivationResultResponse{}, nil + }, + } + + lis, server := runMockServer(mockServer) + defer func() { + server.GracefulStop() + lis.Close() + }() + + // Create mock executor with failed activation output (no CIRA: Configured). + mockExecutor := &mockCommandExecutor{ + activationOutput: []byte(`msg="Activation failed"`), + activationError: nil, + } + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + client := comms.NewClient("mock-service", tlsConfig, + WithBufconnDialer(lis), + WithMockExecutor(mockExecutor)) + + err := client.Connect(context.Background()) + assert.NoError(t, err, "Client should connect successfully") + + err = client.RetrieveActivationDetails(context.Background(), "host-id", &config.Config{ + RPSAddress: "mock-service", + }) + assert.NoError(t, err, "RetrieveActivationDetails should succeed") + + // Verify that the activation result was reported with FAILED status. + assert.NotNil(t, capturedRequest, "Activation result should have been reported") + assert.Equal(t, "host-id", capturedRequest.HostId, "Host ID should match") + assert.Equal(t, pb.ActivationStatus_FAILED, capturedRequest.ActivationStatus, + "Activation status should be FAILED when CIRA is not configured") +} + +func TestReportAMTStatus_Success(t *testing.T) { + lis, server := runMockServer(&mockDeviceManagementServer{}) + defer func() { + server.GracefulStop() + lis.Close() + }() + + mockExecutor := &mockCommandExecutor{ + amtInfoOutput: []byte("Version: 16.1.25.1424\nBuild Number: 3425\nRecovery Version: 16.1.25.1424"), + amtInfoError: nil, + } + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + client := comms.NewClient("mock-service", tlsConfig, + WithBufconnDialer(lis), + WithMockExecutor(mockExecutor), + ) + + err := client.Connect(context.Background()) + assert.NoError(t, err, "Client should connect successfully") + + _, err = client.ReportAMTStatus(context.Background(), "host-id") + assert.NoError(t, err, "ReportAMTStatus should succeed") +} + +func TestReportAMTStatus_CommandFailure(t *testing.T) { + lis, server := runMockServer(&mockDeviceManagementServer{}) + defer func() { + server.GracefulStop() + lis.Close() + }() + + mockExecutor := &mockCommandExecutor{ + amtInfoOutput: nil, + amtInfoError: fmt.Errorf("command failed"), + } + + tlsConfig := &tls.Config{InsecureSkipVerify: true} + client := comms.NewClient("mock-service", tlsConfig, + WithBufconnDialer(lis), + WithMockExecutor(mockExecutor)) + + err := client.Connect(context.Background()) + assert.NoError(t, err, "Client should connect successfully") + + status, err := client.ReportAMTStatus(context.Background(), "host-id") + assert.Error(t, err, "ReportAMTStatus should fail when command fails") + assert.Equal(t, pb.AMTStatus_DISABLED, status, "AMT should be disabled on failure") +} diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index 43c43ca8..d534c456 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -12,7 +12,16 @@ import ( log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" ) -func ExecuteWithRetries(command string, args []string) ([]byte, error) { +// CommandExecutor defines an interface for executing commands. +// This allows for mocking in tests. +type CommandExecutor interface { + ExecuteWithRetries(command string, args []string) ([]byte, error) + ExecuteCommand(name string, args ...string) ([]byte, error) +} + +type RealCommandExecutor struct{} + +func (r *RealCommandExecutor) ExecuteWithRetries(command string, args []string) ([]byte, error) { maxRetries := 3 retryInterval := 5 * time.Second @@ -31,6 +40,11 @@ func ExecuteWithRetries(command string, args []string) ([]byte, error) { return nil, fmt.Errorf("command `%s` failed after %d retries: %v", command, maxRetries, err) } +func (r *RealCommandExecutor) ExecuteCommand(name string, args ...string) ([]byte, error) { + cmd := exec.Command(name, args...) + return cmd.CombinedOutput() +} + func GetSystemUUID() (string, error) { cmd := exec.Command("sudo", "dmidecode", "-s", "system-uuid") uuid, err := cmd.Output() diff --git a/platform-manageability-agent/test/server-cert.pem b/platform-manageability-agent/test/server-cert.pem new file mode 100644 index 00000000..941a80ae --- /dev/null +++ b/platform-manageability-agent/test/server-cert.pem @@ -0,0 +1,30 @@ +-----BEGIN CERTIFICATE----- +MIIFJTCCAw2gAwIBAgIUaagGTC3GU6IXhPV7jnFBMZPH0SgwDQYJKoZIhvcNAQEL +BQAwFDESMBAGA1UEAwwJbG9jYWxob3N0MB4XDTI1MDcyMjA5MjQxN1oXDTI2MDcy +MjA5MjQxN1owFDESMBAGA1UEAwwJbG9jYWxob3N0MIICIjANBgkqhkiG9w0BAQEF +AAOCAg8AMIICCgKCAgEApEABHMxeXb0GjLPioSr1r7Ox4UZTFxKizN/r5o/6DdWm +PZf7vfroXSywIIy7GMft6ePs+J7tHRfuXpaHtEWVOI8luOvB7QXHbIi/mnFzHc2A +48TKeA8omHazfOiJsLMF0D5O8STzpBOOmLYXwIXDY03iuv6J1u8M4nay+2biIP+q +dnFwxqeJNh4xc5hKj+wMLilQecJ5H2PBKM+SDxuvewHB38NIG+/pt2pwSkhw/+yb +SbiWq7bccOkqMs+yvrBRYd4Xt7XAjfig4jqK5LdZmqyQcfx5pQNe2D/MXx/uP8sw +t/plDhcdy4gECGERxL34/VgGD3UcHDww73rVrWgJg+XDsNbf8sDy7UYTPWmKEu3y +n4/zS90uaiGs+LJsmNtk/Jgmc1eO73SaLWL/EQjWQgW73ML5NBWvwFc9wZ1PdL4a +BcNMrnWlhboZyjv0Mqf/iGBPBfEshhPmEJlkRPMPKOOhj3sVeVlRibRdcOg2LbpB +idX9gy4iW07lcc0FYL1s5Tz3wPp4TY/7jqLkXtG30R41UfG3w8KAEcuzo1Gz7jKx +PiEg31fH3ipf8FO/3q/hkbDWLoj7Cz5PV2A8pSCZbfcJNXDmTcFJb9q9kvm6gNyj +z2PM4KlcQwidrcL7Kkb4QxKORvabLOcPaEeCt7xetrwqw18pwyCxFiquEBtaN7kC +AwEAAaNvMG0wHQYDVR0OBBYEFI/UrUVGjHkF6yxdXcZQLy64ITySMB8GA1UdIwQY +MBaAFI/UrUVGjHkF6yxdXcZQLy64ITySMA8GA1UdEwEB/wQFMAMBAf8wGgYDVR0R +BBMwEYIJbG9jYWxob3N0hwR/AAABMA0GCSqGSIb3DQEBCwUAA4ICAQAB6K9RcPrM +2hSzC//OLQJf0D/yUFT/jnZ7k6Z4yJrDMo/+dUA8lgB4ZZeIcFKGw5kxJ5WKNeMD +W00jOuCVj0pHQPblMXPdelNqHjf3bjtDzpbbzlAcBjlzAj8S/pHXW8q2zvTa9xim +zvYn/M9CH/zfL3DuouYhOMMoAOviBZIlpT3b2lg55zYyb6cCCXFy9BHlLjC1IIbV +RS1KWCJaknbjUQmj6X2KPhbCzRgz9KrKlAnTtd0wQCtBBwXFAA5G/p8W6c4tAjSM +mmTxtLl7qDmLHZLGzAcubbkpwKXSKnSLprtyy5JnVtzTmkHvkMjgc3sFSX57O8sF +1/OJXtMvSb+0TWkQJFWA5DIjhsKK0W7mw0+kTR7DJBtr3x2qs5IWN1C4A8QSVfLx +XbZKE+bWL2vIQfuxvao8kVcgxekcV/PPzMNYNq0bu23GsfIafGsKF8L53jhskJhF +mPHK8SChkeWmiCVfSDfyEY/5twJHLsZWDNZarQPg6h3YiLubKDFhvY3d3rh30jr+ +LWlDBEl4y6g5ROsXzO4mQi1t8uqcUOfR7HEjeGGcTGSby5jkL1axBlsE5othzdiS +xCrp2qk4YR57URNGPdvHR1i4ydJln3PbkwoqpQzVcmkpf9YhSbyMKOa4ibj8N1AM +0UAPTptaL3d5Ned8uOM+QvqOZYbU/CIHTg== +-----END CERTIFICATE----- \ No newline at end of file diff --git a/platform-manageability-agent/test/server-key.pem b/platform-manageability-agent/test/server-key.pem new file mode 100644 index 00000000..a13dc87e --- /dev/null +++ b/platform-manageability-agent/test/server-key.pem @@ -0,0 +1,52 @@ +-----BEGIN PRIVATE KEY----- +MIIJRAIBADANBgkqhkiG9w0BAQEFAASCCS4wggkqAgEAAoICAQCkQAEczF5dvQaM +s+KhKvWvs7HhRlMXEqLM3+vmj/oN1aY9l/u9+uhdLLAgjLsYx+3p4+z4nu0dF+5e +loe0RZU4jyW468HtBcdsiL+acXMdzYDjxMp4DyiYdrN86ImwswXQPk7xJPOkE46Y +thfAhcNjTeK6/onW7wzidrL7ZuIg/6p2cXDGp4k2HjFzmEqP7AwuKVB5wnkfY8Eo +z5IPG697AcHfw0gb7+m3anBKSHD/7JtJuJarttxw6Soyz7K+sFFh3he3tcCN+KDi +Oorkt1marJBx/HmlA17YP8xfH+4/yzC3+mUOFx3LiAQIYRHEvfj9WAYPdRwcPDDv +etWtaAmD5cOw1t/ywPLtRhM9aYoS7fKfj/NL3S5qIaz4smyY22T8mCZzV47vdJot +Yv8RCNZCBbvcwvk0Fa/AVz3BnU90vhoFw0yudaWFuhnKO/Qyp/+IYE8F8SyGE+YQ +mWRE8w8o46GPexV5WVGJtF1w6DYtukGJ1f2DLiJbTuVxzQVgvWzlPPfA+nhNj/uO +ouRe0bfRHjVR8bfDwoARy7OjUbPuMrE+ISDfV8feKl/wU7/er+GRsNYuiPsLPk9X +YDylIJlt9wk1cOZNwUlv2r2S+bqA3KPPY8zgqVxDCJ2twvsqRvhDEo5G9pss5w9o +R4K3vF62vCrDXynDILEWKq4QG1o3uQIDAQABAoICAAP6C7zZguBkovu1oENUMbIh +GOmyJPFeUWRB+Klyq3R5y4Zav2zcXhNhcVs8ZQxGzO8YhmSHnoiRi4MtRFhcRe5i +YuCtDpsxRGCPmAoCfK3IS7pYCanp/grg9twLmkfVscqWs50Zjq+WjKGMaRtPccld ++aMa5hX24Do8cv1swoVXg1ZOTVN1+3e8t+hLU+TTq4DTnb/w8jNNHEBjdC8a9b71 +7i5/gSeyAZfgAxt9UQUVQrZAep9qNECkmJG13ydISBQlRzA6vZCCaEzJ65J5kPkS +fAXSfdR9tiZotwdHk+JmBkYyEQ/MBfvnTWexh/daPSTPJIhrxrQ1Td8Ce5lw9yf5 +KRbR0mTp7cbpS5OHogPTMP2i0kX0RZW7HXNi0oWziOen47NIMp45UKVZgwSF2tGb +ZqwOpv4vTJXjsBe7G318RUAp5IIi0FqdQdGH0AaPX+UL1hV7yeBirSDJP8pAr6Be +zy8aA+7PBBdH3Z1mokpu5xiLPTxwRjs1mR4Zr/ox6MOvpROb1taEoe9EAGzvCDSZ +ARrs+KOe2MSQojaEevUgwz0oK+QMyfg86nSXy1b+XvrVFOlGdHnjcpD8+LTFc0nN +U4udd61TlhLgqcYhjte/+XUPSmkEIzRr/PgGPQ+MDOqNaEEPbgCV5xgkW1V3vAJf +ft5dEpJmcjGjgMrC0e+NAoIBAQDfaZcBuTpsh8Ja3DKy7TtiMeHio0jvtVIyvUiX ++3ib5h3nKid0MJCtC5vkVLQbZIqsMPrMuyYWL1u6UTCdTBqUWfW/prPPKOYTv8IK +9ILWVnuDkxHvfgMFhsXBpQaQP7vMjJuhRzyWqCRZDzrSJWX94uKd8v7Fc7R1sryI +7Z4wN+nt/gztho52PRSQ/GOQAd7ao/l7vxyjZjgHNCHmywO+vkmLY22jPT2vNNxQ +VNyQC2Mo5Ew2qzwTpDlz9mIhlGThP/DAJxhQ8YlU4ucABhOGH7Jnp7kFg3yw8CcR +EV/lLh6XjNzHyVxXV7kMamf/mckJgBFgN47ntTUJ0Lg1UDflAoIBAQC8NT0jpadM +CehleCtPT138edI8wUPmAUK8XlLtkJ9+Hf9GKfebRH0Kc6OUTlCvwjqkUSvh00bI +FE7J2+vrxSRlaBB5o0/8R2TyJnBQDAJbxXVKQqhR+A2YT/izwb59nI5LoNhkOfUi +rECJ6Hx4IH/o+Xa5RDTeWXgMVEY3sp8JHp8J9jvou6QphbP1pDsSarPVV63gPbCa +LZaWRVHK1AFeEbPxp04uq3RVdxDoXV6JMYFoUvNnhviaHlyOOXobhrV+mT1tn9Jg +WMstsy15hwrAMrayvfikqHxCE5G2ibWNPDsYlNdBueTnsoO35G4Ou0/KGikxQMdw +WOqTOQUyHhtFAoIBAQCZaN4idtihcc+JqK6/opsmYG5lvA33XCDnaoaQpkM5ehiK +ha214StXSCNx9KAAN1fpyXBOaSxMC5UHKX0iZgHSYLuZMJVD6Sej5AcQkrnNCHGj +9bdZJsRYZSUiRXluT/VbDipH4qy+HxNmfEi9yKRyY/uMzjEX0M3YDDXeKJx5K8vc +epjR+ZWVp3eZ31VWq5IlvHcx/BdkNGrAt5GX80vNDMlfh5TfhMv4pWmrV7pkxwz+ +JVYYLBkCu8Af2s2jlUx4R/m/WJkf69uLiItqvsFlVhqhhtBL8vAKZ73LwIyCyNqE +4lswbRqVd01AM0jagPVFKsXDtiLRBOM4rDPxl/XpAoIBAQCcPf5Bh5W49EivY/Fg +Z9z4ahF0SmUZnlZOlmd1vKgLRM/U6aYaieLcOF/GELW/ExknrBMn6ANMuj8mKKJU +Glc6sAdtU3xlTMeqluqKKU4T7XqPaYLeeXSfy1QFtoNzN/KRKjg6DDtKy4DQe+ZI +u4I0YFSFPPA+3jtg0N1yA6EvKvKQjI/zh0KjDdH1zJ8VjOs54w3/qHbhp5LpsSZT +oWh5NW0S4fdvmvp6sNuia1C9yBFMEADtIwliuG3RzDlofI5TSNfMF+/H+C7EA4lu +6AKux+sPu9GcsBpnqGNBDFVhqGzO3VFNyQsUpgffM42CQVHdz5X+w25OACLMkXuU +vZWZAoIBAQC7i3YueBkEg/7yP/4x9cPQE4hP5C2o3Nj3R4QOQiGakNUB5e5aq7fZ +cDrkvL/04jWU7/DcHoulvecm40jNXzYiePeiwADUi4FVKCCl+HtB/EpZVAfI/pZv +oDO7XtBCWmjlRGXENg3WceLtEK0oABtF9ZSzK8gymnFSV4IUqK+bu7X/VlawjRV/ +71oDSMW0VUVrfl8eJ04DdEzX4wBJoWphOGEBoRo6ooNXHC0EAYfeWK9jkDqC+u6h +beq4ZdoovF/HZSVMw6vjMGLMPoMwBjSUtiAwGtwGpmTOWkkVKVsMVFd4kHUXIc3d +UOHyPTqnXNFx31y2kQkDAi4OsDZdFwdo +-----END PRIVATE KEY----- \ No newline at end of file From 1fd0ee72ecde5083242bfb52c9264adc9ae17b97 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 06:34:41 +0000 Subject: [PATCH 15/40] Fix lint issues --- .../internal/comms/comms.go | 16 ++++++---------- 1 file changed, 6 insertions(+), 10 deletions(-) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 54de3659..edcd77eb 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -81,7 +81,7 @@ func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls. for { select { case <-ctx.Done(): - log.Logger.Info("Connecting to DM Manager has been cancelled") + log.Logger.Info("Connecting to DM Manager has been canceled") return nil case <-cyclicalTicker.C: err := dmMgr.Connect(ctx) @@ -97,7 +97,7 @@ func ConnectToDMManager(ctx context.Context, serviceAddr string, tlsConfig *tls. } // parseAMTInfo parses the output of the `rpc amtinfo` command and populates the AMTStatusRequest. -func parseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { +func parseAMTInfo(uuid string, output []byte) *pb.AMTStatusRequest { var ( status = pb.AMTStatus_DISABLED version string @@ -117,11 +117,11 @@ func parseAMTInfo(uuid string, output []byte) (*pb.AMTStatusRequest, error) { } req := &pb.AMTStatusRequest{ - HostId: strings.TrimSpace(string(uuid)), + HostId: uuid, Status: status, Version: version, } - return req, nil + return req } // ReportAMTStatus executes the `rpc amtinfo` command, parses the output, and sends the AMT status to the server. @@ -143,11 +143,7 @@ func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTSt return defaultStatus, fmt.Errorf("failed to execute `rpc amtinfo` command: %w", err) } - req, err := parseAMTInfo(hostID, output) - if err != nil { - return defaultStatus, fmt.Errorf("failed to parse `rpc amtinfo` output: %w", err) - } - + req := parseAMTInfo(hostID, output) _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) if err != nil { return defaultStatus, fmt.Errorf("failed to report AMT status: %w", err) @@ -163,7 +159,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { - return fmt.Errorf("Failed to retrieve activation details: %w", err) + return fmt.Errorf("failed to retrieve activation details: %w", err) } log.Logger.Debugf("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", From adef6c9c55a5a11a73741e6817bc4d01aec79466 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 06:39:01 +0000 Subject: [PATCH 16/40] Rename cert files --- platform-manageability-agent/internal/comms/comms_test.go | 2 +- .../test/{server-cert.pem => _dummy-cert.pem} | 2 +- .../test/{server-key.pem => _dummy-key.pem} | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) rename platform-manageability-agent/test/{server-cert.pem => _dummy-cert.pem} (98%) rename platform-manageability-agent/test/{server-key.pem => _dummy-key.pem} (99%) diff --git a/platform-manageability-agent/internal/comms/comms_test.go b/platform-manageability-agent/internal/comms/comms_test.go index b19a09db..6fff8177 100644 --- a/platform-manageability-agent/internal/comms/comms_test.go +++ b/platform-manageability-agent/internal/comms/comms_test.go @@ -73,7 +73,7 @@ func (m *mockDeviceManagementServer) ReportActivationResults(ctx context.Context func runMockServer(server pb.DeviceManagementServer) (*bufconn.Listener, *grpc.Server) { lis := bufconn.Listen(1024 * 1024) - creds, err := credentials.NewServerTLSFromFile("../../test/server-cert.pem", "../../test/server-key.pem") + creds, err := credentials.NewServerTLSFromFile("../../test/_dummy-cert.pem", "../../test/_dummy-key.pem") if err != nil { log.Logger.Fatalf("Failed to load TLS credentials: %v", err) } diff --git a/platform-manageability-agent/test/server-cert.pem b/platform-manageability-agent/test/_dummy-cert.pem similarity index 98% rename from platform-manageability-agent/test/server-cert.pem rename to platform-manageability-agent/test/_dummy-cert.pem index 941a80ae..0489de41 100644 --- a/platform-manageability-agent/test/server-cert.pem +++ b/platform-manageability-agent/test/_dummy-cert.pem @@ -27,4 +27,4 @@ mPHK8SChkeWmiCVfSDfyEY/5twJHLsZWDNZarQPg6h3YiLubKDFhvY3d3rh30jr+ LWlDBEl4y6g5ROsXzO4mQi1t8uqcUOfR7HEjeGGcTGSby5jkL1axBlsE5othzdiS xCrp2qk4YR57URNGPdvHR1i4ydJln3PbkwoqpQzVcmkpf9YhSbyMKOa4ibj8N1AM 0UAPTptaL3d5Ned8uOM+QvqOZYbU/CIHTg== ------END CERTIFICATE----- \ No newline at end of file +-----END CERTIFICATE----- diff --git a/platform-manageability-agent/test/server-key.pem b/platform-manageability-agent/test/_dummy-key.pem similarity index 99% rename from platform-manageability-agent/test/server-key.pem rename to platform-manageability-agent/test/_dummy-key.pem index a13dc87e..e8734901 100644 --- a/platform-manageability-agent/test/server-key.pem +++ b/platform-manageability-agent/test/_dummy-key.pem @@ -49,4 +49,4 @@ oDO7XtBCWmjlRGXENg3WceLtEK0oABtF9ZSzK8gymnFSV4IUqK+bu7X/VlawjRV/ 71oDSMW0VUVrfl8eJ04DdEzX4wBJoWphOGEBoRo6ooNXHC0EAYfeWK9jkDqC+u6h beq4ZdoovF/HZSVMw6vjMGLMPoMwBjSUtiAwGtwGpmTOWkkVKVsMVFd4kHUXIc3d UOHyPTqnXNFx31y2kQkDAi4OsDZdFwdo ------END PRIVATE KEY----- \ No newline at end of file +-----END PRIVATE KEY----- From fc2dcb999a8b61f0662be38901bca909b8d32705 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 06:50:04 +0000 Subject: [PATCH 17/40] Fix vulnerability issue --- .../internal/utils/utils.go | 24 +++++++++++++++++++ 1 file changed, 24 insertions(+) diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index d534c456..0f73613b 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -6,12 +6,17 @@ package utils import ( "fmt" "os/exec" + "slices" "strings" "time" log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" ) +var allowedCommands = map[string][]string{ + "sudo": {"./rpc", "rpc"}, +} + // CommandExecutor defines an interface for executing commands. // This allows for mocking in tests. type CommandExecutor interface { @@ -25,6 +30,10 @@ func (r *RealCommandExecutor) ExecuteWithRetries(command string, args []string) maxRetries := 3 retryInterval := 5 * time.Second + if !isCommandAllowed(command, args) { + return nil, fmt.Errorf("command not allowed: %s %v", command, args) + } + var err error for i := 1; i <= maxRetries; i++ { cmd := exec.Command(command, args...) @@ -41,6 +50,9 @@ func (r *RealCommandExecutor) ExecuteWithRetries(command string, args []string) } func (r *RealCommandExecutor) ExecuteCommand(name string, args ...string) ([]byte, error) { + if !isCommandAllowed(name, args) { + return nil, fmt.Errorf("command not allowed: %s %v", name, args) + } cmd := exec.Command(name, args...) return cmd.CombinedOutput() } @@ -53,3 +65,15 @@ func GetSystemUUID() (string, error) { } return strings.TrimSpace(string(uuid)), nil } + +func isCommandAllowed(command string, args []string) bool { + allowedArgs, exists := allowedCommands[command] + if !exists { + return false + } + + if len(args) == 0 { + return false + } + return slices.Contains(allowedArgs, args[0]) +} From afeb2d3df247aba8d72569e3a2830dff7d82eae7 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 07:03:58 +0000 Subject: [PATCH 18/40] Fix vulnerability issue --- .../internal/comms/comms.go | 4 +- .../internal/comms/comms_test.go | 14 ++----- .../internal/utils/utils.go | 42 +++++-------------- 3 files changed, 16 insertions(+), 44 deletions(-) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index edcd77eb..6c133b63 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -129,7 +129,7 @@ func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTSt defaultStatus := pb.AMTStatus_DISABLED // TODO: RPC location need to be checked. - output, err := cli.Executor.ExecuteWithRetries("sudo", []string{"./rpc", "amtinfo"}) + output, err := cli.Executor.ExecuteAMTInfo() if err != nil { req := &pb.AMTStatusRequest{ HostId: hostID, @@ -171,7 +171,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, // This is a placeholder, replace with actual logic to fetch the password. // Need to check how to fetch the password from dm-manager, hardcoded for now. password := "P@ssw0rd" - output, err := cli.Executor.ExecuteCommand("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", resp.ProfileName, "-password", password) + output, err := cli.Executor.ExecuteAMTActivate(rpsAddress, resp.ProfileName, password) if err != nil { return fmt.Errorf("failed to execute activation command: %w, Output: %s", err, string(output)) } diff --git a/platform-manageability-agent/internal/comms/comms_test.go b/platform-manageability-agent/internal/comms/comms_test.go index 6fff8177..87127a92 100644 --- a/platform-manageability-agent/internal/comms/comms_test.go +++ b/platform-manageability-agent/internal/comms/comms_test.go @@ -35,18 +35,12 @@ type mockCommandExecutor struct { activationError error } -func (m *mockCommandExecutor) ExecuteWithRetries(command string, args []string) ([]byte, error) { - if command == "sudo" && len(args) >= 2 && args[0] == "./rpc" && args[1] == "amtinfo" { - return m.amtInfoOutput, m.amtInfoError - } - return nil, fmt.Errorf("unexpected command: %s %v", command, args) +func (m *mockCommandExecutor) ExecuteAMTInfo() ([]byte, error) { + return m.amtInfoOutput, m.amtInfoError } -func (m *mockCommandExecutor) ExecuteCommand(name string, args ...string) ([]byte, error) { - if name == "sudo" && len(args) >= 2 && args[0] == "rpc" && args[1] == "activate" { - return m.activationOutput, m.activationError - } - return nil, fmt.Errorf("unexpected command: %s %v", name, args) +func (m *mockCommandExecutor) ExecuteAMTActivate(rpsAddress, profileName, password string) ([]byte, error) { + return m.activationOutput, m.activationError } func (m *mockDeviceManagementServer) ReportAMTStatus(ctx context.Context, req *pb.AMTStatusRequest) (*pb.AMTStatusResponse, error) { diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index 0f73613b..2fa158d5 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -6,54 +6,44 @@ package utils import ( "fmt" "os/exec" - "slices" "strings" "time" log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" ) -var allowedCommands = map[string][]string{ - "sudo": {"./rpc", "rpc"}, -} - // CommandExecutor defines an interface for executing commands. // This allows for mocking in tests. type CommandExecutor interface { - ExecuteWithRetries(command string, args []string) ([]byte, error) - ExecuteCommand(name string, args ...string) ([]byte, error) + ExecuteAMTInfo() ([]byte, error) + ExecuteAMTActivate(rpsAddress, profileName, password string) ([]byte, error) } type RealCommandExecutor struct{} -func (r *RealCommandExecutor) ExecuteWithRetries(command string, args []string) ([]byte, error) { +// ExecuteAMTInfo executes the AMT info command with retries. +func (r *RealCommandExecutor) ExecuteAMTInfo() ([]byte, error) { maxRetries := 3 retryInterval := 5 * time.Second - if !isCommandAllowed(command, args) { - return nil, fmt.Errorf("command not allowed: %s %v", command, args) - } - var err error for i := 1; i <= maxRetries; i++ { - cmd := exec.Command(command, args...) + cmd := exec.Command("sudo", "./rpc", "amtinfo") output, err := cmd.Output() if err == nil { return output, nil } - log.Logger.Warnf("Failed to execute `%s` command (attempt %d/%d): %v", command, i, maxRetries, err) + log.Logger.Warnf("Failed to execute AMT info command (attempt %d/%d): %v", i, maxRetries, err) if i < maxRetries { time.Sleep(retryInterval) } } - return nil, fmt.Errorf("command `%s` failed after %d retries: %v", command, maxRetries, err) + return nil, fmt.Errorf("amtInfo command failed after %d retries: %v", maxRetries, err) } -func (r *RealCommandExecutor) ExecuteCommand(name string, args ...string) ([]byte, error) { - if !isCommandAllowed(name, args) { - return nil, fmt.Errorf("command not allowed: %s %v", name, args) - } - cmd := exec.Command(name, args...) +// ExecuteAMTActivate executes the AMT activate command. +func (r *RealCommandExecutor) ExecuteAMTActivate(rpsAddress, profileName, password string) ([]byte, error) { + cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", profileName, "-password", password) return cmd.CombinedOutput() } @@ -65,15 +55,3 @@ func GetSystemUUID() (string, error) { } return strings.TrimSpace(string(uuid)), nil } - -func isCommandAllowed(command string, args []string) bool { - allowedArgs, exists := allowedCommands[command] - if !exists { - return false - } - - if len(args) == 0 { - return false - } - return slices.Contains(allowedArgs, args[0]) -} From 9f76f4cb48c1fcdbe40b837e8492a63476deb0a3 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 07:50:53 +0000 Subject: [PATCH 19/40] Add .gitleaksignore for test certificates --- .gitleaksignore | 3 +++ 1 file changed, 3 insertions(+) create mode 100644 .gitleaksignore diff --git a/.gitleaksignore b/.gitleaksignore new file mode 100644 index 00000000..d93767c0 --- /dev/null +++ b/.gitleaksignore @@ -0,0 +1,3 @@ +# Test certificates - safe to ignore. +platform-manageability-agent/test/_dummy-key.pem +platform-manageability-agent/test/_dummy-cert.pem From 4b2517c9fc57033d269a6174cac26261e7de2133 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 07:54:01 +0000 Subject: [PATCH 20/40] Fix review comment --- platform-manageability-agent/go.mod | 2 -- platform-manageability-agent/go.sum | 6 ++++++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/platform-manageability-agent/go.mod b/platform-manageability-agent/go.mod index ee5f81fa..80d33bf9 100644 --- a/platform-manageability-agent/go.mod +++ b/platform-manageability-agent/go.mod @@ -2,8 +2,6 @@ module github.com/open-edge-platform/edge-node-agents/platform-manageability-age go 1.24.4 -toolchain go1.24.5 - require ( github.com/cenkalti/backoff/v4 v4.3.0 github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 diff --git a/platform-manageability-agent/go.sum b/platform-manageability-agent/go.sum index 8d3f79e8..2ada8377 100644 --- a/platform-manageability-agent/go.sum +++ b/platform-manageability-agent/go.sum @@ -20,6 +20,8 @@ github.com/go-logr/stdr v1.2.2/go.mod h1:mMo/vtBO5dYbehREoey6XUKy/eSumjCCveDpRre github.com/go-ole/go-ole v1.2.6/go.mod h1:pprOEPIfldk/42T2oK7lQ4v4JSDwmV0As9GaiUsvbm0= github.com/go-ole/go-ole v1.3.0 h1:Dt6ye7+vXGIKZ7Xtk4s6/xVdGDQynvom7xCFEdWr6uE= github.com/go-ole/go-ole v1.3.0/go.mod h1:5LS6F96DhAwUc7C+1HLexzMXY1xGRSryjyPPKW6zv78= +github.com/golang/mock v1.6.0 h1:ErTB+efbowRARo13NNdxyJji2egdxLGQhRaY+DUumQc= +github.com/golang/mock v1.6.0/go.mod h1:p6yTPP+5HYm5mzsMV8JkE6ZKdX+/wYM6Hr+LicevLPs= github.com/golang/protobuf v1.5.4 h1:i7eJL8qZTpSEXOPTxNKhASYpMn+8e5Q6AdndVa1dWek= github.com/golang/protobuf v1.5.4/go.mod h1:lnTiLA8Wa4RWRcIUkrtSVa5nRhsEGBg48fD6rSs7xps= github.com/google/go-cmp v0.7.0 h1:wk8382ETsv4JYUZwIsn6YpYiWiBsYLSJiTsyBybVuN8= @@ -40,6 +42,8 @@ github.com/open-edge-platform/edge-node-agents/common v1.7.1 h1:6BrEqf5w9rTk3a4X github.com/open-edge-platform/edge-node-agents/common v1.7.1/go.mod h1:MbjzFRp5qIY7MxhN2vfC0CE4d/xGwPgfzcBS6ABsHX4= github.com/open-edge-platform/infra-external/dm-manager v0.2.2 h1:hy7/jTqzgrVPcyeo+QH+pVX44iffN+HoxCT2Z0lxFzw= github.com/open-edge-platform/infra-external/dm-manager v0.2.2/go.mod h1:9PoYl/Z+994IeWTD1D4WGdFc/Feje9afT3iw3FBbqiQ= +github.com/pkg/errors v0.9.1 h1:FEBLx1zS214owpjy7qsBeixbURkuhQAwrK5UwLGTwt4= +github.com/pkg/errors v0.9.1/go.mod h1:bwawxfHBFNV+L2hUp1rHADufV3IMtnDRdf1r5NINEl0= github.com/pmezard/go-difflib v1.0.0/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 h1:Jamvg5psRIccs7FGNTlIRMkT8wgtp5eCXdBlqhYGL6U= github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2/go.mod h1:iKH77koFhYxTK1pcRnkKkqfTogsbg7gZNVY4sRDYZ/4= @@ -52,6 +56,8 @@ github.com/shirou/gopsutil/v4 v4.25.4/go.mod h1:xbuxyoZj+UsgnZrENu3lQivsngRR5Bdj github.com/sirupsen/logrus v1.9.3 h1:dueUQJ1C2q9oE3F7wvmSGAaVtTmUizReu6fjN8uqzbQ= github.com/sirupsen/logrus v1.9.3/go.mod h1:naHLuLoDiP4jHNo9R0sCBMtWGeIprob74mVsIT4qYEQ= github.com/stretchr/objx v0.1.0/go.mod h1:HFkY916IF+rwdDfMAkV7OtwuqBVzrE8GR6GFx+wExME= +github.com/stretchr/objx v0.5.2 h1:xuMeJ0Sdp5ZMRXx/aWO6RZxdr3beISkG5/G/aIRr3pY= +github.com/stretchr/objx v0.5.2/go.mod h1:FRsXN1f5AsAjCGJKqEizvkpNtU+EGNCLh3NxZ/8L+MA= github.com/stretchr/testify v1.7.0/go.mod h1:6Fq8oRcR53rry900zMqJjRRixrwX3KX962/h/Wwjteg= github.com/stretchr/testify v1.10.0 h1:Xv5erBjTwe/5IxqUQTdXv5kgmIvbHo3QQyRwhJsOfJA= github.com/stretchr/testify v1.10.0/go.mod h1:r2ic/lqez/lEtzL7wO/rwa5dbSLXVDPFyf8C91i36aY= From 07e06b8a48c57d98b2b2f9203ed1b56153ff9174 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 12:21:48 +0000 Subject: [PATCH 21/40] Fix commands --- platform-manageability-agent/configs/sudoers.d/pm-agent | 2 +- platform-manageability-agent/internal/utils/utils.go | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/platform-manageability-agent/configs/sudoers.d/pm-agent b/platform-manageability-agent/configs/sudoers.d/pm-agent index f2300c43..bed172c6 100644 --- a/platform-manageability-agent/configs/sudoers.d/pm-agent +++ b/platform-manageability-agent/configs/sudoers.d/pm-agent @@ -1 +1 @@ -platform-manageability-agent ALL=(root) NOPASSWD:/usr/bin/sh,/usr/bin/rpc \ No newline at end of file +platform-manageability-agent ALL=(root) NOPASSWD:/usr/bin/sh,/usr/bin/rpc,/usr/sbin/dmidecode \ No newline at end of file diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index 2fa158d5..d6db1105 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -28,7 +28,7 @@ func (r *RealCommandExecutor) ExecuteAMTInfo() ([]byte, error) { var err error for i := 1; i <= maxRetries; i++ { - cmd := exec.Command("sudo", "./rpc", "amtinfo") + cmd := exec.Command("sudo", "./etc/intel_edge_node/rpc", "amtinfo") output, err := cmd.Output() if err == nil { return output, nil @@ -43,7 +43,7 @@ func (r *RealCommandExecutor) ExecuteAMTInfo() ([]byte, error) { // ExecuteAMTActivate executes the AMT activate command. func (r *RealCommandExecutor) ExecuteAMTActivate(rpsAddress, profileName, password string) ([]byte, error) { - cmd := exec.Command("sudo", "rpc", "activate", "-u", rpsAddress, "-n", "-profile", profileName, "-password", password) + cmd := exec.Command("sudo", "./etc/intel_edge_node/rpc", "activate", "-u", rpsAddress, "-profile", profileName, "-password", password, "-n") return cmd.CombinedOutput() } From cbcf693e712e64d4235af179a07806d97a31d4dc Mon Sep 17 00:00:00 2001 From: user Date: Wed, 23 Jul 2025 12:24:53 +0000 Subject: [PATCH 22/40] Fix commands --- platform-manageability-agent/internal/utils/utils.go | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/platform-manageability-agent/internal/utils/utils.go b/platform-manageability-agent/internal/utils/utils.go index d6db1105..7ebdba4c 100644 --- a/platform-manageability-agent/internal/utils/utils.go +++ b/platform-manageability-agent/internal/utils/utils.go @@ -28,7 +28,7 @@ func (r *RealCommandExecutor) ExecuteAMTInfo() ([]byte, error) { var err error for i := 1; i <= maxRetries; i++ { - cmd := exec.Command("sudo", "./etc/intel_edge_node/rpc", "amtinfo") + cmd := exec.Command("sudo", "/etc/intel_edge_node/rpc", "amtinfo") output, err := cmd.Output() if err == nil { return output, nil @@ -43,7 +43,7 @@ func (r *RealCommandExecutor) ExecuteAMTInfo() ([]byte, error) { // ExecuteAMTActivate executes the AMT activate command. func (r *RealCommandExecutor) ExecuteAMTActivate(rpsAddress, profileName, password string) ([]byte, error) { - cmd := exec.Command("sudo", "./etc/intel_edge_node/rpc", "activate", "-u", rpsAddress, "-profile", profileName, "-password", password, "-n") + cmd := exec.Command("sudo", "/etc/intel_edge_node/rpc", "activate", "-u", rpsAddress, "-profile", profileName, "-password", password, "-n") return cmd.CombinedOutput() } From 2bbf0517552dbbb645ac80d2b597514cb709bd7b Mon Sep 17 00:00:00 2001 From: user Date: Thu, 24 Jul 2025 03:44:20 +0000 Subject: [PATCH 23/40] Add license header --- .gitleaksignore | 3 +++ 1 file changed, 3 insertions(+) diff --git a/.gitleaksignore b/.gitleaksignore index d93767c0..c9827507 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -1,3 +1,6 @@ +# SPDX-FileCopyrightText: (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + # Test certificates - safe to ignore. platform-manageability-agent/test/_dummy-key.pem platform-manageability-agent/test/_dummy-cert.pem From 3eb5754dffab6dd09fdaccb01d992ec5127665e1 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 24 Jul 2025 04:21:20 +0000 Subject: [PATCH 24/40] Add more logs --- .../platform-manageability-agent.go | 33 ++++++++++++++----- .../internal/comms/comms.go | 20 ++++++----- 2 files changed, 36 insertions(+), 17 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 93c8632b..cb088355 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -33,8 +33,18 @@ import ( const ( AGENT_NAME = "platform-manageability-agent" MAX_RETRIES = 3 + + // AMTStatus constants representing the state of AMT. + AMTStatusDisabled int32 = 0 + AMTStatusEnabled int32 = 1 ) +var isAMTEnabled int32 + +func isAMTCurrentlyEnabled() bool { + return atomic.LoadInt32(&isAMTEnabled) == AMTStatusEnabled +} + func main() { if len(os.Args) == 2 && os.Args[1] == "version" { fmt.Printf("%v v%v\n", info.Component, info.Version) @@ -112,6 +122,7 @@ func main() { log.Fatalf("TLS configuration creation failed! Error: %v", err) } + log.Infof("Connecting to Device Management Manager at %s", confs.Manageability.ServiceURL) dmMgrClient := comms.ConnectToDMManager(auth.GetAuthContext(ctx, confs.AccessTokenPath), confs.Manageability.ServiceURL, tlsConfig) hostID, err := utils.GetSystemUUID() @@ -133,13 +144,18 @@ func main() { op := func() error { status, err := dmMgrClient.ReportAMTStatus(ctx, hostID) - if err != nil || status == pb.AMTStatus_DISABLED { - log.Errorf("Failed to report AMT status: %v", err) - atomic.StoreInt32(&isAMTEnabled, 0) - return err + if err != nil { + return fmt.Errorf("failed to report AMT status: %w", err) + } + switch status { + case pb.AMTStatus_DISABLED: + atomic.StoreInt32(&isAMTEnabled, AMTStatusDisabled) + case pb.AMTStatus_ENABLED: + atomic.StoreInt32(&isAMTEnabled, AMTStatusEnabled) + default: + log.Warnf("Unknown AMT status: %v, treating as disabled", status) + atomic.StoreInt32(&isAMTEnabled, AMTStatusDisabled) } - log.Info("Successfully reported AMT status") - atomic.StoreInt32(&isAMTEnabled, 1) return nil } for { @@ -165,14 +181,13 @@ func main() { defer activationTicker.Stop() op := func() error { - if atomic.LoadInt32(&isAMTEnabled) == 0 { + if !isAMTCurrentlyEnabled() { log.Info("Skipping activation check because AMT is not enabled") return nil } err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { - log.Errorf("Failed to retrieve activation details: %v", err) - return err + return fmt.Errorf("failed to retrieve activation details: %w", err) } log.Info("Successfully retrieved activation details") return nil diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 6c133b63..8f6c256c 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -128,7 +128,6 @@ func parseAMTInfo(uuid string, output []byte) *pb.AMTStatusRequest { func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTStatus, error) { defaultStatus := pb.AMTStatus_DISABLED - // TODO: RPC location need to be checked. output, err := cli.Executor.ExecuteAMTInfo() if err != nil { req := &pb.AMTStatusRequest{ @@ -148,7 +147,9 @@ func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTSt if err != nil { return defaultStatus, fmt.Errorf("failed to report AMT status: %w", err) } - log.Logger.Info("Successfully reported AMT status") + + log.Logger.Debugf("Reported AMT status: HostID=%s, Status=%v, Version=%s", + req.HostId, req.Status, req.Version) return req.Status, nil } @@ -159,7 +160,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { - return fmt.Errorf("failed to retrieve activation details: %w", err) + return fmt.Errorf("failed to retrieve activation details for host %s: %w", hostID, err) } log.Logger.Debugf("Retrieved activation details: HostID=%s, Operation=%v, ProfileName=%s", @@ -173,7 +174,8 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, password := "P@ssw0rd" output, err := cli.Executor.ExecuteAMTActivate(rpsAddress, resp.ProfileName, password) if err != nil { - return fmt.Errorf("failed to execute activation command: %w, Output: %s", err, string(output)) + return fmt.Errorf("failed to execute activation command for host %s: %w, Output: %s", + hostID, err, string(output)) } var req *pb.ActivationResultRequest @@ -182,23 +184,25 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, HostId: hostID, ActivationStatus: pb.ActivationStatus_PROVISIONED, } - log.Logger.Info("Provisioning successful: CIRA is configured") + log.Logger.Infof("Provisioning successful for host: %s", hostID) } else { req = &pb.ActivationResultRequest{ HostId: hostID, ActivationStatus: pb.ActivationStatus_FAILED, } - log.Logger.Warn("Provisioning failed: CIRA is not configured") + log.Logger.Infof("Provisioning failed for host: %s", hostID) } _, err = cli.DMMgrClient.ReportActivationResults(ctx, req) if err != nil { - return fmt.Errorf("failed to report Activation results: %w", err) + return fmt.Errorf("failed to report Activation results for host: %s, error: %w", hostID, err) } + log.Logger.Debugf("Reported activation results: HostID=%s, ActivationStatus=%v", + hostID, req.ActivationStatus) } return nil } -// isProvisioned checks if the output contains the line indicating provisioning success +// isProvisioned checks if the output contains the line indicating provisioning success. func isProvisioned(output string) bool { scanner := bufio.NewScanner(strings.NewReader(output)) for scanner.Scan() { From e560ec355c30edb161fc8aeb7b93ee6244f496b4 Mon Sep 17 00:00:00 2001 From: user Date: Thu, 24 Jul 2025 08:29:33 +0000 Subject: [PATCH 25/40] Fix bug --- .../platform-manageability-agent.go | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index cb088355..a6a623aa 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -134,7 +134,6 @@ func main() { amtStatusCheckInterval = confs.Manageability.HeartbeatInterval lastAMTStatusCheckTimestamp int64 - isAMTEnabled int32 ) wg.Add(1) go func() { @@ -185,6 +184,8 @@ func main() { log.Info("Skipping activation check because AMT is not enabled") return nil } + + log.Debugf("AMT is enabled, checking activation details for host %s", hostID) err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { return fmt.Errorf("failed to retrieve activation details: %w", err) From 602b6a9076160ac6b1eb8c5c0df60c600269a624 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 25 Jul 2025 09:23:25 +0000 Subject: [PATCH 26/40] Change AMT status report to be called once --- .../platform-manageability-agent.go | 26 +++++++------------ 1 file changed, 10 insertions(+), 16 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index a6a623aa..3c87a708 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -129,17 +129,11 @@ func main() { if err != nil { log.Fatalf("Failed to retrieve system UUID with an error: %v", err) } - var ( - wg sync.WaitGroup - amtStatusCheckInterval = confs.Manageability.HeartbeatInterval - lastAMTStatusCheckTimestamp int64 - ) + var wg sync.WaitGroup wg.Add(1) go func() { defer wg.Done() - amtStatusTicker := time.NewTicker(amtStatusCheckInterval) - defer amtStatusTicker.Stop() op := func() error { status, err := dmMgrClient.ReportAMTStatus(ctx, hostID) @@ -157,16 +151,16 @@ func main() { } return nil } - for { - select { - case <-ctx.Done(): - return - case <-amtStatusTicker.C: - amtStatusTicker.Stop() - updateWithRetry(ctx, log, op, &lastAMTStatusCheckTimestamp) + err := backoff.Retry(op, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) + if err != nil { + if ctx.Err() != nil { + log.Info("AMT status reporting cancelled due to context cancellation") + } else { + log.Errorf("Failed to report AMT status for host %s after retries: %v", hostID, err) } - amtStatusTicker.Reset(amtStatusCheckInterval) + return } + log.Infof("Successfully reported AMT status for host %s", hostID) }() var ( @@ -190,7 +184,7 @@ func main() { if err != nil { return fmt.Errorf("failed to retrieve activation details: %w", err) } - log.Info("Successfully retrieved activation details") + log.Infof("Successfully retrieved activation details for host %s", hostID) return nil } for { From dcc9b55335a3274f5827e3de7d293fb5458785db Mon Sep 17 00:00:00 2001 From: user Date: Fri, 25 Jul 2025 11:20:14 +0000 Subject: [PATCH 27/40] Handle grpc status code --- platform-manageability-agent/internal/comms/comms.go | 9 +++++++++ 1 file changed, 9 insertions(+) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 8f6c256c..f46187d3 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -15,7 +15,9 @@ import ( "github.com/grpc-ecosystem/go-grpc-middleware/v2/interceptors/timeout" "go.opentelemetry.io/contrib/instrumentation/google.golang.org/grpc/otelgrpc" "google.golang.org/grpc" + "google.golang.org/grpc/codes" "google.golang.org/grpc/credentials" + "google.golang.org/grpc/status" "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/config" log "github.com/open-edge-platform/edge-node-agents/platform-manageability-agent/internal/logger" @@ -160,6 +162,13 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, } resp, err := cli.DMMgrClient.RetrieveActivationDetails(ctx, req) if err != nil { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.FailedPrecondition: + log.Logger.Debugf("%v, %v", st.Message(), err.Error()) + return nil + } + } return fmt.Errorf("failed to retrieve activation details for host %s: %w", hostID, err) } From 3103b94523a0feaf1c5a0a484d174b70f690c911 Mon Sep 17 00:00:00 2001 From: user Date: Fri, 25 Jul 2025 11:52:57 +0000 Subject: [PATCH 28/40] Fix AMT Status --- .../platform-manageability-agent.go | 4 +++- platform-manageability-agent/internal/comms/comms.go | 9 ++++++++- 2 files changed, 11 insertions(+), 2 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 3c87a708..c4e926eb 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -136,8 +136,10 @@ func main() { defer wg.Done() op := func() error { + log.Infof("Reporting AMT status for host %s", hostID) status, err := dmMgrClient.ReportAMTStatus(ctx, hostID) if err != nil { + log.Errorf("Failed to report AMT status for host %s: %v", hostID, err) return fmt.Errorf("failed to report AMT status: %w", err) } switch status { @@ -179,7 +181,7 @@ func main() { return nil } - log.Debugf("AMT is enabled, checking activation details for host %s", hostID) + log.Infof("AMT is enabled, checking activation details for host %s", hostID) err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { return fmt.Errorf("failed to retrieve activation details: %w", err) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index f46187d3..863da698 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -147,10 +147,17 @@ func (cli *Client) ReportAMTStatus(ctx context.Context, hostID string) (pb.AMTSt req := parseAMTInfo(hostID, output) _, err = cli.DMMgrClient.ReportAMTStatus(ctx, req) if err != nil { + if st, ok := status.FromError(err); ok { + switch st.Code() { + case codes.FailedPrecondition: + log.Logger.Debugf("Received %v, %v", st.Message(), err.Error()) + return req.Status, nil + } + } return defaultStatus, fmt.Errorf("failed to report AMT status: %w", err) } - log.Logger.Debugf("Reported AMT status: HostID=%s, Status=%v, Version=%s", + log.Logger.Infof("Reported AMT status: HostID=%s, Status=%v, Version=%s", req.HostId, req.Status, req.Version) return req.Status, nil } From bb00344828a5f05b018acb53dd723e2db95df3e2 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 06:24:40 +0000 Subject: [PATCH 29/40] Fix review comments --- platform-manageability-agent/go.mod | 4 ++-- platform-manageability-agent/go.sum | 8 ++++---- 2 files changed, 6 insertions(+), 6 deletions(-) diff --git a/platform-manageability-agent/go.mod b/platform-manageability-agent/go.mod index 80d33bf9..90d9f332 100644 --- a/platform-manageability-agent/go.mod +++ b/platform-manageability-agent/go.mod @@ -24,7 +24,7 @@ require ( github.com/go-logr/stdr v1.2.2 // indirect github.com/go-ole/go-ole v1.3.0 // indirect github.com/google/uuid v1.6.0 // indirect - github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 // indirect + github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 // indirect github.com/lufia/plan9stats v0.0.0-20250317134145-8bc96cf8fc35 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect @@ -45,7 +45,7 @@ require ( golang.org/x/oauth2 v0.30.0 // indirect golang.org/x/sys v0.33.0 // indirect golang.org/x/text v0.26.0 // indirect - google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 // indirect + google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 // indirect google.golang.org/protobuf v1.36.6 // indirect ) diff --git a/platform-manageability-agent/go.sum b/platform-manageability-agent/go.sum index 2ada8377..8f78d087 100644 --- a/platform-manageability-agent/go.sum +++ b/platform-manageability-agent/go.sum @@ -30,8 +30,8 @@ github.com/google/uuid v1.6.0 h1:NIvaJDMOsjHA8n1jAhLSgzrAzy1Hgr+hNrb57e+94F0= github.com/google/uuid v1.6.0/go.mod h1:TIyPZe4MgqvfeYDBFedMoGGpEw/LqOeaOT+nhxU+yHo= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2 h1:sGm2vDRFUrQJO/Veii4h4zG2vvqG6uWNkBHSTqXOZk0= github.com/grpc-ecosystem/go-grpc-middleware/v2 v2.3.2/go.mod h1:wd1YpapPLivG6nQgbf7ZkG1hhSOXDhhn4MLTknx2aAc= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3 h1:5ZPtiqj0JL5oKWmcsq4VMaAW5ukBEgSGXEN89zeH1Jo= -github.com/grpc-ecosystem/grpc-gateway/v2 v2.26.3/go.mod h1:ndYquD05frm2vACXE1nsccT4oJzjhw2arTS2cpUD1PI= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1 h1:X5VWvz21y3gzm9Nw/kaUeku/1+uBhcekkmy4IkffJww= +github.com/grpc-ecosystem/grpc-gateway/v2 v2.27.1/go.mod h1:Zanoh4+gvIgluNqcfMVTJueD4wSS5hT7zTt4Mrutd90= github.com/kr/pretty v0.3.1 h1:flRD4NNwYAUpkphVc1HcthR4KEIFJ65n8Mw5qdRn3LE= github.com/kr/pretty v0.3.1/go.mod h1:hoEshYVHaxMs3cyo3Yncou5ZscifuDolrwPKZanG3xk= github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY= @@ -101,8 +101,8 @@ golang.org/x/sys v0.33.0 h1:q3i8TbbEz+JRD9ywIRlyRAQbM0qF7hu24q3teo2hbuw= golang.org/x/sys v0.33.0/go.mod h1:BJP2sWEmIv4KK5OTEluFJCKSidICx8ciO85XgH3Ak8k= golang.org/x/text v0.26.0 h1:P42AVeLghgTYr4+xUnTRKDMqpar+PtX7KWuNQL21L8M= golang.org/x/text v0.26.0/go.mod h1:QK15LZJUUQVJxhz7wXgxSy/CJaTFjd0G+YLonydOVQA= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237 h1:Kog3KlB4xevJlAcbbbzPfRG0+X9fdoGM+UBRKVz6Wr0= -google.golang.org/genproto/googleapis/api v0.0.0-20250519155744-55703ea1f237/go.mod h1:ezi0AVyMKDWy5xAncvjLWH7UcLBB5n7y2fQ8MzjJcto= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822 h1:oWVWY3NzT7KJppx2UKhKmzPq4SRe0LdCijVRwvGeikY= +google.golang.org/genproto/googleapis/api v0.0.0-20250603155806-513f23925822/go.mod h1:h3c4v36UTKzUiuaOKQ6gr3S+0hovBtUrXzTG/i3+XEc= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822 h1:fc6jSaCT0vBduLYZHYrBBNY4dsWuvgyff9noRNDdBeE= google.golang.org/genproto/googleapis/rpc v0.0.0-20250603155806-513f23925822/go.mod h1:qQ0YXyHHx3XkvlzUtpXDkS29lDSafHMZBAZDc03LQ3A= google.golang.org/grpc v1.73.0 h1:VIWSmpI2MegBtTuFt5/JWy2oXxtjJ/e89Z70ImfD2ok= From 21eff5a426f6e184f10a69ae4e565978441d6d54 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 07:57:28 +0000 Subject: [PATCH 30/40] Update PMA version --- ena-manifest.yaml | 2 +- platform-manageability-agent/VERSION | 2 +- 2 files changed, 2 insertions(+), 2 deletions(-) diff --git a/ena-manifest.yaml b/ena-manifest.yaml index 5913f794..2a133c46 100644 --- a/ena-manifest.yaml +++ b/ena-manifest.yaml @@ -19,7 +19,7 @@ packages: version: 1.7.2 ociArtifact: edge-orch/en/deb/node-agent - name: platform-manageability-agent - version: 0.1.1 + version: 0.1.2 ociArtifact: edge-orch/en/deb/platform-manageability-agent - name: platform-observability-agent version: 1.8.0 diff --git a/platform-manageability-agent/VERSION b/platform-manageability-agent/VERSION index 17e51c38..d917d3e2 100644 --- a/platform-manageability-agent/VERSION +++ b/platform-manageability-agent/VERSION @@ -1 +1 @@ -0.1.1 +0.1.2 From bfd2078e0f4749826fb2a08db415a78e2a28ec8b Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 11:49:04 +0000 Subject: [PATCH 31/40] Fix lint issues --- .../platform-manageability-agent.go | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index c4e926eb..3fe48472 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -156,7 +156,7 @@ func main() { err := backoff.Retry(op, backoff.WithContext(backoff.NewExponentialBackOff(), ctx)) if err != nil { if ctx.Err() != nil { - log.Info("AMT status reporting cancelled due to context cancellation") + log.Info("AMT status reporting canceled due to context cancellation") } else { log.Errorf("Failed to report AMT status for host %s after retries: %v", hostID, err) } From 27424fa79f3b537d927a76807d187ab7dbf19889 Mon Sep 17 00:00:00 2001 From: user Date: Mon, 28 Jul 2025 11:51:50 +0000 Subject: [PATCH 32/40] Fix .gitleaksignore file --- .gitleaksignore | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.gitleaksignore b/.gitleaksignore index c9827507..785530f2 100644 --- a/.gitleaksignore +++ b/.gitleaksignore @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 # Test certificates - safe to ignore. -platform-manageability-agent/test/_dummy-key.pem +platform-manageability-agent/test/_dummy-key.pem:private-key:1 platform-manageability-agent/test/_dummy-cert.pem From 31a3a1d658111139bc96629272f8eb9516283a17 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 03:26:53 +0000 Subject: [PATCH 33/40] Add .semgrepignore file --- .semgrepignore | 5 +++++ 1 file changed, 5 insertions(+) create mode 100644 .semgrepignore diff --git a/.semgrepignore b/.semgrepignore new file mode 100644 index 00000000..c268148c --- /dev/null +++ b/.semgrepignore @@ -0,0 +1,5 @@ +# SPDX-FileCopyrightText: (C) 2025 Intel Corporation +# SPDX-License-Identifier: Apache-2.0 + +# Test data and files +platform-manageability-agent/test/ \ No newline at end of file From 4edd809f2af45e3e9e989730002cd69f617b0499 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 03:37:45 +0000 Subject: [PATCH 34/40] Add specific rule --- .semgrepignore | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/.semgrepignore b/.semgrepignore index c268148c..b441d9e2 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -2,4 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 # Test data and files -platform-manageability-agent/test/ \ No newline at end of file +platform-manageability-agent/test/_dummy-key.pem +platform-manageability-agent/test/_dummy-cert.pem \ No newline at end of file From cb5605465a42c28f45c223ed1cd77b3a3dd5241a Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 03:47:20 +0000 Subject: [PATCH 35/40] Change the .semgrepignore format --- .semgrepignore | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.semgrepignore b/.semgrepignore index b441d9e2..b2294885 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -2,5 +2,5 @@ # SPDX-License-Identifier: Apache-2.0 # Test data and files -platform-manageability-agent/test/_dummy-key.pem -platform-manageability-agent/test/_dummy-cert.pem \ No newline at end of file +/platform-manageability-agent/test/_dummy-key.pem +/platform-manageability-agent/test/_dummy-cert.pem From 0fe4f9ad4254939eeb9169e666d1240f11566b40 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 04:14:34 +0000 Subject: [PATCH 36/40] Try various semgrepignore formats --- .semgrepignore | 9 +++++++-- 1 file changed, 7 insertions(+), 2 deletions(-) diff --git a/.semgrepignore b/.semgrepignore index b2294885..0d48ef6e 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -2,5 +2,10 @@ # SPDX-License-Identifier: Apache-2.0 # Test data and files -/platform-manageability-agent/test/_dummy-key.pem -/platform-manageability-agent/test/_dummy-cert.pem +platform-manageability-agent/test/ +platform-manageability-agent/test/_dummy-key.pem +platform-manageability-agent/test/_dummy-cert.pem +./platform-manageability-agent/test/_dummy-key.pem +./platform-manageability-agent/test/_dummy-cert.pem +**/test/_dummy-key.pem +**/test/_dummy-cert.pem From ed53be9b656457d51987b3e4c27dc48f0215fb6e Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 05:51:46 +0000 Subject: [PATCH 37/40] Add rule in .semgrepignore file --- .semgrepignore | 1 + 1 file changed, 1 insertion(+) diff --git a/.semgrepignore b/.semgrepignore index 0d48ef6e..0c0c4f13 100644 --- a/.semgrepignore +++ b/.semgrepignore @@ -9,3 +9,4 @@ platform-manageability-agent/test/_dummy-cert.pem ./platform-manageability-agent/test/_dummy-cert.pem **/test/_dummy-key.pem **/test/_dummy-cert.pem +/test From b9c8bebb5e52e15b39490cf4f3725ddd3730c860 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 15:43:01 +0000 Subject: [PATCH 38/40] Add debug logs --- .../platform-manageability-agent.go | 4 ++++ .../internal/comms/comms.go | 15 ++++++++++----- 2 files changed, 14 insertions(+), 5 deletions(-) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index 3fe48472..e72e97d1 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -184,6 +184,10 @@ func main() { log.Infof("AMT is enabled, checking activation details for host %s", hostID) err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { + if errors.Is(err, comms.ErrActivationSkipped) { + log.Logger.Debugf("AMT activation skipped for host %s - reason: %v", hostID, err) + return nil + } return fmt.Errorf("failed to retrieve activation details: %w", err) } log.Infof("Successfully retrieved activation details for host %s", hostID) diff --git a/platform-manageability-agent/internal/comms/comms.go b/platform-manageability-agent/internal/comms/comms.go index 863da698..6a4633c5 100644 --- a/platform-manageability-agent/internal/comms/comms.go +++ b/platform-manageability-agent/internal/comms/comms.go @@ -7,6 +7,7 @@ import ( "bufio" "context" "crypto/tls" + "errors" "fmt" "net" "strings" @@ -25,9 +26,13 @@ import ( pb "github.com/open-edge-platform/infra-external/dm-manager/pkg/api/dm-manager" ) -const retryInterval = 10 * time.Second -const tickerInterval = 500 * time.Millisecond -const connTimeout = 5 * time.Second +const ( + retryInterval = 10 * time.Second + tickerInterval = 500 * time.Millisecond + connTimeout = 5 * time.Second +) + +var ErrActivationSkipped = errors.New("activation skipped") type Client struct { DMMgrServiceAddr string @@ -172,8 +177,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, if st, ok := status.FromError(err); ok { switch st.Code() { case codes.FailedPrecondition: - log.Logger.Debugf("%v, %v", st.Message(), err.Error()) - return nil + return fmt.Errorf("%w: host %s precondition failed - %v", ErrActivationSkipped, hostID, st.Message()) } } return fmt.Errorf("failed to retrieve activation details for host %s: %w", hostID, err) @@ -193,6 +197,7 @@ func (cli *Client) RetrieveActivationDetails(ctx context.Context, hostID string, return fmt.Errorf("failed to execute activation command for host %s: %w, Output: %s", hostID, err, string(output)) } + log.Logger.Debugf("Activation command output for host %s: %s", hostID, string(output)) var req *pb.ActivationResultRequest if isProvisioned(string(output)) { From acd2c0b2a4835dc17ae8d63262bb48ee3f0fb5a6 Mon Sep 17 00:00:00 2001 From: user Date: Tue, 29 Jul 2025 15:57:39 +0000 Subject: [PATCH 39/40] Add FIXME comment --- .../platform-manageability-agent.go | 3 +++ 1 file changed, 3 insertions(+) diff --git a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go index e72e97d1..23a0a2d6 100644 --- a/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go +++ b/platform-manageability-agent/cmd/platform-manageability-agent/platform-manageability-agent.go @@ -182,6 +182,9 @@ func main() { } log.Infof("AMT is enabled, checking activation details for host %s", hostID) + // FIXME: https://github.com/open-edge-platform/edge-node-agents/pull/170#discussion_r2236433075 + // The suggestion is to combine the activation check and retrieval of activation details into a single call + // to reduce the number of RPC calls. err = dmMgrClient.RetrieveActivationDetails(ctx, hostID, confs) if err != nil { if errors.Is(err, comms.ErrActivationSkipped) { From ab23369fdaa1f531d527085446dd47a8175eac40 Mon Sep 17 00:00:00 2001 From: user Date: Wed, 30 Jul 2025 08:50:50 +0000 Subject: [PATCH 40/40] Remove .semgrepignore file --- .semgrepignore | 12 ------------ 1 file changed, 12 deletions(-) delete mode 100644 .semgrepignore diff --git a/.semgrepignore b/.semgrepignore deleted file mode 100644 index 0c0c4f13..00000000 --- a/.semgrepignore +++ /dev/null @@ -1,12 +0,0 @@ -# SPDX-FileCopyrightText: (C) 2025 Intel Corporation -# SPDX-License-Identifier: Apache-2.0 - -# Test data and files -platform-manageability-agent/test/ -platform-manageability-agent/test/_dummy-key.pem -platform-manageability-agent/test/_dummy-cert.pem -./platform-manageability-agent/test/_dummy-key.pem -./platform-manageability-agent/test/_dummy-cert.pem -**/test/_dummy-key.pem -**/test/_dummy-cert.pem -/test