Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

add fsm example traffic light #623

Open
wants to merge 1 commit into
base: main
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions go.mod
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,7 @@ require (
require (
github.com/DATA-DOG/go-sqlmock v1.5.0
github.com/briandowns/spinner v1.23.0
github.com/looplab/fsm v1.0.1
github.com/minio/minio-go/v7 v7.0.52
github.com/mochi-co/mqtt v1.3.2
github.com/onsi/ginkgo/v2 v2.9.5
Expand Down
2 changes: 2 additions & 0 deletions go.sum
Original file line number Diff line number Diff line change
Expand Up @@ -134,6 +134,8 @@ github.com/kr/text v0.2.0 h1:5Nx0Ya0ZqY2ygV366QzturHI13Jq95ApcVaJBhpS+AY=
github.com/kr/text v0.2.0/go.mod h1:eLer722TekiGuMkidMxC/pM04lWEeraHUUmBw8l2grE=
github.com/libp2p/go-reuseport v0.2.0 h1:18PRvIMlpY6ZK85nIAicSBuXXvrYoSw3dsBAR7zc560=
github.com/libp2p/go-reuseport v0.2.0/go.mod h1:bvVho6eLMm6Bz5hmU0LYN3ixd3nPPvtIlaURZZgOY4k=
github.com/looplab/fsm v1.0.1 h1:OEW0ORrIx095N/6lgoGkFkotqH6s7vaFPsgjLAaF5QU=
github.com/looplab/fsm v1.0.1/go.mod h1:PmD3fFvQEIsjMEfvZdrCDZ6y8VwKTwWNjlpEr6IKPO4=
github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0=
github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc=
github.com/mattn/go-colorable v0.1.9/go.mod h1:u6P/XSegPjTcexA+o6vUJrdnUu04hMope9wVRipJSqc=
Expand Down
16 changes: 16 additions & 0 deletions pkg/fsm/examples/traffic-light-device/Dockerfile
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
# syntax=docker/dockerfile:1

FROM golang:1.20.2-alpine
WORKDIR /app
ENV GO111MODULE=on
ENV GOPRIVATE=github.com/Edgenesis
ENV GOPROXY=https://goproxy.cn,direct
COPY go.mod ./
COPY go.sum ./
COPY pkg/deviceshifu pkg/deviceshifu
COPY pkg/logger pkg/logger
RUN go mod download
COPY pkg/fsm/examples/traffic-light-device/*.go ./
RUN go build -o /trafficlight
EXPOSE 11111
CMD [ "/trafficlight" ]
33 changes: 33 additions & 0 deletions pkg/fsm/examples/traffic-light-device/README-zh.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
### 1. 运行 *Shifu* 并连接一个虚拟traffic light
在 `shifu` 根目录下,运行下面命令来运行 *shifu* :

```shell
kubectl apply -f pkg/k8s/crd/install/shifu_install.yml
```
在 `shifu` 根目录下,运行下面命令来把虚拟 traffic light 打包成 Docker 镜像:

```shell
docker build -t trafficlight-device:v0.0.1 . -f pkg/fsm/examples/traffic-light-device/Dockerfile
```
在 `shifu` 根目录下,运行下面命令来把虚拟 traffic light 镜像加载到 Kind 中,并部署到 Kubernetes 集群中:

```shell
kind load docker-image trafficlight-device:v0.0.1
kubectl apply -f pkg/fsm/examples/traffic-light-device/configuration
```

### 2. 与 *deviceShifu* 交互
我们可以通过 nginx 应用来和 *deviceShifu* 交互,命令为:

```shell
kubectl run nginx --image=nginx
kubectl exec -it nginx -- bash
```
在 nginx 命令行中通过如下命令与虚拟 traffic light 进行交互:

```shell
curl "trafficlight.devices.svc.cluster.local:11111/get_color";echo
curl "trafficlight.devices.svc.cluster.local:11111/stop";echo
curl "trafficlight.devices.svc.cluster.local:11111/proceed";echo
curl "trafficlight.devices.svc.cluster.local:11111/caution";echo
```
32 changes: 32 additions & 0 deletions pkg/fsm/examples/traffic-light-device/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1,32 @@
### 1. Running Shifu and connecting a virtual traffic light
In the shifu root directory, run the following command to run Shifu:
```shell
kubectl apply -f pkg/k8s/crd/install/shifu_install.yml
```

In the shifu root directory, run the following command to package the virtual traffic light into a Docker image:
```shell
docker build -t trafficlight-device:v0.0.1 . -f pkg/fsm/examples/traffic-light-device/Dockerfile
```

In the shifu root directory, run the following commands to load the virtual traffic light image into Kind and deploy it to the Kubernetes cluster:
```shell
kind load docker-image trafficlight-device:v0.0.1
kubectl apply -f pkg/fsm/examples/traffic-light-device/configuration
```

### 2. Interacting with deviceShifu
We can interact with deviceShifu through the nginx application using the following commands:
```shell
kubectl run nginx --image=nginx
kubectl exec -it nginx -- bash
```

In the nginx command line, use the following commands to interact with the virtual traffic light:
```shell
curl "trafficlight.devices.svc.cluster.local:11111/get_color";echo
curl "trafficlight.devices.svc.cluster.local:11111/stop";echo
curl "trafficlight.devices.svc.cluster.local:11111/proceed";echo
curl "trafficlight.devices.svc.cluster.local:11111/caution";echo
```
These commands allow you to communicate with the virtual traffic light deployed in the Kubernetes cluster by making HTTP requests to the specified endpoints.
Original file line number Diff line number Diff line change
@@ -0,0 +1,29 @@
apiVersion: v1
kind: ConfigMap
metadata:
name: trafficlight-configmap-0.0.1
namespace: deviceshifu
data:
# device name and image address
driverProperties: |
driverSku: Traffic Light
driverImage: trafficlight-device:v0.0.1
# available instructions
instructions: |
instructionSettings:
defaultTimeoutSeconds: 8
instructions:
stop:
caution:
proceed:
get_color:
get_statue:
# telemetry retrieval methods
# in this examples, a device_health telemetry is collected by calling hello instruction every 1 second
telemetries: |
telemetries:
device_health:
properties:
instruction: get_statue
initialDelayMs: 1000
intervalMs: 1000
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: deviceshifu-trafficlight-deployment
name: deviceshifu-trafficlight-deployment
namespace: deviceshifu
spec:
replicas: 1
selector:
matchLabels:
app: deviceshifu-trafficlight-deployment
template:
metadata:
labels:
app: deviceshifu-trafficlight-deployment
spec:
containers:
- image: edgehub/deviceshifu-http-http:nightly
name: deviceshifu-http
ports:
- containerPort: 8080
volumeMounts:
- name: deviceshifu-config
mountPath: "/etc/edgedevice/config"
readOnly: true
env:
- name: EDGEDEVICE_NAME
value: "edgedevice-trafficlight"
- name: EDGEDEVICE_NAMESPACE
value: "devices"
volumes:
- name: deviceshifu-config
configMap:
name: trafficlight-configmap-0.0.1
serviceAccountName: edgedevice-sa

Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: deviceshifu-trafficlight-deployment
name: deviceshifu-trafficlight-service
namespace: deviceshifu
spec:
ports:
- port: 80
protocol: TCP
targetPort: 8080
selector:
app: deviceshifu-trafficlight-deployment
type: LoadBalancer
Original file line number Diff line number Diff line change
@@ -0,0 +1,27 @@
apiVersion: apps/v1
kind: Deployment
metadata:
labels:
app: trafficlight
name: trafficlight
namespace: devices
spec:
replicas: 1
selector:
matchLabels:
app: trafficlight
template:
metadata:
labels:
app: trafficlight
spec:
containers:
- image: trafficlight-device:v0.0.1
name: trafficlight
ports:
- containerPort: 11111
env:
- name: MOCKDEVICE_NAME
value: trafficlight
- name: MOCKDEVICE_PORT
value: "11111"
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
apiVersion: shifu.edgenesis.io/v1alpha1
kind: EdgeDevice
metadata:
name: edgedevice-trafficlight
namespace: devices
spec:
sku: "Traffic Light"
connection: Ethernet
address: trafficlight.devices.svc.cluster.local:11111
protocol: HTTP
Original file line number Diff line number Diff line change
@@ -0,0 +1,16 @@
apiVersion: v1
kind: Service
metadata:
labels:
app: trafficlight
name: trafficlight
namespace: devices
spec:
ports:
- port: 11111
protocol: TCP
targetPort: 11111
selector:
app: trafficlight
type: LoadBalancer

102 changes: 102 additions & 0 deletions pkg/fsm/examples/traffic-light-device/trafficlight.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,102 @@
package main

import (
"context"
"fmt"
"github.com/edgenesis/shifu/pkg/deviceshifu/mockdevice/mockdevice"
"github.com/edgenesis/shifu/pkg/logger"
"github.com/looplab/fsm"
"math/rand"
"net/http"
)

const (
RED string = "RED"
YELLOW string = "YELLOW"
GREEN string = "GREEN"
)

const (
STOP string = "STOP"
CAUTION string = "CAUTION"
PROCEED string = "PROCEED"
)

type TrafficLight struct {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@huerni, to be more specific, assume the trafficlight is the edgedevice, the question would turn to - how can the FSM control this edge device?

Here you wrote a mock device that have the feature we've described while our final goal is try to make the FSM can be applied to any edge device.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I apologize for the confusion, I'm not very familiar with edge devices and FSM either. To clarify, are you asking if it's possible to write an FSM controller that can be used for any edge device? Or are you suggesting that each edge device should be able to quickly build its own FSM?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

We are going to have a proper design for the FSM feature in the following weeks. You can refer to the design for implementation details.

FSM *fsm.FSM
}

func NewTrafficLight(color string) *TrafficLight {
tl := &TrafficLight{}

tl.FSM = fsm.NewFSM(
color,
fsm.Events{
{Name: STOP, Src: []string{YELLOW}, Dst: RED},
{Name: CAUTION, Src: []string{GREEN}, Dst: YELLOW},
{Name: PROCEED, Src: []string{RED}, Dst: GREEN},
},
fsm.Callbacks{},
)

return tl
}

var trafficLight *TrafficLight

func main() {
availableFuncs := []string{
"stop",
"caution",
"proceed",
"get_color",
"get_status",
}
trafficLight = NewTrafficLight(RED)
mockdevice.StartMockDevice(availableFuncs, instructionHandler)
}

func instructionHandler(functionName string) http.HandlerFunc {
return func(w http.ResponseWriter, r *http.Request) {
logger.Infof("Handling: %v", functionName)
switch functionName {
case "stop":
err := trafficLight.FSM.Event(context.Background(), STOP)

if err != nil {
logger.Warnf("Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), RED, YELLOW)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), RED, YELLOW)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Transition from %v to %v", YELLOW, trafficLight.FSM.Current())
case "caution":
err := trafficLight.FSM.Event(context.Background(), CAUTION)
if err != nil {
logger.Warnf("Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), YELLOW, GREEN)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), YELLOW, GREEN)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Transition from %v to %v", GREEN, trafficLight.FSM.Current())
case "proceed":
err := trafficLight.FSM.Event(context.Background(), PROCEED)
if err != nil {
logger.Warnf("Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), GREEN, RED)
w.WriteHeader(http.StatusBadRequest)
fmt.Fprintf(w, "Disable transition from %v to %v, must be %v", trafficLight.FSM.Current(), GREEN, RED)
return
}
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "Transition from %v to %v", RED, trafficLight.FSM.Current())
case "get_color":
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, "traffic light current state: %v", trafficLight.FSM.Current())
case "get_status":
w.WriteHeader(http.StatusOK)
fmt.Fprintf(w, mockdevice.StatusSetList[(rand.Intn(len(mockdevice.StatusSetList)))])
}
}
}