-
Notifications
You must be signed in to change notification settings - Fork 29
Commit
This commit does not belong to any branch on this repository, and may belong to a fork outside of the repository.
Draft agent for validation purpose (#576)
* draft agent * resolve comments * resolve comments * resolve comments
- Loading branch information
1 parent
726864f
commit 761ea7f
Showing
15 changed files
with
784 additions
and
0 deletions.
There are no files selected for viewing
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,34 @@ | ||
# Symphony Remote Agent | ||
|
||
The Symphony Remote Agent is currently in a draft state. It is built for the validation purposes of Symphony remote agent bootstrap and Symphony cluster asynchronous operations. | ||
|
||
## Components | ||
|
||
The agent consists of the following components: | ||
- **Start-up**: `main.go` | ||
- **Binding**: `http.go` | ||
- **Manager**: `agent.go` | ||
- **Execution**: `providers/target/script_provider.go` | ||
|
||
## Script Provider | ||
|
||
The Script Provider is designed to be the most applicable provider for the remote agent. Below is the configuration for the Script Provider: | ||
|
||
- `ApplyScript`: `"mock-apply.sh"` | ||
- `GetScript`: `"mock-get.sh"` | ||
- `RemoveScript`: `"mock-remove.sh"` | ||
- `ScriptFolder`: `./script` | ||
- `StagingFolder`: `"."` | ||
|
||
### Known Issues | ||
|
||
The Script Provider's apply functionality currently has a bug. As a workaround, the script output is added to the `componentSpec` message. | ||
|
||
## Mock Implementation | ||
|
||
The remote agent currently uses a mock to read requests from `samples/request.json` and prints the response body to the console. It should poll requests from the Symphony endpoint and patch the asynchronous operation result to the Symphony patch endpoint. This configuration is set in `config.json`. | ||
|
||
## Run draft agent | ||
``` | ||
go run main.go -config=./config.json | ||
``` |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,66 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
package agent | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"errors" | ||
|
||
tgt "github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/providers/target" | ||
utils "github.com/eclipse-symphony/symphony/remote-agent/common" | ||
) | ||
|
||
type Agent struct { | ||
Providers map[string]tgt.ITargetProvider | ||
} | ||
|
||
func (s Agent) Handle(req []byte, ctx context.Context) utils.AsyncResult { | ||
request := utils.AgentRequest{} | ||
err := json.Unmarshal(req, &request) | ||
if err != nil { | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: err} | ||
} | ||
|
||
body := new([]byte) | ||
|
||
provider := s.Providers[request.Provider] | ||
if provider == nil { | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: errors.New("Provider not found")} | ||
} | ||
|
||
switch request.Action { | ||
case "get": | ||
var getRequest utils.ProviderGetRequest | ||
if err := json.Unmarshal(req, &getRequest); err != nil { | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: err} | ||
} | ||
specs, err := provider.Get(ctx, getRequest.Deployment, getRequest.References) | ||
*body, err = json.Marshal(specs) | ||
return utils.AsyncResult{OperationID: request.OperationID, Body: *body, Error: err} | ||
|
||
case "apply": | ||
var applyRequest utils.ProviderApplyRequest | ||
if err := json.Unmarshal(req, &applyRequest); err != nil { | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: err} | ||
} | ||
specs, err := provider.Apply(ctx, applyRequest.Deployment, applyRequest.Step, applyRequest.Deployment.IsDryRun) | ||
*body, err = json.Marshal(specs) | ||
return utils.AsyncResult{OperationID: request.OperationID, Body: *body, Error: err} | ||
|
||
case "getValidationRule": | ||
var getValidationRuleRequest utils.ProviderGetValidationRuleRequest | ||
if err := json.Unmarshal(req, &getValidationRuleRequest); err != nil { | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: err} | ||
} | ||
rule := provider.GetValidationRule(ctx) | ||
*body, err = json.Marshal(rule) | ||
return utils.AsyncResult{OperationID: request.OperationID, Body: *body, Error: err} | ||
default: | ||
return utils.AsyncResult{OperationID: request.OperationID, Error: errors.New("Action not found")} | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,179 @@ | ||
/* | ||
* Copyright (c) Microsoft Corporation. | ||
* Licensed under the MIT license. | ||
* SPDX-License-Identifier: MIT | ||
*/ | ||
|
||
package http | ||
|
||
import ( | ||
"context" | ||
"encoding/json" | ||
"fmt" | ||
"io/ioutil" | ||
"net/url" | ||
"time" | ||
|
||
v1alpha2 "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2" | ||
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers" | ||
"github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/certs" | ||
autogen "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/certs/autogen" | ||
localfile "github.com/eclipse-symphony/symphony/coa/pkg/apis/v1alpha2/providers/certs/localfile" | ||
"github.com/eclipse-symphony/symphony/coa/pkg/logger/contexts" | ||
"github.com/eclipse-symphony/symphony/remote-agent/agent" | ||
"github.com/valyala/fasthttp" | ||
) | ||
|
||
// MiddlewareConfig configures a HTTP middleware. | ||
type MiddlewareConfig struct { | ||
Type string `json:"type"` | ||
Properties map[string]interface{} `json:"properties"` | ||
} | ||
|
||
type CertProviderConfig struct { | ||
Type string `json:"type"` | ||
Config providers.IProviderConfig `json:"config"` | ||
} | ||
|
||
// HttpBindingConfig configures a HttpBinding. | ||
type HttpBindingConfig struct { | ||
Port int `json:"port"` | ||
Pipeline []MiddlewareConfig `json:"pipeline"` | ||
TLS bool `json:"tls"` | ||
CertProvider CertProviderConfig `json:"certProvider"` | ||
} | ||
|
||
// HttpBinding provides service endpoints as a fasthttp web server | ||
type HttpBinding struct { | ||
CertProvider certs.ICertProvider | ||
Agent agent.Agent | ||
ResponseUrl *url.URL | ||
RequestUrl *url.URL | ||
} | ||
|
||
// Launch the polling agent | ||
func (h *HttpBinding) Launch(config HttpBindingConfig) error { | ||
//handler := h.useRouter(endpoints) | ||
var err error | ||
|
||
if err != nil { | ||
return err | ||
} | ||
|
||
if config.TLS { | ||
switch config.CertProvider.Type { | ||
case "certs.autogen": | ||
h.CertProvider = &autogen.AutoGenCertProvider{} | ||
case "certs.localfile": | ||
h.CertProvider = &localfile.LocalCertFileProvider{} | ||
default: | ||
return v1alpha2.NewCOAError(nil, fmt.Sprintf("cert provider type '%s' is not recognized", config.CertProvider.Type), v1alpha2.BadConfig) | ||
} | ||
err = h.CertProvider.Init(config.CertProvider.Config) | ||
if err != nil { | ||
return err | ||
} | ||
} | ||
|
||
go func() { | ||
// poll http response from a url - Mock | ||
//httpclient := &http.Client{} | ||
for { | ||
//This is the correct logic - Mock | ||
// resp, err := httpclient.Get(h.RequestUrl.Host) | ||
// if err != nil { | ||
// fmt.Println("error:", err) | ||
// time.Sleep(5 * time.Second) // Retry after a delay | ||
// continue | ||
// } | ||
|
||
// Mock to read request from file - Mock | ||
file, err := ioutil.ReadFile("./samples/request.json") | ||
|
||
// read response body - Mock | ||
// body, err := io.ReadAll(resp.Body) | ||
|
||
// - Mock | ||
body := file | ||
|
||
if err != nil { | ||
fmt.Println("error reading body:", err) | ||
} else { | ||
fmt.Println("response body:", string(body)) | ||
} | ||
|
||
// close response body - Mock | ||
// resp.Body.Close() | ||
|
||
requests := make([]map[string]interface{}, 0) | ||
err = json.Unmarshal(body, &requests) | ||
|
||
for _, req := range requests { | ||
// handle request | ||
go func() { | ||
// TODO Ack the requests | ||
|
||
correlationId, ok := req[contexts.ConstructHttpHeaderKeyForActivityLogContext(contexts.Activity_CorrelationId)].(string) | ||
if !ok { | ||
fmt.Println("error: correlationId not found or not a string") | ||
correlationId = "00000000-0000-0000-0000-000000000000" | ||
} | ||
retCtx := context.TODO() | ||
retCtx = context.WithValue(retCtx, contexts.Activity_CorrelationId, correlationId) | ||
|
||
body, err := json.Marshal(req) | ||
if err != nil { | ||
fmt.Println("error marshalling request:", err) | ||
return | ||
} | ||
ret := h.Agent.Handle(body, retCtx) | ||
fmt.Println("Agent response:", string(ret.Body)) | ||
|
||
// Send response back - Mock | ||
// respBody, err := json.Marshal(ret) | ||
// if err != nil { | ||
// fmt.Println("error marshalling response:", err) | ||
// } | ||
// respRet, err := httpclient.Do(&http.Request{ | ||
// URL: h.ResponseUrl, | ||
// Method: "POST", | ||
// Body: io.NopCloser(strings.NewReader(string(respBody))), | ||
// }) | ||
// if err != nil { | ||
// fmt.Println("error sending response:", err) | ||
// } else { | ||
// fmt.Println("response status:", respRet.Status) | ||
// } | ||
}() | ||
} | ||
|
||
// Sleep for a while before polling again | ||
time.Sleep(15 * time.Second) | ||
} | ||
|
||
}() | ||
return nil | ||
} | ||
|
||
func toHttpState(state v1alpha2.State) int { | ||
switch state { | ||
case v1alpha2.OK: | ||
return fasthttp.StatusOK | ||
case v1alpha2.Accepted: | ||
return fasthttp.StatusAccepted | ||
case v1alpha2.BadRequest: | ||
return fasthttp.StatusBadRequest | ||
case v1alpha2.Unauthorized: | ||
return fasthttp.StatusUnauthorized | ||
case v1alpha2.NotFound: | ||
return fasthttp.StatusNotFound | ||
case v1alpha2.MethodNotAllowed: | ||
return fasthttp.StatusMethodNotAllowed | ||
case v1alpha2.Conflict: | ||
return fasthttp.StatusConflict | ||
case v1alpha2.InternalError: | ||
return fasthttp.StatusInternalServerError | ||
default: | ||
return fasthttp.StatusInternalServerError | ||
} | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,39 @@ | ||
package utils | ||
|
||
import ( | ||
"github.com/eclipse-symphony/symphony/api/pkg/apis/v1alpha1/model" | ||
) | ||
|
||
type AgentRequest struct { | ||
OperationID string `json:"operationID"` | ||
Provider string `json:"provider"` | ||
Action string `json:"action"` | ||
} | ||
|
||
type ProviderGetRequest struct { | ||
AgentRequest | ||
Deployment model.DeploymentSpec `json:"deployment"` | ||
References []model.ComponentStep `json:"references"` | ||
} | ||
|
||
type ProviderApplyRequest struct { | ||
AgentRequest | ||
Deployment model.DeploymentSpec `json:"deployment"` | ||
Step model.DeploymentStep `json:"step"` | ||
IsDryRun bool `json:"isDryRun,omitempty"` | ||
} | ||
|
||
type ProviderGetValidationRuleRequest struct { | ||
AgentRequest | ||
} | ||
|
||
type AsyncResult struct { | ||
OperationID string `json:"operationID"` | ||
Error error `json:"error,omitempty"` | ||
Body []byte `json:"body"` | ||
} | ||
|
||
type SymphonyEndpoint struct { | ||
RequestEndpoint string `json:"requestEndpoint,omitempty"` | ||
ResponseEndpoint string `json:"responseEndpoint,omitempty"` | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,4 @@ | ||
{ | ||
"requestEndpoint": "http://localhost:3000/agent/request", | ||
"responseEndpoint": "http://localhost:3000/agent/response" | ||
} |
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
Original file line number | Diff line number | Diff line change |
---|---|---|
@@ -0,0 +1,57 @@ | ||
module github.com/eclipse-symphony/symphony/remote-agent | ||
|
||
go 1.22.4 | ||
|
||
toolchain go1.22.6 | ||
|
||
replace github.com/eclipse-symphony/symphony/coa => ../coa | ||
|
||
replace github.com/eclipse-symphony/symphony/packages/mage => ../packages/mage | ||
|
||
require ( | ||
github.com/eclipse-symphony/symphony/api v0.0.0-20241129081400-5ea7d2a7ec27 | ||
github.com/eclipse-symphony/symphony/coa v0.0.0 | ||
github.com/valyala/fasthttp v1.57.0 | ||
) | ||
|
||
require ( | ||
github.com/andybalholm/brotli v1.1.1 // indirect | ||
github.com/cenkalti/backoff/v4 v4.3.0 // indirect | ||
github.com/go-logr/logr v1.4.2 // indirect | ||
github.com/go-logr/stdr v1.2.2 // indirect | ||
github.com/google/uuid v1.6.0 // indirect | ||
github.com/grpc-ecosystem/grpc-gateway/v2 v2.22.0 // indirect | ||
github.com/klauspost/compress v1.17.11 // indirect | ||
github.com/openzipkin/zipkin-go v0.4.1 // indirect | ||
github.com/pkg/errors v0.9.1 // indirect | ||
github.com/sirupsen/logrus v1.9.3 // indirect | ||
github.com/valyala/bytebufferpool v1.0.0 // indirect | ||
go.opentelemetry.io/contrib/bridges/otellogrus v0.3.0 // indirect | ||
go.opentelemetry.io/otel v1.31.0 // indirect | ||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploggrpc v0.7.0 // indirect | ||
go.opentelemetry.io/otel/exporters/otlp/otlplog/otlploghttp v0.7.0 // indirect | ||
go.opentelemetry.io/otel/exporters/otlp/otlpmetric/otlpmetricgrpc v1.28.0 // indirect | ||
go.opentelemetry.io/otel/exporters/otlp/otlptrace v1.22.0 // indirect | ||
go.opentelemetry.io/otel/exporters/otlp/otlptrace/otlptracegrpc v1.22.0 // indirect | ||
go.opentelemetry.io/otel/exporters/stdout/stdoutlog v0.4.0 // indirect | ||
go.opentelemetry.io/otel/exporters/stdout/stdouttrace v1.21.0 // indirect | ||
go.opentelemetry.io/otel/exporters/zipkin v1.11.1 // indirect | ||
go.opentelemetry.io/otel/log v0.7.0 // indirect | ||
go.opentelemetry.io/otel/metric v1.31.0 // indirect | ||
go.opentelemetry.io/otel/sdk v1.31.0 // indirect | ||
go.opentelemetry.io/otel/sdk/log v0.7.0 // indirect | ||
go.opentelemetry.io/otel/sdk/metric v1.28.0 // indirect | ||
go.opentelemetry.io/otel/trace v1.31.0 // indirect | ||
go.opentelemetry.io/proto/otlp v1.3.1 // indirect | ||
golang.org/x/exp v0.0.0-20231006140011-7918f672742d // indirect | ||
golang.org/x/net v0.30.0 // indirect | ||
golang.org/x/sys v0.26.0 // indirect | ||
golang.org/x/text v0.19.0 // indirect | ||
google.golang.org/genproto/googleapis/api v0.0.0-20241007155032-5fefd90f89a9 // indirect | ||
google.golang.org/genproto/googleapis/rpc v0.0.0-20241007155032-5fefd90f89a9 // indirect | ||
google.golang.org/grpc v1.67.1 // indirect | ||
google.golang.org/protobuf v1.35.1 // indirect | ||
helm.sh/helm/v3 v3.15.4 // indirect | ||
k8s.io/client-go v0.30.3 // indirect | ||
sigs.k8s.io/yaml v1.4.0 // indirect | ||
) |
Oops, something went wrong.