Skip to content

Commit

Permalink
refactor: improve code readability and add English documents
Browse files Browse the repository at this point in the history
  • Loading branch information
cyb0225 committed Oct 26, 2023
1 parent a023b53 commit 3ff0b08
Show file tree
Hide file tree
Showing 11 changed files with 130 additions and 121 deletions.
2 changes: 1 addition & 1 deletion components/hello/pluggable.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,7 +23,7 @@ import (

func init() {
// spec.proto.pluggable.v1.Hello
pluggable.AddServiceDiscoveryCallback(helloproto.Hello_ServiceDesc.ServiceName, func(compType string, dialer pluggable.GRPCConnectionDialer) interface{} {
pluggable.AddServiceDiscoveryCallback(helloproto.Hello_ServiceDesc.ServiceName, func(compType string, dialer pluggable.GRPCConnectionDialer) pluggable.Component {
return NewHelloFactory(compType, func() HelloService {
return NewGRPCHello(dialer)
})
Expand Down
4 changes: 3 additions & 1 deletion components/pluggable/register.go
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,9 @@ var (
onServiceDiscovered map[string]CallbackFunc
)

type CallbackFunc func(compType string, dialer GRPCConnectionDialer) interface{}
type Component interface{}

type CallbackFunc func(compType string, dialer GRPCConnectionDialer) Component

// AddServiceDiscoveryCallback register callback function, not concurrent secure
func AddServiceDiscoveryCallback(serviceName string, callback CallbackFunc) {
Expand Down
2 changes: 1 addition & 1 deletion components/pluggable/register_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -21,7 +21,7 @@ import (

func Test_onServiceDiscovered(t *testing.T) {
t.Run("add service callback should add a new entry when called", func(t *testing.T) {
AddServiceDiscoveryCallback("fake", func(string, GRPCConnectionDialer) interface{} {
AddServiceDiscoveryCallback("fake", func(string, GRPCConnectionDialer) Component {
return nil
})
assert.NotEmpty(t, onServiceDiscovered)
Expand Down
20 changes: 20 additions & 0 deletions demo/pluggable/hello/main.go
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@ import (
"fmt"
"net"
"os"
"path/filepath"

"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
Expand Down Expand Up @@ -44,6 +45,7 @@ func (h *HelloService) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.
}

func main() {
checkSocketDir()
listen, err := net.Listen("unix", SocketFilePath)
if err != nil {
panic(err)
Expand All @@ -60,3 +62,21 @@ func main() {
fmt.Println(err)
}
}

func checkSocketDir() {
if _, err := os.Stat(SocketFilePath); os.IsNotExist(err) {
// 创建Socket文件
err = os.MkdirAll(filepath.Dir(SocketFilePath), 0755)
if err != nil {
fmt.Println("Failed to create directory:", err)
os.Exit(1)
}
}
if _, err := os.Stat(SocketFilePath); err == nil {
err = os.Remove(SocketFilePath)
if err != nil {
fmt.Println("Failed to remove socket file:", err)
os.Exit(1)
}
}
}
1 change: 1 addition & 0 deletions docs/_sidebar.md
Original file line number Diff line number Diff line change
Expand Up @@ -97,6 +97,7 @@
- [RPC design doc](en/design/rpc/rpc-design-doc.md)
- [Distributed lock API design](en/design/lock/lock-api-design.md)
- [FaaS design](en/design/faas/faas-poc-design.md)
- [Pluggable Component design doc](en/design/pluggable/design.md)
- Contributing
- [Document Contribution Guide](en/development/contributing-doc.md)
- [Automate testing of Quickstart documentation with tools](en/development/test-quickstart.md)
Expand Down
41 changes: 41 additions & 0 deletions docs/en/design/pluggable/design.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,41 @@
# Pluggable Component Design Document

## Background

Currently, the components of Layotto are implemented in Layotto's projects, which requires users to develop new components using Golang language and implement them in the Layotto project before compiling them uniformly.
It is very unfriendly for multilingual users, so Layotto needs to provide the capability of pluggable components, allowing users to implement their own components in any language. Layotto communicates with external components through the GRPC protocol.

## Programme

- Implementing local cross language component service discovery based on UDS (Unix domain socket) to reduce communication overhead.
- Implement cross language implementation capabilities for components based on protobuf.

## Data Flow Architecture

![](../../../img/pluggable/layotto_datatflow.png)

This is the data flow starting from the current user's call to SDK.
The dashed portion is the data flow that the pluggable component primarily participates in.

### Component Discovery

![](../../../img/pluggable/layotto.png)

As shown in the above figure, the user-defined component starts the socket service and places the socket file in the specified directory.
When Layotto starts, it will read all socket files in the directory (skipping folders) and establish a socket connection.

At present, layotto aligns with dapr and is not responsible for the lifecycle of user components.
If the user components are offline during the service period, there will be no reconnection, and the component service cannot be used.

Based on the usage of the community, it will be decided whether Layotto needs to support a process management module or use a separate service for management.

Due to the incomplete support for UDS in Windows and the cancellation of compatibility with Windows by Layotto itself,
the new feature adopts a UDS discovery mode that is not compatible with Windows systems.

## Component Register

- As shown in the data flow framework composition above, the components registered by the user need to implement the GRPC service defined by the pluggable proto.
Layotto will generate golang's grpc file based on the protobuf idl. Here Corresponds to the wrap component in the data flow diagram.
- There is no difference between wrap component and build in component for layotto runtime, and there is no special perception for users.
- Layotto uses the GRPC reflect library to obtain which components are implemented by the user's provided services, and registers them in the global component registry for users to use.

145 changes: 51 additions & 94 deletions docs/en/design/pluggable/usage.md
Original file line number Diff line number Diff line change
@@ -1,100 +1,57 @@
# pluggable Component Usage Guide

## Complete component

let's take the example of implementing the hello component in Go

Find the proto file for the corresponding component in `layotto/spec/proto/pluggable` and generate the grpc files for the corresponding implementation language.

The pb files for the Go language have already been generated and are located in `spec/proto/pluggable/v1`. Users can directly reference them when using.

```go
package main

import (
"context"
"errors"
"fmt"
"github.com/golang/protobuf/ptypes/empty"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
pb "mosn.io/layotto/spec/proto/pluggable/v1/hello"
"net"
"os"
)

const (
AuthToken = "123456"
TokenConfigKey = "token"
SocketFilePath = "/tmp/runtime/component-sockets/hello-grpc-demo.sock"
)

type HelloService struct {
pb.UnimplementedHelloServer
hello string
token string
}

func (h *HelloService) Init(ctx context.Context, config *pb.HelloConfig) (*empty.Empty, error) {
h.hello = config.GetHelloString()
h.token = config.Metadata[TokenConfigKey]
if h.token != AuthToken {
return nil, errors.New("auth failed")
}

return nil, nil
}

func (h *HelloService) SayHello(ctx context.Context, req *pb.HelloRequest) (*pb.HelloResponse, error) {
res := &pb.HelloResponse{
HelloString: h.hello,
}
return res, nil
}

func main() {
listen, err := net.Listen("unix", SocketFilePath)
if err != nil {
panic(err)
}
defer os.RemoveAll(SocketFilePath)

server := grpc.NewServer()
srv := &HelloService{}
pb.RegisterHelloServer(server, srv)
reflection.Register(server)

fmt.Println("start grpc server")
if err := server.Serve(listen); err != nil && !errors.Is(err, net.ErrClosed) {
fmt.Println(err)
}
}
# Pluggable Component Usage Guide

This example demonstrates how users can implement and register their own components through the pluggable component capabilities provided by Layotto.
And verify the correctness of your component writing through Layotto SDK calls.

## step1.Write and run pluggable components

Next, run the already written code

```shell
cd demo/pluggable/hello
go run .
```

Printing the following result indicates successful service startup

```shell
start grpc server
```

>1. Taking the go implementation of the `hello` component as an example, find the corresponding component's proto file in `layotto/spec/proto/pluggable` and generate the corresponding implementation language's grpc file.
The Go language's pb file has been generated and placed under `spec/proto/pluggable/v1`, and users can directly reference it when using it.
>2. In addition to implementing the interfaces defined in the protobuf file, the component also needs to use socket mode to start the file and store the socket file in the default path of `/tmp/runtime/component-sockets`,
You can also use the environment variable `LAYOTTO_COMPONENTS_SOCKETS_FOLDER` modify the sock storage path location.
>3. In addition, users also need to register the reflection service in the GRPC server, which is used to obtain the specific implementation interface spec of the GRPC service during layotto service discovery. For specific code, please refer to `demo/pluggable/hello/main.go`
## step2. Launch Layotto

```shell
cd cmd/layotto
go build -o layotto .
./layotto start -c ../../configs/config_hello_component.json
```

1. Implement the gRPC service for the corresponding component's proto file.
2. Start the socket service. The sock file should be placed under `/tmp/runtime/component-sockets`, or you can also set the `LAYOTTO_COMPONENTS_SOCKETS_FOLDER` environment variable for configuration.
3. Register the gRPC service. In addition to registering the "hello" service, it is also necessary to register the reflection service. This service is used for Layotto service discovery to determine which services defined in the proto files are implemented by this socket service. 。
5. Start service and wait for layotto registering it.

## Component Register

Fill in the configuration file and add the relevant configuration items under the corresponding component. Taking the "hello" component mentioned above as an example.

```json
"grpc_config": {
"hellos": {
"helloworld": {
"type": "hello-grpc-demo",
"hello": "hello",
"metadata": {
"token": "123456"
}
}
}
}
> The type of the component filled in the configuration file is `hello-grpc-demo`, which is determined by the prefix name of the socket file.
> The configuration items are consistent with registering regular hello components.
> Provide metadata items for users to set custom configuration requirements.
## step3. Component verification

Based on existing component testing code, test the correctness of user implemented pluggable components.

```shell
cd demo/hello/common
go run . -s helloworld
```

The component's type is `hello-grpc-demo`, determined by the prefix name of the socket file.
The program outputs the following results to indicate successful registration and operation of pluggable components.

```shell
runtime client initializing for: 127.0.0.1:34904
hello
```

The configuration items are the same as registering a regular "hello" component. Provide the `metadata` field to allow users to set custom configuration requirements.
## Understand the implementation principle of Layotto pluggable components

If you are interested in the implementation principles or want to extend some functions, you can read the [Design Document for Pluggable Components](en/design/pluggable/design.md)
11 changes: 0 additions & 11 deletions docs/zh/design/pluggable/usage.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,17 +17,6 @@ go run .
start grpc server
```

若出现以下错误,代表 sock 文件已经存在,可能是上次启动服务时强制关闭导致的,使用 `rm /tmp/runtime/component-sockets/hello-grpc-demo.sock` 删除后重新启动即可。

```shell
panic: listen unix /tmp/runtime/component-sockets/hello-grpc-demo.sock: bind: address already in use

goroutine 1 [running]:
main.main()
/home/cyb/project/ospp/layotto/demo/pluggable/hello/main.go:49 +0x236
exit status 2
```

> 1. 以 go 实现 hello 组件为例,在 `layotto/spec/proto/pluggable` 中找到对应组件的 proto 文件,生成对应实现语言的 grpc 文件。
go 语言的 pb 文件已经生成并放在了 `spec/proto/pluggable/v1` 下,用户在使用时直接引用即可。
> 2. 组件除了需要实现 protobuf 文件中定义的接口外,还需要使用 socket 方式启动文件并将 sock 文件存放在 `/tmp/runtime/component-sockets` 默认路径下,
Expand Down
8 changes: 4 additions & 4 deletions pkg/runtime/pluggable/discover.go
Original file line number Diff line number Diff line change
Expand Up @@ -46,7 +46,7 @@ func GetSocketFolderPath() string {
// Discover discovers pluggable component.
// At present, layotto only support register component from unix domain socket connection,
// and not compatible with windows.
func Discover() ([]interface{}, error) {
func Discover() ([]pluggable.Component, error) {
// 1. discover pluggable component
serviceList, err := discover()
if err != nil {
Expand Down Expand Up @@ -85,7 +85,7 @@ func discover() ([]grpcService, error) {
conn, err := pluggable.SocketDial(
ctx,
socket,
grpc.WithBlock(),
grpc.WithBlock(), // 超时设置
)
if err != nil {
return nil, nil, err
Expand Down Expand Up @@ -168,8 +168,8 @@ func serviceDiscovery(reflectClientFactory func(socket string) (client reflectSe
}

// callback use callback function to register pluggable component factories into MosnRuntime
func callback(services []grpcService) []interface{} {
res := make([]interface{}, 0, len(services))
func callback(services []grpcService) []pluggable.Component {
res := make([]pluggable.Component, 0, len(services))
mapper := pluggable.GetServiceDiscoveryMapper()
for _, service := range services {
fn, ok := mapper[service.protoRef]
Expand Down
2 changes: 1 addition & 1 deletion pkg/runtime/pluggable/discover_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -76,7 +76,7 @@ func Test_Callback(t *testing.T) {
t.Run("callback should be called when service ref is registered", func(t *testing.T) {
const fakeComponentName, fakeServiceName = "fake-comp", "fake-svc"
called := 0
pluggable.AddServiceDiscoveryCallback(fakeServiceName, func(name string, _ pluggable.GRPCConnectionDialer) interface{} {
pluggable.AddServiceDiscoveryCallback(fakeServiceName, func(name string, _ pluggable.GRPCConnectionDialer) pluggable.Component {
called++
assert.Equal(t, name, fakeComponentName)
return nil
Expand Down
15 changes: 7 additions & 8 deletions pkg/runtime/runtime.go
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,8 @@ import (
"strings"
"time"

"google.golang.org/grpc/credentials/insecure"

"mosn.io/layotto/components/pkg/common"
"mosn.io/layotto/pkg/runtime/lifecycle"

Expand Down Expand Up @@ -591,7 +593,7 @@ func (m *MosnRuntime) initAppCallbackConnection() error {
port := m.runtimeConfig.AppManagement.GrpcCallbackPort
address := fmt.Sprintf("%v:%v", host, port)
opts := []rawGRPC.DialOption{
rawGRPC.WithInsecure(),
rawGRPC.WithTransportCredentials(insecure.NewCredentials()),
}
// dial
ctx, cancel := context.WithTimeout(context.Background(), dialTimeout)
Expand Down Expand Up @@ -683,9 +685,7 @@ func (m *MosnRuntime) initRuntime(r *runtimeOptions) error {
st := time.Now()

// register pluggable component
if err := m.registerPluggableComponent(); err != nil {
return err
}
m.registerPluggableComponent()

// check default handler
if len(m.initRuntimeStages) == 0 {
Expand All @@ -703,10 +703,11 @@ func (m *MosnRuntime) initRuntime(r *runtimeOptions) error {
return nil
}

func (m *MosnRuntime) registerPluggableComponent() error {
func (m *MosnRuntime) registerPluggableComponent() {
list, err := pluggable.Discover()
if err != nil {
return fmt.Errorf("discover pluggable component: %w", err)
log.DefaultLogger.Errorf("[runtime] discover pluggable components failed: %v", err)
return
}

for _, v := range list {
Expand Down Expand Up @@ -740,8 +741,6 @@ func (m *MosnRuntime) registerPluggableComponent() error {
log.DefaultLogger.Warnf("[runtime]unknown pluggable component factory type %v", t)
}
}

return nil
}

func (m *MosnRuntime) SetCustomComponent(kind string, name string, component custom.Component) {
Expand Down

0 comments on commit 3ff0b08

Please sign in to comment.