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 9e57943
Show file tree
Hide file tree
Showing 12 changed files with 172 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
42 changes: 42 additions & 0 deletions docs/zh/development/pluggable-component.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,42 @@
# Pluggable Component 贡献指南

感谢您对 layotto 的支持!

本文档描述了如何开发 Layotto 组件。Layotto 组件用 Go 语言编写,如果您对 Go 语言不熟悉可以看下 [Go 教程](https://go.dev/tour/welcome/1)

本文档描述了如何为组件提供 pluggable component 能力。在此之前,你可以通过以下文档对 pluggable component 有更深入的了解。

- [pluggable component 设计文档](zh/design/pluggable/design.md)
- [pluggable component 使用文档](zh/design/pluggable/usage.md)

在让组件支持可插拔能力的时候,可以参考已经实现了该能力的组件,这里提供 `components/hello/pluggable.go` 作为参考。

## 一、准备工作

1. git clone 下载代码到您喜欢的目录。
2. 该功能目前仅支持 linux 和 mac 等类 unix 机器开发使用,若用户是 windows 机器,可以考虑使用 wsl2、docker 或虚拟机帮助完成编译运行。

## 二、功能开发和单元测试

### 2.1 在 `spec/proto/pluggable/v1` 下创建同名的目录及 proto 文件

用户需要从组件目录下的同名 go 文件中(如 hello 组件中的 `components/hello/hello.go`)了解该组件的基本能力。
接下来, 编写一份与组件同名的 protobuf 文件,存放在 `spec/proto/pluggable/v1` 下,其中定义了用户满足该可插拔

protobuf 编写规范及 code 生成[参考文档](zh/api_reference/how_to_generate_api_doc.md)

### 2.2


### 2.3 记得编写单元测试呦!
单元测试编写注意事项:

## 三、新增demo,用于集成测试

### a.如果该组件有通用客户端,就不用开发啦

### b.如果该组件没有通用客户端,或者需要定制一些 metadata 传参,那就复制粘贴改一改

## 四、新增说明文档

以上就算完成了代码工作,最好能新增用户如何接入该可插拔组件的配置说明文档。
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
Loading

0 comments on commit 9e57943

Please sign in to comment.