Welcome to BAETYL’s documentation!

_images/logo-with-name.png

Baetyl, extend cloud computing, data and service seamlessly to edge devices.

What is Baetyl

Baetyl is an open edge computing framework of Linux Foundation Edge that extends cloud computing, data and service seamlessly to edge devices. It can provide temporary offline, low-latency computing services, and include device connect, message routing, remote synchronization, function computing, video access pre-processing, AI inference, device resources report etc. The combination of Baetyl and the Cloud Management Suite of BIE(Baidu IntelliEdge) will achieve cloud management and application distribution, enable applications running on edge devices and meet all kinds of edge computing scenario.

About architecture design, Baetyl takes modularization and containerization design mode. Based on the modular design pattern, Baetyl splits the product to multiple modules, and make sure each one of them is a separate, independent module. In general, Baetyl can fully meet the conscientious needs of users to deploy on demand. Besides, Baetyl also takes containerization design mode to build images. Due to the cross-platform characteristics of docker to ensure the running environment of each operating system is consistent. In addition, Baetyl also isolates and limits the resources of containers, and allocates the CPU, memory and other resources of each running instance accurately to improve the efficiency of resource utilization.

Advantages

  • Shielding Computing Framework: Baetyl provides two official computing modules(Local Function Module and Python Runtime Module), also supports customize module(which can be written in any programming language or any machine learning framework).
  • Simplify Application Production: Baetyl combines with Cloud Management Suite of BIE and many other productions of Baidu Cloud(such as CFC, Infinite, EasyEdge, TSDB, IoT Visualization) to provide data calculation, storage, visible display, model training and many more abilities.
  • Service Deployment on Demand: Baetyl adopts containerization and modularization design, and each module runs independently and isolated. Developers can choose modules to deploy based on their own needs.
  • Support multiple platforms: Baetyl supports multiple hardware and software platforms, such as X86 and ARM CPU, Linux and Darwin operating systems.

Components

As an edge computing platform, Baetyl not only provides features such as underlying service management, but also provides some basic functional modules, as follows:

  • Baetyl Master is responsible for the management of service instances, such as start, stop, supervise, etc., consisting of Engine, API, Command Line. And supports two modes of running service: native process mode and docker container mode
  • The official module baetyl-agent is responsible for communication with the BIE cloud management suite, which can be used for application delivery, device information reporting, etc. Mandatory certificate authentication to ensure transmission security;
  • The official module baetyl-hub provides message subscription and publishing functions based on the MQTT protocol, and supports four access methods: TCP, SSL, WS, and WSS;
  • The official module baetyl-remote-mqtt is used to bridge two MQTT Servers for message synchronization and supports configuration of multiple message route rules. ;
  • The official module baetyl-function-manager provides computing power based on MQTT message mechanism, flexible, high availability, good scalability, and fast response;
  • The official module baetyl-function-python27 provides the Python2.7 function runtime, which can be dynamically started by baetyl-function-manager;
  • The official module baetyl-function-python36 provides the Python3.6 function runtime, which can be dynamically started by baetyl-function-manager;
  • The official module baetyl-function-node85 provides the Node 8.5 function runtime, which can be dynamically started by baetyl-function-manager;
  • SDK (Golang) can be used to develop custom modules.

Architecture

_images/design_overview.pngArchitecture

Contributing

If you are passionate about contributing to open source community, Baetyl will provide you with both code contributions and document contributions. More details, please see: How to contribute code or document to Baetyl.

Contact us

As the first open edge computing framework in China, Baetyl aims to create a lightweight, secure, reliable and scalable edge computing community that will create a good ecological environment. In order to create a better development of Baetyl, if you have better advice about Baetyl, please contact us:

Baetyl Design

Concepts

  • System: Refers to the Baetyl system, including Master, Service, Volume and system resources used.
  • Master: Refers to the core part of the Baetyl, responsible for managing Volume and Service, built-in Engine, external RESTful API and command line.
  • Module: Provides an executable package for Service, such as a docker image, to launch instances of Service.
  • Service: Refers to a set of running programs that managed by Baetyl to provide specific functions such as message routing services, function computing services, micro-services, etc.
  • Instance: Refers to the specific running program or container launched by the Service, a Service can start multiple instances, or can be dynamically started by other services. For example, the instances of function runtime service are dynamically started and stopped by the function manager service.
  • Volume: Refers to the directory used by the Service, can be a read-only directory, such as a directory for placing resources such as configuration, certificates, scripts, etc., or a writable directory to persist data, such as logs and database.
  • Engine: Refers to the operational abstractions and concrete implementations of the various running modes of the Service, such as the docker container mode and the native process mode.
  • Services and System Relationships: Baetyl systems can start multiple services, there is no dependency between services, and their startup order should not be assumed (although it is currently started sequentially). All data generated by the service at runtime is temporary and will be deleted when the service is stopped, unless it is mapped to a persistent directory. The program in the service may stop for various reasons, and the service will restart the program according to the user’s configuration. This situation is not equal to the stop of the service, so the temporary data will not be deleted.

Components

A complete Baetyl system consists of Master, Service, Volume and system resources used. The Master loads all modules according to the application configuration to start the corresponding services, and a service can start several instances, all of which are managed and supervised by Master. NOTE that the instances of the same service shares the storage volume bound to the service. Therefore, if an exclusive resource exists, such as listening to the same port, only one instance can be successfully started.

At present, Baetyl has the following official modules:

  • baetyl-agent: Provides cloud agent service for status reporting and application OTA.
  • baetyl-hub: Provides an MQTT-based message routing service.
  • baetyl-remote-mqtt: Provides a bridge services for synchronizing messages between Hub and remote MQTT services.
  • baetyl-function-manager: Provides function services for function instance management and message-triggered function calls.
  • baetyl-function-python27: Provides a GRPC micro-service that loads Python scripts based on Python2.7 runtime that can be managed by baetyl-function-manager as a function instance provider.
  • baetyl-function-python36: Provides a GRPC micro-service that loads Python scripts based on Python3.6 runtime that can be managed by baetyl-function-manager as a function instance provider.
  • baetyl-function-node85: Provides a GRPC micro-service that loads javascripts based on Node8.5 runtime that can be managed by baetyl-function-manager as a function instance provider.

Structure Diagram:

_images/design_overview.pngStructure Diagram

Master

Master as the core of the Baetyl system, it manages all storage volumes and services, has a built-in runtime engine system, and provides RESTful APIs and command lines.

The start and stop process of the Master is as follows:

  1. Execute the startup command: sudo systemctl start baetyl to start Baetyl in Docker mode and then execute the command sudo systemctl status baetyl to check whether baetyl is running. In darwin, excute sudo baetyl start to run the Baetyl in the foreground of the terminal.
  2. The Master will first load etc/baetyl/conf.yml in the working directory, initialize the running mode, API server, log and exit timeout, etc. These configurations can not be changed during application OTA. If no error is reported, the baetyl.sock (only on Linux) file is generated in the var/run/ directory.
  3. The Master will then attempt to load the application configuration var/db/baetyl/application.yml and will not start any service if the configuration does not exist, otherwise the list of services and storage volumes in the application configuration will be loaded. This file will be updated during application OTA, and the system will update the services according to the new configuration.
  4. Before starting all services, the Master will first call the Engine interface to perform some preparatory work. For example, in container mode, it will try to download the image of all services first.
  5. After the preparation is completed, start all services in sequence, and if the service fails to start, the Master will exit. In the container mode, the storage volumes are mapped to the inside of the container; in the process mode, a temporary working directory is created for each service, and the storage volumes are soft linked to the working directory. If the service is stopped, the temporary working directory will be cleaned up, and the behavior is the same with container mode.
  6. Finally, you can stop baetyl by ctrl + c, and the Master will notify all service instances to exit and wait. If it times out, it will force the instance to be killed. Then clean up baetyl.sock and exit.

The complete application.yml configuration as follows:

// AppConfig application configuration
type AppConfig struct {
	// specifies the version of the application configuration
	Version  string        `yaml:"version" json:"version"`
	// specifies the service information of the application
	Services []ServiceInfo `yaml:"services" json:"services" default:"[]"`
	// specifies the storage volume information of the application
	Volumes  []VolumeInfo  `yaml:"volumes" json:"volumes" default:"[]"`
}

// VolumeInfo storage volume configuration
type VolumeInfo struct {
	// specifies a unique name for the storage volume
	Name     string `yaml:"name" json:"name" validate:"regexp=^[a-zA-Z0-9][a-zA-Z0-9_-]{0\\,63}$"`
	// specifies the directory where the storage volume is on the host
	Path     string `yaml:"path" json:"path" validate:"nonzero"`
}

// MountInfo storage volume mapping configuration
type MountInfo struct {
	// specifies the name of the mapped storage volume
	Name     string `yaml:"name" json:"name" validate:"regexp=^[a-zA-Z0-9][a-zA-Z0-9_-]{0\\,63}$"`
	// specifies the directory where the storage volume is in the container
	Path     string `yaml:"path" json:"path" validate:"nonzero"`
	// specifies the operation permission of the storage volume, read-only or writable
	ReadOnly bool   `yaml:"readonly" json:"readonly"`
}

// ServiceInfo service configuration
type ServiceInfo struct {
	// specifies the unique name of the service
	Name      string            `yaml:"name" json:"name" validate:"regexp=^[a-zA-Z0-9][a-zA-Z0-9_-]{0\\,63}$"`
	// specifies the image of the service, usually using the docker image name
	Image     string            `yaml:"image" json:"image" validate:"nonzero"`
	// specifies the number of instances started
	Replica   int               `yaml:"replica" json:"replica" validate:"min=0"`
	// specifies the storage volumes that the service needs, map the storage volume to the directory in the container
	Mounts    []MountInfo       `yaml:"mounts" json:"mounts" default:"[]"`
    // specifies the port bindings which exposed by the service, only for docker container mode
	Ports     []string          `yaml:"ports" json:"ports" default:"[]"`
	// specifies the device bindings which used by the service, only for docker container mode
	Devices   []string          `yaml:"devices" json:"devices" default:"[]"`
	// specifies the startup arguments of the service program, but does not include `arg[0]`
	Args      []string          `yaml:"args" json:"args" default:"[]"`
	// specifies the environment variable of the service program
	Env       map[string]string `yaml:"env" json:"env" default:"{}"`
	// specifies the restart policy of the instance of the service
	Restart   RestartPolicyInfo `yaml:"restart" json:"restart"`
	// specifies resource limits for a single instance of the service,  only for docker container mode
	Resources Resources         `yaml:"resources" json:"resources"`
}

Engine

Engine is responsible for the storage volume mapping of services, instance start and stop, daemon, etc.. It abstracts the service operation, can implement different service running modes. Depending on the capabilities of the device, different running modes can be selected to run the services. The docker container mode and the native process mode are currently supported, and the k3s container mode will be supported later.

Docker Engine

The docker engine interprets the service Image as a docker image address and starts the service by calling the Docker Engine client. All services use a custom network provided by Docker Engine (default is baetyl), and the ports are exposed according to the Ports information. The directories are mapped according to the Mounts information, the devices are mapped according to the Devices information, and the resources that the containers can use, such as CPU, memory, etc., are configured according to the Resources information. Services can be accessed directly using the service name, which is routed by docker’s DNS server. Each instance of the service corresponds to a container, and the engine is responsible for starting and stopping the container.

Native Engine

On platforms that do not provide container services (such as older versions of Windows), the Native engine simulates the container’s experience as much as possible. The engine interprets the service image as the package name. The package is provided by the storage volume and contains the program required by the service,but the dependencies of this program (such as Python interpreter, Node interpreter,lib, etc.) need to be installed on the host in advance. All services use the host network directly, all ports are exposed, and users need to be careful to avoid port conflicts. Each instance of the service corresponds to a process, and the engine is responsible for starting and stopping the process.

NOTE: Process mode does not support resource restrictions, no need to expose ports, map devices.

At present, the above two modes basically achieve unified configuration, leaving only the difference in service address configuration, so the configuration in example is divided into two directories, native and docker, but will eventually be unified.

RESTful API

The Baetyl Master exposes a set of RESTful APIs, adopts HTTP/1. By default, Unix Domain Socket is used on Linux systems, and the fixed address is /var/run/baetyl.sock. Other environments use TCP. The default address is tcp://127.0.0.1:50050. At present, the authentication mode of the interface adopts a simple dynamic token. When the Master starts the services, it will dynamically generate a Token for each service, and the service name and Token are transmitted to the service instance as environment variables which can be read by instance and sent to the Master in request header. It should be noted that the dynamically launched instance cannot obtain the Token, so the dynamic instance cannot dynamically start other instances.

For the service instance, after the instance is started, you can get the API Server address of the Baetyl Master, the name and Token of the service, and the name of the instance from the environment variable. For details, see Environment Variable.

The Header key is as follows:

  • x-openedge-username: service name as username
  • x-openedge-password: dynamic token as password

The following are the currently available interfaces:

  • GET /v1/system/inspect gets system information and status
  • PUT /v1/system/update updates system and services
  • GET /v1/ports/available gets available port on host
  • PUT /v1/services/{serviceName}/instances/{instanceName}/start starts an instance of a service dynamically
  • PUT /v1/services/{serviceName}/instances/{instanceName}/stop stops an instance of a service dynamically
  • PUT /v1/services/{serviceName}/instances/{instanceName}/report reports the custom info or stats of the instance of the service
System Inspect

This interface is used to obtain the following information and status:

// Inspect all baetyl information and status inspected
type Inspect struct {
	// exception information
	Error    string    `json:"error,omitempty"`
	// inspect time
	Time     time.Time `json:"time,omitempty"`
	// software information
	Software Software  `json:"software,omitempty"`
	// hardware information
	Hardware Hardware  `json:"hardware,omitempty"`
	// service information, including service name, instance running status, etc.
	Services Services  `json:"services,omitempty"`
	// storage volume information, including name and version
	Volumes  Volumes   `json:"volumes,omitempty"`
}

// Software software information
type Software struct {
	// operating system information of host
	OS          string `json:"os,omitempty"`
	// CPU information of host
	Arch        string `json:"arch,omitempty"`
	// Baetyl process work directory
	PWD         string `json:"pwd,omitempty"`
	// Baetyl running mode of application services
	Mode        string `json:"mode,omitempty"`
	// Baetyl compiled Golang version
	GoVersion   string `json:"go_version,omitempty"`
	// Baetyl release version
	BinVersion  string `json:"bin_version,omitempty"`
	// Baetyl git revision
	GitRevision string `json:"git_revision,omitempty"`
	// Baetyl loaded application configuration version
	ConfVersion string `json:"conf_version,omitempty"`
}

// Hardware hardware information
type Hardware struct {
	// memory usage information of host
	MemInfo  *utils.MemInfo  `json:"mem_stats,omitempty"`
	// CPU usage information of host
	CPUInfo  *utils.CPUInfo  `json:"cpu_stats,omitempty"`
	// disk usage information of host
	DiskInfo *utils.DiskInfo `json:"disk_stats,omitempty"`
	// CPU usage information of host
	GPUInfo  []utils.GPUInfo `json:"gpu_stats,omitempty"`
}
System Update

This interface is used to update the application or the master binary in the system, which called the application OTA or the master OTA. The configuration of the volumes, networks, and services will be compared during application OTA. If the service and its related configuration are not changed, the service will not be restarted, otherwise it will be restarted.

The process of application OTA is as follows:

_images/design_app_ota.pngupdate

Instance Start&Stop

This interface is used to dynamically start and stop an instance of a service. You need to specify the service name and instance name. If you repeatedly launch an instance of the same name with the same service, the previously started instance will be stopped first, and then the new instance will be started.

This interface supports the dynamic configuration of the service to cover the static configuration in the storage volume. The overlay logic adopts the environment variable. When the instance starts, the environment variable can be loaded to overwrite the configuration in the storage volume to avoid resource conflicts. For example, in the native process mode, when the function manager service starts the function runtime instance, the free ports are allocated in advance, so that the function runtime instances can listen to different ports.

Instance Report

This interface is used to periodically report the custom status information of the service instance to the Baetyl Master. The content of the report is placed in the body of the request, and JSON format is used. The first layer of the JSON field is used as the key and its value will be overwritten if it is reported multiple times. For example:

If the instance of the service infer reports the following information for the first time, including info and stats:

{
    "info": {
        "company": "baidu",
        "scope": "ai"
    },
    "stats": {
        "msg_count": 124,
        "infer_count": 120
    }
}

The subsequent JSON that baetyl-agent reports to the cloud is as follows:

{
    ...
    "time": "0001-01-01T00:00:00Z",
    "services": [
        {
            "name": "infer",
            "instances": [
                {
                    "name": "infer",
                    "start_time": "2019-04-18T16:04:45.920152Z",
                    "status": "running",
                    ...

                    "info": {
                        "company": "baidu",
                        "scope": "ai"
                    },
                    "stats": {
                        "msg_count": 124,
                        "infer_count": 120
                    }
                }
            ]
        },
    ]
    ...
}

If the instance of the service infer reports the following information for the second time, containing only stats, the old stats will be overwritten:

{
    "stats": {
        "msg_count": 344,
        "infer_count": 320
    }
}

The subsequent JSON that baetyl-agent reports to the cloud is as follows, the old info is kept and the old stats is overwritten:

{
    ...
    "time": "0001-01-01T00:00:00Z",
    "services": [
        {
            "name": "infer",
            "instances": [
                {
                    "name": "infer",
                    "start_time": "2019-04-18T16:04:46.920152Z",
                    "status": "running",
                    ...

                    "info": {
                        "company": "baidu",
                        "scope": "ai"
                    },
                    "stats": {
                        "msg_count": 344,
                        "infer_count": 320
                    }
                }
            ]
        },
    ]
    ...
}

Environment Variable

Baetyl currently sets the following system environment variables for the service instance:

  • BAETYL_HOST_OS: Operate system of the device (host) where Baetyl is located
  • BAETYL_HOST_ID: ID of the device (host) where Baetyl is located, can be used as device fingerprint
  • BAETYL_MASTER_API_ADDRESS: API Server address of the Baetyl Master
  • BAETYL_MASTER_API_VERSION: API version of the Baetyl Master
  • BAETYL_SERVICE_MODE: Service running mode adopted by the Baetyl Master
  • BAETYL_SERVICE_NAME: The name of the service
  • BAETYL_SERVICE_TOKEN: Dynamically assigned Token
  • BAETYL_SERVICE_INSTANCE_NAME: The name of the instance of the service
  • BAETYL_SERVICE_INSTANCE_ADDRESS: The address of the instance of the service

The official function manager service is to connect to the Baetyl Master by reading BAETYL_MASTER_API_ADDRESS. For example, the BAETYL_MASTER_API_ADDRESS under Linux system is unix:///var/run/baetyl.sock; In the container mode under other systems, the default value of BAETYL_MASTER_API_ADDRESS is tcp://host.docker.internal:50050; In the process mode under other systems, the default value of BAETYL_MASTER_API_ADDRESS is tcp://127.0.0.1:50050.

NOTE: Environment variables configured in the application will be overwritten if they are the same as the above system environment variables.

Official Modules

Currently, several modules are officially provided to meet some common application scenarios. Of course, developers can also develop their own modules.

baetyl-agent

The baetyl-agent, also known as the cloud agent module, is responsible for communicating with the BIE Cloud Management Suite. It has MQTT and HTTPS channels. MQTT enforces two-way authentication for SSL/TLS certificates. HTTPS enforces one-way authentication for SSL/TLS certificates. Developers can refer to this module to implement their own Agent module to connect their own cloud platform.

The cloud agent do three things at the moment:

  1. After the startup, periodically obtain status information from the Master and report it to the cloud.
  2. Listen to the events sent by the cloud, trigger the corresponding operations, and currently only process the application OTA event.
  3. Responsible for cleaning the volume directory, the master will not be notified to do APP OTA during the volume cleaning period.

After receiving the application OTA command from the BIE Cloud Management Suite, the cloud agent first downloads the storage volume data packets used in all configurations and decompresses them to the specified location. If the storage volume data packets already exist and the MD5 is the same, the download will be skipped. After all storage volumes are ready, the cloud agent module will call the Master’s /update/system interface to trigger the Master to update the system.

_NOTE: If the device cannot connect to the external network or needs to leave the cloud management suite, you can remove the Agent module from the application configuration and run offline. _

baetyl-hub

The baetyl-hub is a stand-alone version of the message subscription and distribution center that uses the MQTT3.1.1 protocol to provide reliable messaging services in low-bandwidth, unreliable networks. It acts as a messaging middleware for the Baetyl system, providing message-driven interconnect capabilities for all services.

Currently supports 4 access methods: TCP, SSL (TCP + SSL), WS (Websocket) and WSS (Websocket + SSL). The MQTT protocol support is as follows:

  • Support Connect, Disconnect, Subscribe, Publish, Unsubscribe, Ping, etc.
  • Support QoS levels 0 and 1
  • Support Retain, Will, Clean Session
  • Support topics subscribed with wildcards such as +, #
  • Support validation of ClientID and Payload
  • Not Support topics subscribed with prefix $
  • Not Support Client’s Keep Alive feature and QoS Level 2

NOTE:

  • The maximum number of separators / in the publish and subscribe topics is no more than 8, and the topic name can be up to 255 characters in length.
  • The maximum length of the message message is 32k. The maximum length that can be supported is 268, 435, 455 (Byte), about 256 MB, which can be modified by the message configuration item.
  • ClientID supports uppercase and lowercase letters, numbers, underscores, hyphens (minus sign), and empty characters (not allowed to be empty if CleanSession is false), up to 128 characters in length
  • The QoS of the message can only be dropped. For example, when the QoS of the original message is 0, even if the subscription QoS is 1, the message is sent at the level of QoS 0.
  • If certificate mutual authentication is used, the client must send a non-empty username and empty password when connecting, username will be used for topic authentication. If password is not empty, it will further check if the password is correct.

The Hub supports simple topic routing, such as subscribing to a message with the topic t and publishing it back with a new topic t/topic.

If this module does not meet your requirements, you can also use a third-party MQTT Broker/Server to replace it.

baetyl-function-manager

The baetyl-function-manager, also known as the function manager module, provides the computing power based on the MQTT message mechanism, flexible, highly available, scalable, and responsive, and compatible Baidu CFC. It is important to note that function service do not guarantee message order, unless only one function instance is started.

The function manager module is responsible for managing all function instances and message routing rules, and supports automatic scaling. The structure diagram is as follows:

_images/design_function.pngStructure Diagram

If the function executes incorrectly, the function server returns a message in the following format for subsequent processing. Where functionMessage is the message input by the function (the message being processed), not the message returned by the function. An example is as follows:

{
    "errorMessage": "rpc error: code = Unknown desc = Exception calling application",
    "errorType": "*errors.Err",
    "functionMessage": {
        "ID": 0,
        "QOS": 0,
        "Topic": "t",
        "Payload": "eyJpZCI6MSwiZGV2aWNlIjoiMTExIn0=",
        "FunctionName": "sayhi",
        "FunctionInvokeID": "50f8f102-2b8c-4904-86df-0728811a5a4b"
    }
}

baetyl-function-python27

The design motion of module baetyl-function-python27 is the same as the module baetyl-function-python36 ,but their python runtime are different. The module baetyl-function-python27 is based on python27 runtime,and provide the libs protobuf3、grpcio based on Python2.7.

baetyl-function-python36

baetyl-function-python36 provides Python functions similar to Baidu CFC, where users can handle messages by writing their own functions. It is very flexible to use for filtering, converting and forwarding messages. This module can be started separately as a GRPC service or as a function instance provider for the function manager module.

The input and output of a Python function can be either JSON or binary. The message Payload will try a JSON decoding (json.loads(payload)) before passing it as a parameter. If it succeeds, it will pass the dictionary type. If it fails, it will pass the original binary data.

Python functions support reading environment variables such as os.environ[‘PATH’].

Python functions support reading contexts such as context[‘functionName’].

An example is shown below:

#!/usr/bin/env python3
#-*- coding:utf-8 -*-
"""
module to say hi
"""

def handler(event, context):
    """
    function handler
    """
    event['functionName'] = context['functionName']
    event['functionInvokeID'] = context['functionInvokeID']
    event['messageQOS'] = context['messageQOS']
    event['messageTopic'] = context['messageTopic']
    event['sayhi'] = 'hello, world'
    return event

_NOTE: In the native process mode, to run sayhi.py provided in the example of this project, you need to install Python3.6 and its packages pyyaml, protobuf3 and grpcio (pip installation can be used, **pip3** install pyyaml protobuf grpcio). _

baetyl-function-node85

The design motion of module baetyl-function-node85 is the same as the module baetyl-function-python36, and provide Node8.5 runtime for Baetyl, where users can write javascripts to handle messages in JSON or binary format. An example is shown below:

#!/usr/bin/env node

exports.handler = (event, context, callback) => {
  result = {};
  
  if (Buffer.isBuffer(event)) {
      const message = event.toString();
      result["msg"] = message;
      result["type"] = 'non-dict';
  }else {
      result["msg"] = event;
      result["type"] = 'dict';
  }

  result["say"] = 'hello world';
  callback(null, result);
};

NOTE: In the native process mode, to run index.js provided in the example of this project, you need to install Node8.5.

baetyl-remote-mqtt

baetyl-remote-mqtt, also known as the remote MQTT communication module, bridges two MQTT Servers for message synchronization. Currently, you can configure multiple message routing rules. The structure is as follows:

_images/design_remote_mqtt.pngRemote MQTT Communication Example

As shown in the figure above, the Baetyl remote communication module is used to forward and synchronize messages between the Baetyl Local Hub Module and the remote cloud Hub. Further, the edge-cloud collaborative message forwarding and delivery can be realized by accessing the MQTT Client at both ends.

Contributing

Welcome to Baetyl Open Source Project. To contribute to Baetyl, please follow the process below.

We sincerely appreciate your contribution. This document explains our workflow and work style.

Workflow

Baetyl use this Git branching model. The following steps guide usual contributions.

  1. Fork

    Our development community has been growing fast, so we encourage developers to submit code. And please file Pull Requests from your fork. To make a fork, please refer to Github page and click on the “Fork” button.

  2. Prepare for the development environment

    go get github.com/baetyl/baetyl # clone baetyl official repository
    cd $GOPATH/src/github.com/baetyl/baetyl # step into baetyl
    git checkout master  # verify master branch
    git remote add fork https://github.com/<your_github_account>/baetyl  # specify remote repository
    
  3. Push changes to your forked repository

    git status   # view current code change status
    git add .    # add all local changes
    git commit -c "modify description"  # commit changes with comment
    git push fork # push code changes to remote repository which specifies your forked repository
    
  4. Create pull request

    You can push and file a pull request to Baetyl official repository https://github.com/baetyl/baetyl. To create a pull request, please follow these steps. Once the Baetyl repository reviewer approves and merges your pull request, you will see the code which contributed by you in the Baetyl official repository.

Code Review

  • About Golang format, please refer to Go Code Review Comments.
  • Please feel free to ping your reviewers by sending them the URL of your pull request via email. Please do this after your pull request passes the CI.
  • Please answer reviewers’ every comment. If you are to follow the comment, please write “Done”; please give a reason otherwise.
  • If you don’t want your reviewers to get overwhelmed by email notifications, you might reply their comments by in a batch.
  • Reduce the unnecessary commits. Some developers commit often. It is recommended to append a sequence of small changes into one commit by running git commit --amend instead of git commit.

Merge Rule

  • Please run command govendor fmt +local before push changes, more details refer to govendor
  • Must run command make test before push changes(unit test should be contained), and make sure all unit test and data race test passed
  • Only the passed(unit test and data race test) code can be allowed to submit to Baetyl official repository
  • At least one reviewer approved code can be merged into Baetyl official repository

Note: The document’s contribution rules are the same as the rules above.

Quick Install Baetyl

Compared to manually download software in previous version, it supports installing Baetyl through package manager in newer version. With this method, you can quickly install Baetyl by simply typing a few commands at terminal.

Installation packages are provided for Ubuntu16.04, Ubuntu18.04, Debian9, CentOS7 and Raspbian-stretch currently. The supported platforms are amd64, i386, armv7l, and arm64.

Baetyl supports two running modes: docker container mode and native process mode. This document will be described in docker container mode.

Install Docker

Baetyl relies on Docker Engine in docker container mode. You can install Docker (for Linux-like systems) with the following command if it’s not installed yet:

curl -sSL https://get.docker.com | sh

View the version of installed Docker:

docker version

NOTE: According to the Official Release Log, the version of Docker lower than 18.09.2 has some security implications. It is recommended to install/update the Docker to 18.09.2 and above.

For more details, please see the official documentation.

Install Baetyl

The rpm and deb packages will be released accordingly when Baetyl releases a new version. You can install Baetyl to the device through package manager with following command:

curl -sSL http://dl.baetyl.io/install.sh | sudo -E bash -

If everything is ok, Baetyl will be installed on the /usr/local directory after the execution is complete.

Import the example configuration (optional)

As an edge computing framework, Baetyl provides MQTT connect service through hub module, provides local functional service through function manager module and some runtime modules like python27, python36, nodejs85, sql and so on. What’s more, all the modules are started by Baetyl master through a configuration file. More detailed contents about the module’s configuration please refer to Configuration Interpretation for further information.

Baetyl officially provides an example configuration for some module which can be imported using following command:

curl -sSL http://dl.baetyl.io/install_with_docker_example.sh | sudo -E bash -

The example configuration is for learning and testing purposes only. You should perform on-demand configuration according to actual working scenarios.

There is no need to import any configuration files if no modules need to launch.

Start Baetyl

The newer version of Baetyl uses Systemd as a daemon, and you can start Baetyl with the following command:

sudo systemctl start baetyl

If you have previously installed Baetyl or imported a new configuration file, it is recommended to use the reboot method:

sudo systemctl restart baetyl

Stop Baetyl:

sudo systemctl stop baetyl

If you only want to run Baetyl in the foreground, execute the following command::

sudo baetyl start

Verify successful installation

After installation, you can verify whether Baetyl is successfully installed or not by the following steps:

  • executing the command sudo systemctl status baetyl to check whether baetyl is running, as shown below. Otherwise, baetyl fails to start.

_images/systemctl-status1.pngBaetyl

  • Executing the command docker stats to view the running status of Docker containers. Since the Baetyl master will first pull required images from Docker mirror repository, it will take 2~5 minutes to see the baetyl starts successfully. Take the example configurations as above, the running status of containers are as shown below. If some containers are missing, it means they failed to start.

_images/docker-stats.pngdocker stats

  • Under the condition of two above failures, you need to view the log of the Baetyl master. And the log file which is stored in /usr/local/var/log/baetyl/baetyl.log by default. Once found errors in the log file, you can refer to FAQ. If necessary, just Submit an issue.

Install from source

Compared to the quick installation of Baetyl, you can build Baetyl from source to get the latest features.

Prerequisites

  • The Go tools and modules

The minimum required go version is 1.12. Refer to golang.org or golang.google.cn to download and install the Go tools. Now we use Go Modules to manage packages, you can refer goproxy.io to enable the Go Modules.

  • The Docker Engine and Buildx

The minimum required Docker version is 19.03, because the Docker Buildx feature is introduced to build multi-platform images. Refer to docker.com/install to install the Docker Engine and refer to github.com/docker/buildx to enable the Docker Buildx.

Download source code

Download the source code from Baetyl Github.

go get github.com/baetyl/baetyl

Build Baetyl and modules

Go into Baetyl project directory and build the Baetyl and all modules for build machine.

cd $GOPATH/src/github.com/baetyl/baetyl
# default platform and all modules
make # make all

After the build command is completed, the Baetyl and modules will be generated in output directory.

If you want to specify the platforms and the modules, use the following command:

# all platforms and all modules
make PLATFORMS=all
# specify platforms and modules
make PLATFORMS="linux/amd64 linux/arm64" MODULES="agent hub"

Rebuild the Baetyl and the modules:

# default platform and all modules
make rebuild
# all platforms and all modules:
make rebuild PLATFORMS=all
# specify platforms and modules
make rebuild PLATFORMS="linux/amd64 linux/arm64" MODULES="agent hub"

NOTE: the build command will read the git revision and tag as the binary version, so you should commit or discard local changes before running the build commands.

Build module images

It is recommended use of officially released images in container mode. If you want to build the images by yourself, the Docker Buildx must be enabled according to prerequisites.

Go into Baetyl project directory and build the module images for build machine.

cd $GOPATH/src/github.com/baetyl/baetyl
# default platform and all modules
make image
# specify some modules
make image MODULES="agent hub"

Then you can find the images by running docker images.

docker images

REPOSITORY                TAG                 IMAGE ID            CREATED             SIZE
baetyl-function-python3   git-e8fe527         12b669a36a9c        54 minutes ago      202MB
baetyl-function-python2   git-e8fe527         278e5c465e17        About an hour ago   162MB
baetyl-hub                git-e8fe527         abd5bef8ba92        2 hours ago         16.9MB
baetyl-agent              git-e8fe527         7ac8dfecdb63        2 hours ago         18MB
baetyl-function-manager   git-e8fe527         e6564cd87768        2 hours ago         16.7MB
baetyl-remote-mqtt        git-e8fe527         0daa114b968d        2 hours ago         16MB
baetyl-timer              git-e8fe527         88a408e4512a        2 hours ago         16MB
baetyl-function-node8     git-e8fe527         d7bf1abb6d24        4 days ago          221MB

If you want to build multi-platform images, you must specify the Docker image register and push flag, because Docker Buildx not support to load the manifest of images now.

# all platform and all modules
make image PLATFORMS=all XFLAGS=--push REGISTRY=<your docker image register>/
# specify platforms and modules
make image PLATFORMS="linux/amd64 linux/arm64" MODULES="agent hub" XFLAGS=--push REGISTRY=<your docker image register>/ 

Install Baetyl and example

Use the following command to install the Baetyl and example configuration to default path: /usr/local.

cd $GOPATH/src/github.com/baetyl/baetyl
sudo make install # install for docker mode with example configuration
sudo make install MODE=native # install for native mode with example configuration

Specify the installation path, such as installing into the output directory:

cd $GOPATH/src/github.com/baetyl/baetyl
make install PREFIX=output # for docker mode 
make install MODE=native PREFIX=output # for native mode

On the Darwin platform, you need to set the /usr/local/var directory to make it (and it’s subdirectories) can be bind mounted into Docker containers which would be used by Baetyl.

_images/docker-path-mount-on-mac.pngMount path on Mac

Run Baetyl and example

If the Baetyl is already installed to the default path: /usr/local.

sudo baetyl start

If the Baetyl has been installed to the specified path, such as installing into the output directory:

sudo ./output/bin/baetyl start

NOTE:

  1. After the baetyl is started, you can check if the baetyl has run successfully by ps -ef | grep "baetyl" and determine the parameters used at startup. And you can check the log file for details. Log files are stored by default in the var/log/baetyl directory of the working directory.
  2. If run in docker container mode, the container runtime status can be viewed via the docker ps or docker stats command.
  3. To use your own image, you need to modify the image of the modules and functions in the application configuration to specify your own image.
  4. For custom configuration, follow the instructions in Configuration Interpretation to make the relevant settings.

Uninstall Baetyl and example

If the Baetyl is already installed to the default path: /usr/local.

sudo make uninstall

If the installation path is specified, for example, it is installed into the output directory.

make uninstall PREFIX=output

Baetyl Configuration Interpretation

Supported units:

  • Size unit
    • b(byte)
    • k(kilobyte)
    • m(megabyte)
    • g(gigabyte)
  • Time unit
    • s(second)
    • m(minute)
    • h(hour)

Configuration examples can be found in the example directory of baetyl project.

Master Configuration

The Master configuration and application configuration are separated. The default configuration file is etc/baetyl/conf.yml in the working directory. The configuration is interpreted as follows:

mode: The default value is `docker`, running mode of services. **docker** container mode or **native** process mode
grace: The default value is `30s`, the timeout for waiting services to gracefully exit.
server: API Server configuration of Master.
  address: The default value can be read from environment variable `BAETYL_MASTER_API_ADDRESS`, address of API Server.
  timeout: The default value is `30s`, timeout of API Server.
snfile: The serial number (SN) file for master to read as device fingerprint. If set, the content can be read from environment variable `BAETYL_HOST_SN`.
docker:
  api_version: The default value is `1.38`, the api version for client to call Docker Engine server.
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  format: The default value is `text`, log print format, support `text` and `json`.
  age:
    max: The default value is `15`, means maximum number of days the log file is kept.
  size:
    max: The default value is `50`, log file size limit, default unit is `MB`.
  backup:
    max: The default value is `15`, the maximum number of log files to keep.

Application Configuration

The default configuration file for the application configuration is var/db/baetyl/application.yml in the working directory. The configuration is interpreted as follows:

version: Application version
services: Service list configuration
  - name: [MUST] Service name, must be unique in the service list
    image: [MUST] Service entry. In the docker container mode, which means the address of image. In the native process mode indicates where the service program package is located.
    replica: The default value is 0, the number of service copies, indicating the number of service instances started. Usually the service only needs to start one. The function runtime service is generally set to 0, not started by the Master, but is dynamically started by the function manager service.
    mounts: Storage volume mapping list
      - name: [MUST] The volume name, corresponding to one of the storage volume lists
        path: [MUST] The path mapped by the volume in the container
        readonly: The default value is false, whether the storage volume is read-only
    ports: Ports exposed in docker container mode, for example
      - 0.0.0.0:1883:1883
      - 0.0.0.0:1884:1884/tcp
      - 8080:8080/tcp
      - 9884:8884
    devices: Device mapping in docker container mode, for example
      - /dev/video0
      - /dev/sda:/dev/xvdc:r
    args: Service instance startup arguments, for example
      - '-c'
      - 'conf/conf.yml'
    env: Environment variables of service instance, for example
      version: v1
    restart: Service restart policy configuration
      retry:
        max: The default is `empty`(none configuration), which means always retry. If not, which means the maximum number of service restarts.
      policy: The default value is `always`, restart policy, support `no`, `always` and `on-failure`. And `no` means none restart, `always` means always restart, `on-failure` means restart the service if it exits abnormally.
      backoff:
        min: The default value is `1s`, minimum interval of restart.
        max: The default value is `5m`, maximum interval of restart.
        factor: The default value is `2`, factor of interval increase.
    resources: Service instance resource limit configuration in docker container mode
      cpu:
        cpus: The percentage of CPU available of the service instance, for example `1.5`, means that `1.5` CPU cores can be used.
        setcpus: The CPU core available for the service instance, for example `0-2`, means that `0` to `2` CPU cores can be used; `0` means that the 0th CPU core can be used; `1`, which means the 1st CPU core can be used.
      memory:
        limit: The available memory of the service, for example `500m`, means that 500 megabytes of memory can be used.
        swap: The swap space available to the service, for example `1g`, means that 1G of memory can be used.
      pids:
        limit: Number of processes the service can create.
volumes: Storage volume list
  - name: [MUST] The volume name, must be unique in the list of storage volumes
    path: [MUST] The path of the storage volume on the host, relative to the working directory of the Master

Module Configuration

The default configuration file for the module configuration is etc/baetyl/service.yml in the working directory.

baetyl-agent

remote:
  mqtt: MQTT channel configuration
    clientid: [MUST] The Client ID, must be the id of cloud core device.
    address: [MUST] The endpoint address for client to connect with cloud management suit, must use ssl endpoint.
    username: [MUST] The client username, must be the username of cloud core device.
    ca: [MUST] The CA path for client to connect with cloud management suit.
    key: [MUST] The private key path for client to connect with cloud management suit.
    cert: [MUST] The public key path for client to connect with cloud management suit.
    timeout: The default value is `30s`, means timeout of the client connects to cloud.
    interval: The default value is `1m`, means maximum interval of client reconnection, doubled from 500 microseconds to maximum.
    keepalive: The default value is `10m`, means keep alive time between the client and cloud after connection has been established.
    cleansession: The default value is `false`, , means whether keep session in cloud after client disconnected.
    validatesubs: The default value is `false`, means whether the client checks the subscription result. If it is true, client exits and return errors when subscription is failure.
    buffersize: The default value is `10`, means the size of the memory queue sent by the client to the cloud management suit. If found exception, the client will exit and lose message.
  http: HTTPS channel configuration
    address: This address is automatically inferred based on the address of the MQTT channel. No configuration required
    timeout: The default value is `30s`, connection timeout period
  report: Agent report configuration.
    url: The report URL. No configuration required
    topic: The template of report topic. No configuration required
    interval: The default value is `20s`, interval of reporting.
  desire: Agent desire configuration.
    topic: The template of desire topic. No configuration required

baetyl-hub

listen: [MUST] Listening address, for example
  - tcp://0.0.0.0:1883
  - ssl://0.0.0.0:1884
  - ws://:8080/mqtt
  - wss://:8884/mqtt
certificate: SSL/TLS certificate authentication configuration, if `ssl` or `wss` is enabled, it must be configured.
  ca: Server CA certificate path
  key: Server private key path
  cert: Server public key path
principals: ACL configuration. If not configured, client cannot connect to this Hub, support username/password and certificate authentication.
  - username: Username for client non-ssl connection
    password: Password for client connection
    permissions:
      - action: Operation type of permission. `pub` means publish permission, `sub` means subscription permission.
        permit: List of topics allowed by the operation type, support `+` and `#` wildcards.
  - username: Username for client ssl connection
    permissions:
      - action: Operation type of permission. `pub` means publish permission, `sub` means subscription permission.
        permit: List of topics allowed by the operation type, support `+` and `#` wildcards.
subscriptions: Topic routing configuration
  - source:
      topic: subscribe topic
      qos: QoS of topic
    target:
      topic: publish topic
      qos: QoS of topic
message: MQTT message related configuration
  length:
    max: The default value is `32k`, which means maximum message length that can be allowed to be transmitted. The maximum can be set to 268,435,455 Byte(about 256MB).
  ingress: Message receive configuration
    qos0:
      buffer:
        size: The default value is `10000`, means the number of messages that can be cached in memory with QoS0. Increasing the cache can improve the performance of message reception. If the device loses power, it will directly discard the message with QoS0.
    qos1:
      buffer:
        size:  The default value is `100`, means the message cache size of waiting for persistent with QoS1. Increasing the cache can improve the performance of message reception, but the potential risk is that the service will exit abnormally(such as device power failure), it will lose the cached message, and will not reply(puback). The service exits normally and waits for the cached message to be processed without losing data.
      batch:
        max:  The default value is `50`, means the maximum number of messages with QoS1 can be insert into the database (persistence). After the message is persisted, it will reply with confirmation(ack).
      cleanup:
        retention:  The default value is `48h`, means the time that the message with QoS1 can be saved in the database. Messages that exceed this time will be physically deleted during cleanup.
        interval:  The default value is `1m`, means cleanup interval with QoS1.
  egress: Message publish configuration
    qos0:
      buffer:
        size:  The default value is `10000`, means the number of messages to be sent in the in-memory cache wit QoS0. If the device is powered off, the message will be discarded directly. After the buffer is full, the newly pushed message will be discarded directly.
    qos1:
      buffer:
        size:  The default value is `100`, means the size of the message buffer is not confirmed(ack) after the message with QoS1 is sent. After the buffer is full, the new message is no longer read, and the message in the cache is always acknowledged(ack). After the message with QoS1 is sent to the client, it waits for the client to confirm(puback). If the client does not reply within the specified time, the message will be resent until the client replies or the session is closed.
      batch:
        max:  The default value is `50`, means the maximum number of messages read from the database in batches.
      retry:
        interval:  The default value is `20s`, means the re-publish interval of message.
  offset: Message serial number persistence related configuration
    buffer:
      size:  The default value is `10000`, means the size of the cache queue for the serial number of the message that was acknowledged(ack). For example, three messages with QoS1 and serial numbers 1, 2, and 3 are sent to the client in batches. The client confirms the messages of sequence numbers 1 and 3. At this time, sequence number 1 will be queued and persisted. Although sequence number 3 has been confirmed, it still has to wait for the serial number 2 to be confirmed before entering the column. This design can ensure that the message can be recovered from the persistent serial number after the service restarts abnormally, ensuring that the message is not lost, but the message retransmission will occur, and therefore the message with QoS 2 is not supported.
    batch:
      max:  The default value is `100`, means the maximum number of batches of message serial numbers can be insert into the database.
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  format: The default value is `text`, log print format, support `text` and `json`.
  age:
    max: The default value is `15`, means maximum number of days the log file is kept.
  size:
    max: The default value is `50`, log file size limit, default unit is `MB`.
  backup:
    max: The default value is `15`, the maximum number of log files to keep.
status: Service status configuration
  logging:
    enable: The default value is `false`, means whether to print baetyl status information.
    interval: The default value is `60s`, means interval of printing baetyl status information.
storage: Database storage configuration
  dir: The default value is `var/db/baetyl/data`, means database storage directory.
shutdown: Service exit configuration
  timeout: The default value is `10m`, means timeout of service exit.

baetyl-function-manager Configuration

hub:
  clientid: [MUST] The Client ID for the client to connect with the local Hub.
  address: [MUST] The endpoint address for the client to connect with the local Hub.
  username: The username for the client to connect with the local hub.
  password: The password for the client to connect with the local hub.
  ca: The CA path for the client to connect with the local hub.
  key: The private key path for the client to connect with the local hub.
  cert: The public key path for the client to connect with the local hub.
  timeout: The default value is `30s`, means timeout of the client connection with the local hub.
  interval: The default value is `1m`, means maximum interval of client reconnection, doubled from 500 microseconds to maximum.
  keepalive: The default value is `10m`, means keep alive time between the client and the local hub after connection has been established.
  cleansession: The default value is `false`, , means whether keep session in the local Hub after client disconnected.
  validatesubs: The default value is `false`, means whether the client checks the subscription result. If it is true, client exits and return errors when subscription is failure.
  buffersize: The default value is `10`, means the size of the memory queue sent by the client to the local Hub. If found exception, the client will exit and lose messages.
rules: Router rules configuration
  - clientid: [MUST] The Client ID for client to connect with the local Hub
    subscribe:
      topic: [MUST] The message topic subscribed from the local Hub.
      qos: The default value is `0`, the message QoS subscribed from the local Hub.
    function:
      name: [MUST] The name of the function that processes the message.
    publish:
      topic: [MUST] The message topic published to the local Hub.
      qos: The default value is `0`, means the message QoS published to the local Hub.
functions:
  - name: [MUST] The function name, must be unique in the function list.
    service: [MUST] The service name which provides the function runtime instance.
    instance: function instance configuration
      min: The default value is `0`, means the minimum number of function instance. And the minimum configuration allowed to be set is `0`, the maximum configuration allowed to be set is `100`.
      max: The default value is `1`, means the maximum number of function instance. And the minimum configuration allowed to be set is `1`, the maximum configuration allowed to be set is `100`.
      idletime: The default value is `10m`, maximum idle time of function instance.
      evicttime: The default value is `1m`, interval time between two evict operations.
      message:
        length:
          max: The default value is `4m`, means the maximum message length allowed for function instances to be received and publish.
    backoff:
      max: The default value is `1m`, the maximum reconnection interval of the client connection function instance
    timeout: The default value is `30s`, Client connection function instance timeout

baetyl-function-python

# the configurations of the two modules(python27 and python36) are the same, so we can follow this sample below
server: GRPC Server configuration; Do not configure if the instances of this service are managed by baetyl-function-manager
  address: GRPC Server address, <host>:<port>
  workers:
    max: The default value is the number of CPU core multiplied by 5, the maximum capacity of the thread pool
  concurrent:
    max: The default value is `empty`, means no limit, the maximum number of concurrent connections
  message:
    length:
      max: The default value is `4m`, the maximum message length allowed for function instances to receive and send
  ca: Server CA certificate path
  key: Server private key path
  cert: Server public key path
functions: function list
  - name: [MUST] The function name, must be unique in the function list.
    handler: [MUST] The function of Python code to handle message, for example, 'sayhi.handler'
    codedir: [MUST] The path of Python code
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  age:
    max: The default value is `15`, means maximum number of days the log file is kept.
  backup:
    max: The default value is `15`, the maximum number of log files to keep.

baetyl-function-node

server: GRPC Server configuration; Do not configure if the instances of this service are managed by baetyl-function-manager
  address: GRPC Server address, <host>:<port>
  message:
    length:
      max: The default value is `4m`, the maximum message length allowed for function instances to receive and send
  ca: Server CA certificate path
  key: Server private key path
  cert: Server public key path
functions: function list
  - name: [MUST] The function name, must be unique in the function list.
    handler: [MUST] The function of Node code to handle message, for example, 'sayjs.handler'
    codedir: [MUST] The path of Node code
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  backupCount:
    max: The default value is `15`, the maximum number of log files to keep.

baetyl-video-infer

hub:
  clientid: [MUST] The Client ID for the client to connect with the local Hub.
  address: [MUST] The endpoint address for the client to connect with the local Hub.
  username: The username for the client to connect with the local hub.
  password: The password for the client to connect with the local hub.
  ca: The CA path for the client to connect with the local hub.
  key: The private key path for the client to connect with the local hub.
  cert: The public key path for the client to connect with the local hub.
  timeout: The default value is `30s`, means timeout of the client connection with the local hub.
  interval: The default value is `1m`, means maximum interval of client reconnection, doubled from 500 microseconds to maximum.
  keepalive: The default value is `10m`, means keep alive time between the client and the local hub after connection has been established.
  cleansession: The default value is `false`, , means whether keep session in the local Hub after client disconnected.
  validatesubs: The default value is `false`, means whether the client checks the subscription result. If it is true, client exits and return errors when subscription is failure.
  buffersize: The default value is `10`, means the size of the memory queue sent by the client to the local Hub. If found exception, the client will exit and lose messages.
video:
  uri: [MUST] The video file path or camera address. 
    # For IP camera, the configuration just like `rtsp://<username>:<password>@<ip>:<port>/Streaming/channels/<stream_number>/`
      # `<username>` and `<password>` are the login authentication element
      # `<ip>` is the IP-address of camera
      # `<port>` is the port number of RTSP protocol, the default value is `554`
      # `<stream_number>` is the channel number, if it is equal to `1`, it indicates that the main stream is being captured; if it is equal to `2`, it indicates that the secondary stream is being captured
    # For USB camera, the configuration just like "0"(represents mapping device `/dev/video0` into container, also should be mounted on video infer service)
    # For video file, the configuration just like `var/db/baetyl/data/test.mp4`(mount the volume(store the video file) on video infer service)
  limit:
    fps: [MUST] The max number of video frames handled by inference per second. If the video fps is N, limit.fps is M, then Ceil(N/M) - 1 frames will be skipped.
infer:
  model: [MUST] The path of model file, more detailed contents please refer to https://docs.opencv.org/4.1.1/d6/d0f/group__dnn.html#ga3b34fe7a29494a6a4295c169a7d32422.
  config: [MUST] The path of model config file, more detailed contents please refer to https://docs.opencv.org/4.1.1/d6/d0f/group__dnn.html#ga3b34fe7a29494a6a4295c169a7d32422.
  backend: [Optional] The network backend which is used to improve inference efficiency. Now support `halide`, `openvino`, `opencv`, `vulkan` and `default`. More detailed contents please refer to https://docs.opencv.org/4.1.1/d6/d0f/group__dnn.html#ga186f7d9bfacac8b0ff2e26e2eab02625.
  device: [Optional] The target device of DNN processing. Now support `cpu`(default), `fp32`, `fp16`, `vpu`, `vulkan` and `fpga`. More detailed contents please refer to https://docs.opencv.org/4.1.1/d6/d0f/group__dnn.html#ga709af7692ba29788182cf573531b0ff5.
process: 
  before: creates 4-dimensional blob from image. Optionally resizes and crops image from center, subtract mean values, scales values by scalefactor, swap Blue and Red channels. More detailed contents please refer to https://docs.opencv.org/4.1.1/d6/d0f/group__dnn.html#ga29f34df9376379a603acd8df581ac8d7.
    scale: multiplier for image values.
    swaprb: flag which indicates that swap first and last channels in 3-channel image is necessary. 
    width: width of spatial size for output image.
    hight: hight of spatial size for output image.
    mean: scalar with mean values which are subtracted from channels. Values are intended to be in (mean-R, mean-G, mean-B) order if image has BGR ordering and swapRB is true.
      v1: blue component of type Scalar(Scalar is a 4-element(v1, v2, v3, v4) vector widely used in OpenCV to pass pixel values).
      v2: green component of type Scalar.
      v3: red component of type Scalar.
      v4: alpha component of type Scalar.
    crop: flag which indicates whether image will be cropped after resize or not.
  after:
    function: 
      name: [MUST] The name of the function that handle the inference result.
functions:
  - name: [MUST] The function name, must be unique in the function list.
    address: The function manager/server address, <host>:<port>, such as `function-manager:50051`
    message:
      length:
        max: The default value is `4m`, means the maximum message length allowed for function instances to be received and publish.
    backoff:
      max: The default value is `1m`, the maximum reconnection interval of the client connection function instance
    timeout: The default value is `30s`, Client connection function instance timeout
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  format: The default value is `text`, log print format, support `text` and `json`.
  age:
    max: The default value is `15`, means maximum number of days the log file is kept.
  size:
    max: The default value is `50`, log file size limit, default unit is `MB`.
  backup:
    max: The default value is `15`, the maximum number of log files to keep.

baetyl-remote-mqtt

hub:
  clientid: [MUST] The Client ID for the client to connect with the local Hub.
  address: [MUST] The endpoint address for the client to connect with the local Hub.
  username: The username for the client to connect with the local hub.
  password: The password for the client to connect with the local hub.
  ca: The CA path for the client to connect with the local hub.
  key: The private key path for the client to connect with the local hub.
  cert: The public key path for the client to connect with the local hub.
  timeout: The default value is `30s`, means timeout of the client connection with the local hub.
  interval: The default value is `1m`, means maximum interval of client reconnection, doubled from 500 microseconds to maximum.
  keepalive: The default value is `10m`, means keep alive time between the client and the local hub after connection has been established.
  cleansession: The default value is `false`, , means whether keep session in the local Hub after client disconnected.
  validatesubs: The default value is `false`, means whether the client checks the subscription result. If it is true, client exits and return errors when subscription is failure.
  buffersize: The default value is `10`, means the size of the memory queue sent by the client to the local Hub. If found exception, the client will exit and lose messages.
rules: Message routing rules configuration
  - hub:
      clientid: The client ID for the client to connect with the local Hub.
      subscriptions: The topics subscribed by client from Hub, for example
        - topic: say
          qos: 1
        - topic: hi
          qos: 0
    remote:
      name: [MUST] The remote name, must be one of the remote list
      clientid: The client ID for the client to connect with remote Hub.
      subscriptions: The topics subscribed by client from remote Hub, for example
        - topic: remote/say
          qos: 0
        - topic: remote/hi
          qos: 0
remotes: The remote list
  - name: [MUST] The remote name, must be unique in this list.
    clientid: The client ID for the client to connect with the remote Hub.
    address: [MUST] The address for the client connect with the remote Hub.
    username: The username for the client connect with the remote Hub.
    password: The password for the client connect with the remote Hub.
    ca: The CA path for the client connect with the remote Hub.
    key: The private key path for the client connect with the remote Hub.
    cert: The public key path for the client connect with the remote Hub.
    timeout: The default value is `30s`, means timeout of the client connect to the remote Hub.
    interval: The default value is `1m`, means maximum interval of client reconnection, doubled from 500 microseconds to maximum.
    keepalive: The default value is `10m`, means keep alive time between the client and the local hub after connection has been established.
    cleansession: The default value is `false`, , means whether keep session in the local Hub after client disconnected.
    validatesubs: The default value is `false`, means whether the client checks the subscription result. If it is true, client exits and return errors when subscription is failure.
    buffersize: The default value is `10`, means the size of the memory queue sent by the client to the local Hub. If found exception, the client will exit and lose messages.
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.
  format: The default value is `text`, log print format, support `text` and `json`.
  age:
    max: The default value is `15`, means maximum number of days the log file is kept.
  size:
    max: The default value is `50`, log file size limit, default unit is `MB`.
  backup:
    max: The default value is `15`, the maximum number of log files to keep.

baetyl-timer

hub: Hub configuration
  address: The address for the client to connect with the Hub.
  username: The username for the client to connect with the Hub.
  password: The password for the client to connect with the Hub.
  clientid: The client id for the client to connect with the Hub.
timer: timer configuration
  interval: Timing interval
publish:
  topic: The message topic published to the Hub.
  payload: The payload data, for example
    id: 1
logger: Logger configuration
  path: The default is `empty` (none configuration), that is, it does not write to the file. If the path is specified, it writes to the file.
  level: The default value is `info`, log level, support `debug`、`info`、`warn` and `error`.

Device connect to Hub Service

Statement:

  • The operating system used in this test is Ubuntu 18.04
  • The MQTT.fx and MQTTBox are used as MQTT Clients, MQTT.fx for TCP and SSL connection test and MQTTBox for WS (Websocket) connection test

The complete configuration reference for Hub Module Configuration.

NOTE: You can install Baetyl from source on Darwin. Please refer to Install Baetyl from source for more information.

Workflow

  • Step 1: Install Baetyl and its example configuration, more details please refer to Quickly install Baetyl
  • Step 2: Modify the configuration according to the usage requirements, and then execute sudo systemctl start baetyl to start the Baetyl in Docker container mode, or execute sudo systemctl restart baetyl to restart the Baetyl. Then execute the command sudo systemctl status baetyl to check whether baetyl is running.
  • Step 3: Configure the MQTT Client according to the connection protocol selected.
    • If TCP protocol was selected, you only need to configure the username and password(see the configuration option username and password of principals) and fill in the corresponding port.
    • If SSL protocol was selected, username, private key, certificate and CA should be need. then fill in the corresponding port;
    • If WS protocol was selected, you only need to configure the username, password, and corresponding port.
  • Step 4: If all the above steps are normal and operations are correct, you can check the connection status through the log of Baetyl or MQTT Client.

Connection Test

If the Baetyl’s example configuration is installed according to Step 1, to modify the configuration of the application and Hub service.

Baetyl Application Configuration

If the official installation method is used, replace the Baetyl application configuration with the following configuration:

# /usr/local/var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
      - 8883:8883
      - 8080:8080
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-cert
        path: var/db/baetyl/cert
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
volumes:
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-cert
    path: var/db/baetyl/localhub-cert-only-for-test
  - name: localhub-log
    path: var/db/baetyl/localhub-log

Replace the configuration of the Baetyl Hub service with the following configuration:

# /usr/local/var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
  - ssl://0.0.0.0:8883
  - ws://0.0.0.0:8080/mqtt
certificate:
  ca: var/db/baetyl/cert/ca.pem
  cert: var/db/baetyl/cert/server.pem
  key: var/db/baetyl/cert/server.key
principals:
  - username: two-way-tls
    permissions:
      - action: 'pub'
        permit: ['tls/#']
      - action: 'sub'
        permit: ['tls/#']
  - username: test
    password: hahaha
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']
subscriptions:
  - source:
      topic: 't'
    target:
      topic: 't/topic'
logger:
  path: var/log/baetyl/service.log
  level: 'debug'

Baetyl Startup

According to Step 2, execute sudo systemctl start baetyl to start Baetyl in Docker mode and then execute the command sudo systemctl status baetyl to check whether baetyl is running. The normal situation is shown as below.

_images/systemctl-status.pngBaetyl status

NOTE: Darwin can install Baetyl by using Baetyl source code, and excute sudo baetyl start to start the Baetyl in Docker container mode.

Look at the log of the Baetyl master by executing sudo tail -f /usr/local/var/log/baetyl/baetyl.log as shown below:

_images/master-start-log.pngBaetyl startup

As you can see, the image of Hub service has been loaded after Baetyl starts up normally. Alternatively, you can use docker ps command to check which docker container is currently running.

_images/docker-ps.pngdocker ps

Container mode requires port mapping, allowing external access to the container, the configuration item is the ports field in the application configuration file.

As mentioned above, when the Hub Module starts, it will open ports 1883, 8883 and 8080 at the same time, which are used for TCP, SSL, WS (Websocket) protocol. Then we will use MQTTBox and MQTT.fx as MQTT client to check the connection between MQTT client and Baetyl.

TCP Connection Test

Startup MQTT.fx, enter the Edit Connection Profiles page, fill in the Profile Name, Broker Address and Port according to the connection configuration of Baetyl Hub service, and then configure the username & password in User Credentials according to the principals configuration. Then click Apply button to complete the connection configuration of MQTT.fx with TCP protocol.

_images/mqttbox-tcp-connect-config.pngTCP connection configuration

Then close the configuration page, select the Profile Name configured, then click Connect button, if the connection configuration information matches the principals configuration of Baetyl Hub service, you can see the connection success flag which as shown below.

_images/mqttbox-tcp-connect-success.pngTCP connection success

SSL Connection Test

Startup MQTT.fx and enter the Edit Connection Profiles page. Similar to the TCP connection configuration, fill in the profile name, broker address, and port. For SSL protocol, you need to fill in the username in User Credentials and configure SSL/TLS option as shown below. Then click the Apply button to complete the connection configuration of MQTT.fx in SSL connection method.

_images/mqttbox-ssl-connect-config1.pngSSL connection configuration1

_images/mqttbox-ssl-connect-config2.pngSSL connection configuration2

Then close the configuration page, select the Profile Name configured, then click Connect button, if the connection configuration information matches the principals configuration of Baetyl Hub service, you can see the connection success flag which as shown below.

_images/mqttbox-ssl-connect-success.pngSSL connection success

WS (Websocket) Connection Test

Startup MQTTBox, enter the Client creation page, select the ws protocol, configure the broker address and port according to the Baetyl Hub service, fill in the username and password according to the principals configuration option, and click the save button. Then complete the connection configuration of MQTTBox in WS connection method which as shown below.

_images/mqttbox-ws-connect-config.pngWS(Websocket)connection configuration

Once the above operation is correct, you can see the sign of successful connection with Baetyl Hub in MQTTBox, which is shown in the figure as below.

_images/mqttbox-ws-connect-success.pngWS(Websocket)connection success

In summary, we successfully completed the connection test for the Baetyl Hub service through MQTT.fx and MQTTBox. In addition, we can also write test scripts to connect to Baetyl Hub through Paho MQTT. For details, please refer to Related Resources Download.

Message transferring among devices with Hub Service

Statement

  • The operating system used in this test is Ubuntu 18.04
  • The MQTTBox is used as MQTT client in this test

Different from Device connect to Hub Service, if you want to transfer MQTT messages among multiple MQTT clients, you need to configure the connect information, topic permission, and router rules. More detailed configuration of Hub service, please refer to Hub service configuration.

This document uses the TCP connection method as an example to test the message routing and forwarding capabilities of the Hub service.

Workflow

  • Step 1: Install Baetyl and its example configuration, more details please refer to Quickly install Baetyl
  • Step 2: Modify the configuration according to the usage requirements, and then execute sudo systemctl start baetyl to start the Baetyl in Docker container mode, or execute sudo systemctl restart baetyl to restart the Baetyl. Then execute the command sudo systemctl status baetyl to check whether baetyl is running.
  • Step 3: MQTTBox connect to Hub Service by TCP connection method, more detailed contents please refer to Device connect to Hub Service.
    • If connect successfully, then subscribe the MQTT topic due to the configuration of Hub Service.
    • If connect unsuccessfully, then retry Step 3 operation until it connect successfully.
  • Step 4: Check the publishing and receiving messages via MQTTBox.

Message Routing Test

The Baetyl application configuration is replaced with the following configuration:

# /usr/local/var/db/baetyl/application.yml
version: V2
services:
  - name: hub
    image: 'hub.baidubce.com/baetyl/baetyl-hub'
    replica: 1
    ports:
      - '1883:1883'
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub_data
        path: var/db/baetyl/data
      - name: log-V1
        path: var/log/baetyl
volumes:
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf/V1
  - name: log-V1
    path: var/db/baetyl/log
  - name: localhub_data
    path: var/db/baetyl/localhub_data

The Baetyl Hub service configuration is replaced with the following configuration:

# /usr/local/var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: 'test'
    password: 'hahaha'
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']
subscriptions:
  - source:
      topic: 't'
    target:
      topic: 't/topic'
logger:
  path: var/log/baetyl/service.log
  level: 'debug'

As configured above, message routing rules depends on the subscriptions configuration item, which means that messages published to the topic t will be forwarded to all devices(users, or mqtt clients) that subscribe the topic t/topic.

NOTE: In the above configuration, the permitted topics which are configured in permit item support the + and # wildcards configuration. More detailed contents of + and # wildcards will be explained as follows.

# wildcard

For MQTT protocol, the number sign(# U+0023) is a wildcard character that matches any number of levels within a topic. The multi-level wildcard represents the parent and any number of child levels. The multi-level wildcard character MUST be specified either on its own or following a topic level separator(/ U+002F). In either case it MUST be the last character specified in the topic.

For example, the configuration of permit item of sub action is sport/tennis/player1/#, it would receive messages published using these topic names:

  • sport/tennis/player1
  • sport/tennis/player1/ranking
  • sport/tennis/player1/score/wimbledon

Besides, topic sport/# also matches the singular sport, since # includes the parent level.

For Baetyl, if the topic # is configured in the permit item list(whether pub action or sub action), there is no need to configure any other topics. And the specified account(depends on username/password) will have permission to all legal topics of MQTT protocol.

+ wildcard

As described in the MQTT protocol, the plus sign(+ U+002B) is a wildcard character that matches only one topic level. The single-level wildcard can be used at any level in the topic, including first and last levels. Where it is used it MUST occupy an entire level of the topic. It can be used at more than one level in the topic and can be used in conjunction with the multi-level wildcard.

For example, topic sport/tennis/+ matches sport/tennis/player1 and sport/tennis/player2, but not sport/tennis/player1/ranking. Also, because the single-level wildcard matches only a single level, sport/+ does not match sport but it does match sport/.

For Baetyl, if the topic + is configured in the permit item list(whether pub action or sub action), the specified account(depends on username/password) will have permission to all single-level legal topics of MQTT protocol.

NOTE: For MQTT protocol, wildcard ONLY can be used in Topic Filter(sub action), and MUST NOT be used in Topic Name(pub action). But in the design of Baetyl, in order to enhance the flexibility of the topic permissions configuration, wildcard configured in permit item(whether in pub action or sub action) is valid, as long as the topic of the published or subscribed meets the requirements of MQTT protocol is ok. In particular, wildcards ( # and + ) policies are recommended for developers who need to configure a large number of publish and subscribe topics in the principals configuration.

Message Transfer Test Among Devices

The message transferring and routing workflow among devices are as follows:

_images/trans-flow.pngMessage transfer test among devices

Specifically, as shown in the above figure, client1, client2, and client3 respectively establish a connection to Baetyl with Hub Service, client1 has the permission to publish messages to the topic t, and client2 and client3 respectively have the permission to subscribe topic t and t/topic.

Once the connection to Baetyl for the above three clients with Hub Service is established, as the configuration of the above three clients, client2 and client3 will respectively get the message from client1 published to the topic t to Hub Service.

In particular, client1, client2, and client3 can be combined into one client, and the new client will have the permission to publish messages to the topic t, with permissions to subscribe messages to the topic t and t/topic. Here, using MQTTBox as the new client, click the Add subscriber button to subscribe the topic t and t/topic.

Then clicks the Publish button to publish message with the payload This is a new message. and with the topic t to Hub service, you will find this message is received by MQTTBox with the subscribed topics t and t/topic. More detailed contents are as below.

_images/mqttbox-tcp-trans-message-success.pngMQTTBox received message successfully

In summary, the message forwarding and routing test between devices based on Hub service is completed through MQTTBox.

Message handling with Function Service

Statement

  • The operating system used in this test is Ubuntu 18.04
  • The function runtime used in this test is Python3
  • The MQTTBox is used as MQTT client in this test

NOTE: You can install Baetyl from source. Please refer to Install Baetyl from source for more details.

Different from the Hub service to transfer message among devices(mqtt clients), this document describes the message handling with Local Function Manager service(also include Hub service and Python3.6 runtime service). In the document, Hub service is used to establish connection between Baetyl and mqtt client, Python3.6 runtime service is used to handle MQTT messages, and the Local Function Manager service is used to combine Hub service with Python3.6 runtime service with message context.

This document will take the TCP connection method as an example to show the message handling, calculation and forwarding with Local Function Manager service.

Workflow

  • Step 1: Install Baetyl and its example configuration, more details please refer to Quickly install Baetyl
  • Step 2: Modify the configuration according to the usage requirements, and then execute sudo systemctl start baetyl to start the Baetyl in Docker container mode, or execute sudo systemctl restart baetyl to restart the Baetyl. Then execute the command sudo systemctl status baetyl to check whether baetyl is running.
  • Step 3: MQTTBox connect to Hub Service by TCP connection method, more detailed contents please refer to Device connect to Hub Service
    • If connect successfully, then subscribe the MQTT topic due to the configuration of Hub Service, and observe the log of Baetyl.
      • If the Baetyl’s log shows that the Python Runtime Service has been started, it indicates that the published message was handled by the specified function.
      • If the Baetyl’s log shows that the Python Runtime Service has not been started, then retry it until the Python Runtime Service has been started.
    • If connect unsuccessfully, then retry Step 3 operation until it connect successfully
  • Step 4: Check the publishing and receiving messages via MQTTBox.

_images/python-flow.pngWorkflow of using Local Function Manager Service to handle MQTT messages

Message Handling Test

If the Baetyl’s example configuration is installed according to Step 1, to modify the configuration of the application, Hub service and function services.

Change the Baetyl application configuration to the following configuration:

# /usr/local/var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
  - name: function-manager
    image: hub.baidubce.com/baetyl/baetyl-function-manager
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: function-manager-log
        path: var/log/baetyl
  - name: function-python27-sayhi
    image: hub.baidubce.com/baetyl/baetyl-function-python27
    replica: 0
    mounts:
      - name: function-sayhi-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayhi-code
        path: var/db/baetyl/function-sayhi
        readonly: true
  - name: function-python36-sayhi
    image: hub.baidubce.com/baetyl/baetyl-function-python36
    replica: 0
    mounts:
      - name: function-sayhi-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayhi-code
        path: var/db/baetyl/function-sayhi
        readonly: true
  - name: function-node85-sayhi
    image: hub.baidubce.com/baetyl/baetyl-function-node85
    replica: 0
    mounts:
      - name: function-sayjs-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayjs-code
        path: var/db/baetyl/function-sayhi
        readonly: true
  - name: function-sql-filter
    image: hub.baidubce.com/baetyl/baetyl-function-sql
    replica: 0
    mounts:
      - name: function-filter-conf
        path: etc/baetyl
        readonly: true
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-cert
    path: var/db/baetyl/localhub-cert-only-for-test
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # function
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-manager-log
    path: var/db/baetyl/function-manager-log
  - name: function-sayhi-conf
    path: var/db/baetyl/function-sayhi-conf
  - name: function-sayhi-code
    path: var/db/baetyl/function-sayhi-code
  - name: function-sayjs-conf
    path: var/db/baetyl/function-sayjs-conf
  - name: function-sayjs-code
    path: var/db/baetyl/function-sayjs-code
  - name: function-filter-conf
    path: var/db/baetyl/function-filter-conf

Change the Baetyl Hub service configuration to the following configuration:

# /usr/local/var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: test
    password: hahaha
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']
subscriptions:
  - source:
      topic: 't'
    target:
      topic: 't/topic'
logger:
  path: var/log/baetyl/service.log
  level: "debug"

The configuration of the Baetyl local function services do not need to be modified. The specific configuration is as follows:

# /usr/local/var/db/baetyl/function-manager-conf/service.yml
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
rules:
  - clientid: func-python27-sayhi-1
    subscribe:
      topic: t
    function:
      name: python27-sayhi
    publish:
      topic: t/py2hi
  - clientid: func-sql-filter-1
    subscribe:
      topic: t
      qos: 1
    function:
      name: sql-filter
    publish:
      topic: t/sqlfilter
      qos: 1
  - clientid: func-python36-sayhi-1
    subscribe:
      topic: t
    function:
      name: python36-sayhi
    publish:
      topic: t/py3hi
  - clientid: func-node85-sayhi-1
    subscribe:
      topic: t
    function:
      name: node85-sayhi
    publish:
      topic: t/node8hi
functions:
  - name: python27-sayhi
    service: function-python27-sayhi
    instance:
      min: 0
      max: 10
  - name: sql-filter
    service: function-sql-filter
  - name: python36-sayhi
    service: function-python36-sayhi
  - name: node85-sayhi
    service: function-node85-sayhi
logger:
  path: var/log/baetyl/service.log
  level: "debug"

# /usr/local/var/db/baetyl/function-filter-conf/service.yml
functions:
  - name: sql-filter
    handler: 'select qos() as qos, topic() as topic, * where id < 10'

# /usr/local/var/db/baetyl/function-sayhi-conf/service.yml
functions:
  - name: 'python27-sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'
  - name: 'python36-sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'

# /usr/local/var/db/baetyl/function-sayjs-conf/service.yml
functions:
  - name: 'node85-sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'

Python function code does not need to be changed. /usr/local/var/db/baetyl/function-sayhi-code/index.py is implemented as follows:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
function to say hi in python
"""

def handler(event, context):
    """
    function handler
    """ 
    res = {}
    if isinstance(event, dict):
        if "err" in event:
            raise TypeError(event['err'])
        res = event
    elif isinstance(event, bytes):
        res['bytes'] = event.decode("utf-8")

    if 'messageQOS' in context:
        res['messageQOS'] = context['messageQOS']
    if 'messageTopic' in context:
        res['messageTopic'] = context['messageTopic']
    if 'messageTimestamp' in context:
        res['messageTimestamp'] = context['messageTimestamp']
    if 'functionName' in context:
        res['functionName'] = context['functionName']
    if 'functionInvokeID' in context:
        res['functionInvokeID'] = context['functionInvokeID']

    res['Say'] = 'Hello Baetyl'
    return res

The Node function code does not need to be changed. /usr/local/var/db/baetyl/function-sayjs-code/index.js is implemented as follows:

#!/usr/bin/env node

const hasAttr = (obj, attr) => {
    if (obj instanceof Object && !(obj instanceof Array)) {
        if (obj[attr] != undefined) {
            return true;
        }
    }
    return false;
};

const passParameters = (event, context) => {
    if (hasAttr(context, 'messageQOS')) {
        event['messageQOS'] = context['messageQOS'];
    }
    if (hasAttr(context, 'messageTopic')) {
        event['messageTopic'] = context['messageTopic'];
    }
    if (hasAttr(context, 'messageTimestamp')) {
        event['messageTimestamp'] = context['messageTimestamp'];
    }
    if (hasAttr(context, 'functionName')) {
        event['functionName'] = context['functionName'];
    }
    if (hasAttr(context, 'functionInvokeID')) {
        event['functionInvokeID'] = context['functionInvokeID'];
    }
};

exports.handler = (event, context, callback) => {
    // support Buffer & json object
    if (Buffer.isBuffer(event)) {
        const message = event.toString();
        event = {}
        event["bytes"] = message;
    }
    else if("err" in event) {
        return callback(new TypeError(event['err']))
    }

    passParameters(event, context);
    event['Say'] = 'Hello Baetyl'
    callback(null, event);
};

As configured above, if the MQTTBox has established a connection with the Hub service based on the above configuration, a message with the topic t is sent to the Hub, and the function service will route the message to python27-sayhi, python36-sayhi, node85-sayhi and sql-filter functions to process, and messages with topic t/py2hi, t/py3hi, t/node8hi, and t/sqlfilter are output separately. At this time, the MQTT client subscribed to the topic # will receive these messages, as well as the original message t and the message with topic t/topic which is renamed by Hub service directly .

NOTE: Any function that appears in the rules configuration must be configured in the functions configuration, otherwise the function runtime instances can not be started normally.

Baetyl Start

According to Step 2, execute sudo systemctl start baetyl to start Baetyl in Docker mode and then execute the command sudo systemctl status baetyl to check whether baetyl is running.

Look at the log of the Baetyl master by executing sudo tail -f -n 40 /usr/local/var/log/baetyl/baetyl.log as shown below:

_images/function-start-log.pngBaetyl start

Also, we can execute the command docker ps to view the list of docker containers currently running.

_images/docker-ps1.pngView the list of docker containers currently running

After comparison, it is not difficult to find that the Hub service and the function service have been successfully loaded when Baetyl starts. The function runtime instance is not started because the function runtime instance is dynamically started by function service when a message is triggered.

MQTTBox Establish Connection

In this test, we configured the connection information of MQTTBox by TCP connection, and then clicked the Add subscriber button to subscribe to the topic #, which is used to receive all messages received by the Hub services.

Message Handling Check

By looking at the /usr/local/var/db/baetyl/function-sayhi-code/index.py code file, you can see that after receiving a message, the function handler will perform a series of processes and return the result. The returned results include some context information, such as messageTopic, functionName, and so on.

Here, we publish the message {"id":1} with the topic t to Hub service via MQTTBox, and then observe the receiving messages as follows.

_images/mqttbox-tcp-process-success.pngMQTTBox received messages

After sending the message, we quickly execute the command docker ps to see the list of the currently running containers. All function runtime service instances are started. The result is shown below.

_images/docker-ps-after-trigger.pngView the list of docker containers

In summary, we simulated the process of local processing of messages through the Hub service and function services. It can be seen that the framework is very suitable to process message flows at edge.

Message Synchronize between baetyl-hub and Baidu IoTHub via Remote Service

Statement

  • The operating system used in this test is Ubuntu 18.04
  • The Baetyl and example configuration should be installed firstly, refer to Quick Install
  • The MQTT client toolkit which is used to connect to Baidu IoTHub is MQTT.fx
  • The MQTT client toolkit which is used to connect to baetyl-hub is MQTTBox
  • The Remote Hub used in this test is Baidu IoTHub

The baetyl-remote-mqtt service was developed to meet the needs of the IoT scenario. The Baetyl(via baetyl-hub service) can synchronize message with remote hub, such as Baidu IoTHub via the baetyl-remote-mqtt service. That is to say, through the baetyl-remote-mqtt service, we can either subscribe the message from Remote Hub and publish it to the baetyl-hub service or subscribe the message from baetyl-hub service and publish it to remote hub platform. The configuration of baetyl-remote-mqtt service can refer to Remote service configuration.

Workflow

  • Step 1: Create device(MQTT client) connection info(include endpoint, user, principal, policy, etc.) via Baidu IoTHub.
  • Step 2: Select MQTT.fx as the MQTT client that used to connect to Baidu IoTHub.
    • If connect successfully, then do the following next.
    • If connect unsuccessfully, then retry it until it connect successfully. More detailed contents can refer to Connect to Baidu IoTHub with MQTT.fx
  • Step 3: Startup Baetyl in docker container mode, and observe the log of Baetyl.
    • If the baetyl-hub service and baetyl-remote-mqtt service start successfully, then do the following next.
    • If the baetyl-hub service and baetyl-remote-mqtt service start unsuccessfully, then retry Step 3 until they start successfully.
  • Step 4: Select MQTTBox as the MQTT client that connect to BAETYL framework, more detailed contents please refer to Device connect to Hub Service.
    • If connect successfully, then do the following next.
    • If connect unsuccessfully, then retry Step 4 until it connect successfully.
  • Step 5: Due to the configuration of baetyl-remote-mqtt service, using MQTTBox publish message to the specified topic, and observing the receiving message via MQTT.fx. Similarly, using MQTT.fx publish message to the specified topic, and observing the receiving message via MQTTBox.
  • Step 6: If both parties in Step 5 can receive the message content posted by the other one, it indicates the Remote function test passes smoothly.

The workflow diagram are as follows.

_images/remote-flow.pngusing baetyl-remote-mqtt service to synchronize message

Message Synchronize via baetyl-remote-mqtt service

The application configuration in var/db/baetyl/application.yml are as follows:

version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub:latest
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
   - name: remote-iothub
    image: hub.baidubce.com/baetyl/baetyl-remote-mqtt:latest
    replica: 1
    mounts:
      - name: remote-iothub-conf
        path: etc/baetyl
        readonly: true
      - name: remote-iothub-cert
        path: var/db/baetyl/cert
        readonly: true
      - name: remote-iothub-log
        path: var/log/baetyl
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # remote mqtt
  - name: remote-iothub-conf
    path: var/db/baetyl/remote-iothub-conf
  - name: remote-iothub-cert
    path: var/db/baetyl/remote-iothub-cert
  - name: remote-iothub-log
    path: var/db/baetyl/remote-iothub-log

Configuration file location for baetyl-hub service is: var/db/baetyl/localhub-conf/service.yml.

The configuration of baetyl-hub service is as follow:

listen:
  - tcp://0.0.0.0:1883
principals:
  - username: test
    password: hahaha
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']
logger:
  path: var/log/baetyl/localhub-service.log
  level: "debug"

Configuration file location for baetyl-remote-mqtt service is: var/db/baetyl/remote-iothub-conf/service.yml.

The configuration of baetyl-remote-mqtt service is as follow:

name: remote-iothub
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
remotes:
  - name: iothub
    address: '<iothub_endpoint>' # copy the ssl address from Baidu IoTHub endpoint list, then replace <iothub_endpoint>, such as ssl://xxxxxx.mqtt.iot.gz.baidubce.com:1884, and the xxxxxx represents for endpoint name.
    clientid: remote-iothub-1
    username: '<username>' # copy the username which supports ssl connect authentication from the above(address) endpoint's user list, such as xxxxxx/test, and the xxxxxx represents for endpoint name.
    ca: var/db/baetyl/cert/ca.pem
    cert: var/db/baetyl/cert/client.pem
    key: var/db/baetyl/cert/client.key
rules:
  - hub:
      subscriptions:
        - topic: t1
    remote:
      name: iothub
      subscriptions:
        - topic: t2
          qos: 1
logger:
  path: var/log/baetyl/remote-service.log
  level: 'debug'

According to the configuration of the above, it means that the baetyl-remote-mqtt service subscribes the topic t1 from the baetyl-hub service, subscribes the topic t2 from Baidu IoTHub. When MQTTBox publishes a message to the topic t1, the baetyl-hub service will receive this message and forward it to Baidu IoTHub via baetyl-remote-mqtt service, and MQTT.fx will also receive this message(suppose MQTT.fx has already subscribed the topic t1 before) from Baidu IoTHub. Similarly, When we use MQTT.fx to publish a message to the topic t2, then Baidu IoTHub will receive it and forward it to the baetyl-hub service via baetyl-remote-mqtt service. Finally, MQTTBox will receive this message(suppose MQTTBox has already subscribed the topic t2 before).

In a word, from MQTTBox publishes a message to the topic t1, to MQTT.fx receives the message, the routing path of the message are as follows.

MQTTBox -> baetyl-hub service -> baetyl-remote-mqtt service -> Baidu IoTHub -> MQTT.fx

Similarly, from MQTT.fx publishes a message to the topic t2, to MQTTBox receives the message, the routing path of the message are as follows.

MQTT.fx -> Baidu IoTHub -> baetyl-remote-mqtt service -> baetyl-hub service -> MQTTBox

Establish a Connection between MQTT.fx and Baidu IoTHub

As described in Step 1, Step 2, the detailed contents of the connection between MQTT.fx and Baidu IoTHub are as follows.

_images/cloud-iothub-config.pngCreate endpoint via Baidu IoTHub

_images/mqttfx-connect-hub-config.pngConfiguration of MQTT.fx

After set the configuration of MQTT.fx, click OK or Apply button, then click Connect button, and wait for the connecting. Also, we can check if the connection status is OK via the color button. When the button’s color change to Green, that is to say, the connection is established. Then switch to the Subscribe page and subscribe the topic t1. More detailed contents are shown below.

_images/mqttfx-connect-success.pngSuccessfully establish a connection between MQTT.fx and Baidu IoTHub

Establish a Connection between MQTTBox and the baetyl-hub service

As described in Step 3, the baetyl-hub service and baetyl-remote-mqtt service also loaded when Baetyl started. Also, we can lookup the running status of Baetyl through the command sudo systemctl status baetyl.

_images/systemctl-status.pnglookup the running status of Baetyl

In addition, we can execute the command docker stats to view the list of docker containers currently running on the system.

_images/docker-ps-after-remote-start.pngView the list of docker containers currently running

After Baetyl successfully startup, set the configuration of connection, then establish the connection with the baetyl-hub service and subscribe the topic t2.

_images/mqttbox-sub-t2-success.pngMQTTBox successfully subscribe the topic t2

Message Synchronize Test

Here, MQTT.fx and MQTTBox will be used as message publishers, and the other one will be used as a message receiver.

MQTT.fx publishes message, and MQTTBox receives message

Firstly, using MQTT.fx publishes a message This message is from MQTT.fx. to the topic t2.

_images/mqttfx-pub-t2-success.pngPublishing a message to the topic t2 via MQTT.fx

At the same time, observing the message receiving status of MQTTBox via the topic t2.

_images/mqttbox-receive-t2-message-success.pngMQTTBox successfully received the message

MQTTBox publishes message, and MQTT.fx receives message

Similarly, publishing the message This message is from MQTTBox. to the topic t1 via MQTTBox.

_images/mqttbox-pub-t1-success.pngPublishing a message to the topic t1 via MQTTBox

Then we can observe the message receiving status of MQTT.fx via the topic t1.

_images/mqttfx-receive-t1-message-success.pngMQTT.fx successfully received the message

In summary, both MQTT.fx and MQTTBox have correctly received the specified message, and the content is consistent.

Image capturing and AI model inference with Video infer Service

Statement

  • The operating system used in this test is Ubuntu 18.04
  • The camera used in this test is Hikvision DS-IPC-B12-1
  • The AI model inference is running on CPU with video infer service
  • The AI model used in this test is ssd_mobilenet_v1_coco_2017_11_17
  • The MQTTBox is used as MQTT client in this test

Workflow

  • Step 1: Install Baetyl on Ubuntu18.04, more detailed contents please refer to Quickly install Baetyl
  • Step 2: Write all services configuration file, and start Baetyl with command sudo systemctl start baetyl. Also, we can view the Baetyl’s running status and all running containers through command sudo systemctl status baetyl and docker ps. More detailed contents of all services configuration please refer to Configuration
  • Step 3: Select MQTTBox as the MQTT client that connect to BAETYL framework, more detailed contents please refer to Device connect to Hub Service
  • Step 4: Subscribe topic video/infer/result and observe whether it can be received normally

The workflow of above operation is as follow:

_images/video-infer-flow.pngImage capturing and AI model inference with Video infer Service

Configuration

The application configuration of Baetyl is located in var/db/baetyl/application.yml, more detailed contents are as follows:

version: V0
services:
  - name: localhub
    image: 'hub.baidubce.com/baetyl/baetyl-hub:latest'
    replica: 1
    ports:
      - '1883:1883'
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-persist-data
        path: var/db/baetyl/data
      - name: demo-log
        path: var/log/baetyl
  - name: function-manager
    image: 'hub.baidubce.com/baetyl/baetyl-function-manager:latest'
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: demo-log
        path: var/log/baetyl
  - name: function-python
    image: 'hub.baidubce.com/baetyl/baetyl-function-python36:0.1.6-opencv41'
    replica: 0
    mounts:
      - name: function-python-conf
        path: etc/baetyl
        readonly: true
      - name: function-python-code
        path: var/db/baetyl/code
        readonly: true
      - name: image-data
        path: var/db/baetyl/image
  - name: video-infer
    image: 'hub.baidubce.com/baetyl-beta/baetyl-video-infer:latest'
    replica: 1
    mounts:
      - name: infer-person-model
        path: var/db/baetyl/model
        readonly: true
      - name: image-data
        path: var/db/baetyl/image
      - name: demo-log
        path: var/log/baetyl
      - name: video-infer-conf
        path: etc/baetyl
        readonly: true
volumes:
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-persist-data
    path: var/db/baetyl/localhub-persist-data
  - name: demo-log
    path: var/db/baetyl/demo-log
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-python-conf
    path: var/db/baetyl/function-python-conf
  - name: function-python-code
    path: var/db/baetyl/function-python-code
  - name: image-data
    path: var/db/baetyl/image-data
  - name: remote-mqtt-conf
    path: var/db/baetyl/remote-mqtt-conf
  - name: infer-person-model
    path: var/db/baetyl/infer-person-model
  - name: video-infer-conf
    path: var/db/baetyl/video-infer-conf

The configuration of baetyl-hub service is located in var/db/baetyl/localhub-conf/service.yml, more detailed contents are as follows:

listen:
  - tcp://0.0.0.0:1883
principals:
  - username: test
    password: hahaha
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']
logger:
  path: var/log/baetyl/localhub-service.log
  level: "debug"

The configuration of baetyl-function-manager service is located in var/db/baetyl/function-manager-conf/service.yml, more detailed contents are as follows:

server:
  address: 0.0.0.0:50051
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
functions:
  - name: analyse
    service: function-python
    instance:
      max: 10
logger:
  path: var/log/baetyl/func-service.log
  level: "debug"

The configuration of baetyl-function-python service is loacated in var/db/baetyl/function-python-conf/service.yml, more detailed contents are as follows:

functions:
  - name: 'analyse'
    handler: 'analyse.handler'
    codedir: 'var/db/baetyl/code'
logger:
  path: "var/log/baetyl/python-service.log"
  level: "debug"

The configuration of baetyl-video-infer service is located in var/db/baetyl/video-infer-conf/service.yml, more detailed contents are as follows:

hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
video:
  uri: "rtsp://admin:admin@192.168.1.2:554/Streaming/channels/1/"
  limit:
    fps: 1
infer:
  model: var/db/baetyl/model/frozen_inference_graph.pb
  config: var/db/baetyl/model/ssd_mobilenet_v1_coco_2017_11_17.pbtxt
process:
  before:
    swaprb: true
    width: 300
    hight: 300
  after:
    function:
      name: analyse
functions:
  - name: analyse
    address: function-manager:50051
logger:
  path: var/log/baetyl/infer-service.log
  level: "debug"

NOTE:

  • The uri configuration item stands for the address of IP-camera, it’s normal format is rtsp://<username>:<password>@<ip>:<port>/Streaming/channels/<stream_number>
    • <username> and <password> are the login authentication element
    • <ip> is the IP-address of camera
    • <port> is the port number of RTSP protocol, the default value is 554
    • <stream_number> is the channel number, if it is equal to 1, it indicates that the main stream is being captured; if it is equal to 2, it indicates that the secondary stream is being captured

Besides, video infer service can also support to capture frame from USB-camera and video file. In addition, if the captured device is USB-camera, the uri configuration item is deviceID, the normal value of it is 0, and also need to map the device /dev/video0 into the container. If the captured device is an video file, the uri configuration item is the path of the video file, and also need to mount it as volume on video infer service. More detailed contents about volume create and mount on service please refer to How to correctly create and mount volume on service.

For example, the configuration of USB-camera are as follows:

video:
  uri: "0"
  limit:
    fps: 1

And it also need to map device /dev/video0 to video infer service, more detailed contents are as follows:

version: V0
services:
  - name: video-infer
    image: 'hub.baidubce.com/baetyl-beta/baetyl-video-infer:latest'
    replica: 1
    devices:
      - /dev/video0 # map the device `/dev/video0` to container
    mounts:
      - name: infer-person-model
        path: var/db/baetyl/model
        readonly: true
      - name: image-data
        path: var/db/baetyl/image
      - name: demo-log
        path: var/log/baetyl
      - name: video-infer-conf
        path: etc/baetyl
        readonly: true

It is not difficult to find that the above mapping method also applies to serial port device and other mounted devices.

Test and verification

As mentioned in the beginning of this document, we select an object detection model of tensorflow framework(ssd_mobilenet_v1_coco_2017_11_17) which can be used to detection person, banner, apple and other fruits. Here, we provide an example python script(be used in baetyl-function-python service) of person detection, more detailed contents are as follows:

#!/usr/bin/env python
# -*- coding:utf-8 -*-
"""
function to analyse video infer result in python
"""
import numpy as np

location = "var/db/baetyl/image/{}.jpg"
classes = {
    1: 'person'
}

def handler(event, context):
    """
    function handler
    """
    data = np.fromstring(event, np.float32)
    mat = np.reshape(data, (-1, 7))
    objects = []
    scores = {}
    for obj in mat:
        clazz = int(obj[1])
        if clazz in classes:
            score = float(obj[2])
            if classes[clazz] not in scores or scores[classes[clazz]] < score:
                scores[classes[clazz]] = score
            if score < 0.6:
                continue
            objects.append({
                'class': classes[clazz],
                'score': score,
                'left': float(obj[3]),
                'top': float(obj[4]),
                'right': float(obj[5]),
                'bottom': float(obj[6])
            })

    res = {}
    res["imageDiscard"] = len(objects) == 0
    res["imageObjects"] = objects
    res["imageScores"] = scores
    res["publishTopic"] = "video/infer/result"
    res["messageTimestamp"] = int(context["messageTimestamp"]/1000000)
    if len(objects) != 0:
        res["imageLocation"] = location.format(context["messageTimestamp"])

    return res

If you want to detect other supported object, modify it directly. The supported detection object please refer to mscoco_label_map.

When everything is done, we start Baetyl and view the baetyl running status and running containers through the command sudo systemctl status baetyl and docker ps.

_images/running-status-of-baetyl.pngthe running status of baetyl

_images/running-container-view.pngthe running containers

We can find the Baetyl service is running, and the running containers include: baetyl-hub, baetyl-function-manager, baetyl-function-python and baetyl-video-infer.

Then we start MQTTBox and set the connection configuration. If it works normally, we can find MQTTBox is connecting to baetyl-hub service.

_images/MQTTBox-connect-to-hub.png MQTTBox is connection to baetyl-hub service

As mentioned in workflow, we subscribe topic video/infer/result through MQTTBox, and observe the message receiving result.

_images/model-infer-result-none-person.pngMQTTBox receive message, none person

_images/model-infer-result-person.pngMQTTBox receive message, person

As above shown, one image shows thant it has detected person, and the other one has not detected person. Compare with them, we can find the message has class item when person detected. Besides, it also shows the location information of the detected person. That is to say we can mark the edge of the detected person with it. An example python script was provided in here.

In summary, we have implemented IP-camera frame capturing and AI model inference based on the video infer service.

How to write a python script for Python runtime

Statement

  • The operating system as mentioned in this document is Ubuntu16.04.
  • The version of runtime is Python3.6, and for Python2.7, configuration is the same except for the language difference when coding the scripts
  • The MQTT client toolkit as mentioned in this document is MQTTBox.
  • In this article, the service created based on the Hub module is called localhub service. And for the test case mentioned here, the localhub service, function calculation service, and other services are configured as follows:
# The configuration of Local Hub service
# Configuration file location is: `var/db/baetyl/localhub-conf/service.yml`.
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: 'test'
    password: 'hahaha'
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']

# The configuration of Local Function Manager service
# Configuration file location is: var/db/baetyl/function-manager-conf/service.yml
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
rules:
  - clientid: localfunc-1
    subscribe:
      topic: py
    function:
      name: sayhi3
    publish:
      topic: py/hi
functions:
  - name: sayhi3
    service: function-sayhi3
    instance:
      min: 0
      max: 10
      idletime: 1m

# The configuration of python function runtime
# Configuration file location is: var/db/baetyl/function-sayhi-conf/service.yml
functions:
  - name: 'sayhi3'
    handler: 'sayhi.handler'
    codedir: 'var/db/baetyl/function-sayhi'

# The configuration of application.yml
# Configuration file location is: var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
  - name: function-manager
    image: hub.baidubce.com/baetyl/baetyl-function-manager
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: function-manager-log
        path: var/log/baetyl
  - name: function-sayhi3
    image: hub.baidubce.com/baetyl/baetyl-function-python36
    replica: 0
    mounts:
      - name: function-sayhi-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayhi-code
        path: var/db/baetyl/function-sayhi
        readonly: true
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # function manager
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-manager-log
    path: var/db/baetyl/function-manager-log
  # function python runtime sayhi
  - name: function-sayhi-conf
    path: var/db/baetyl/function-sayhi-conf
  - name: function-sayhi-code
    path: var/db/baetyl/function-sayhi-code

Baetyl officially provides the Python runtime to load python scripts written by users. The following description is about the name of the python script, the execution function name, input, output parameters, and so on.

Function Name Convention

The name of a python script can refer to Python’s universal naming convention, which Baetyl does not specifically limit. If you want to apply a python script to handle an MQTT message, the configuration of Python3.6 runtime service is as follows:

functions:
  - name: 'sayhi3'
    handler: 'sayhi.handler'
    codedir: 'var/db/baetyl/function-sayhi'

Here, we focus on the handler attribute, where sayhi represents the script name and the handler represents the entry function called in the file.

function-sayhi-code/
├── __init__.py
└── sayhi.py

More detailed configuration of Python runtime, please refer to Python runtime configuration.

Parameter Convention

def handler(event, context):
    # do something
    return event

The Python runtime provided by Baetyl supports two parameters: event and context, which are described separately below.

  • event: Depend on the Payload in the MQTT message
    • If the original Payload is a json format data, then pass in the data handled by json.loads(Payload)
    • If the original Payload is Byte, string(not Json), then pass in the original Payload
  • context: MQTT message context
    • context.messageQOS // MQTT QoS
    • context.messageTopic // MQTT Topic
    • context.functionName // MQTT functionName
    • context.functionInvokeID //MQTT function invokeID
    • context.invokeid // as above, be used to compatible with CFC

NOTE: When testing in the cloud CFC, please don’t use the context defined by Baetyl directly. The recommended method is to first determine whether the field is exists or not in the context. If exists, read it.

Hello World

Now we will implement a simple python script with the goal of appending a hello world message to each MQTT message. For a dictionary format message, return it directly, and for an none dictionary format message, convert it to string and return.

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

def handler(event, context):
    result = {}
    if isinstance(event, dict):
        result['msg'] = event
        result['type'] = 'dict'
        result['say'] = 'hello world'
    else:
        result['msg'] = event.decode("utf-8")
        result['type'] = 'non-dict'
        result['say'] = 'hello world'

    return result

Publish a dict format message:

_images/write-python-script-dict.pngPublish a dict format message

Publish an non-dict format message:

_images/write-python-script-none-dict.pngPublish an non-dict format message

As above, for some general needs, we can implement it through the Python Standard Library. However, for some more complex demands, it is often necessary to import third-party libraries to complete. How to solve the problem? We’ve provided a general solution in How to import third-party libraries for Python runtime.

How to write a javascript for Node runtime

Statement:

  • The operating system as mentioned in this document is Ubuntu16.04.
  • The version of runtime is Node8.5
  • The MQTT client toolkit as mentioned in this document is MQTTBox.
  • In this article, the service created based on the Hub module is called localhub service. And for the test case mentioned here, the localhub service, function calculation service, and other services are configured as follows:
# The configuration of Local Hub service
# Configuration file location is: var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: 'test'
    password: 'hahaha'
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']

# The configuration of Local Function Manager service
# Configuration file location is: var/db/baetyl/function-manager-conf/service.yml
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
rules:
  - clientid: localfunc-1
    subscribe:
      topic: node
    function:
      name: sayhi
    publish:
      topic: t/hi
functions:
  - name: sayhi
    service: function-sayhi
    instance:
      min: 0
      max: 10
      idletime: 1m

# The configuration of Node function runtime
# Configuration file location is: var/db/baetyl/function-sayjs-conf/service.yml
functions:
  - name: 'sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'

# The configuration of application.yml
# Configuration file location is: var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
  - name: function-manager
    image: hub.baidubce.com/baetyl/baetyl-function-manager
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: function-manager-log
        path: var/log/baetyl
  - name: function-sayhi
    image: hub.baidubce.com/baetyl/baetyl-function-node85
    replica: 0
    mounts:
      - name: function-sayjs-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayjs-code
        path: var/db/baetyl/function-sayhi
        readonly: true
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # function manager
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-manager-log
    path: var/db/baetyl/function-manager-log
  # function node runtime sayhi
  - name: function-sayjs-conf
    path: var/db/baetyl/function-sayjs-conf
  - name: function-sayjs-code
    path: var/db/baetyl/function-sayjs-code

Baetyl officially provides the Node runtime to load javascripts written by users. The following description is about the name of a javascript, the execution function name, input, output parameters, and so on.

Function Name Convention

The name of a javascript can refer to universal naming convention, which Baetyl does not specifically limit. If you want to apply a javascript to handle an MQTT message, the configuration of Node runtime service is as follows:

functions:
  - name: 'sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'

Here, we focus on the handler attribute, where index represents the script name and the handler represents the entry function called in the file.

function-sayjs-code/
└── index.js

More detailed configuration of Node runtime, please refer to Node runtime configuration.

Parameter Convention

exports.handler = (event, context, callback) => {
    callback(null, event);
};

The Node runtime provided by Baetyl supports two parameters: event and context, which are described separately below.

  • event: Depend on the Payload in the MQTT message
    • If the original Payload is a json format data, then pass in the data handled by json.loads(Payload)
    • If the original Payload is Byte, string(not Json), then pass in the original Payload
  • context: MQTT message context
    • context.messageQOS // MQTT QoS
    • context.messageTopic // MQTT Topic
    • context.functionName // MQTT functionName
    • context.functionInvokeID //MQTT function invokeID
    • context.invokeid // as above, be used to compatible with CFC

NOTE: When testing in the cloud CFC, please don’t use the context defined by Baetyl directly. The recommended method is to first determine whether the field is exists or not in the context. If exists, read it.

Hello World

Now we will implement a simple javascript with the goal of appending a hello world message to each MQTT message. For a dictionary format message, return it directly, and for an none dictionary format message, convert it to string and return.

#!/usr/bin/env node

exports.handler = (event, context, callback) => {
  result = {};
  
  if (Buffer.isBuffer(event)) {
      const message = event.toString();
      result["msg"] = message;
      result["type"] = 'non-dict';
  }else {
      result["msg"] = event;
      result["type"] = 'dict';
  }

  result["say"] = 'hello world';
  callback(null, result);
};

Publish a dict format message:

_images/write-node-script-dict.png发送字典类数据

Publish an non-dict format message:

_images/write-node-script-none-dict.png发送非字典类数据

As above, for some general needs, we can implement it through the Node Standard Library. However, for some more complex demands, it is often necessary to import third-party libraries to complete. How to solve the problem? We’ve provided a general solution in How to import third-party libraries for Node runtime.

How to import third-party libraries for Python runtime

Statement

  • The operating system as mentioned in this document is Ubuntu16.04.
  • The version of runtime is Python3.6, and for Python2.7, configurations are the same except for the language differences when coding the scripts.
  • The MQTT client toolkit as mentioned in this document is MQTTBox.
  • In this document, the third-party libraries we’ll import are requests and Pytorch.
  • In this article, the service created based on the Hub module is called localhub service. And for the test case mentioned here, the localhub service, function calculation service, and other services are configured as follows:
# The configuration of localhub service
# Configuration file location is: var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: 'test'
    password: 'hahaha'
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']

# The configuration of Local Function Manager service
# Configuration file location is: var/db/baetyl/function-manager-conf/service.yml
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
rules:
  - clientid: localfunc-1
    subscribe:
      topic: py
    function:
      name: sayhi3
    publish:
      topic: py/hi
functions:
  - name: sayhi3
    service: function-sayhi3
    instance:
      min: 0
      max: 10
      idletime: 1m

# The configuration of application.yml
# Configuration file location is: var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
  - name: function-manager
    image: hub.baidubce.com/baetyl/baetyl-function-manager
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: function-manager-log
        path: var/log/baetyl
  - name: function-sayhi3
    image: hub.baidubce.com/baetyl/baetyl-function-python36
    replica: 0
    mounts:
      - name: function-sayhi-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayhi-code
        path: var/db/baetyl/function-sayhi
        readonly: true
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # function manager
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-manager-log
    path: var/db/baetyl/function-manager-log
  # function python runtime sayhi
  - name: function-sayhi-conf
    path: var/db/baetyl/function-sayhi-conf
  - name: function-sayhi-code
    path: var/db/baetyl/function-sayhi-code

Generally, using the Python Standard Library may not meet our needs. In fact, it is often necessary to import some third-party libraries. Two examples are given below.

Import requests third-party libraries

Suppose we want to crawl a website and get the response. Here, we can import a third-party library requests. How to import it, as shown below:

  • Step 1: change path to the directory of Python scripts, then download requests package and its dependency packages(idna、urllib3、chardet、certifi)
cd /directory/of/Python/script
pip download requests
  • Step 2: inflate the downloaded .whl files for getting the source packages, then remove useless .whl files and package-description files
unzip \*.whl
rm -rf *.whl *.dist-info
  • Step 3: make the current directory be a package
touch __init__.py
  • Step 4: import the third-party library requests in the Python script as shown below:
import requests
  • Step 5: execute your Python script
python your_script.py

If the above operations are normal, the resulting script directory structure is as shown in the following figure.

_images/python-third-lib-dir-requests.pngthe directory of the Python script

Now we write the Python script get.py to get the headers information of https://baidu.com, assuming the trigger condition is that Python3.6 runtime receives the “A” command from the localhub service. More detailed contents are as follows:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import requests

def handler(event, context):
    """
    data: {"action": "A"}
    """
    if 'action' in event:
        if event['action'] == 'A':
            r = requests.get('https://baidu.com')
            if str(r.status_code) == '200':
                event['info'] = dict(r.headers)
            else:
                event['info'] = 'exception found'
        else:
            event['info'] = 'action error'
    else:
        event['error'] = 'action not found'

return event

The configuration of Python function runtime is as below:

# The configuration of Python function runtime
functions:
  - name: 'sayhi3'
    handler: 'get.handler'
    codedir: 'var/db/baetyl/function-sayhi'

As above, after receiving the message publish to the topic py, the localhub service will call the get.py script to handle, and following it publish the result to the topic py/hi. So in the test case, we use MQTTBox to subscribe the topic py/hi and publish the message {"action": "A"} to the localhub service by the topic py. If everything works correctly, MQTTBox can receive the message of the topic py/hi which contains the headers information of https://baidu.com as shown below.

_images/write-python-script-third-lib-requests.pngGet the header information of https://baetyl.io

Import Pytorch third-party libraries

Pytorch is a widely used deep learning framework for machine learning. We can import a third-party library Pytorch to use its functions. How to import it, as shown below:

  • Step 1: change path to the directory of Python scripts, then download Pytorch package and its dependency packages(PIL、caffee2、numpy、six、torchvision)
cd /directory/of/Python/script
pip3 download torch torchvision
  • Step 2: inflate the downloaded .whl files for getting the source packages, then remove useless .whl files and package-description files
unzip \*.whl
rm -rf *.whl *.dist-info
  • Step 3: make the current directory be a package
touch __init__.py
  • Step 4: import the third-party library Pytorch in the Python script as shown below:
import torch
  • Step 5: execute your Python script
python your_script.py

If the above operations are normal, the resulting script directory structure is as shown in the following figure.

_images/python-third-lib-dir-Pytorch.pngthe directory of the Python script

Now we write the Python script calc.py to use functions provided by Pytorch for generating a random tensor, assuming the trigger condition is that Python3.6 runtime receives the “B” command from the localhub service. More detailed contents are as follows:

#!/usr/bin/env python3
# -*- coding: utf-8 -*-

import torch

def handler(event, context):
  """
  data: {"action": "B"}
  """
  if 'action' in event:
    if event['action'] == 'B':
      x = torch.rand(5, 3)
      event['info'] = x.tolist()
    else:
      event['info'] = 'exception found'
  else:
    event['error'] = 'action not found'

  return event

The configuration of Python function runtime is as below:

# The configuration of Python function runtime
functions:
  - name: 'sayhi3'
    handler: 'calc.handler'
    codedir: 'var/db/baetyl/function-sayhi'

As above, after receiving the message publish to the topic py, the localhub service will call the calc.py script to handle, and following it publish the result to the topic py/hi. So in the test case, we use MQTTBox to subscribe the topic py/hi and publish the message {"action": "B"} to the localhub service by the topic py. If everything works correctly, MQTTBox can receive the message of the topic py/hi in which we can get a random tensor as shown below.

_images/write-python-script-third-lib-Pytorch.pnggenerate a random tensor

How to import third-party libraries for Node runtime

Statement:

  • The operating system as mentioned in this document is Ubuntu16.04.
  • The version of runtime is Node8.5
  • The MQTT client toolkit as mentioned in this document is MQTTBox.
  • In this document, we give an example about how import the third-party library Lodash.
  • In this article, the service created based on the Hub module is called localhub service. And for the test case mentioned here, the localhub service, function calculation service, and other services are configured as follows:
# The configuration of Local Hub service
# Configuration file location is: var/db/baetyl/localhub-conf/service.yml
listen:
  - tcp://0.0.0.0:1883
principals:
  - username: 'test'
    password: 'hahaha'
    permissions:
      - action: 'pub'
        permit: ['#']
      - action: 'sub'
        permit: ['#']

# The configuration of Local Function Manager service
# Configuration file location is: var/db/baetyl/function-manager-conf/service.yml
hub:
  address: tcp://localhub:1883
  username: test
  password: hahaha
rules:
  - clientid: localfunc-1
    subscribe:
      topic: node
    function:
      name: sayhi
    publish:
      topic: t/hi
functions:
  - name: sayhi
    service: function-sayhi
    instance:
      min: 0
      max: 10
      idletime: 1m

# The configuration of application.yml
# Configuration file location is: var/db/baetyl/application.yml
version: v0
services:
  - name: localhub
    image: hub.baidubce.com/baetyl/baetyl-hub
    replica: 1
    ports:
      - 1883:1883
    mounts:
      - name: localhub-conf
        path: etc/baetyl
        readonly: true
      - name: localhub-data
        path: var/db/baetyl/data
      - name: localhub-log
        path: var/log/baetyl
  - name: function-manager
    image: hub.baidubce.com/baetyl/baetyl-function-manager
    replica: 1
    mounts:
      - name: function-manager-conf
        path: etc/baetyl
        readonly: true
      - name: function-manager-log
        path: var/log/baetyl
  - name: function-sayhi
    image: hub.baidubce.com/baetyl/baetyl-function-node85
    replica: 0
    mounts:
      - name: function-sayjs-conf
        path: etc/baetyl
        readonly: true
      - name: function-sayjs-code
        path: var/db/baetyl/function-sayhi
        readonly: true
volumes:
  # hub
  - name: localhub-conf
    path: var/db/baetyl/localhub-conf
  - name: localhub-data
    path: var/db/baetyl/localhub-data
  - name: localhub-log
    path: var/db/baetyl/localhub-log
  # function manager
  - name: function-manager-conf
    path: var/db/baetyl/function-manager-conf
  - name: function-manager-log
    path: var/db/baetyl/function-manager-log
  # function node runtime sayhi
  - name: function-sayjs-conf
    path: var/db/baetyl/function-sayjs-conf
  - name: function-sayjs-code
    path: var/db/baetyl/function-sayjs-code

Generally, using the Node Standard Library may not meet our needs. In fact, it is often necessary to import some third-party libraries. We’ll give one example below.

Import Lodash third-party libraries

Lodash is a modern JavaScript utility library delivering modularity, performance & extras. Baetyl support import third-party libraries such as Lodash to use its functions. How to import it, as shown below:

  • Step 1: change path to the directory of javascripts, then install Lodash package
cd /directory/of/Node/script
npm install --save lodash
  • Step 2: import Lodash in a javascript:
const _ = require('lodash');
  • Step 3: execute your javascript:
node your_script.js

If the above operations are normal, the resulting script directory structure is as shown in the following figure.

_images/node-third-lib-dir-Lodash.pngthe directory of Lodash

Now we write the script index.js to use functions provided by Lodash. More detailed contents are as follows:

#!/usr/bin/env node

const _ = require('lodash');

exports.handler = (event, context, callback) => {
  result = {}
  
  //remove repeating elements in array
  result["unique_array"] = _.uniq(event['array']);
  //sort
  result['sorted_users'] = _.sortBy(event['users'], function(o) { return o.age; });
  //filter
  result['filtered_users'] = _.filter(event['users'], function(o) { return !o.active; });

  callback(null, result);
}

The configuration of Node function runtime is as below:

# The configuration of Node function runtime
functions:
  - name: 'sayhi'
    handler: 'index.handler'
    codedir: 'var/db/baetyl/function-sayhi'

First define the following json data as an input message:

{
    "array": ["Jane", 1, "Jane", 1, 2],
    "users": [
        { "user": "barney", "age": 36, "active": true  },
        { "user": "fred",   "age": 40, "active": false },
        { "user": "Jane",   "age": 32, "active": true  }
    ]
}

As above, after the localhub service receives the message sent to the topic node, it calls index.js script to execute the concrete logic to remove repeated elements, filter, sort of array in input data. The result is then fed back to the topic t/hi as an MQTT message. We subscribe to the topic t/hi via MQTTBox and we can observe the following message:

{
    "unique_array": ["Jane", 1, 2],
    "sorted_users": [
        { "user": "Jane",   "age": 32, "active": true  },
        { 'user': 'barney', "age": 36, "active": true  },
        { "user": "fred",   "age": 40, "active": false }
    ],
    "filtered_users": [
        { "user": "fred",   "age": 40, "active": false }
    ],
}

_images/write-node-script-third-lib-Lodash.pngusing_lodash

Customize Runtime Module

The function runtime is the carrier of the function execution. The function is executed by dynamically loading the function code, which is strongly related to the language of the function implementation. For example, Python code needs to be called using the Python runtime. This is a multi-language issue. In order to unify the interface and protocol, we finally chose GRPC to create a flexible functional computing framework with its powerful cross-language IDL and high-performance RPC communication capabilities.

In the function compute service (FaaS), baetyl-function-manager is responsible for the management and invocation of function instances. The function instance is provided by the function runtime service, and the function runtime service only needs to meet the conventions described below.

Protocol Convention

Developers can use the function.proto in sdk/baetyl-go to generate messages and service implementations for their respective programming languages, as defined below. For the usage of GRPC, refer to GRPC Official Documents.

syntax = "proto3";

package baetyl;

// The function server definition.
service Function {
    rpc Call(FunctionMessage) returns (FunctionMessage) {}
    // rpc Talk(stream Message) returns (stream Message) {}
}

// FunctionMessage function message
message FunctionMessage {
    uint64 ID                 = 1;
    uint32 QOS                = 2;
    string Topic              = 3;
    bytes  Payload            = 4;

    string  FunctionName      = 11;
    string  FunctionInvokeID  = 12;
}

NOTE: In docker container mode, the resource limit of the function instance should not be lower than 50M memory and 20 threads.

Configuration Convention

The function runtime module does not enforce the configuration. However, for the unified configuration mode, the following configuration items are recommended.

  • name: function name
  • handler: function processing interface
  • codedir: The path to the function code, if any.

The following is a configuration example of a Python function runtime service:

functions:
  - name: 'sayhi'
    handler: 'sayhi.handler'
    codedir: 'var/db/baetyl/function-sayhi'

Start/Stop Convention

The function runtime service is the same as other services, the only difference is that the instance is dynamically started by other services. For example, to avoid listening port conflicts, you can specify the port dynamically. The function runtime module can read BAETYL_SERVICE_INSTANCE_ADDRESS from the environment variable as the address that the GRPC Server listens on. In addition, dynamically launched function instances do not have permission to call the master API. Finally, the module listens for the SIGTERM signal to gracefully exit. A complete implementation can be found in the Python2.7、Python3.6 runtime module (baetyl-function-python27baetyl-function-python36).

Customize Module

Read Install Baetyl from source before developing custom modules to understand the build environment of Baetyl.

Custom modules do not limit the development language. Understand these conventions below to integrate custom modules better and faster.

The custom module does not limit the development language. As long as it is a runnable program, you can even use the image already on hub.docker.com, such as eclipse-mosquitto. But understanding the conventions described below will help you develop custom modules better and faster.

Directory Convention

At present, the native process mode, like the docker container mode, opens up a separate workspace for each service. Although it does not achieve the effect of isolation, it can guarantee the consistency of the user experience. The process mode creates a separate directory for each service in the var/run/baetyl/services directory, using service name. When the server starts, it specifies the directory as the working directory, and the service-bound storage volumes will be mapped (soft link) to the working directory. Here we keep the definition of the docker container mode, the workspace under the directory is also called the container, then the directory in the container has the following recommended usage:

  • Default working directory in the container: /
  • Default configuration file in the container: /etc/baetyl/service.yml
  • Default persistence path in the container: /var/db/baetyl
  • Default log directory in the container: /var/log/baetyl

NOTE: If the data needs to be persisted on the device (host), such as database and log, the directory in the container must be mapped to the host directory through the storage volume, otherwise the data will be lost after the service is stopped.

Start/Stop Convention

There is no excessive requirement for the module to be started. But it is recommended to load the YMAL format configuration from the default file, then run the module’s business logic, and finally listen to the SIGTERM signal to gracefully exit. A simple Golang module implementation can refer to the MQTT remote communication module (baetyl-remote-mqtt).

SDK

If the module is developed using Golang, you can use the SDK provided by Baetyl, located in the sdk directory of the project, and the functional interfaces are provided by Context. At present, the SDK capabilities provided are still not enough, and the follow-up will be gradually strengthened.

The list of Context interfaces are as follows:

// returns the system configuration of the service, such as hub and logger
Config() *ServiceConfig
// loads the custom configuration of the service
LoadConfig(interface{}) error
// creates a Client that connects to the Hub through system configuration,
// you can specify the Client ID and the topic information of the subscription.
NewHubClient(string, []mqtt.TopicInfo) (*mqtt.Dispatcher, error)
// returns logger interface
Log() logger.Logger
// check running mode
IsNative() bool
// waiting to exit, receiving SIGTERM and SIGINT signals
Wait()
// returns wait channel
WaitChan() <-chan os.Signal

// Master RESTful API

// updates system and
UpdateSystem(string, bool) error
// inspects system stats
InspectSystem() (*Inspect, error)
// gets an available port of the host
GetAvailablePort() (string, error)
// reports the stats of the instance of the service
ReportInstance(stats map[string]interface{}) error
// starts an instance of the service
StartInstance(serviceName, instanceName string, dynamicConfig map[string]string) error
// stop the instance of the service
StopInstance(serviceName, instanceName string) error

The following uses the simple timer module implementation as an example to introduce the usage of the SDK.

package main

import (
	"encoding/json"
	"time"

	"github.com/baetyl/baetyl/protocol/mqtt"
	baetyl "github.com/baetyl/baetyl/sdk/baetyl-go"
)

// custom configuration of the timer module
type config struct {
	Timer struct {
		Interval time.Duration `yaml:"interval" json:"interval" default:"1m"`
	} `yaml:"timer" json:"timer"`
	Publish mqtt.TopicInfo `yaml:"publish" json:"publish" default:"{\"topic\":\"timer\"}"`
}

func main() {
	// Running module in baetyl context
	baetyl.Run(func(ctx baetyl.Context) error {
		var cfg config
		// load custom config
		err := ctx.LoadConfig(&cfg)
		if err != nil {
			return err
		}
		// create a hub client
		cli, err := ctx.NewHubClient("", nil)
		if err != nil {
			return err
		}
		// start client to keep connection with hub
		cli.Start(nil)
		// create a timer
		ticker := time.NewTicker(cfg.Timer.Interval)
		defer ticker.Stop()
		for {
			select {
			case t := <-ticker.C:
				msg := map[string]int64{"time": t.Unix()}
				pld, _ := json.Marshal(msg)
				// send a message to hub triggered by timer
				err := cli.Publish(cfg.Publish, pld)
				if err != nil {
					// log error message
					ctx.Log().Errorf(err.Error())
				}
			case <-ctx.WaitChan():
				// wait until service is stopped
				return nil
			}
		}
	})
}

FAQ

This document mainly provides related issues and solutions for Baetyl deployment and startup in various platforms.

Q1: Prompt missing startup dependency configuration item when starting Baetyl in docker container mode.

_images/docker-engine-conf-miss.pngPicture

Suggested Solution: As shown in the above picture, Baetyl startup lacks configuration dependency files, refer to GitHub-Baetyl example folder(the location is etc/baetyl/conf.yml).

Q2: Execute the command docker info get the following result on Ubuntu/Debian: “WARNING: No swap limit support”

Suggested Solution:

  1. Open /etc/default/grub with your favorite text editor. Make sure the following lines are commented out or add them if they don’t exist:
GRUB_CMDLINE_LINUX=”cgroup_enable=memory swapaccount=1”
  1. Save and exit and then run: sudo update-grub and reboot.

NOTE: If you got some error when you execute step2, it may be that the grub setting is incorrect. Please repeat steps 1 and 2.

Q3: Found “WARNING: Your kernel does not support swap limit capabilities. Limitation discarded” when Baetyl start.

Suggested Solution: Refer to Q2.

Q4: Found “Got permission denied while trying to connect to the docker daemon socket at unix:///var/run/docker.sock: Get http://%2Fvar%2Frun%2Fdocker.sock/v1.38/images/json: dial unix /var/run/docker.sock: connect: permission denied” when Baetyl start.

Suggested Solution: Add the docker group if it doesn’t already exist:

sudo groupadd docker

Add the current user to the docker group:

sudo usermod -aG docker ${USER}
su - ${USER}

Q5: Found “Cannot connect to the docker daemon at unix:///var/run/docker.sock. Is the docker daemon running?” when Baetyl start.

Suggested Solution: If you still report this issue after the solution of Q4 solution is executed, restart the docker service.

For example, execute the following command on CentOS:

systemctl start docker

Q6: Found “failed to create master: Error response from daemon: client version 1.39 is too new. Maximum supported API version is 1.38” when Baetyl start.

Suggested Solution:

  • If the version of Baetyl is equal to or greater than 1.0.0, to set API version in the configuration file (etc/baetyl/conf.yml) of Baetyl:
docker:
  api_version: 1.38
  • If less than 1.0.0, to set environment variable DOCKER_API_VERSION=1.38, for example:
sudo vim ~/.bash_profile
export DOCKER_API_VERSION=1.38
source ~/.bash_profile

Q7: How does Baetyl connect to NB-IOT network?

Suggested Solution: NB-IoT is a network standard similar to 2/3/4G with low bandwidth and low power consumption. NB-IoT supports TCP-based MQTT protocol, so you can use NB-IoT card to connect to Baidu Cloud IotHub, deploy Baetyl application and communicate with BIE Cloud Management Suite. However, among the three major operators in China, Telecom have imposed whitelist restrictions on their NB cards, and only allow to connect to Telecom Cloud service IP. Therefore, only Mobile NB cards and Unicom NB cards can be used to connect to Baidu Cloud service.

Q8: Does Baetyl support to push data to Kafka?

Suggested Solution: Support, you can refer to How to write a python script for python runtime, and subscribe messages from the local Hub module and writing them to Kafka service. Besides, you can also refer to How to develop a customize module for Baetyl, which subscribes message from the local Hub module and then writes it to Kafka.

Q9: What are the ways to change Baetyl configurations? Can I only make configuration changes through the BIE Cloud Management Suite?

Suggested Solution: Currently, we recommend changing configurations through the BIE Cloud Management Suite, but you can also manually change the configuration file on the core device and then restart Baetyl to take effect.

Q10: I download MQTTBox client, extract it to a directory, and copy/move the executable file MQTTBox to /usr/local/bin(other directory is similar, such as /usr/bin, /bin, /usr/sbin, etc.). But it reports an error of “error while loading shared libraries: libgconf-2.so.4: cannot open shared object file: No such file or directorywhenMQTTBox” start.

Suggested Solution: As above description, this is because the lack of libgconf-2.so.4 library when MQTTBox start, and the recommended use is as follows:

  • Step 1: Download and extract the MQTTBox software package;
  • Step 2: cd /pat/to/MQTTBox/directory and sudo chmod +x MQTTBox;
  • Step 3: sudo ln -s /path/to/MQTTBox /usr/local/bin/MQTTBox;
  • Step 4: Open terminal and execute the command MQTTBox.

Q11: localfunc can’t process the message, check funclog has the following error message:

level=error msg=”failed to create new client” dispatcher=mqtt error=”dial tcp 0.0.0.0:1883:connect:connection refused”

Suggested Solution: If you are using the BIE Cloud Management Suite to deliver the configuration, there are a few points to note:

  1. Cloud delivery configuration currently only supports container mode.
  2. If the configuration is sent in the cloud, the hub address configured in localfunc should be localhub instead of 0.0.0.0.

According to the above information, the actual error is judged, and the configuration is delivered from the cloud as needed, or by referring to Configuration Analysis Document for verification and configuration.

Q12: How can i use BIE Cloud Management Suite with CFC(Cloud Function Compute)?

Suggested Solution:

  1. Make sure your BIE configuration and CFC functions in the same region, such as beijing/guangzhou.
  2. Make sure your CFC functions are published.
  3. Select CFC function template when volume create, more detailed contents please refer to How-to-apply-volume-in-the-right-way

Q13: What‘s the relationship between the parameter ports and the parameter listen which in the hub configuration file?

Suggested Solution:

  1. ports: Port exposed configuration in docker container mode.
  2. listen: Which address the hub module will listen on. In docker container mode, it’s means container address. In native process mode, it’s means host address.
  3. By referring to Configuration Analysis Document

Q14: How to process data in the cloud platform after message send to Baidu IoT Hub by Baetyl?

Suggested Solution: In the cloud platform, the Rule Engine can be used to transmit data to other cloud services, such as CFC(Cloud Function Compute), TSDB.

Q15: How to connect the Device management of Baidu IoT Hub?

Suggested Solution: The Device management of Baidu IoT Hub does not support ssl authentication. As a temporary solution, you can configure Remote Feature to connect the Device management with username and password authentication manually.

Q16: If I don’t want to lose messages and want to ensure all messages are synchronized to cloud, how can I do?

Suggested Solution:

You must meet the following 2 conditions:

  • To make sure messages will be persist in local disk which are sent to local hub, the topic’s QoS must be set to 1.
  • To make sure messages will be sent to cloud successful, the QoS of rules configuration of Remote module must be set to 1, which includes remote sub’s QoS and the pub’s QoS. By referring to Configuration Analysis Document

Q17: After the configuration is sent from the cloud to the edge, the default startup mode is docker container mode. After modifying mode: native in etc/baetyl/conf.yml the startup error is similar to the following: “failed to update system: open /Users/ Xxx/baetyl_native/var/run/baetyl/services/agent/lib/baetyl/hub.baidubce.com/baetyl/baetyl-agent:latest/package.yml: no such file or directory”.

Suggested Solution: At present, our cloud management does not support the process mode. If you need to start Baetyl in process mode locally, please refer to the configuration content in example/native and execute the command make install-native. Install and start by process with the command sudo baetyl start.

Q18: There is a similar error when downloading the image: “error=”Error response from daemon: Get https://hub.baidubce.com/v2/: x509: failed to load system roots and no roots provided” baetyl= Master”.

Suggested Solution: This is because the ca-certificates package is missing from the system and can be installed to solve this problem. For example, if the host system is Debian, you can use the following command to install it:

sudo apt-get update
sudo apt-get install ca-certificates

For other systems, please check the relevant installation operations yourself.

Resources Download

MQTT download

MQTT client sample

C sample of MQTT client: C-sample-of-MQTT-client

Python sample of MQTT client: Python-sample-of-MQTT-client

MQTT.fx download

Official website: http://www.jensd.de/apps/mqttfx/1.7.1/

MQTT.fx download link of BOS:

OS CPU SHA256 Download link
Linux(rpm) x86 b3301ef2f1abad4e8298766f43060e5ce9906099da096c4e0b601485dcca849d Download
Linux(rpm) x86_64 f665a6a97ff5ecd77ceb713a38c0aa2bb1af281a3ebe647d06579d1489845d6a Download
Linux(deb) x86 252ae7bfb621eebfcab2d47c500896b44d6f23ce82c33e0217ac43a4c735acfa Download
Linux(deb) x86_64 6fb14c739cbf8b54a373a0c5c173ef3657c01c675374ded8f0a292c610e549c2 Download
Windows(exe) x86 aa3902a2b76e427c4ba90b2b49dd337e95e614ba59f8cd64b5a95b5080766965 Download
Windows(exe) x86_64 4adfbb0eee65273bead6ce9885cf064de72d00d437bbaf3b9ff5236634fb6057 Download
Darwin(dmg) x86_64 544b6ac0afefb80b5a56c2a6f2411f999862fb8f0f20502c912f28a9d1aa3b4a Download

MQTTBox download

Official website: http://workswithweb.com/html/mqttbox/downloads.html

MQTTBox download link of BOS:

OS SHA256 Download link
Linux(deb) 15ff821634d7c2dbef3cbc3e7252bc86e3890a2c3c094842405cb442902d467d Download
Linux(tar.gz) 0ea0566f40fdf544510090441faaba745816ad1d438f3efe72197eb630f0e1f7 Download
Darwin(mac-dmg) 5ebd95c8bd29bea5974f2e38662d0b85cc2d0015fd4bbcdc777ffa3fe09d1c94 Download
Darwin(mac-zip) dda9549dbf35e6ad2dfbdb640020a0153ab62d6b27fbbe86da9ef0a8b997b4b9 Download
Windows(win-exe) 86ab9beb3e1241119f8a4e36550da755fead40be84a91eff3c0eb96ce4e4621e Download

Paho MQTT Client SDK

Official website: http://www.eclipse.org/paho

Paho MQTT Client Comparison

Client MQTT 3.1 MQTT 3.1.1 MQTT 5.0 LWT SSL/TLS Automatic Reconnect Offline Buffering Message Persistence WebSocket Support Standard MQTT Support Blocking API Non-Blocking API High Availability
Java
Python
JavaScript
GoLang
C
C++
Rust
.Net (C#)
Android Service
Embedded C/C++

Pre-release 0.1.6(2019-09-23)

New features

  • #361 print git revision in version command
  • #341 support docker compose configuration
  • #316 support custom docker network
  • #339 add new module baetyl-video-infer for AI app

Bugfixes

  • #347 check savepath exists before write image to path in baetyl-video-infer
  • Fix all backward compatibility issues after rename

Others

  • N/A

/* Title: Baetyl 0.1.5 Sort: 10 */

Pre-release 0.1.5(2019-08-15)

New features

  • #302 support master OTA, new OTA logic for master and app
  • #305 support config template in golang sdk
  • #300 #301 support systemd in deb installation
  • #298 collect core num of CPU of host
  • #297 support sock config and file mount
  • #290 support deb build and publish
  • #289 run openedge in the foreground (openedge should be managed by daemon tools, for example, systemd)
  • #293 add new function runtime openedge-function-python36 with opencv 4.1.0 to handle images or AI inference results

Bugfixes

  • #303 fix “address already in use” issue of openedge.sock
  • #292 check service list in app config in agent module

Others

  • N/A

/* Title: Baetyl 0.1.4 Sort: 20 */

Pre-release 0.1.4(2019-07-05)

New features

  • #251 add node85 function runtime
  • #260 collect network ip address and MAC information
  • #263 optimize app reload logic in master, keep service running of its config not changed
  • #264 optimize volume clean logic and move it from master to agent module, will remove all volumes not in app’s volumes list
  • #266 stats the cpu and memory of the service instances

Bugfixes

  • #246 change the interval of stats report of agent module from 1m to 20s

Others

/* Title: Baetyl 0.1.3 Sort: 30 */

Pre-release 0.1.3(2019-05-10)

New features

  • #199 Supports the custom status information of the service instance and collects more system status information. For details, please refer to: https://github.com/baidu/openedge/blob/master/doc/zh-cn/overview/Baetyl-design .md#system-inspect
  • #209 When the openedge starts, the old instance will be cleaned up (the residual instance is usually caused by the abnormal exit of the openedge)
  • #211 Added Python 3.6 version of the function runtime, using Ubuntu16.04 as the base image
  • #222 Docker container mode supports runtime and args configuration
  • Added a simple timer module openedge-timer

Bugfixes

  • #201 When the function instance pool destroys the function instance, be sure to stop the function instance
  • #208 The openedge stop command waits for the openingge to stop running and exits, ensuring that the pid file is cleaned up
  • #234 The hub module posts a message waiting for the ack to time out and quickly resend the message.
  • atomic.addUint64() panics if the pointer to its argument is not 64byte aligned. ref: https://github.com/golang/go/issues/23345

Others(include release engineering)

  • #230 Release Baetyl 2019 Roadmap
  • #228 Release of the Baetyl Community Participant Convention

/* Title: Baetyl 0.1.2 Sort: 40 */

Pre-release 0.1.2(2019-04-04)

New features

  • Separate the agent module from the master and report the status periodically
  • Introduce volume to abstract resources in configuration and support existing images, such as mosquitto from hub.docker.com
  • Publish the command line and support background startup
  • Uniform configuration of the two modes, such as create a separate working directory for each service in native process mode
  • Introduce service replace module and support to start multiple instance
  • Support device mapping in docker container mode

Bugfixes

  • Add openedge.sock clean logic
  • Upgrade openedge-hub, change auth logic of password and tls
  • Upgrade openedge-function-x, add retry logic and remove keep order logic

Others(include release engineering)

  • Rich test example support, such as for hub module, provide mosquitto configuration
  • All documents in English

/* Title: Baetyl 0.1.1 Sort: 50 */

Pre-release 0.1.1(2018-12-28)

New features

  • optimize MQTT Remote module, support multiple remotes and message router
  • add validate subscription config to check MQTT client subscribe result or not

Bugfixes

  • isolate module directory in docker mode
  • remove network scope filter to support old docker

Others(include release engineering)

  • rich build, test and release scripts and documents
  • check in vendor to solve all build issues
  • refactor code and format all messages
  • replace shell with makefile
  • update gomqtt
  • add travis CI

/* Title: Baetyl 0.1.0 Sort: 60 */

Pre-release 0.1.0(2018-12-05)

Initial open source.

Features

  • support module management after modularization
  • support two modes: docker container and native process
  • support resource constraints in docker container mode(cpu, memory, etc)
  • provide some official modules, such as hub, function(and python2.7 runtime), MQTT remote, etc.

Bugfixes

  • N/A