Skip to content

Commit

Permalink
Draft agent for validation purpose (#576)
Browse files Browse the repository at this point in the history
* draft agent

* resolve comments

* resolve comments

* resolve comments
  • Loading branch information
iwangjintian authored Dec 4, 2024
1 parent 726864f commit 761ea7f
Show file tree
Hide file tree
Showing 15 changed files with 784 additions and 0 deletions.
34 changes: 34 additions & 0 deletions remote-agent/ReadMe.md
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
```
66 changes: 66 additions & 0 deletions remote-agent/agent/agent.go
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")}
}
}
179 changes: 179 additions & 0 deletions remote-agent/bindings/http/http.go
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
}
}
39 changes: 39 additions & 0 deletions remote-agent/common/utils.go
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"`
}
4 changes: 4 additions & 0 deletions remote-agent/config.json
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"
}
57 changes: 57 additions & 0 deletions remote-agent/go.mod
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
)
Loading

0 comments on commit 761ea7f

Please sign in to comment.