/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package options
import (
cliflag "k8s.io/component-base/cli/flag"
)
// CSIDriverOptions config
type CSIDriverOptions struct {
Endpoint string
DriverName string
NodeID string
KubeEdgeEndpoint string
Version string
}
// NewCSIDriverOptions returns options object
func NewCSIDriverOptions() *CSIDriverOptions {
return &CSIDriverOptions{}
}
// Flags return flag sets
func (o *CSIDriverOptions) Flags() (fss cliflag.NamedFlagSets) {
fs := fss.FlagSet("csidriver")
fs.StringVar(&o.Endpoint, "endpoint", "unix:///csi/csi.sock", "CSI endpoint")
fs.StringVar(&o.DriverName, "drivername", "csidriver", "name of the driver")
fs.StringVar(&o.NodeID, "nodeid", "", "node id determines which node will be used to create/delete volumes")
fs.StringVar(&o.KubeEdgeEndpoint, "kubeedge-endpoint", "unix:///kubeedge/kubeedge.sock", "kubeedge endpoint")
return
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package common
func FuzzVolumeRegExp(data []byte) int {
VolumeRegExp.MatchString(string(data))
return 1
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"encoding/json"
"fmt"
"regexp"
"strings"
"time"
"k8s.io/apimachinery/pkg/api/meta"
v1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/klog/v2"
beehivemodel "github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/cloudhub/common/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
edgecon "github.com/kubeedge/kubeedge/cloud/pkg/edgecontroller/constants"
"github.com/kubeedge/kubeedge/common/constants"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/conn"
)
// VolumePattern constants for error message
const (
VolumePattern = `^\w[-\w.+]*/` + constants.CSIResourceTypeVolume + `/\w[-\w.+]*`
)
// VolumeRegExp is used to validate the volume resource
var VolumeRegExp = regexp.MustCompile(VolumePattern)
func IsVolumeResource(resource string) bool {
return VolumeRegExp.MatchString(resource)
}
// GetMessageUID returns the UID of the object in message
func GetMessageUID(msg beehivemodel.Message) (string, error) {
accessor, err := meta.Accessor(msg.Content)
if err != nil {
return "", err
}
return string(accessor.GetUID()), nil
}
// GetMessageDeletionTimestamp returns the deletionTimestamp of the object in message
func GetMessageDeletionTimestamp(msg *beehivemodel.Message) (*v1.Time, error) {
accessor, err := meta.Accessor(msg.Content)
if err != nil {
return nil, err
}
return accessor.GetDeletionTimestamp(), nil
}
// TrimMessage trims resource field in message
// before: node/{nodename}/{namespace}/pod/{podname}
// after: {namespace}/pod/{podname}
func TrimMessage(msg *beehivemodel.Message) {
resource := msg.GetResource()
if strings.HasPrefix(resource, model.ResNode) {
tokens := strings.Split(resource, "/")
if len(tokens) < 3 {
klog.Warningf("event resource %s starts with node but length less than 3", resource)
} else {
msg.SetResourceOperation(strings.Join(tokens[2:], "/"), msg.GetOperation())
}
}
}
func ConstructConnectMessage(info *model.HubInfo, isConnected bool) *beehivemodel.Message {
connected := model.OpConnect
if !isConnected {
connected = model.OpDisConnect
}
body := map[string]interface{}{
"event_type": connected,
"timestamp": time.Now().Unix(),
"client_id": info.NodeID,
}
content, _ := json.Marshal(body)
msg := beehivemodel.NewMessage("")
msg.BuildRouter(modules.CloudHubModuleName, model.GpResource,
model.NewResource(model.ResNode, info.NodeID, nil), connected)
msg.FillBody(content)
return msg
}
func DeepCopy(msg *beehivemodel.Message) *beehivemodel.Message {
if msg == nil {
return nil
}
out := new(beehivemodel.Message)
out.Header = msg.Header
out.Router = msg.Router
out.Content = msg.Content
return out
}
func NotifyEventQueueError(conn conn.Connection, nodeID string) {
msg := beehivemodel.NewMessage("").BuildRouter(model.GpResource, modules.CloudHubModuleName,
model.NewResource(model.ResNode, nodeID, nil), model.OpDisConnect)
err := conn.WriteMessageAsync(msg)
if err != nil {
klog.Errorf("fail to notify node %s event queue disconnected, reason: %s", nodeID, err.Error())
}
}
func AckMessageKeyFunc(obj interface{}) (string, error) {
msg, ok := obj.(*beehivemodel.Message)
if !ok {
return "", fmt.Errorf("object type %T is not message type", msg)
}
if msg.GetGroup() == edgecon.GroupResource {
return GetMessageUID(*msg)
}
return "", fmt.Errorf("failed to get message key")
}
func NoAckMessageKeyFunc(obj interface{}) (string, error) {
msg, ok := obj.(*beehivemodel.Message)
if !ok {
return "", fmt.Errorf("object is not message type")
}
return msg.Header.ID, nil
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package common
import (
"fmt"
"k8s.io/client-go/tools/cache"
"k8s.io/client-go/util/workqueue"
beehivemodel "github.com/kubeedge/beehive/pkg/core/model"
)
// NodeMessagePool is a collection of all downstream messages sent to an
// edge node. There are two types of messages, one that requires an ack
// and one that does not. For each type of message, we use the `queue` to
// mark the order of sending, and use the `store` to store specific messages
type NodeMessagePool struct {
// AckMessageStore store message that will send to edge node
// and require acknowledgement from edge node.
AckMessageStore cache.Store
// AckMessageQueue store message key that will send to edge node
// and require acknowledgement from edge node.
AckMessageQueue workqueue.RateLimitingInterface
// NoAckMessageStore store message that will send to edge node
// and do not require acknowledgement from edge node.
NoAckMessageStore cache.Store
// NoAckMessageQueue store message key that will send to edge node
// and do not require acknowledgement from edge node.
NoAckMessageQueue workqueue.RateLimitingInterface
}
// InitNodeMessagePool init node message pool for node
func InitNodeMessagePool(nodeID string) *NodeMessagePool {
return &NodeMessagePool{
AckMessageStore: cache.NewStore(AckMessageKeyFunc),
AckMessageQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), nodeID),
NoAckMessageStore: cache.NewStore(NoAckMessageKeyFunc),
NoAckMessageQueue: workqueue.NewNamedRateLimitingQueue(workqueue.DefaultControllerRateLimiter(), nodeID),
}
}
// GetAckMessage get message that requires ack with the key
func (nsp *NodeMessagePool) GetAckMessage(key string) (*beehivemodel.Message, error) {
obj, exist, err := nsp.AckMessageStore.GetByKey(key)
if err != nil {
return nil, fmt.Errorf("get message %s err: %v", key, err)
}
if !exist {
return nil, fmt.Errorf("message %s not found", key)
}
msg, ok := obj.(*beehivemodel.Message)
if !ok {
return nil, fmt.Errorf("message type %T is invalid", obj)
}
if msg == nil {
return nil, fmt.Errorf("message is nil for key: %s", key)
}
return msg, nil
}
// GetNoAckMessage get message that does not require ack with the key
func (nsp *NodeMessagePool) GetNoAckMessage(key string) (*beehivemodel.Message, error) {
obj, exist, err := nsp.NoAckMessageStore.GetByKey(key)
if err != nil {
return nil, fmt.Errorf("get message %s err: %v", key, err)
}
if !exist {
return nil, fmt.Errorf("message %s not found", key)
}
msg, ok := obj.(*beehivemodel.Message)
if !ok {
return nil, fmt.Errorf("message type %T is invalid", obj)
}
if msg == nil {
return nil, fmt.Errorf("message is nil for key: %s", key)
}
return msg, nil
}
// ShutDown will close all the message queue in the message pool
func (nsp *NodeMessagePool) ShutDown() {
nsp.AckMessageQueue.ShutDown()
nsp.NoAckMessageQueue.ShutDown()
}
package model
import (
_ "encoding/json" // Mapping value of json to struct member
"fmt"
"strings"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/messagelayer"
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
)
// constants for resource types
const (
ResNode = "node"
ResMember = "membership"
ResTwin = "twin"
ResAuth = "auth_info"
ResDevice = "device"
)
// constants for resource operations
const (
OpGet = "get"
OpResult = "get_result"
OpList = "list"
OpDetail = "detail"
OpDelta = "delta"
OpDoc = "document"
OpUpdate = "updated"
OpInsert = "insert"
OpDelete = "deleted"
OpConnect = "connected"
OpDisConnect = "disconnected"
OpKeepalive = "keepalive"
)
// GpResource constants for message group
const (
GpResource = "resource"
)
// constants for message source
const (
SrcManager = "edgemgr"
)
// constants for identifier information for edge hub
const (
ProjectID = "project_id"
NodeID = "node_id"
)
var cloudModuleArray = []string{
modules.CloudHubModuleName,
modules.CloudStreamModuleName,
modules.DeviceControllerModuleName,
modules.EdgeControllerModuleName,
modules.SyncControllerModuleName,
}
// HubInfo saves identifier information for edge hub
type HubInfo struct {
ProjectID string
NodeID string
}
// NewResource constructs a resource field using resource type and ID
func NewResource(resType, resID string, info *HubInfo) string {
var prefix string
if info != nil {
prefix = fmt.Sprintf("%s/%s/", model.ResourceTypeNode, info.NodeID)
}
if resID == "" {
return fmt.Sprintf("%s%s", prefix, resType)
}
return fmt.Sprintf("%s%s/%s", prefix, resType, resID)
}
// IsNodeStopped indicates if the node is stopped or running
func IsNodeStopped(msg *model.Message) bool {
resourceType, _ := messagelayer.GetResourceType(*msg)
if resourceType != model.ResourceTypeNode {
return false
}
if msg.Router.Operation == model.DeleteOperation {
return true
}
return false
}
// IsFromEdge judges if the event is sent from edge
func IsFromEdge(msg *model.Message) bool {
source := msg.Router.Source
for _, item := range cloudModuleArray {
if source == item {
return false
}
}
return true
}
// IsToEdge judges if the vent should be sent to edge
func IsToEdge(msg *model.Message) bool {
if msg.Router.Source != SrcManager {
return true
}
resource := msg.Router.Resource
if strings.HasPrefix(resource, ResNode) {
tokens := strings.Split(resource, "/")
if len(tokens) >= 3 {
resource = strings.Join(tokens[2:], "/")
}
}
// apply special check for edge manager
resOpMap := map[string][]string{
ResMember: {OpGet},
ResTwin: {OpDelta, OpDoc, OpGet},
ResAuth: {OpGet},
ResNode: {OpDelete},
}
for res, ops := range resOpMap {
for _, op := range ops {
if msg.Router.Operation == op && strings.Contains(resource, res) {
return false
}
}
}
return true
}
package udsserver
import (
"encoding/json"
"errors"
"fmt"
"k8s.io/klog/v2"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
"github.com/kubeedge/kubeedge/common/constants"
)
// StartServer serves
func StartServer(address string) {
uds := NewUnixDomainSocket(address)
uds.SetContextHandler(func(context string) string {
// receive message from client
klog.Infof("uds server receives context: %s", context)
msg, err := ExtractMessage(context)
if err != nil {
klog.Errorf("Failed to extract message: %v", err)
return feedbackError(err, msg)
}
// Send message to edge
resp, err := beehiveContext.SendSync(modules.CloudHubModuleName, *msg,
constants.CSISyncMsgRespTimeout)
if err != nil {
klog.Errorf("failed to send message to edge: %v", err)
return feedbackError(err, msg)
}
// Marshal response message
data, err := json.Marshal(resp)
if err != nil {
klog.Errorf("marshal response failed with error: %v", err)
return feedbackError(err, msg)
}
klog.Infof("uds server send back data: %s resp: %v", string(data), resp)
return string(data)
})
klog.Info("start unix domain socket server")
if err := uds.StartServer(); err != nil {
klog.Exitf("failed to start uds server: %v", err)
return
}
}
// ExtractMessage extracts message from clients
func ExtractMessage(context string) (*model.Message, error) {
var msg model.Message
if context == "" {
return &msg, errors.New("failed with error: context is empty")
}
err := json.Unmarshal([]byte(context), &msg)
if err != nil {
return &msg, err
}
return &msg, nil
}
// feedbackError sends back error message
func feedbackError(err error, request *model.Message) string {
// Build message
errResponse := model.NewErrorMessage(request, err.Error()).
SetRoute(modules.CloudHubModuleName, request.GetGroup())
// Marshal message
data, err := json.Marshal(errResponse)
if err != nil {
return fmt.Sprintf("feedbackError marshal failed with error: %v", err)
}
return string(data)
}
package udsserver
import (
"fmt"
"net"
"os"
"strings"
"k8s.io/klog/v2"
)
const (
// DefaultBufferSize represents default buffer size
DefaultBufferSize = 10480
)
// UnixDomainSocket struct
type UnixDomainSocket struct {
filename string
buffersize int
handler func(string) string
}
// NewUnixDomainSocket create new socket
func NewUnixDomainSocket(filename string, buffersize ...int) *UnixDomainSocket {
size := DefaultBufferSize
if len(buffersize) != 0 {
size = buffersize[0]
}
return &UnixDomainSocket{filename: filename, buffersize: size}
}
// parseEndpoint parses endpoint
func parseEndpoint(ep string) (string, string, error) {
if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") {
s := strings.SplitN(ep, "://", 2)
if s[1] != "" {
return s[0], s[1], nil
}
}
return "", "", fmt.Errorf("invalid endpoint: %v", ep)
}
// SetContextHandler set handler for server
func (us *UnixDomainSocket) SetContextHandler(f func(string) string) {
us.handler = f
}
// StartServer start for server
func (us *UnixDomainSocket) StartServer() error {
proto, addr, err := parseEndpoint(us.filename)
if err != nil {
klog.Errorf("failed to parseEndpoint: %v", err)
return err
}
if proto == "unix" {
addr = "/" + addr
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) { //nolint: vetshadow
klog.Errorf("failed to remove addr: %v", err)
return err
}
}
// Listen
listener, err := net.Listen(proto, addr)
if err != nil {
klog.Errorf("failed to listen addr: %v", err)
return err
}
defer listener.Close()
klog.Infof("listening on: %v", listener.Addr())
for {
c, err := listener.Accept()
if err != nil {
klog.Errorf("accept to error: %v", err)
continue
}
go us.handleServerConn(c)
}
}
// handleServerConn handler for server
func (us *UnixDomainSocket) handleServerConn(c net.Conn) {
defer c.Close()
buf := make([]byte, us.buffersize)
nr, err := c.Read(buf)
if err != nil {
klog.Errorf("failed to read buffer: %v", err)
return
}
result := us.handleServerContext(string(buf[0:nr]))
_, err = c.Write([]byte(result))
if err != nil {
klog.Errorf("failed to write buffer: %v", err)
}
}
// HandleServerContext handler for server
func (us *UnixDomainSocket) handleServerContext(context string) string {
if us.handler != nil {
return us.handler(context)
}
return ""
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package udsserver
// Tests ExtractMessage found here:
// https://github.com/kubeedge/kubeedge/blob/master/cloud/pkg/cloudhub/servers/udsserver/server.go#L52
func FuzzExtractMessage(data []byte) int {
msg, err := ExtractMessage(string(data))
if err != nil {
_ = feedbackError(err, msg)
}
return 1
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package messagelayer
import (
"strings"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
"github.com/kubeedge/kubeedge/common/constants"
)
// MessageLayer define all functions that message layer must implement
type MessageLayer interface {
Send(message model.Message) error
Receive() (model.Message, error)
Response(message model.Message) error
}
// ContextMessageLayer build on context
type ContextMessageLayer struct {
// SendModuleName indicates which module will send message to
SendModuleName string
// SendRouterModuleName indicates which module will send router message to
SendRouterModuleName string
// ReceiveModuleName indicates which module will receive message from
ReceiveModuleName string
// ResponseModuleName indicates which module will response message to
ResponseModuleName string
}
// Send message
func (cml *ContextMessageLayer) Send(message model.Message) error {
module := cml.SendModuleName
// if message is rule/ruleEndpoint type, send to router module.
if len(cml.SendRouterModuleName) != 0 && isRouterMsg(message) {
module = cml.SendRouterModuleName
}
beehiveContext.Send(module, message)
return nil
}
// Receive message
func (cml *ContextMessageLayer) Receive() (model.Message, error) {
return beehiveContext.Receive(cml.ReceiveModuleName)
}
// Response message
func (cml *ContextMessageLayer) Response(message model.Message) error {
beehiveContext.Send(cml.ResponseModuleName, message)
return nil
}
func isRouterMsg(message model.Message) bool {
resourceArray := strings.Split(message.GetResource(), constants.ResourceSep)
return len(resourceArray) == 2 && (resourceArray[0] == model.ResourceTypeRule || resourceArray[0] == model.ResourceTypeRuleEndpoint)
}
func EdgeControllerMessageLayer() MessageLayer {
return &ContextMessageLayer{
SendModuleName: modules.CloudHubModuleName,
SendRouterModuleName: modules.RouterModuleName,
ReceiveModuleName: modules.EdgeControllerModuleName,
ResponseModuleName: modules.CloudHubModuleName,
}
}
func DeviceControllerMessageLayer() MessageLayer {
return &ContextMessageLayer{
SendModuleName: modules.CloudHubModuleName,
ReceiveModuleName: modules.DeviceControllerModuleName,
ResponseModuleName: modules.CloudHubModuleName,
}
}
func DynamicControllerMessageLayer() MessageLayer {
return &ContextMessageLayer{
SendModuleName: modules.CloudHubModuleName,
ReceiveModuleName: modules.DynamicControllerModuleName,
ResponseModuleName: modules.CloudHubModuleName,
}
}
func TaskManagerMessageLayer() MessageLayer {
return &ContextMessageLayer{
SendModuleName: modules.CloudHubModuleName,
ReceiveModuleName: modules.TaskManagerModuleName,
ResponseModuleName: modules.CloudHubModuleName,
}
}
func PolicyControllerMessageLayer() MessageLayer {
return &ContextMessageLayer{
SendModuleName: modules.CloudHubModuleName,
ReceiveModuleName: modules.PolicyControllerModuleName,
ResponseModuleName: modules.CloudHubModuleName,
}
}
/*
Copyright 2025 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package messagelayer
import (
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/modules"
nodetaskmsg "github.com/kubeedge/kubeedge/pkg/nodetask/message"
)
// BuildNodeTaskRouter builds the *model.Message of the node task
// and set the route and operation(action) in the message,
// which will be sent to the edge from the cloud.
func BuildNodeTaskRouter(r nodetaskmsg.Resource, opr string) *model.Message {
return model.NewMessage("").
SetRoute(modules.TaskManagerModuleName, modules.TaskManagerModuleGroup).
SetResourceOperation(r.String(), opr)
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package messagelayer
import (
"errors"
"fmt"
"strings"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/common/constants"
pkgutil "github.com/kubeedge/kubeedge/pkg/util"
)
const (
ResourceNode = "node"
ResourceNodeIDIndex = 1
ResourceNamespaceIndex = 2
ResourceResourceTypeIndex = 3
ResourceResourceNameIndex = 4
ResourceDeviceIndex = 2
ResourceDeviceNamespaceIndex = 3
ResourceDevice = "device"
ResourceTypeTwinEdgeUpdated = "twin/edge_updated"
ResourceTypeMembershipDetail = "membership/detail"
ResourceDeviceStateUpdated = "state/update"
)
// BuildResource return a string as "beehive/pkg/core/model".Message.Router.Resource
func BuildResource(nodeID, namespace, resourceType, resourceID string) (resource string, err error) {
if namespace == "" || resourceType == "" || nodeID == "" {
err = fmt.Errorf("required parameter are not set (node id, namespace or resource type)")
return
}
resource = fmt.Sprintf("%s%s%s%s%s%s%s", ResourceNode, constants.ResourceSep, nodeID, constants.ResourceSep, namespace, constants.ResourceSep, resourceType)
if resourceID != "" {
resource += fmt.Sprintf("%s%s", constants.ResourceSep, resourceID)
}
return
}
// getElementByIndex returns a string from "beehive/pkg/core/model".Message.Router.Resource by index
func getElementByIndex(msg model.Message, index int) string {
sli := strings.Split(msg.GetResource(), constants.ResourceSep)
if len(sli) <= index {
return ""
}
return sli[index]
}
// GetNodeID from "beehive/pkg/core/model".Message.Router.Resource
func GetNodeID(msg model.Message) (string, error) {
res := getElementByIndex(msg, ResourceNodeIDIndex)
if res == "" {
return "", fmt.Errorf("node id not found")
}
klog.V(4).Infof("The node id %s, %d", res, ResourceNodeIDIndex)
return res, nil
}
// GetNamespace from "beehive/pkg/core/model".Model.Router.Resource
func GetNamespace(msg model.Message) (string, error) {
res := getElementByIndex(msg, ResourceNamespaceIndex)
if res == "" {
return "", fmt.Errorf("namespace not found")
}
klog.V(4).Infof("The namespace %s, %d", res, ResourceNamespaceIndex)
return res, nil
}
// GetResourceType from "beehive/pkg/core/model".Model.Router.Resource
func GetResourceType(msg model.Message) (string, error) {
res := getElementByIndex(msg, ResourceResourceTypeIndex)
if res == "" {
return "", fmt.Errorf("resource type not found")
}
klog.V(4).Infof("The resource type is %s, %d", res, ResourceResourceTypeIndex)
return res, nil
}
// GetResourceName from "beehive/pkg/core/model".Model.Router.Resource
func GetResourceName(msg model.Message) (string, error) {
res := getElementByIndex(msg, ResourceResourceNameIndex)
if res == "" {
return "", fmt.Errorf("resource name not found")
}
klog.V(4).Infof("The resource name is %s, %d", res, ResourceResourceNameIndex)
return res, nil
}
// BuildResourceForRouter return a string as "beehive/pkg/core/model".Message.Router.Resource
func BuildResourceForRouter(resourceType, resourceID string) (string, error) {
if resourceID == "" || resourceType == "" {
return "", fmt.Errorf("required parameter are not set (resourceID or resource type)")
}
return pkgutil.ConcatStrings(resourceType, constants.ResourceSep, resourceID), nil
}
// BuildResourceForDevice return a string as "beehive/pkg/core/model".Message.Router.Resource
func BuildResourceForDevice(nodeID, resourceType, resourceID string) (resource string, err error) {
if nodeID == "" || resourceType == "" {
err = fmt.Errorf("required parameter are not set (node id, namespace or resource type)")
return
}
resource = fmt.Sprintf("%s%s%s%s%s", ResourceNode, constants.ResourceSep, nodeID, constants.ResourceSep, resourceType)
if resourceID != "" {
resource += fmt.Sprintf("%s%s", constants.ResourceSep, resourceID)
}
return
}
// GetDeviceID returns the ID of the device,resource's format:$hw/events/device/{namespace}/{deviceName}
func GetDeviceID(resource string) (string, error) {
res := strings.Split(resource, "/")
if len(res) >= ResourceDeviceNamespaceIndex+2 && res[ResourceDeviceIndex] == ResourceDevice {
return res[ResourceDeviceNamespaceIndex] + "/" + res[ResourceDeviceNamespaceIndex+1], nil
}
return "", errors.New("failed to get device id")
}
// GetResourceTypeForDevice returns the resourceType of message received from edge
func GetResourceTypeForDevice(resource string) (string, error) {
if strings.Contains(resource, ResourceTypeTwinEdgeUpdated) {
return ResourceTypeTwinEdgeUpdated, nil
} else if strings.Contains(resource, ResourceTypeMembershipDetail) {
return ResourceTypeMembershipDetail, nil
} else if strings.Contains(resource, ResourceDeviceStateUpdated) {
return ResourceDeviceStateUpdated, nil
}
return "", fmt.Errorf("unknown resource, found: %s", resource)
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csidriver
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/golang/protobuf/jsonpb"
"github.com/google/uuid"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/common/constants"
)
type controllerServer struct {
caps []*csi.ControllerServiceCapability
nodeID string
kubeEdgeEndpoint string
}
// newControllerServer creates controller server
func newControllerServer(nodeID, kubeEdgeEndpoint string) *controllerServer {
return &controllerServer{
caps: getControllerServiceCapabilities(
[]csi.ControllerServiceCapability_RPC_Type{
csi.ControllerServiceCapability_RPC_CREATE_DELETE_VOLUME,
csi.ControllerServiceCapability_RPC_PUBLISH_UNPUBLISH_VOLUME,
}),
nodeID: nodeID,
kubeEdgeEndpoint: kubeEdgeEndpoint,
}
}
// CreateVolume issues create volume func
func (cs *controllerServer) CreateVolume(_ context.Context, req *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) {
// Check arguments
if len(req.GetName()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Name missing in request")
}
caps := req.GetVolumeCapabilities()
if caps == nil {
return nil, status.Error(codes.InvalidArgument, "Volume Capabilities missing in request")
}
volumeID := uuid.New().String()
// Build message struct
resource, err := buildResource(cs.nodeID,
DefaultNamespace,
constants.CSIResourceTypeVolume,
volumeID)
if err != nil {
klog.Errorf("build message resource failed with error: %s", err)
return nil, err
}
m := jsonpb.Marshaler{}
js, err := m.MarshalToString(req)
if err != nil {
klog.Errorf("failed to marshal to string with error: %s", err)
return nil, err
}
klog.V(4).Infof("create volume marshal to string: %s", js)
msg := model.NewMessage("").
BuildRouter(DefaultReceiveModuleName, GroupResource, resource, constants.CSIOperationTypeCreateVolume).
FillBody(js)
// Marshal message
reqData, err := json.Marshal(msg)
if err != nil {
klog.Errorf("marshal request failed with error: %v", err)
return nil, err
}
// Send message to KubeEdge
resdata, err := sendToKubeEdge(string(reqData), cs.kubeEdgeEndpoint)
if err != nil {
klog.Errorf("send to kubeedge failed with error: %v", err)
return nil, err
}
// Unmarshal message
result, err := extractMessage(resdata)
if err != nil {
klog.Errorf("unmarshal response failed with error: %v", err)
return nil, err
}
klog.V(4).Infof("create volume result: %v", result)
data, ok := result.GetContent().(string)
if !ok {
klog.Errorf("content is not string type: %v", result.GetContent())
return nil, fmt.Errorf("content type %T is not string", result.GetContent())
}
if result.GetOperation() == model.ResponseErrorOperation {
klog.Errorf("create volume with error: %s", data)
return nil, errors.New(data)
}
decodeBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
klog.Errorf("create volume decode with error: %v", err)
return nil, err
}
response := &csi.CreateVolumeResponse{}
err = json.Unmarshal(decodeBytes, response)
if err != nil {
klog.Errorf("create volume unmarshal with error: %v", err)
return nil, err
}
klog.V(4).Infof("create volume response: %v", response)
createVolumeResponse := &csi.CreateVolumeResponse{
Volume: &csi.Volume{
VolumeId: response.Volume.VolumeId,
CapacityBytes: req.GetCapacityRange().GetRequiredBytes(),
VolumeContext: req.GetParameters(),
},
}
if req.GetVolumeContentSource() != nil {
createVolumeResponse.Volume.ContentSource = req.GetVolumeContentSource()
}
return createVolumeResponse, nil
}
// DeleteVolume issues delete volume func
func (cs *controllerServer) DeleteVolume(_ context.Context, req *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) {
// Check arguments
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID missing in request")
}
// Build message struct
resource, err := buildResource(cs.nodeID,
DefaultNamespace,
constants.CSIResourceTypeVolume,
req.GetVolumeId())
if err != nil {
klog.Errorf("build message resource failed with error: %s", err)
return nil, err
}
m := jsonpb.Marshaler{}
js, err := m.MarshalToString(req)
if err != nil {
klog.Errorf("failed to marshal to string with error: %s", err)
return nil, err
}
klog.V(4).Infof("delete volume marshal to string: %s", js)
msg := model.NewMessage("").
BuildRouter(DefaultReceiveModuleName, GroupResource, resource, constants.CSIOperationTypeDeleteVolume).
FillBody(js)
// Marshal message
reqData, err := json.Marshal(msg)
if err != nil {
klog.Errorf("marshal request failed with error: %v", err)
return nil, err
}
// Send message to KubeEdge
resdata, err := sendToKubeEdge(string(reqData), cs.kubeEdgeEndpoint)
if err != nil {
klog.Errorf("send to kubeedge failed with error: %v", err)
return nil, err
}
// Unmarshal message
result, err := extractMessage(resdata)
if err != nil {
klog.Errorf("unmarshal response failed with error: %v", err)
return nil, err
}
klog.V(4).Infof("delete volume result: %v", result)
data, ok := result.GetContent().(string)
if !ok {
klog.Errorf("content is not string type: %v", result.GetContent())
return nil, fmt.Errorf("content type %T is not string", result.GetContent())
}
if msg.GetOperation() == model.ResponseErrorOperation {
klog.Errorf("delete volume with error: %s", data)
return nil, errors.New(data)
}
decodeBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
klog.Errorf("delete volume decode with error: %v", err)
return nil, err
}
deleteVolumeResponse := &csi.DeleteVolumeResponse{}
err = json.Unmarshal(decodeBytes, deleteVolumeResponse)
if err != nil {
klog.Errorf("delete volume unmarshal with error: %v", err)
return nil, err
}
klog.V(4).Infof("delete volume response: %v", deleteVolumeResponse)
return deleteVolumeResponse, nil
}
// ControllerPublishVolume issues controller publish volume func
func (cs *controllerServer) ControllerPublishVolume(_ context.Context, req *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) {
instanceID := req.GetNodeId()
volumeID := req.GetVolumeId()
if len(volumeID) == 0 {
return nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Volume ID must be provided")
}
if len(instanceID) == 0 {
return nil, status.Error(codes.InvalidArgument, "ControllerPublishVolume Instance ID must be provided")
}
// Build message struct
resource, err := buildResource(cs.nodeID,
DefaultNamespace,
constants.CSIResourceTypeVolume,
volumeID)
if err != nil {
klog.Errorf("build message resource failed with error: %s", err)
return nil, err
}
m := jsonpb.Marshaler{}
js, err := m.MarshalToString(req)
if err != nil {
klog.Errorf("failed to marshal to string with error: %s", err)
return nil, err
}
klog.V(4).Infof("controller publish volume marshal to string: %s", js)
msg := model.NewMessage("").
BuildRouter(DefaultReceiveModuleName, GroupResource, resource, constants.CSIOperationTypeControllerPublishVolume).
FillBody(js)
// Marshal message
reqData, err := json.Marshal(msg)
if err != nil {
klog.Errorf("marshal request failed with error: %v", err)
return nil, err
}
// Send message to KubeEdge
resdata, err := sendToKubeEdge(string(reqData), cs.kubeEdgeEndpoint)
if err != nil {
klog.Errorf("send to kubeedge failed with error: %v", err)
return nil, err
}
// Unmarshal message
result, err := extractMessage(resdata)
if err != nil {
klog.Errorf("unmarshal response failed with error: %v", err)
return nil, err
}
klog.V(4).Infof("controller publish volume result: %v", result)
data, ok := result.GetContent().(string)
if !ok {
klog.Errorf("content is not string type: %v", result.GetContent())
return nil, fmt.Errorf("content type %T is not string", result.GetContent())
}
if msg.GetOperation() == model.ResponseErrorOperation {
klog.Errorf("controller publish volume with error: %s", data)
return nil, errors.New(data)
}
decodeBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
klog.Errorf("controller publish volume decode with error: %v", err)
return nil, err
}
controllerPublishVolumeResponse := &csi.ControllerPublishVolumeResponse{}
err = json.Unmarshal(decodeBytes, controllerPublishVolumeResponse)
if err != nil {
klog.Errorf("controller publish volume unmarshal with error: %v", err)
return nil, err
}
klog.V(4).Infof("controller publish volume response: %v", controllerPublishVolumeResponse)
return controllerPublishVolumeResponse, nil
}
// ControllerUnpublishVolume issues controller unpublish volume func
func (cs *controllerServer) ControllerUnpublishVolume(_ context.Context, req *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) {
instanceID := req.GetNodeId()
volumeID := req.GetVolumeId()
if len(volumeID) == 0 {
return nil, status.Error(codes.InvalidArgument, "ControllerUnpublishVolume Volume ID must be provided")
}
if len(instanceID) == 0 {
return nil, status.Error(codes.InvalidArgument, "ControllerUnpublishVolume Instance ID must be provided")
}
// Build message struct
resource, err := buildResource(cs.nodeID,
DefaultNamespace,
constants.CSIResourceTypeVolume,
volumeID)
if err != nil {
klog.Errorf("Build message resource failed with error: %s", err)
return nil, err
}
m := jsonpb.Marshaler{}
js, err := m.MarshalToString(req)
if err != nil {
klog.Errorf("failed to marshal to string with error: %s", err)
return nil, err
}
klog.V(4).Infof("controller Unpublish Volume marshal to string: %s", js)
msg := model.NewMessage("").
BuildRouter(DefaultReceiveModuleName, GroupResource, resource, constants.CSIOperationTypeControllerUnpublishVolume).
FillBody(js)
// Marshal message
reqData, err := json.Marshal(msg)
if err != nil {
klog.Errorf("marshal request failed with error: %v", err)
return nil, err
}
// Send message to KubeEdge
resdata, err := sendToKubeEdge(string(reqData), cs.kubeEdgeEndpoint)
if err != nil {
klog.Errorf("send to kubeedge failed with error: %v", err)
return nil, err
}
// Unmarshal message
result, err := extractMessage(resdata)
if err != nil {
klog.Errorf("unmarshal response failed with error: %v", err)
return nil, err
}
klog.V(4).Infof("controller Unpublish Volume result: %v", result)
data, ok := result.GetContent().(string)
if !ok {
klog.Errorf("content is not string type: %v", result.GetContent())
return nil, fmt.Errorf("content type %T is not string", result.GetContent())
}
if msg.GetOperation() == model.ResponseErrorOperation {
klog.Errorf("controller Unpublish Volume with error: %s", data)
return nil, errors.New(data)
}
decodeBytes, err := base64.StdEncoding.DecodeString(data)
if err != nil {
klog.Errorf("controller Unpublish Volume decode with error: %v", err)
return nil, err
}
controllerUnpublishVolumeResponse := &csi.ControllerUnpublishVolumeResponse{}
err = json.Unmarshal(decodeBytes, controllerUnpublishVolumeResponse)
if err != nil {
klog.Errorf("controller Unpublish Volume unmarshal with error: %v", err)
return nil, err
}
klog.V(4).Infof("controller Unpublish Volume response: %v", controllerUnpublishVolumeResponse)
return controllerUnpublishVolumeResponse, nil
}
func (cs *controllerServer) ValidateVolumeCapabilities(_ context.Context, req *csi.ValidateVolumeCapabilitiesRequest) (*csi.ValidateVolumeCapabilitiesResponse, error) {
// Check arguments
if len(req.GetVolumeId()) == 0 {
return nil, status.Error(codes.InvalidArgument, "Volume ID cannot be empty")
}
if len(req.VolumeCapabilities) == 0 {
return nil, status.Error(codes.InvalidArgument, req.VolumeId)
}
for _, cap := range req.GetVolumeCapabilities() {
if cap.GetMount() == nil && cap.GetBlock() == nil {
return nil, status.Error(codes.InvalidArgument, "cannot have both mount and block access type be undefined")
}
}
return &csi.ValidateVolumeCapabilitiesResponse{
Confirmed: &csi.ValidateVolumeCapabilitiesResponse_Confirmed{
VolumeContext: req.GetVolumeContext(),
VolumeCapabilities: req.GetVolumeCapabilities(),
Parameters: req.GetParameters(),
},
}, nil
}
func (cs *controllerServer) ControllerGetCapabilities(context.Context, *csi.ControllerGetCapabilitiesRequest) (*csi.ControllerGetCapabilitiesResponse, error) {
return &csi.ControllerGetCapabilitiesResponse{
Capabilities: cs.caps,
}, nil
}
func getControllerServiceCapabilities(cl []csi.ControllerServiceCapability_RPC_Type) []*csi.ControllerServiceCapability {
var csc []*csi.ControllerServiceCapability
for _, cap := range cl {
klog.V(4).Infof("Enabling controller service capability: %v", cap.String())
csc = append(csc, &csi.ControllerServiceCapability{
Type: &csi.ControllerServiceCapability_Rpc{
Rpc: &csi.ControllerServiceCapability_RPC{
Type: cap,
},
},
})
}
return csc
}
func (cs *controllerServer) GetCapacity(context.Context, *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *controllerServer) ListVolumes(context.Context, *csi.ListVolumesRequest) (*csi.ListVolumesResponse, error) {
return nil, status.Error(codes.Unimplemented, "")
}
func (cs *controllerServer) ControllerExpandVolume(context.Context, *csi.ControllerExpandVolumeRequest) (*csi.ControllerExpandVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "ControllerExpandVolume is not yet implemented")
}
func (cs *controllerServer) CreateSnapshot(context.Context, *csi.CreateSnapshotRequest) (*csi.CreateSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "CreateSnapshot is not yet implemented")
}
func (cs *controllerServer) DeleteSnapshot(context.Context, *csi.DeleteSnapshotRequest) (*csi.DeleteSnapshotResponse, error) {
return nil, status.Error(codes.Unimplemented, "DeleteSnapshot is not yet implemented")
}
func (cs *controllerServer) ListSnapshots(context.Context, *csi.ListSnapshotsRequest) (*csi.ListSnapshotsResponse, error) {
return nil, status.Error(codes.Unimplemented, "ListSnapshots is not yet implemented")
}
func (cs *controllerServer) ControllerGetVolume(context.Context, *csi.ControllerGetVolumeRequest) (*csi.ControllerGetVolumeResponse, error) {
return nil, status.Error(codes.Unimplemented, "ControllerGetVolume is not yet implemented")
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package csidriver
// This fuzzer mimicks the usage of extractMessage() found here:
// https://github.com/kubeedge/kubeedge/blob/master/cloud/pkg/csidriver/utils.go.
func FuzzextractMessage(data []byte) int {
result, err := extractMessage(string(data))
if err == nil {
_ = result.GetContent().(string)
_ = result.GetOperation()
}
return 1
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csidriver
import (
"github.com/container-storage-interface/spec/lib/go/csi"
"golang.org/x/net/context"
"google.golang.org/grpc/codes"
"google.golang.org/grpc/status"
"k8s.io/klog/v2"
)
type identityServer struct {
name string
version string
}
// newIdentityServer creates identity server
func newIdentityServer(name, version string) *identityServer {
return &identityServer{
name: name,
version: version,
}
}
func (ids *identityServer) GetPluginInfo(context.Context, *csi.GetPluginInfoRequest) (*csi.GetPluginInfoResponse, error) {
klog.V(4).Info("using default GetPluginInfo")
if ids.name == "" {
return nil, status.Error(codes.Unavailable, "driver name not configured")
}
if ids.version == "" {
return nil, status.Error(codes.Unavailable, "driver is missing version")
}
return &csi.GetPluginInfoResponse{
Name: ids.name,
VendorVersion: ids.version,
}, nil
}
func (ids *identityServer) Probe(context.Context, *csi.ProbeRequest) (*csi.ProbeResponse, error) {
return &csi.ProbeResponse{}, nil
}
func (ids *identityServer) GetPluginCapabilities(context.Context, *csi.GetPluginCapabilitiesRequest) (*csi.GetPluginCapabilitiesResponse, error) {
klog.V(4).Info("using default capabilities")
return &csi.GetPluginCapabilitiesResponse{
Capabilities: []*csi.PluginCapability{
{
Type: &csi.PluginCapability_Service_{
Service: &csi.PluginCapability_Service{
Type: csi.PluginCapability_Service_CONTROLLER_SERVICE,
},
},
},
},
}, nil
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csidriver
import (
"fmt"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/cloud/cmd/csidriver/app/options"
)
type CSIDriver struct {
options.CSIDriverOptions
ids *identityServer
cs *controllerServer
}
func NewCSIDriver(opts *options.CSIDriverOptions) (*CSIDriver, error) {
if opts.Endpoint == "" {
return nil, fmt.Errorf("no driver endpoint provided")
}
if opts.DriverName == "" {
return nil, fmt.Errorf("no driver name provided")
}
if opts.NodeID == "" {
return nil, fmt.Errorf("no node id provided")
}
if opts.KubeEdgeEndpoint == "" {
return nil, fmt.Errorf("no kubeedge endpoint provided")
}
if opts.Version == "" {
return nil, fmt.Errorf("no version provided")
}
return &CSIDriver{
CSIDriverOptions: *opts,
}, nil
}
func (cd *CSIDriver) Run() {
klog.Infof("driver information: %v", cd)
// Create GRPC servers
cd.ids = newIdentityServer(cd.DriverName, cd.Version)
cd.cs = newControllerServer(cd.NodeID, cd.KubeEdgeEndpoint)
s := newNonBlockingGRPCServer()
s.Start(cd.Endpoint, cd.ids, cd.cs, nil)
s.Wait()
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csidriver
import (
"net"
"k8s.io/klog/v2"
)
const (
// DefaultBufferSize represents default buffer size
DefaultBufferSize = 10480
)
// UnixDomainSocket struct
type UnixDomainSocket struct {
filename string
buffersize int
}
// NewUnixDomainSocket create new socket
func NewUnixDomainSocket(filename string, buffersize ...int) *UnixDomainSocket {
size := DefaultBufferSize
if buffersize != nil {
size = buffersize[0]
}
us := UnixDomainSocket{filename: filename, buffersize: size}
return &us
}
// Connect for client
func (us *UnixDomainSocket) Connect() (net.Conn, error) {
// parse
proto, addr, err := parseEndpoint(us.filename)
if err != nil {
klog.Errorf("failed to parseEndpoint: %v", err)
return nil, err
}
// dial
c, err := net.Dial(proto, addr)
if err != nil {
klog.Errorf("failed to dial: %v", err)
return nil, err
}
return c, nil
}
// Send msg for client
func (us *UnixDomainSocket) Send(c net.Conn, context string) (string, error) {
// send msg
_, err := c.Write([]byte(context))
if err != nil {
klog.Errorf("failed to write buffer: %v", err)
return "", err
}
// read response
buf := make([]byte, us.buffersize)
nr, err := c.Read(buf)
if err != nil {
klog.Errorf("failed to read buffer: %v", err)
return "", err
}
return string(buf[0:nr]), nil
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package csidriver
import (
"encoding/json"
"errors"
"fmt"
"net"
"os"
"strings"
"sync"
"github.com/container-storage-interface/spec/lib/go/csi"
"github.com/kubernetes-csi/csi-lib-utils/protosanitizer"
"golang.org/x/net/context"
"google.golang.org/grpc"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/common/constants"
)
// Constant defines csi related parameters
const (
GroupResource = "resource"
DefaultNamespace = "default"
DefaultReceiveModuleName = "cloudhub"
)
// newNonBlockingGRPCServer creates a new nonblocking server
func newNonBlockingGRPCServer() *nonBlockingGRPCServer {
return &nonBlockingGRPCServer{}
}
// NonBlocking server
type nonBlockingGRPCServer struct {
wg sync.WaitGroup
server *grpc.Server
}
func (s *nonBlockingGRPCServer) Start(endpoint string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer) {
s.wg.Add(1)
go func() {
defer s.wg.Done()
err := s.serve(endpoint, ids, cs, ns)
if err != nil {
panic(err.Error())
}
}()
}
func (s *nonBlockingGRPCServer) Wait() {
s.wg.Wait()
}
func (s *nonBlockingGRPCServer) Stop() {
s.server.GracefulStop()
}
func (s *nonBlockingGRPCServer) ForceStop() {
s.server.Stop()
}
func (s *nonBlockingGRPCServer) serve(endpoint string, ids csi.IdentityServer, cs csi.ControllerServer, ns csi.NodeServer) error {
proto, addr, err := parseEndpoint(endpoint)
if err != nil {
klog.Error(err.Error())
return err
}
if proto == "unix" {
addr = "/" + addr
if err := os.Remove(addr); err != nil && !os.IsNotExist(err) {
klog.Warningf("failed to remove %s, error: %s", addr, err.Error())
}
}
listener, err := net.Listen(proto, addr)
if err != nil {
klog.Errorf("failed to listen: %v", err)
return err
}
opts := []grpc.ServerOption{
grpc.UnaryInterceptor(logGRPC),
}
server := grpc.NewServer(opts...)
s.server = server
if ids != nil {
csi.RegisterIdentityServer(server, ids)
}
if cs != nil {
csi.RegisterControllerServer(server, cs)
}
if ns != nil {
csi.RegisterNodeServer(server, ns)
}
klog.Infof("listening for connections on address: %#v", listener.Addr())
return server.Serve(listener)
}
func parseEndpoint(ep string) (string, string, error) {
if strings.HasPrefix(strings.ToLower(ep), "unix://") || strings.HasPrefix(strings.ToLower(ep), "tcp://") {
s := strings.SplitN(ep, "://", 2)
if s[1] != "" {
return s[0], s[1], nil
}
}
return "", "", fmt.Errorf("invalid endpoint: %v", ep)
}
func logGRPC(ctx context.Context, req interface{}, info *grpc.UnaryServerInfo, handler grpc.UnaryHandler) (interface{}, error) {
klog.Infof("grpc call: %s", info.FullMethod)
klog.Infof("grpc request: %+v", protosanitizer.StripSecrets(req))
resp, err := handler(ctx, req)
if err != nil {
klog.Errorf("grpc error: %v", err)
} else {
klog.Infof("grpc response: %+v", protosanitizer.StripSecrets(resp))
}
return resp, err
}
// buildResource return a string as "beehive/pkg/core/model".Message.Router.Resource
func buildResource(nodeID, namespace, resourceType, resourceID string) (string, error) {
if nodeID == "" || namespace == "" || resourceType == "" {
return "", fmt.Errorf("required parameter are not set (node id, namespace or resource type)")
}
resource := fmt.Sprintf("%s%s%s%s%s%s%s", "node", constants.ResourceSep, nodeID, constants.ResourceSep, namespace, constants.ResourceSep, resourceType)
if resourceID != "" {
resource += fmt.Sprintf("%s%s", constants.ResourceSep, resourceID)
}
return resource, nil
}
// sendToKubeEdge sends messages to KubeEdge
func sendToKubeEdge(context, kubeEdgeEndpoint string) (string, error) {
us := NewUnixDomainSocket(kubeEdgeEndpoint)
// connect
r, err := us.Connect()
if err != nil {
return "", err
}
// send
res, err := us.Send(r, context)
if err != nil {
return "", err
}
return res, nil
}
// extractMessage extracts message
func extractMessage(context string) (*model.Message, error) {
var msg model.Message
if context == "" {
err := errors.New("failed to extract message with empty context")
klog.Errorf("%v", err)
return nil, err
}
err := json.Unmarshal([]byte(context), &msg)
if err != nil {
return nil, err
}
return &msg, nil
}
package utils
import (
"regexp"
"strings"
"k8s.io/klog/v2"
)
const (
pathRegex = "[-A-Za-z0-9+&@#%?=~_|!:,.;]+"
tailRegex = "/?"
)
var paramRegex = regexp.MustCompile("{" + pathRegex + "}")
// URLToURLRegex return url regex and replace {} in url,e.g.,/abc/{Aa1} to /abc/[-A-Za-z0-9+&@#%?=~_|!:,.;]+/?
func URLToURLRegex(url string) string {
params := paramRegex.FindAllString(url, -1)
for _, param := range params {
url = strings.Replace(url, param, pathRegex, -1)
}
url = url + tailRegex
return url
}
// IsMatch return true if the path match rule using regex
func IsMatch(reg, path string) bool {
match, err := regexp.MatchString(URLToURLRegex(reg), path)
if err != nil {
klog.Errorf("failed to validate res %s and reqPath %s, err: %v", reg, path, err)
return false
}
return match
}
// RuleContains return true if rule 1 contains rule 2, e.g., path /a contains /a/b
func RuleContains(rulePath, rule2Path string) bool {
path1 := strings.Split(rulePath, "/")
path2 := strings.Split(rule2Path, "/")
if len(path1) == 0 {
return true
}
if len(path2) == 0 {
return false
}
for i := 0; i < len(path1) && i < len(path2); i++ {
// rule1[i] contains rule2[i] when rule1[i] = {} or rule1[i] == rule2[i]
if path1[i] != path2[i] && URLToURLRegex(path1[i]) != URLToURLRegex(path2[i]) {
return false
}
}
return true
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package utils
import (
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
func FuzzRuleContains(data []byte) int {
f := fuzz.NewConsumer(data)
path1, err := f.GetString()
if err != nil {
return 0
}
path2, err := f.GetString()
if err != nil {
return 0
}
RuleContains(path1, path2)
return 1
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/beego/beego/v2/client/orm (interfaces: Ormer)
// Package beego is a generated GoMock package.
package beego
import (
context "context"
sql "database/sql"
orm "github.com/beego/beego/v2/client/orm"
utils "github.com/beego/beego/v2/core/utils"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockOrmer is a mock of Ormer interface
type MockOrmer struct {
ctrl *gomock.Controller
recorder *MockOrmerMockRecorder
}
// MockOrmerMockRecorder is the mock recorder for MockOrmer
type MockOrmerMockRecorder struct {
mock *MockOrmer
}
// NewMockOrmer creates a new mock instance
func NewMockOrmer(ctrl *gomock.Controller) *MockOrmer {
mock := &MockOrmer{ctrl: ctrl}
mock.recorder = &MockOrmerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockOrmer) EXPECT() *MockOrmerMockRecorder {
return m.recorder
}
// Begin mocks base method
func (m *MockOrmer) Begin() (orm.TxOrmer, error) {
ret := m.ctrl.Call(m, "Begin")
ret0, _ := ret[0].(orm.TxOrmer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Begin indicates an expected call of Begin
func (mr *MockOrmerMockRecorder) Begin() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Begin", reflect.TypeOf((*MockOrmer)(nil).Begin))
}
// BeginWithCtx mocks base method
func (m *MockOrmer) BeginWithCtx(arg0 context.Context) (orm.TxOrmer, error) {
ret := m.ctrl.Call(m, "BeginWithCtx", arg0)
ret0, _ := ret[0].(orm.TxOrmer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginWithCtx indicates an expected call of BeginWithCtx
func (mr *MockOrmerMockRecorder) BeginWithCtx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginWithCtx", reflect.TypeOf((*MockOrmer)(nil).BeginWithCtx), arg0)
}
// BeginWithCtxAndOpts mocks base method
func (m *MockOrmer) BeginWithCtxAndOpts(arg0 context.Context, arg1 *sql.TxOptions) (orm.TxOrmer, error) {
ret := m.ctrl.Call(m, "BeginWithCtxAndOpts", arg0, arg1)
ret0, _ := ret[0].(orm.TxOrmer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginWithCtxAndOpts indicates an expected call of BeginWithCtxAndOpts
func (mr *MockOrmerMockRecorder) BeginWithCtxAndOpts(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginWithCtxAndOpts", reflect.TypeOf((*MockOrmer)(nil).BeginWithCtxAndOpts), arg0, arg1)
}
// BeginWithOpts mocks base method
func (m *MockOrmer) BeginWithOpts(arg0 *sql.TxOptions) (orm.TxOrmer, error) {
ret := m.ctrl.Call(m, "BeginWithOpts", arg0)
ret0, _ := ret[0].(orm.TxOrmer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// BeginWithOpts indicates an expected call of BeginWithOpts
func (mr *MockOrmerMockRecorder) BeginWithOpts(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "BeginWithOpts", reflect.TypeOf((*MockOrmer)(nil).BeginWithOpts), arg0)
}
// DBStats mocks base method
func (m *MockOrmer) DBStats() *sql.DBStats {
ret := m.ctrl.Call(m, "DBStats")
ret0, _ := ret[0].(*sql.DBStats)
return ret0
}
// DBStats indicates an expected call of DBStats
func (mr *MockOrmerMockRecorder) DBStats() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DBStats", reflect.TypeOf((*MockOrmer)(nil).DBStats))
}
// Delete mocks base method
func (m *MockOrmer) Delete(arg0 interface{}, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Delete", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Delete indicates an expected call of Delete
func (mr *MockOrmerMockRecorder) Delete(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockOrmer)(nil).Delete), varargs...)
}
// DeleteWithCtx mocks base method
func (m *MockOrmer) DeleteWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "DeleteWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeleteWithCtx indicates an expected call of DeleteWithCtx
func (mr *MockOrmerMockRecorder) DeleteWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWithCtx", reflect.TypeOf((*MockOrmer)(nil).DeleteWithCtx), varargs...)
}
// DoTx mocks base method
func (m *MockOrmer) DoTx(arg0 func(context.Context, orm.TxOrmer) error) error {
ret := m.ctrl.Call(m, "DoTx", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// DoTx indicates an expected call of DoTx
func (mr *MockOrmerMockRecorder) DoTx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoTx", reflect.TypeOf((*MockOrmer)(nil).DoTx), arg0)
}
// DoTxWithCtx mocks base method
func (m *MockOrmer) DoTxWithCtx(arg0 context.Context, arg1 func(context.Context, orm.TxOrmer) error) error {
ret := m.ctrl.Call(m, "DoTxWithCtx", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DoTxWithCtx indicates an expected call of DoTxWithCtx
func (mr *MockOrmerMockRecorder) DoTxWithCtx(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoTxWithCtx", reflect.TypeOf((*MockOrmer)(nil).DoTxWithCtx), arg0, arg1)
}
// DoTxWithCtxAndOpts mocks base method
func (m *MockOrmer) DoTxWithCtxAndOpts(arg0 context.Context, arg1 *sql.TxOptions, arg2 func(context.Context, orm.TxOrmer) error) error {
ret := m.ctrl.Call(m, "DoTxWithCtxAndOpts", arg0, arg1, arg2)
ret0, _ := ret[0].(error)
return ret0
}
// DoTxWithCtxAndOpts indicates an expected call of DoTxWithCtxAndOpts
func (mr *MockOrmerMockRecorder) DoTxWithCtxAndOpts(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoTxWithCtxAndOpts", reflect.TypeOf((*MockOrmer)(nil).DoTxWithCtxAndOpts), arg0, arg1, arg2)
}
// DoTxWithOpts mocks base method
func (m *MockOrmer) DoTxWithOpts(arg0 *sql.TxOptions, arg1 func(context.Context, orm.TxOrmer) error) error {
ret := m.ctrl.Call(m, "DoTxWithOpts", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// DoTxWithOpts indicates an expected call of DoTxWithOpts
func (mr *MockOrmerMockRecorder) DoTxWithOpts(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DoTxWithOpts", reflect.TypeOf((*MockOrmer)(nil).DoTxWithOpts), arg0, arg1)
}
// Driver mocks base method
func (m *MockOrmer) Driver() orm.Driver {
ret := m.ctrl.Call(m, "Driver")
ret0, _ := ret[0].(orm.Driver)
return ret0
}
// Driver indicates an expected call of Driver
func (mr *MockOrmerMockRecorder) Driver() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Driver", reflect.TypeOf((*MockOrmer)(nil).Driver))
}
// Insert mocks base method
func (m *MockOrmer) Insert(arg0 interface{}) (int64, error) {
ret := m.ctrl.Call(m, "Insert", arg0)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Insert indicates an expected call of Insert
func (mr *MockOrmerMockRecorder) Insert(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Insert", reflect.TypeOf((*MockOrmer)(nil).Insert), arg0)
}
// InsertMulti mocks base method
func (m *MockOrmer) InsertMulti(arg0 int, arg1 interface{}) (int64, error) {
ret := m.ctrl.Call(m, "InsertMulti", arg0, arg1)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertMulti indicates an expected call of InsertMulti
func (mr *MockOrmerMockRecorder) InsertMulti(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertMulti", reflect.TypeOf((*MockOrmer)(nil).InsertMulti), arg0, arg1)
}
// InsertMultiWithCtx mocks base method
func (m *MockOrmer) InsertMultiWithCtx(arg0 context.Context, arg1 int, arg2 interface{}) (int64, error) {
ret := m.ctrl.Call(m, "InsertMultiWithCtx", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertMultiWithCtx indicates an expected call of InsertMultiWithCtx
func (mr *MockOrmerMockRecorder) InsertMultiWithCtx(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertMultiWithCtx", reflect.TypeOf((*MockOrmer)(nil).InsertMultiWithCtx), arg0, arg1, arg2)
}
// InsertOrUpdate mocks base method
func (m *MockOrmer) InsertOrUpdate(arg0 interface{}, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "InsertOrUpdate", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertOrUpdate indicates an expected call of InsertOrUpdate
func (mr *MockOrmerMockRecorder) InsertOrUpdate(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOrUpdate", reflect.TypeOf((*MockOrmer)(nil).InsertOrUpdate), varargs...)
}
// InsertOrUpdateWithCtx mocks base method
func (m *MockOrmer) InsertOrUpdateWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "InsertOrUpdateWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertOrUpdateWithCtx indicates an expected call of InsertOrUpdateWithCtx
func (mr *MockOrmerMockRecorder) InsertOrUpdateWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertOrUpdateWithCtx", reflect.TypeOf((*MockOrmer)(nil).InsertOrUpdateWithCtx), varargs...)
}
// InsertWithCtx mocks base method
func (m *MockOrmer) InsertWithCtx(arg0 context.Context, arg1 interface{}) (int64, error) {
ret := m.ctrl.Call(m, "InsertWithCtx", arg0, arg1)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// InsertWithCtx indicates an expected call of InsertWithCtx
func (mr *MockOrmerMockRecorder) InsertWithCtx(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "InsertWithCtx", reflect.TypeOf((*MockOrmer)(nil).InsertWithCtx), arg0, arg1)
}
// LoadRelated mocks base method
func (m *MockOrmer) LoadRelated(arg0 interface{}, arg1 string, arg2 ...utils.KV) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "LoadRelated", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadRelated indicates an expected call of LoadRelated
func (mr *MockOrmerMockRecorder) LoadRelated(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadRelated", reflect.TypeOf((*MockOrmer)(nil).LoadRelated), varargs...)
}
// LoadRelatedWithCtx mocks base method
func (m *MockOrmer) LoadRelatedWithCtx(arg0 context.Context, arg1 interface{}, arg2 string, arg3 ...utils.KV) (int64, error) {
varargs := []interface{}{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "LoadRelatedWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// LoadRelatedWithCtx indicates an expected call of LoadRelatedWithCtx
func (mr *MockOrmerMockRecorder) LoadRelatedWithCtx(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LoadRelatedWithCtx", reflect.TypeOf((*MockOrmer)(nil).LoadRelatedWithCtx), varargs...)
}
// QueryM2M mocks base method
func (m *MockOrmer) QueryM2M(arg0 interface{}, arg1 string) orm.QueryM2Mer {
ret := m.ctrl.Call(m, "QueryM2M", arg0, arg1)
ret0, _ := ret[0].(orm.QueryM2Mer)
return ret0
}
// QueryM2M indicates an expected call of QueryM2M
func (mr *MockOrmerMockRecorder) QueryM2M(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryM2M", reflect.TypeOf((*MockOrmer)(nil).QueryM2M), arg0, arg1)
}
// QueryM2MWithCtx mocks base method
func (m *MockOrmer) QueryM2MWithCtx(arg0 context.Context, arg1 interface{}, arg2 string) orm.QueryM2Mer {
ret := m.ctrl.Call(m, "QueryM2MWithCtx", arg0, arg1, arg2)
ret0, _ := ret[0].(orm.QueryM2Mer)
return ret0
}
// QueryM2MWithCtx indicates an expected call of QueryM2MWithCtx
func (mr *MockOrmerMockRecorder) QueryM2MWithCtx(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryM2MWithCtx", reflect.TypeOf((*MockOrmer)(nil).QueryM2MWithCtx), arg0, arg1, arg2)
}
// QueryTable mocks base method
func (m *MockOrmer) QueryTable(arg0 interface{}) orm.QuerySeter {
ret := m.ctrl.Call(m, "QueryTable", arg0)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// QueryTable indicates an expected call of QueryTable
func (mr *MockOrmerMockRecorder) QueryTable(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryTable", reflect.TypeOf((*MockOrmer)(nil).QueryTable), arg0)
}
// QueryTableWithCtx mocks base method
func (m *MockOrmer) QueryTableWithCtx(arg0 context.Context, arg1 interface{}) orm.QuerySeter {
ret := m.ctrl.Call(m, "QueryTableWithCtx", arg0, arg1)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// QueryTableWithCtx indicates an expected call of QueryTableWithCtx
func (mr *MockOrmerMockRecorder) QueryTableWithCtx(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryTableWithCtx", reflect.TypeOf((*MockOrmer)(nil).QueryTableWithCtx), arg0, arg1)
}
// Raw mocks base method
func (m *MockOrmer) Raw(arg0 string, arg1 ...interface{}) orm.RawSeter {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Raw", varargs...)
ret0, _ := ret[0].(orm.RawSeter)
return ret0
}
// Raw indicates an expected call of Raw
func (mr *MockOrmerMockRecorder) Raw(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Raw", reflect.TypeOf((*MockOrmer)(nil).Raw), varargs...)
}
// RawWithCtx mocks base method
func (m *MockOrmer) RawWithCtx(arg0 context.Context, arg1 string, arg2 ...interface{}) orm.RawSeter {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RawWithCtx", varargs...)
ret0, _ := ret[0].(orm.RawSeter)
return ret0
}
// RawWithCtx indicates an expected call of RawWithCtx
func (mr *MockOrmerMockRecorder) RawWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RawWithCtx", reflect.TypeOf((*MockOrmer)(nil).RawWithCtx), varargs...)
}
// Read mocks base method
func (m *MockOrmer) Read(arg0 interface{}, arg1 ...string) error {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Read", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// Read indicates an expected call of Read
func (mr *MockOrmerMockRecorder) Read(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockOrmer)(nil).Read), varargs...)
}
// ReadForUpdate mocks base method
func (m *MockOrmer) ReadForUpdate(arg0 interface{}, arg1 ...string) error {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadForUpdate", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// ReadForUpdate indicates an expected call of ReadForUpdate
func (mr *MockOrmerMockRecorder) ReadForUpdate(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadForUpdate", reflect.TypeOf((*MockOrmer)(nil).ReadForUpdate), varargs...)
}
// ReadForUpdateWithCtx mocks base method
func (m *MockOrmer) ReadForUpdateWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) error {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadForUpdateWithCtx", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// ReadForUpdateWithCtx indicates an expected call of ReadForUpdateWithCtx
func (mr *MockOrmerMockRecorder) ReadForUpdateWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadForUpdateWithCtx", reflect.TypeOf((*MockOrmer)(nil).ReadForUpdateWithCtx), varargs...)
}
// ReadOrCreate mocks base method
func (m *MockOrmer) ReadOrCreate(arg0 interface{}, arg1 string, arg2 ...string) (bool, int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadOrCreate", varargs...)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ReadOrCreate indicates an expected call of ReadOrCreate
func (mr *MockOrmerMockRecorder) ReadOrCreate(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadOrCreate", reflect.TypeOf((*MockOrmer)(nil).ReadOrCreate), varargs...)
}
// ReadOrCreateWithCtx mocks base method
func (m *MockOrmer) ReadOrCreateWithCtx(arg0 context.Context, arg1 interface{}, arg2 string, arg3 ...string) (bool, int64, error) {
varargs := []interface{}{arg0, arg1, arg2}
for _, a := range arg3 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadOrCreateWithCtx", varargs...)
ret0, _ := ret[0].(bool)
ret1, _ := ret[1].(int64)
ret2, _ := ret[2].(error)
return ret0, ret1, ret2
}
// ReadOrCreateWithCtx indicates an expected call of ReadOrCreateWithCtx
func (mr *MockOrmerMockRecorder) ReadOrCreateWithCtx(arg0, arg1, arg2 interface{}, arg3 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1, arg2}, arg3...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadOrCreateWithCtx", reflect.TypeOf((*MockOrmer)(nil).ReadOrCreateWithCtx), varargs...)
}
// ReadWithCtx mocks base method
func (m *MockOrmer) ReadWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) error {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ReadWithCtx", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// ReadWithCtx indicates an expected call of ReadWithCtx
func (mr *MockOrmerMockRecorder) ReadWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ReadWithCtx", reflect.TypeOf((*MockOrmer)(nil).ReadWithCtx), varargs...)
}
// Update mocks base method
func (m *MockOrmer) Update(arg0 interface{}, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Update", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update
func (mr *MockOrmerMockRecorder) Update(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockOrmer)(nil).Update), varargs...)
}
// UpdateWithCtx mocks base method
func (m *MockOrmer) UpdateWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UpdateWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateWithCtx indicates an expected call of UpdateWithCtx
func (mr *MockOrmerMockRecorder) UpdateWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWithCtx", reflect.TypeOf((*MockOrmer)(nil).UpdateWithCtx), varargs...)
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/beego/beego/v2/client/orm (interfaces: QuerySeter)
// Package beego is a generated GoMock package.
package beego
import (
context "context"
orm "github.com/beego/beego/v2/client/orm"
order_clause "github.com/beego/beego/v2/client/orm/clauses/order_clause"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockQuerySeter is a mock of QuerySeter interface
type MockQuerySeter struct {
ctrl *gomock.Controller
recorder *MockQuerySeterMockRecorder
}
// MockQuerySeterMockRecorder is the mock recorder for MockQuerySeter
type MockQuerySeterMockRecorder struct {
mock *MockQuerySeter
}
// NewMockQuerySeter creates a new mock instance
func NewMockQuerySeter(ctrl *gomock.Controller) *MockQuerySeter {
mock := &MockQuerySeter{ctrl: ctrl}
mock.recorder = &MockQuerySeterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockQuerySeter) EXPECT() *MockQuerySeterMockRecorder {
return m.recorder
}
// Aggregate mocks base method
func (m *MockQuerySeter) Aggregate(arg0 string) orm.QuerySeter {
ret := m.ctrl.Call(m, "Aggregate", arg0)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Aggregate indicates an expected call of Aggregate
func (mr *MockQuerySeterMockRecorder) Aggregate(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Aggregate", reflect.TypeOf((*MockQuerySeter)(nil).Aggregate), arg0)
}
// All mocks base method
func (m *MockQuerySeter) All(arg0 interface{}, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "All", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// All indicates an expected call of All
func (mr *MockQuerySeterMockRecorder) All(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "All", reflect.TypeOf((*MockQuerySeter)(nil).All), varargs...)
}
// AllWithCtx mocks base method
func (m *MockQuerySeter) AllWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "AllWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AllWithCtx indicates an expected call of AllWithCtx
func (mr *MockQuerySeterMockRecorder) AllWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AllWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).AllWithCtx), varargs...)
}
// Count mocks base method
func (m *MockQuerySeter) Count() (int64, error) {
ret := m.ctrl.Call(m, "Count")
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Count indicates an expected call of Count
func (mr *MockQuerySeterMockRecorder) Count() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Count", reflect.TypeOf((*MockQuerySeter)(nil).Count))
}
// CountWithCtx mocks base method
func (m *MockQuerySeter) CountWithCtx(arg0 context.Context) (int64, error) {
ret := m.ctrl.Call(m, "CountWithCtx", arg0)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// CountWithCtx indicates an expected call of CountWithCtx
func (mr *MockQuerySeterMockRecorder) CountWithCtx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CountWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).CountWithCtx), arg0)
}
// Delete mocks base method
func (m *MockQuerySeter) Delete() (int64, error) {
ret := m.ctrl.Call(m, "Delete")
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Delete indicates an expected call of Delete
func (mr *MockQuerySeterMockRecorder) Delete() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Delete", reflect.TypeOf((*MockQuerySeter)(nil).Delete))
}
// DeleteWithCtx mocks base method
func (m *MockQuerySeter) DeleteWithCtx(arg0 context.Context) (int64, error) {
ret := m.ctrl.Call(m, "DeleteWithCtx", arg0)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// DeleteWithCtx indicates an expected call of DeleteWithCtx
func (mr *MockQuerySeterMockRecorder) DeleteWithCtx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "DeleteWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).DeleteWithCtx), arg0)
}
// Distinct mocks base method
func (m *MockQuerySeter) Distinct() orm.QuerySeter {
ret := m.ctrl.Call(m, "Distinct")
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Distinct indicates an expected call of Distinct
func (mr *MockQuerySeterMockRecorder) Distinct() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Distinct", reflect.TypeOf((*MockQuerySeter)(nil).Distinct))
}
// Exclude mocks base method
func (m *MockQuerySeter) Exclude(arg0 string, arg1 ...interface{}) orm.QuerySeter {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Exclude", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Exclude indicates an expected call of Exclude
func (mr *MockQuerySeterMockRecorder) Exclude(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exclude", reflect.TypeOf((*MockQuerySeter)(nil).Exclude), varargs...)
}
// Exist mocks base method
func (m *MockQuerySeter) Exist() bool {
ret := m.ctrl.Call(m, "Exist")
ret0, _ := ret[0].(bool)
return ret0
}
// Exist indicates an expected call of Exist
func (mr *MockQuerySeterMockRecorder) Exist() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exist", reflect.TypeOf((*MockQuerySeter)(nil).Exist))
}
// ExistWithCtx mocks base method
func (m *MockQuerySeter) ExistWithCtx(arg0 context.Context) bool {
ret := m.ctrl.Call(m, "ExistWithCtx", arg0)
ret0, _ := ret[0].(bool)
return ret0
}
// ExistWithCtx indicates an expected call of ExistWithCtx
func (mr *MockQuerySeterMockRecorder) ExistWithCtx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ExistWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).ExistWithCtx), arg0)
}
// Filter mocks base method
func (m *MockQuerySeter) Filter(arg0 string, arg1 ...interface{}) orm.QuerySeter {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Filter", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Filter indicates an expected call of Filter
func (mr *MockQuerySeterMockRecorder) Filter(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Filter", reflect.TypeOf((*MockQuerySeter)(nil).Filter), varargs...)
}
// FilterRaw mocks base method
func (m *MockQuerySeter) FilterRaw(arg0, arg1 string) orm.QuerySeter {
ret := m.ctrl.Call(m, "FilterRaw", arg0, arg1)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// FilterRaw indicates an expected call of FilterRaw
func (mr *MockQuerySeterMockRecorder) FilterRaw(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "FilterRaw", reflect.TypeOf((*MockQuerySeter)(nil).FilterRaw), arg0, arg1)
}
// ForUpdate mocks base method
func (m *MockQuerySeter) ForUpdate() orm.QuerySeter {
ret := m.ctrl.Call(m, "ForUpdate")
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// ForUpdate indicates an expected call of ForUpdate
func (mr *MockQuerySeterMockRecorder) ForUpdate() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForUpdate", reflect.TypeOf((*MockQuerySeter)(nil).ForUpdate))
}
// ForceIndex mocks base method
func (m *MockQuerySeter) ForceIndex(arg0 ...string) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ForceIndex", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// ForceIndex indicates an expected call of ForceIndex
func (mr *MockQuerySeterMockRecorder) ForceIndex(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ForceIndex", reflect.TypeOf((*MockQuerySeter)(nil).ForceIndex), arg0...)
}
// GetCond mocks base method
func (m *MockQuerySeter) GetCond() *orm.Condition {
ret := m.ctrl.Call(m, "GetCond")
ret0, _ := ret[0].(*orm.Condition)
return ret0
}
// GetCond indicates an expected call of GetCond
func (mr *MockQuerySeterMockRecorder) GetCond() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GetCond", reflect.TypeOf((*MockQuerySeter)(nil).GetCond))
}
// GroupBy mocks base method
func (m *MockQuerySeter) GroupBy(arg0 ...string) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "GroupBy", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// GroupBy indicates an expected call of GroupBy
func (mr *MockQuerySeterMockRecorder) GroupBy(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "GroupBy", reflect.TypeOf((*MockQuerySeter)(nil).GroupBy), arg0...)
}
// IgnoreIndex mocks base method
func (m *MockQuerySeter) IgnoreIndex(arg0 ...string) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "IgnoreIndex", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// IgnoreIndex indicates an expected call of IgnoreIndex
func (mr *MockQuerySeterMockRecorder) IgnoreIndex(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "IgnoreIndex", reflect.TypeOf((*MockQuerySeter)(nil).IgnoreIndex), arg0...)
}
// Limit mocks base method
func (m *MockQuerySeter) Limit(arg0 interface{}, arg1 ...interface{}) orm.QuerySeter {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Limit", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Limit indicates an expected call of Limit
func (mr *MockQuerySeterMockRecorder) Limit(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Limit", reflect.TypeOf((*MockQuerySeter)(nil).Limit), varargs...)
}
// Offset mocks base method
func (m *MockQuerySeter) Offset(arg0 interface{}) orm.QuerySeter {
ret := m.ctrl.Call(m, "Offset", arg0)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// Offset indicates an expected call of Offset
func (mr *MockQuerySeterMockRecorder) Offset(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Offset", reflect.TypeOf((*MockQuerySeter)(nil).Offset), arg0)
}
// One mocks base method
func (m *MockQuerySeter) One(arg0 interface{}, arg1 ...string) error {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "One", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// One indicates an expected call of One
func (mr *MockQuerySeterMockRecorder) One(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "One", reflect.TypeOf((*MockQuerySeter)(nil).One), varargs...)
}
// OneWithCtx mocks base method
func (m *MockQuerySeter) OneWithCtx(arg0 context.Context, arg1 interface{}, arg2 ...string) error {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "OneWithCtx", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// OneWithCtx indicates an expected call of OneWithCtx
func (mr *MockQuerySeterMockRecorder) OneWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OneWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).OneWithCtx), varargs...)
}
// OrderBy mocks base method
func (m *MockQuerySeter) OrderBy(arg0 ...string) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "OrderBy", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// OrderBy indicates an expected call of OrderBy
func (mr *MockQuerySeterMockRecorder) OrderBy(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrderBy", reflect.TypeOf((*MockQuerySeter)(nil).OrderBy), arg0...)
}
// OrderClauses mocks base method
func (m *MockQuerySeter) OrderClauses(arg0 ...*order_clause.Order) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "OrderClauses", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// OrderClauses indicates an expected call of OrderClauses
func (mr *MockQuerySeterMockRecorder) OrderClauses(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OrderClauses", reflect.TypeOf((*MockQuerySeter)(nil).OrderClauses), arg0...)
}
// PrepareInsert mocks base method
func (m *MockQuerySeter) PrepareInsert() (orm.Inserter, error) {
ret := m.ctrl.Call(m, "PrepareInsert")
ret0, _ := ret[0].(orm.Inserter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PrepareInsert indicates an expected call of PrepareInsert
func (mr *MockQuerySeterMockRecorder) PrepareInsert() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareInsert", reflect.TypeOf((*MockQuerySeter)(nil).PrepareInsert))
}
// PrepareInsertWithCtx mocks base method
func (m *MockQuerySeter) PrepareInsertWithCtx(arg0 context.Context) (orm.Inserter, error) {
ret := m.ctrl.Call(m, "PrepareInsertWithCtx", arg0)
ret0, _ := ret[0].(orm.Inserter)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// PrepareInsertWithCtx indicates an expected call of PrepareInsertWithCtx
func (mr *MockQuerySeterMockRecorder) PrepareInsertWithCtx(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "PrepareInsertWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).PrepareInsertWithCtx), arg0)
}
// RelatedSel mocks base method
func (m *MockQuerySeter) RelatedSel(arg0 ...interface{}) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "RelatedSel", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// RelatedSel indicates an expected call of RelatedSel
func (mr *MockQuerySeterMockRecorder) RelatedSel(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RelatedSel", reflect.TypeOf((*MockQuerySeter)(nil).RelatedSel), arg0...)
}
// RowsToMap mocks base method
func (m *MockQuerySeter) RowsToMap(arg0 *orm.Params, arg1, arg2 string) (int64, error) {
ret := m.ctrl.Call(m, "RowsToMap", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RowsToMap indicates an expected call of RowsToMap
func (mr *MockQuerySeterMockRecorder) RowsToMap(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RowsToMap", reflect.TypeOf((*MockQuerySeter)(nil).RowsToMap), arg0, arg1, arg2)
}
// RowsToStruct mocks base method
func (m *MockQuerySeter) RowsToStruct(arg0 interface{}, arg1, arg2 string) (int64, error) {
ret := m.ctrl.Call(m, "RowsToStruct", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RowsToStruct indicates an expected call of RowsToStruct
func (mr *MockQuerySeterMockRecorder) RowsToStruct(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RowsToStruct", reflect.TypeOf((*MockQuerySeter)(nil).RowsToStruct), arg0, arg1, arg2)
}
// SetCond mocks base method
func (m *MockQuerySeter) SetCond(arg0 *orm.Condition) orm.QuerySeter {
ret := m.ctrl.Call(m, "SetCond", arg0)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// SetCond indicates an expected call of SetCond
func (mr *MockQuerySeterMockRecorder) SetCond(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetCond", reflect.TypeOf((*MockQuerySeter)(nil).SetCond), arg0)
}
// Update mocks base method
func (m *MockQuerySeter) Update(arg0 orm.Params) (int64, error) {
ret := m.ctrl.Call(m, "Update", arg0)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Update indicates an expected call of Update
func (mr *MockQuerySeterMockRecorder) Update(arg0 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Update", reflect.TypeOf((*MockQuerySeter)(nil).Update), arg0)
}
// UpdateWithCtx mocks base method
func (m *MockQuerySeter) UpdateWithCtx(arg0 context.Context, arg1 orm.Params) (int64, error) {
ret := m.ctrl.Call(m, "UpdateWithCtx", arg0, arg1)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// UpdateWithCtx indicates an expected call of UpdateWithCtx
func (mr *MockQuerySeterMockRecorder) UpdateWithCtx(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UpdateWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).UpdateWithCtx), arg0, arg1)
}
// UseIndex mocks base method
func (m *MockQuerySeter) UseIndex(arg0 ...string) orm.QuerySeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "UseIndex", varargs...)
ret0, _ := ret[0].(orm.QuerySeter)
return ret0
}
// UseIndex indicates an expected call of UseIndex
func (mr *MockQuerySeterMockRecorder) UseIndex(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "UseIndex", reflect.TypeOf((*MockQuerySeter)(nil).UseIndex), arg0...)
}
// Values mocks base method
func (m *MockQuerySeter) Values(arg0 *[]orm.Params, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Values", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Values indicates an expected call of Values
func (mr *MockQuerySeterMockRecorder) Values(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Values", reflect.TypeOf((*MockQuerySeter)(nil).Values), varargs...)
}
// ValuesFlat mocks base method
func (m *MockQuerySeter) ValuesFlat(arg0 *orm.ParamsList, arg1 string) (int64, error) {
ret := m.ctrl.Call(m, "ValuesFlat", arg0, arg1)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesFlat indicates an expected call of ValuesFlat
func (mr *MockQuerySeterMockRecorder) ValuesFlat(arg0, arg1 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesFlat", reflect.TypeOf((*MockQuerySeter)(nil).ValuesFlat), arg0, arg1)
}
// ValuesFlatWithCtx mocks base method
func (m *MockQuerySeter) ValuesFlatWithCtx(arg0 context.Context, arg1 *orm.ParamsList, arg2 string) (int64, error) {
ret := m.ctrl.Call(m, "ValuesFlatWithCtx", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesFlatWithCtx indicates an expected call of ValuesFlatWithCtx
func (mr *MockQuerySeterMockRecorder) ValuesFlatWithCtx(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesFlatWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).ValuesFlatWithCtx), arg0, arg1, arg2)
}
// ValuesList mocks base method
func (m *MockQuerySeter) ValuesList(arg0 *[]orm.ParamsList, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValuesList", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesList indicates an expected call of ValuesList
func (mr *MockQuerySeterMockRecorder) ValuesList(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesList", reflect.TypeOf((*MockQuerySeter)(nil).ValuesList), varargs...)
}
// ValuesListWithCtx mocks base method
func (m *MockQuerySeter) ValuesListWithCtx(arg0 context.Context, arg1 *[]orm.ParamsList, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValuesListWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesListWithCtx indicates an expected call of ValuesListWithCtx
func (mr *MockQuerySeterMockRecorder) ValuesListWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesListWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).ValuesListWithCtx), varargs...)
}
// ValuesWithCtx mocks base method
func (m *MockQuerySeter) ValuesWithCtx(arg0 context.Context, arg1 *[]orm.Params, arg2 ...string) (int64, error) {
varargs := []interface{}{arg0, arg1}
for _, a := range arg2 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValuesWithCtx", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesWithCtx indicates an expected call of ValuesWithCtx
func (mr *MockQuerySeterMockRecorder) ValuesWithCtx(arg0, arg1 interface{}, arg2 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0, arg1}, arg2...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesWithCtx", reflect.TypeOf((*MockQuerySeter)(nil).ValuesWithCtx), varargs...)
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/beego/beego/v2/client/orm (interfaces: RawSeter)
// Package beego is a generated GoMock package.
package beego
import (
sql "database/sql"
orm "github.com/beego/beego/v2/client/orm"
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockRawSeter is a mock of RawSeter interface
type MockRawSeter struct {
ctrl *gomock.Controller
recorder *MockRawSeterMockRecorder
}
// MockRawSeterMockRecorder is the mock recorder for MockRawSeter
type MockRawSeterMockRecorder struct {
mock *MockRawSeter
}
// NewMockRawSeter creates a new mock instance
func NewMockRawSeter(ctrl *gomock.Controller) *MockRawSeter {
mock := &MockRawSeter{ctrl: ctrl}
mock.recorder = &MockRawSeterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockRawSeter) EXPECT() *MockRawSeterMockRecorder {
return m.recorder
}
// Exec mocks base method
func (m *MockRawSeter) Exec() (sql.Result, error) {
ret := m.ctrl.Call(m, "Exec")
ret0, _ := ret[0].(sql.Result)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Exec indicates an expected call of Exec
func (mr *MockRawSeterMockRecorder) Exec() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Exec", reflect.TypeOf((*MockRawSeter)(nil).Exec))
}
// Prepare mocks base method
func (m *MockRawSeter) Prepare() (orm.RawPreparer, error) {
ret := m.ctrl.Call(m, "Prepare")
ret0, _ := ret[0].(orm.RawPreparer)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Prepare indicates an expected call of Prepare
func (mr *MockRawSeterMockRecorder) Prepare() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Prepare", reflect.TypeOf((*MockRawSeter)(nil).Prepare))
}
// QueryRow mocks base method
func (m *MockRawSeter) QueryRow(arg0 ...interface{}) error {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "QueryRow", varargs...)
ret0, _ := ret[0].(error)
return ret0
}
// QueryRow indicates an expected call of QueryRow
func (mr *MockRawSeterMockRecorder) QueryRow(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryRow", reflect.TypeOf((*MockRawSeter)(nil).QueryRow), arg0...)
}
// QueryRows mocks base method
func (m *MockRawSeter) QueryRows(arg0 ...interface{}) (int64, error) {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "QueryRows", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// QueryRows indicates an expected call of QueryRows
func (mr *MockRawSeterMockRecorder) QueryRows(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "QueryRows", reflect.TypeOf((*MockRawSeter)(nil).QueryRows), arg0...)
}
// RowsToMap mocks base method
func (m *MockRawSeter) RowsToMap(arg0 *orm.Params, arg1, arg2 string) (int64, error) {
ret := m.ctrl.Call(m, "RowsToMap", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RowsToMap indicates an expected call of RowsToMap
func (mr *MockRawSeterMockRecorder) RowsToMap(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RowsToMap", reflect.TypeOf((*MockRawSeter)(nil).RowsToMap), arg0, arg1, arg2)
}
// RowsToStruct mocks base method
func (m *MockRawSeter) RowsToStruct(arg0 interface{}, arg1, arg2 string) (int64, error) {
ret := m.ctrl.Call(m, "RowsToStruct", arg0, arg1, arg2)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// RowsToStruct indicates an expected call of RowsToStruct
func (mr *MockRawSeterMockRecorder) RowsToStruct(arg0, arg1, arg2 interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RowsToStruct", reflect.TypeOf((*MockRawSeter)(nil).RowsToStruct), arg0, arg1, arg2)
}
// SetArgs mocks base method
func (m *MockRawSeter) SetArgs(arg0 ...interface{}) orm.RawSeter {
varargs := []interface{}{}
for _, a := range arg0 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "SetArgs", varargs...)
ret0, _ := ret[0].(orm.RawSeter)
return ret0
}
// SetArgs indicates an expected call of SetArgs
func (mr *MockRawSeterMockRecorder) SetArgs(arg0 ...interface{}) *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetArgs", reflect.TypeOf((*MockRawSeter)(nil).SetArgs), arg0...)
}
// Values mocks base method
func (m *MockRawSeter) Values(arg0 *[]orm.Params, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "Values", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Values indicates an expected call of Values
func (mr *MockRawSeterMockRecorder) Values(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Values", reflect.TypeOf((*MockRawSeter)(nil).Values), varargs...)
}
// ValuesFlat mocks base method
func (m *MockRawSeter) ValuesFlat(arg0 *orm.ParamsList, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValuesFlat", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesFlat indicates an expected call of ValuesFlat
func (mr *MockRawSeterMockRecorder) ValuesFlat(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesFlat", reflect.TypeOf((*MockRawSeter)(nil).ValuesFlat), varargs...)
}
// ValuesList mocks base method
func (m *MockRawSeter) ValuesList(arg0 *[]orm.ParamsList, arg1 ...string) (int64, error) {
varargs := []interface{}{arg0}
for _, a := range arg1 {
varargs = append(varargs, a)
}
ret := m.ctrl.Call(m, "ValuesList", varargs...)
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// ValuesList indicates an expected call of ValuesList
func (mr *MockRawSeterMockRecorder) ValuesList(arg0 interface{}, arg1 ...interface{}) *gomock.Call {
varargs := append([]interface{}{arg0}, arg1...)
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ValuesList", reflect.TypeOf((*MockRawSeter)(nil).ValuesList), varargs...)
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
Source: database/sql/sql.go (interfaces: Result)
Package beego is a generated GoMock package.
*/
package beego
import (
"reflect"
"github.com/golang/mock/gomock"
)
// MockSQLResult is a mock of SQL Result
type MockSQLResult struct {
ctrl *gomock.Controller
recorder *MockSQLResultMockRecorder
}
// MockSQLResultMockRecorder is the mock recorder for MockSQLResult
type MockSQLResultMockRecorder struct {
mock *MockSQLResult
}
// NewMockDriverRes creates a new mock instance
func NewMockDriverRes(ctrl *gomock.Controller) *MockSQLResult {
mock := &MockSQLResult{ctrl: ctrl}
mock.recorder = &MockSQLResultMockRecorder{mock: mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSQLResult) EXPECT() *MockSQLResultMockRecorder {
return m.recorder
}
// RowsAffected indicates an expected call of RowsAffected
func (mr *MockSQLResultMockRecorder) RowsAffected() *gomock.Call {
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RowsAffected", reflect.TypeOf((*MockSQLResult)(nil).RowsAffected))
}
// LastInsertId mocks base LastInsertId method
func (m *MockSQLResult) LastInsertId() (int64, error) {
return 1, nil
}
// RowsAffected mocks base RowsAffected method
func (m *MockSQLResult) RowsAffected() (int64, error) {
ret := m.ctrl.Call(m, "RowsAffected")
ret0, _ := ret[0].(int64)
ret1, _ := ret[1].(error)
return ret0, ret1
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package cloudconnection
import (
"errors"
"sync"
)
// constants for cloud connection
const (
CloudConnected = "cloud_connected"
CloudDisconnected = "cloud_disconnected"
)
var (
// isCloudConnected indicate Whether the connection was
// successfully established between edge and cloud
isCloudConnected = false
lock sync.RWMutex
// ErrConnectionLost is sentinel err to indicate connection is lost between EdgeCore and CloudCore
ErrConnectionLost = errors.New("connection lost between EdgeCore and CloudCore")
)
// SetConnected set isCloudConnected value
// true indicates edge and cloud establish connection successfully
// false indicates edge and cloud connection interrupted.
func SetConnected(isConnected bool) {
lock.Lock()
defer lock.Unlock()
isCloudConnected = isConnected
}
// IsConnected return isCloudConnected
func IsConnected() bool {
lock.RLock()
defer lock.RUnlock()
return isCloudConnected
}
package dbm
import (
"sync"
"github.com/beego/beego/v2/client/orm"
_ "github.com/mattn/go-sqlite3"
"k8s.io/klog/v2"
)
// DBAccess is Ormer object interface for all transaction processing and switching database
var DBAccess orm.Ormer
var once sync.Once
// InitDBConfig Init DB info
func InitDBConfig(driverName, dbName, dataSource string) {
once.Do(func() {
if err := orm.RegisterDriver(driverName, orm.DRSqlite); err != nil {
klog.Exitf("Failed to register driver: %v", err)
}
if err := orm.RegisterDataBase(
dbName,
driverName,
dataSource); err != nil {
klog.Exitf("Failed to register db: %v", err)
}
// sync database schema
if err := orm.RunSyncdb(dbName, false, true); err != nil {
klog.Errorf("run sync db error %v", err)
}
defer func() {
if err := recover(); err != nil {
klog.Errorf("Using db access error as %v", err)
}
}()
// create orm
DBAccess = orm.NewOrmUsingDB(dbName)
klog.Infof("!!!!!!!!!!in db.go, DBAccess = %v", DBAccess)
})
}
type newOrmerFunc func() orm.Ormer
var DefaultOrmFunc newOrmerFunc = newOrmer
func newOrmer() orm.Ormer {
return orm.NewOrm()
}
// RollbackTransaction rollback transaction and log err if rollback fail
func RollbackTransaction(to orm.TxOrmer) {
err := to.Rollback()
if err != nil {
klog.Errorf("failed to rollback transaction, err: %v", err)
}
}
package message
import (
"fmt"
"strings"
"github.com/kubeedge/beehive/pkg/core/model"
)
// constant defining node connection types
const (
ResourceTypeNodeConnection = "node/connection"
SourceNodeConnection = "edgehub"
OperationNodeConnection = "connect"
OperationSubscribe = "subscribe"
OperationUnsubscribe = "unsubscribe"
OperationMessage = "message"
OperationPublish = "publish"
OperationGetResult = "get_result"
OperationResponse = "response"
OperationKeepalive = "keepalive"
OperationStart = "start"
OperationStop = "stop"
ResourceGroupName = "resource"
FuncGroupName = "func"
UserGroupName = "user"
)
// BuildMsg returns message object with router and content details
func BuildMsg(group, parentID, sourceName, resource, operation string, content interface{}) *model.Message {
msg := model.NewMessage(parentID).BuildRouter(sourceName, group, resource, operation).FillBody(content)
return msg
}
// ParseResourceEdge parses resource at edge and returns namespace, resource_type, resource_id.
// If operation of msg is query list, return namespace, pod.
func ParseResourceEdge(resource string, operation string) (string, string, string, error) {
resourceSplits := strings.Split(resource, "/")
if len(resourceSplits) == 3 {
return resourceSplits[0], resourceSplits[1], resourceSplits[2], nil
} else if operation == model.QueryOperation || operation == model.ResponseOperation && len(resourceSplits) == 2 {
return resourceSplits[0], resourceSplits[1], "", nil
}
return "", "", "", fmt.Errorf("resource: %s format incorrect, or Operation: %s is not query/response", resource, operation)
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package message
import (
"fmt"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/common/types"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
taskmsg "github.com/kubeedge/kubeedge/pkg/nodetask/message"
)
// ReportTaskResult reports the status of node tasks, only for v1alpha2 version.
// Deprecated: It will be removed when v1alpha1 is no longer supported.
func ReportTaskResult(taskType, taskID string, resp types.NodeTaskResponse) {
msg := model.NewMessage("").SetRoute(modules.EdgeHubModuleName, modules.HubGroup).
SetResourceOperation(fmt.Sprintf("task/%s/node/%s", taskID, resp.NodeName), taskType).FillBody(resp)
beehiveContext.Send(modules.EdgeHubModuleName, *msg)
}
// ReportNodeTaskStatus reports the status of node tasks, used in v1alpha2 and later versions.
// The message will be send to the cloud from the edge.
func ReportNodeTaskStatus(res taskmsg.Resource, msgbody taskmsg.UpstreamMessage) {
msg := model.NewMessage("").SetRoute(modules.EdgeHubModuleName, modules.HubGroup).
SetResourceOperation(res.String(), taskmsg.OperationUpdateNodeActionStatus).
FillBody(msgbody)
beehiveContext.Send(modules.EdgeHubModuleName, *msg)
}
package config
import (
"sync"
"github.com/kubeedge/api/apis/componentconfig/edgecore/v1alpha2"
)
var config Configure
var once sync.Once
type Configure struct {
v1alpha2.DeviceTwin
NodeName string
}
func InitConfigure(deviceTwin *v1alpha2.DeviceTwin, nodeName string) {
once.Do(func() {
config = Configure{
DeviceTwin: *deviceTwin,
NodeName: nodeName,
}
})
}
func Get() *Configure {
return &config
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dmiclient
import (
"fmt"
"net"
"sync"
"time"
"golang.org/x/net/context"
"google.golang.org/grpc"
"k8s.io/klog/v2"
"github.com/kubeedge/api/apis/devices/v1beta1"
dmiapi "github.com/kubeedge/api/apis/dmi/v1beta1"
deviceconst "github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/constants"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
)
type DMIClient struct {
protocol string
socket string
Client dmiapi.DeviceMapperServiceClient
Ctx context.Context
Conn *grpc.ClientConn
CancelFunc context.CancelFunc
}
type DMIClients struct {
mutex sync.Mutex
clients map[string]*DMIClient
}
var DMIClientsImp *DMIClients
func init() {
DMIClientsImp = &DMIClients{
mutex: sync.Mutex{},
clients: make(map[string]*DMIClient),
}
}
func (dc *DMIClient) connect() error {
dialer := func(addr string, t time.Duration) (net.Conn, error) {
return net.Dial(deviceconst.UnixNetworkType, addr)
}
conn, err := grpc.Dial(dc.socket, grpc.WithInsecure(), grpc.WithDialer(dialer))
if err != nil {
klog.Errorf("did not connect: %v\n", err)
return err
}
c := dmiapi.NewMapperClient(conn)
ctx, cancel := context.WithTimeout(context.Background(), 10*time.Second)
dc.Client = c
dc.Ctx = ctx
dc.Conn = conn
dc.CancelFunc = cancel
return nil
}
func (dc *DMIClient) close() {
dc.Conn.Close()
dc.CancelFunc()
}
func createDeviceRequest(device *v1beta1.Device) (*dmiapi.RegisterDeviceRequest, error) {
d, err := dtcommon.ConvertDevice(device)
if err != nil {
return nil, err
}
return &dmiapi.RegisterDeviceRequest{
Device: d,
}, nil
}
func removeDeviceRequest(device *v1beta1.Device) (*dmiapi.RemoveDeviceRequest, error) {
return &dmiapi.RemoveDeviceRequest{
DeviceName: device.Name,
DeviceNamespace: device.Namespace,
}, nil
}
func updateDeviceRequest(device *v1beta1.Device) (*dmiapi.UpdateDeviceRequest, error) {
d, err := dtcommon.ConvertDevice(device)
if err != nil {
return nil, err
}
return &dmiapi.UpdateDeviceRequest{
Device: d,
}, nil
}
func createDeviceModelRequest(model *v1beta1.DeviceModel) (*dmiapi.CreateDeviceModelRequest, error) {
m, err := dtcommon.ConvertDeviceModel(model)
if err != nil {
return nil, err
}
return &dmiapi.CreateDeviceModelRequest{
Model: m,
}, nil
}
func updateDeviceModelRequest(model *v1beta1.DeviceModel) (*dmiapi.UpdateDeviceModelRequest, error) {
m, err := dtcommon.ConvertDeviceModel(model)
if err != nil {
return nil, err
}
return &dmiapi.UpdateDeviceModelRequest{
Model: m,
}, nil
}
func removeDeviceModelRequest(deviceModel *v1beta1.DeviceModel) (*dmiapi.RemoveDeviceModelRequest, error) {
return &dmiapi.RemoveDeviceModelRequest{
ModelName: deviceModel.Name,
ModelNamespace: deviceModel.Namespace,
}, nil
}
func (dcs *DMIClients) getDMIClientByProtocol(protocol string) (*DMIClient, error) {
dcs.mutex.Lock()
defer dcs.mutex.Unlock()
dc, ok := dcs.clients[protocol]
if !ok {
return nil, fmt.Errorf("fail to get dmi client of protocol %s", protocol)
}
return dc, nil
}
func (dcs *DMIClients) CreateDMIClient(protocol, sockPath string) {
dc, err := dcs.getDMIClientByProtocol(protocol)
if err == nil {
dcs.mutex.Lock()
dc.protocol = protocol
dc.socket = sockPath
dcs.mutex.Unlock()
return
}
dcs.mutex.Lock()
dcs.clients[protocol] = &DMIClient{
protocol: protocol,
socket: sockPath,
}
dcs.mutex.Unlock()
}
func (dcs *DMIClients) getDMIClientConn(protocol string) (*DMIClient, error) {
dc, err := dcs.getDMIClientByProtocol(protocol)
if err != nil {
return nil, err
}
err = dc.connect()
if err != nil {
return nil, err
}
return dc, nil
}
func (dcs *DMIClients) RegisterDevice(device *v1beta1.Device) error {
protocol := device.Spec.Protocol.ProtocolName
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
cdr, err := createDeviceRequest(device)
if err != nil {
return fmt.Errorf("fail to create createDeviceRequest for device %s with err: %v", device.Name, err)
}
_, err = dc.Client.RegisterDevice(dc.Ctx, cdr)
if err != nil {
return err
}
return nil
}
func (dcs *DMIClients) RemoveDevice(device *v1beta1.Device) error {
protocol := device.Spec.Protocol.ProtocolName
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
rdr, err := removeDeviceRequest(device)
if err != nil {
return fmt.Errorf("fail to generate RemoveDeviceRequest for device %s with err: %v", device.Name, err)
}
_, err = dc.Client.RemoveDevice(dc.Ctx, rdr)
if err != nil {
return err
}
return nil
}
func (dcs *DMIClients) UpdateDevice(device *v1beta1.Device) error {
protocol := device.Spec.Protocol.ProtocolName
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
udr, err := updateDeviceRequest(device)
if err != nil {
return fmt.Errorf("fail to generate UpdateDeviceRequest for device %s with err: %v", device.Name, err)
}
_, err = dc.Client.UpdateDevice(dc.Ctx, udr)
if err != nil {
return err
}
return nil
}
func (dcs *DMIClients) CreateDeviceModel(model *v1beta1.DeviceModel) error {
protocol := model.Spec.Protocol
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
cdmr, err := createDeviceModelRequest(model)
if err != nil {
return fmt.Errorf("fail to create RegisterDeviceModelRequest for device model %s with err: %v", model.Name, err)
}
_, err = dc.Client.CreateDeviceModel(dc.Ctx, cdmr)
if err != nil {
return err
}
return nil
}
func (dcs *DMIClients) RemoveDeviceModel(model *v1beta1.DeviceModel) error {
protocol := model.Spec.Protocol
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
rdmr, err := removeDeviceModelRequest(model)
if err != nil {
return fmt.Errorf("fail to create RemoveDeviceModelRequest for device model %s with err: %v", model.Name, err)
}
_, err = dc.Client.RemoveDeviceModel(dc.Ctx, rdmr)
if err != nil {
return err
}
return nil
}
func (dcs *DMIClients) UpdateDeviceModel(model *v1beta1.DeviceModel) error {
protocol := model.Spec.Protocol
dc, err := dcs.getDMIClientConn(protocol)
if err != nil {
return err
}
defer dc.close()
udmr, err := updateDeviceModelRequest(model)
if err != nil {
return fmt.Errorf("fail to create UpdateDeviceModelRequest for device model %s with err: %v", model.Name, err)
}
_, err = dc.Client.UpdateDeviceModel(dc.Ctx, udmr)
if err != nil {
return err
}
return nil
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dmiserver
import (
"time"
"github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/types"
)
// DeviceTwinUpdate the structure of device twin update.
type DeviceTwinUpdate struct {
types.BaseMessage
Twin map[string]*types.MsgTwin `json:"twin"`
}
// DeviceStateUpdate the structure of device state update.
type DeviceStateUpdate struct {
types.BaseMessage
State string
}
// getTimestamp get current timestamp.
func getTimestamp() int64 {
return time.Now().UnixNano() / 1e6
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dmiserver
import (
"encoding/base64"
"encoding/json"
"errors"
"fmt"
"net"
"sync"
"time"
"golang.org/x/net/context"
"golang.org/x/time/rate"
"google.golang.org/grpc"
"google.golang.org/grpc/reflection"
"k8s.io/klog/v2"
"github.com/kubeedge/api/apis/devices/v1beta1"
pb "github.com/kubeedge/api/apis/dmi/v1beta1"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
beehiveModel "github.com/kubeedge/beehive/pkg/core/model"
deviceconst "github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/constants"
"github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/types"
"github.com/kubeedge/kubeedge/common/constants"
messagepkg "github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
deviceconfig "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/config"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dmiclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
"github.com/kubeedge/kubeedge/pkg/util"
)
const (
Limit = 1000
Burst = 100
)
type server struct {
pb.UnimplementedDeviceManagerServiceServer
limiter *rate.Limiter
dmiCache *DMICache
}
type DMICache struct {
MapperMu *sync.Mutex
DeviceMu *sync.Mutex
DeviceModelMu *sync.Mutex
MapperList map[string]*pb.MapperInfo
DeviceModelList map[string]*v1beta1.DeviceModel
DeviceList map[string]*v1beta1.Device
}
func (s *server) MapperRegister(_ context.Context, in *pb.MapperRegisterRequest) (*pb.MapperRegisterResponse, error) {
if !s.limiter.Allow() {
return nil, fmt.Errorf("fail to register mapper because of too many request: %s", in.Mapper.Name)
}
if in.Mapper.Protocol == "" {
klog.Errorf("fail to register mapper %s because the protocol is nil", in.Mapper.Name)
return nil, fmt.Errorf("fail to register mapper %s because the protocol is nil", in.Mapper.Name)
}
klog.V(4).Infof("receive mapper register: %+v", in.Mapper)
err := saveMapper(in.Mapper)
if err != nil {
klog.Errorf("fail to save mapper %s to db with err: %v", in.Mapper.Name, err)
return nil, err
}
s.dmiCache.MapperMu.Lock()
s.dmiCache.MapperList[in.Mapper.Name] = in.Mapper
s.dmiCache.MapperMu.Unlock()
if !in.WithData {
return &pb.MapperRegisterResponse{}, nil
}
var deviceList []*pb.Device
var deviceModelList []*pb.DeviceModel
s.dmiCache.DeviceMu.Lock()
for _, device := range s.dmiCache.DeviceList {
if device.Spec.Protocol.ProtocolName == in.Mapper.Protocol {
dev, err := dtcommon.ConvertDevice(device)
if err != nil {
klog.Errorf("fail to convert device %s with err: %v", device.Name, err)
continue
}
modelID := util.GetResourceID(device.Namespace, device.Spec.DeviceModelRef.Name)
s.dmiCache.DeviceModelMu.Lock()
model, ok := s.dmiCache.DeviceModelList[modelID]
s.dmiCache.DeviceModelMu.Unlock()
if !ok {
klog.Errorf("fail to get device model %s in deviceModelList", device.Spec.DeviceModelRef.Name)
continue
}
dm, err := dtcommon.ConvertDeviceModel(model)
if err != nil {
klog.Errorf("fail to convert device model %s with err: %v", device.Spec.DeviceModelRef.Name, err)
continue
}
deviceList = append(deviceList, dev)
deviceModelList = append(deviceModelList, dm)
}
}
s.dmiCache.DeviceMu.Unlock()
dmiclient.DMIClientsImp.CreateDMIClient(in.Mapper.Protocol, string(in.Mapper.Address))
return &pb.MapperRegisterResponse{
DeviceList: deviceList,
ModelList: deviceModelList,
}, nil
}
func (s *server) ReportDeviceStatus(_ context.Context, in *pb.ReportDeviceStatusRequest) (*pb.
ReportDeviceStatusResponse, error) {
if !s.limiter.Allow() {
return nil, fmt.Errorf("fail to report device status because of too many request: %s", in.DeviceName)
}
if in != nil && in.ReportedDevice != nil && in.ReportedDevice.Twins != nil {
for _, twin := range in.ReportedDevice.Twins {
msg, err := CreateMessageTwinUpdate(twin)
if err != nil {
klog.Errorf("fail to create message data for property %s of device %s with err: %v", twin.PropertyName, in.DeviceName, err)
return nil, err
}
handleDeviceTwin(in, msg)
}
} else {
return &pb.ReportDeviceStatusResponse{}, errors.New("ReportDeviceStatusRequest does not have twin data")
}
return &pb.ReportDeviceStatusResponse{}, nil
}
func (s *server) ReportDeviceStates(_ context.Context, in *pb.ReportDeviceStatesRequest) (*pb.
ReportDeviceStatesResponse, error) {
if !s.limiter.Allow() {
return nil, fmt.Errorf("fail to report device states because of too many request: %s", in.DeviceName)
}
if in != nil && in.State != "" && in.DeviceName != "" {
msg, err := CreateMessageStateUpdate(in)
if err != nil {
klog.Errorf("fail to create state message data of device %s with err: %v", in.DeviceName, err)
return nil, err
}
handleDeviceState(in, msg)
} else {
return &pb.ReportDeviceStatesResponse{}, fmt.Errorf("ReportDeviceStatesRequest is invalid data")
}
return &pb.ReportDeviceStatesResponse{}, nil
}
func handleDeviceTwin(in *pb.ReportDeviceStatusRequest, payload []byte) {
deviceID := util.GetResourceID(in.DeviceNamespace, in.DeviceName)
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.TwinETUpdateSuffix
target := modules.TwinGroup
resource := base64.URLEncoding.EncodeToString([]byte(topic))
// routing key will be $hw.<project_id>.events.user.bus.response.cluster.<cluster_id>.node.<node_id>.<base64_topic>
message := beehiveModel.NewMessage("").BuildRouter(modules.BusGroup, modules.UserGroup,
resource, messagepkg.OperationResponse).FillBody(string(payload))
beehiveContext.SendToGroup(target, *message)
}
func handleDeviceState(in *pb.ReportDeviceStatesRequest, payload []byte) {
deviceID := util.GetResourceID(in.DeviceNamespace, in.DeviceName)
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.DeviceETStateUpdateSuffix
target := modules.TwinGroup
resource := base64.URLEncoding.EncodeToString([]byte(topic))
// routing key will be $hw.<project_id>.events.user.bus.response.cluster.<cluster_id>.node.<node_id>.<base64_topic>
message := beehiveModel.NewMessage("").BuildRouter(modules.BusGroup, modules.UserGroup,
resource, messagepkg.OperationResponse).FillBody(string(payload))
beehiveContext.SendToGroup(target, *message)
}
// CreateMessageTwinUpdate create twin update message.
func CreateMessageTwinUpdate(twin *pb.Twin) ([]byte, error) {
var updateMsg DeviceTwinUpdate
updateMsg.BaseMessage.Timestamp = getTimestamp()
updateMsg.Twin = map[string]*types.MsgTwin{}
updateMsg.Twin[twin.PropertyName] = &types.MsgTwin{}
updateMsg.Twin[twin.PropertyName].Expected = &types.TwinValue{Value: &twin.ObservedDesired.Value}
updateMsg.Twin[twin.PropertyName].Actual = &types.TwinValue{Value: &twin.Reported.Value}
msg, err := json.Marshal(updateMsg)
return msg, err
}
// CreateMessageStateUpdate create state update message.
func CreateMessageStateUpdate(in *pb.ReportDeviceStatesRequest) ([]byte, error) {
var stateMsg DeviceStateUpdate
stateMsg.BaseMessage.Timestamp = getTimestamp()
stateMsg.State = in.State
msg, err := json.Marshal(stateMsg)
return msg, err
}
func StartDMIServer(cache *DMICache) {
var DMISockPath string
if deviceconfig.Get().DeviceTwin.DMISockPath != "" {
DMISockPath = deviceconfig.Get().DeviceTwin.DMISockPath
} else {
DMISockPath = SockPath
}
err := initSock(DMISockPath)
if err != nil {
klog.Fatalf("failed to remove uds socket with err: %v", err)
return
}
lis, err := net.Listen(deviceconst.UnixNetworkType, DMISockPath)
if err != nil {
klog.Errorf("failed to start DMI Server with err: %v", err)
return
}
limiter := rate.NewLimiter(rate.Every(Limit*time.Millisecond), Burst)
s := grpc.NewServer()
pb.RegisterDeviceManagerServiceServer(s, &server{
limiter: limiter,
dmiCache: cache,
})
reflection.Register(s)
if err := s.Serve(lis); err != nil {
klog.Errorf("failed to start DMI Server with err: %v", err)
return
}
klog.Infoln("success to start DMI Server")
}
func saveMapper(mapper *pb.MapperInfo) error {
content, err := json.Marshal(mapper)
if err != nil {
klog.Errorf("marshal mapper info failed, %s: %v", mapper.Name, err)
return err
}
resource := fmt.Sprintf("%s%s%s%s%s%s%s%s%s", "node", constants.ResourceSep, "nodeID",
constants.ResourceSep, "namespace", constants.ResourceSep, deviceconst.ResourceTypeDeviceMapper, constants.ResourceSep, mapper.Name)
meta := &dao.Meta{
Key: resource,
Type: deviceconst.ResourceTypeDeviceMapper,
Value: string(content)}
err = dao.SaveMeta(meta)
if err != nil {
klog.Errorf("save meta failed, %s: %v", mapper.Name, err)
return err
}
klog.Infof("success to save mapper info of %s to db", mapper.Name)
return nil
}
//go:build !windows
package dmiserver
import (
"fmt"
"os"
"k8s.io/klog/v2"
)
func initSock(sockPath string) error {
klog.Infof("init uds socket: %s", sockPath)
_, err := os.Stat(sockPath)
if err == nil {
err = os.Remove(sockPath)
if err != nil {
return err
}
return nil
} else if os.IsNotExist(err) {
return nil
} else {
return fmt.Errorf("fail to stat uds socket path")
}
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dtclient
import (
"errors"
"testing"
"github.com/beego/beego/v2/client/orm"
"github.com/kubeedge/kubeedge/edge/mocks/beego"
"github.com/kubeedge/kubeedge/pkg/testtools"
)
// errFailedDBOperation is common DB operation fail error
var errFailedDBOperation = errors.New("Failed DB Operation")
// CasesSaveStr is a struct for cases of save
type CasesSaveStr []struct {
// name is name of the testcase
name string
// doTXReturnErr is return of mock interface ormerMock's DoTX function
doTXReturnErr error
}
// CasesDeleteStr is a struct for cases of delete
type CasesDeleteStr []struct {
// name is name of the testcase
name string
// filterReturn is the return of mock interface querySeterMock's filter function
filterReturn orm.QuerySeter
// deleteReturnInt is the first return of mock interface querySeterMock's delete function
deleteReturnInt int64
// deleteReturnErr is the second return of mock interface querySeterMocks's delete function also expected error
deleteReturnErr error
// queryTableReturn is the return of mock interface ormerMock's QueryTable function
queryTableReturn orm.QuerySeter
// doTXReturnErr is return of mock interface ormerMock's DoTX function
doTXReturnErr error
}
// CasesUpdateStr is a struct for cases of update
type CasesUpdateStr []struct {
// name is name of the testcase
name string
// filterReturn is the return of mock interface querySeterMock's filter function
filterReturn orm.QuerySeter
// updateReturnInt is the first return of mock interface querySeterMock's update function
updateReturnInt int64
// updateReturnErr is the second return of mock interface querySeterMock's update function also expected error
updateReturnErr error
// queryTableReturn is the return of mock interface ormerMock's QueryTable function
queryTableReturn orm.QuerySeter
}
// CasesQueryStr is a struct for cases of query
type CasesQueryStr []struct {
// name is name of the testcase
name string
// filterReturn is the return of mock interface querySeterMock's filter function
filterReturn orm.QuerySeter
// allReturnInt is the first return of mock interface querySeterMock's all function
allReturnInt int64
// allReturnErr is the second return of mock interface querySeterMocks's all function also expected error
allReturnErr error
// queryTableReturn is the return of mock interface ormerMock's QueryTable function
queryTableReturn orm.QuerySeter
// beginReturn is the return of mock interface ormerMock's Begin function
beginReturn orm.TxOrmer
}
// CasesTransStr is a struct for cases of trans
type CasesTransStr []struct {
// name is name of the testcase
name string
// rollBackTimes is number of times rollback is expected
rollBackTimes int
// commitTimes is number of times commit is expected
commitTimes int
// beginTimes is number of times begin is expected
beginTimes int
// filterReturn is the return of mock interface querySeterMock's filter function
filterReturn orm.QuerySeter
// filterTimes is the number of times filter is called
filterTimes int
// insertReturnInt is the first return of mock interface ormerMock's Insert function
insertReturnInt int64
// insertReturnErr is the second return of mock interface ormerMock's Insert function
insertReturnErr error
// insertTimes is number of times Insert is expected
insertTimes int
// deleteReturnInt is the first return of mock interface ormerMock's Delete function
deleteReturnInt int64
// deleteReturnErr is the second return of mock interface ormerMock's Delete function
deleteReturnErr error
// deleteTimes is number of times Delete is expected
deleteTimes int
// updateReturnInt is the first return of mock interface ormerMock's Update function
updateReturnInt int64
// updateReturnErr is the second return of mock interface ormerMock's Update function
updateReturnErr error
// updateTimes is number of times Update is expected
updateTimes int
// queryTableReturn is the return of mock interface ormerMock's QueryTable function
queryTableReturn orm.QuerySeter
// queryTableTimes is the number of times queryTable is called
queryTableTimes int
// wantErr is expected error
wantErr error
}
// GetCasesSave get cases for save
func GetCasesSave(t *testing.T) (*beego.MockOrmer, CasesSaveStr) {
ormerMock, _ := testtools.InitOrmerMock(t)
return ormerMock, CasesSaveStr{{
// Success Case
name: "SuccessCase",
doTXReturnErr: nil,
}, {
// Failure Case
name: "FailureCase",
doTXReturnErr: errFailedDBOperation,
},
}
}
// GetCasesDelete get cases for delete
func GetCasesDelete(t *testing.T) (*beego.MockOrmer, *beego.MockQuerySeter, CasesDeleteStr) {
ormerMock, querySeterMock := testtools.InitOrmerMock(t)
return ormerMock, querySeterMock, CasesDeleteStr{{
// Success Case
name: "SuccessCase",
filterReturn: querySeterMock,
deleteReturnInt: int64(1),
deleteReturnErr: nil,
queryTableReturn: querySeterMock,
}, {
// Failure Case
name: "FailureCase",
filterReturn: querySeterMock,
deleteReturnInt: int64(0),
deleteReturnErr: errFailedDBOperation,
queryTableReturn: querySeterMock,
},
}
}
// GetCasesUpdate get cases for update
func GetCasesUpdate(t *testing.T) (*beego.MockOrmer, *beego.MockQuerySeter, CasesUpdateStr) {
ormerMock, querySeterMock := testtools.InitOrmerMock(t)
return ormerMock, querySeterMock, CasesUpdateStr{{
// Success Case
name: "SuccessCase",
filterReturn: querySeterMock,
updateReturnInt: int64(1),
updateReturnErr: nil,
queryTableReturn: querySeterMock,
}, {
// Failure Case
name: "FailureCase",
filterReturn: querySeterMock,
updateReturnInt: int64(0),
updateReturnErr: errFailedDBOperation,
queryTableReturn: querySeterMock,
},
}
}
// GetCasesQuery get cases for query
func GetCasesQuery(t *testing.T) (*beego.MockOrmer, *beego.MockQuerySeter, CasesQueryStr) {
ormerMock, querySeterMock := testtools.InitOrmerMock(t)
return ormerMock, querySeterMock, CasesQueryStr{{
// Success Case
name: "SuccessCase",
filterReturn: querySeterMock,
allReturnInt: int64(1),
allReturnErr: nil,
queryTableReturn: querySeterMock,
}, {
// Failure Case
name: "FailureCase",
filterReturn: querySeterMock,
allReturnInt: int64(0),
allReturnErr: errFailedDBOperation,
queryTableReturn: querySeterMock,
},
}
}
// GetCasesTrans get cases for trans
func GetCasesTrans(caseName string, t *testing.T) (*beego.MockOrmer, *beego.MockQuerySeter, CasesTransStr) {
ormerMock, querySeterMock := testtools.InitOrmerMock(t)
return ormerMock, querySeterMock, CasesTransStr{{
// Failure Case SaveDeviceAttr
name: caseName + "TransSaveFailureCase",
rollBackTimes: 1,
commitTimes: 0,
beginTimes: 1,
filterReturn: nil,
filterTimes: 0,
insertReturnInt: int64(1),
insertReturnErr: errFailedDBOperation,
insertTimes: 1,
deleteReturnInt: int64(1),
deleteReturnErr: nil,
deleteTimes: 0,
updateReturnInt: int64(1),
updateReturnErr: nil,
updateTimes: 0,
queryTableReturn: nil,
queryTableTimes: 0,
wantErr: errFailedDBOperation,
}, {
// Failure Case DeleteDeviceAttr
name: caseName + "TransDeleteFailureCase",
rollBackTimes: 1,
commitTimes: 0,
beginTimes: 1,
filterReturn: querySeterMock,
filterTimes: 2,
insertReturnInt: int64(1),
insertReturnErr: nil,
insertTimes: 1,
deleteReturnInt: int64(1),
deleteReturnErr: errFailedDBOperation,
deleteTimes: 1,
updateReturnInt: int64(1),
updateReturnErr: nil,
updateTimes: 0,
queryTableReturn: querySeterMock,
queryTableTimes: 1,
wantErr: errFailedDBOperation,
}, {
// Failure Case UpdateDeviceAttrFields
name: caseName + "TransUpdateFieldsFailureCase",
rollBackTimes: 1,
commitTimes: 0,
beginTimes: 1,
filterReturn: querySeterMock,
filterTimes: 4,
insertReturnInt: int64(1),
insertReturnErr: nil,
insertTimes: 1,
deleteReturnInt: int64(1),
deleteReturnErr: nil,
deleteTimes: 1,
updateReturnInt: int64(1),
updateReturnErr: errFailedDBOperation,
updateTimes: 1,
queryTableReturn: querySeterMock,
queryTableTimes: 2,
wantErr: errFailedDBOperation,
}, {
// Success Case
name: caseName + "TransSuccessCase",
rollBackTimes: 0,
commitTimes: 1,
beginTimes: 1,
filterReturn: querySeterMock,
filterTimes: 4,
insertReturnInt: int64(1),
insertReturnErr: nil,
insertTimes: 1,
deleteReturnInt: int64(1),
deleteReturnErr: nil,
deleteTimes: 1,
updateReturnInt: int64(1),
updateReturnErr: nil,
updateTimes: 1,
queryTableReturn: querySeterMock,
queryTableTimes: 2,
wantErr: nil,
},
}
}
package dtclient
import (
"context"
"github.com/beego/beego/v2/client/orm"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
// Device the struct of device
type Device struct {
ID string `orm:"column(id); size(64); pk"`
Name string `orm:"column(name); null; type(text)"`
Description string `orm:"column(description); null; type(text)"`
State string `orm:"column(state); null; type(text)"`
LastOnline string `orm:"column(last_online); null; type(text)"`
}
// SaveDevice save device
func SaveDevice(o orm.Ormer, doc *Device) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// insert data
// Using txOrm to execute SQL
_, e := txOrm.Insert(doc)
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when insert Device data: %v", err)
return err
}
klog.V(4).Info("insert Device data successfully")
return nil
}
// DeleteDeviceByID delete device by id
func DeleteDeviceByID(o orm.Ormer, id string) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// delete data
// Using txOrm to execute SQL
_, e := txOrm.QueryTable(DeviceTableName).Filter("id", id).Delete()
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when deleting Device data: %v", err)
return err
}
klog.V(4).Info("Delete Device data successfully")
return nil
}
// UpdateDeviceField update special field
func UpdateDeviceField(deviceID string, col string, value interface{}) error {
num, err := dbm.DBAccess.QueryTable(DeviceTableName).Filter("id", deviceID).Update(map[string]interface{}{col: value})
klog.V(4).Infof("Update affected Num: %d, %s", num, err)
return err
}
// UpdateDeviceFields update special fields
func UpdateDeviceFields(deviceID string, cols map[string]interface{}) error {
num, err := dbm.DBAccess.QueryTable(DeviceTableName).Filter("id", deviceID).Update(cols)
klog.V(4).Infof("Update affected Num: %d, %s", num, err)
return err
}
// QueryDevice query Device
func QueryDevice(key string, condition string) (*[]Device, error) {
devices := new([]Device)
_, err := dbm.DBAccess.QueryTable(DeviceTableName).Filter(key, condition).All(devices)
if err != nil {
return nil, err
}
return devices, nil
}
// QueryDeviceAll query twin
func QueryDeviceAll() (*[]Device, error) {
devices := new([]Device)
_, err := dbm.DBAccess.QueryTable(DeviceTableName).All(devices)
if err != nil {
return nil, err
}
return devices, nil
}
// DeviceUpdate the struct for updating device
type DeviceUpdate struct {
DeviceID string
Cols map[string]interface{}
}
// UpdateDeviceMulti update device multi
func UpdateDeviceMulti(updates []DeviceUpdate) error {
var err error
for _, update := range updates {
err = UpdateDeviceFields(update.DeviceID, update.Cols)
if err != nil {
return err
}
}
return nil
}
// AddDeviceTrans the transaction of add device
func AddDeviceTrans(adds []Device, addAttrs []DeviceAttr, addTwins []DeviceTwin) error {
obm := dbm.DefaultOrmFunc()
var err error
for _, add := range adds {
err = SaveDevice(obm, &add)
if err != nil {
klog.Errorf("save device failed: %v", err)
return err
}
}
for _, attr := range addAttrs {
err = SaveDeviceAttr(obm, &attr)
if err != nil {
klog.Errorf("save device attr failed: %v", err)
return err
}
}
for _, twin := range addTwins {
err = SaveDeviceTwin(obm, &twin)
if err != nil {
klog.Errorf("save device twin failed: %v", err)
return err
}
}
return err
}
// DeleteDeviceTrans the transaction of delete device
func DeleteDeviceTrans(deletes []string) error {
obm := dbm.DefaultOrmFunc()
var err error
for _, delete := range deletes {
err = DeleteDeviceByID(obm, delete)
if err != nil {
return err
}
err = DeleteDeviceAttrByDeviceID(obm, delete)
if err != nil {
return err
}
err = DeleteDeviceTwinByDeviceID(obm, delete)
if err != nil {
return err
}
}
return err
}
package dtclient
import (
"context"
"github.com/beego/beego/v2/client/orm"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
// DeviceAttr the struct of device attributes
type DeviceAttr struct {
ID int64 `orm:"column(id);size(64);auto;pk"`
DeviceID string `orm:"column(deviceid); null; type(text)"`
Name string `orm:"column(name);null;type(text)"`
Description string `orm:"column(description);null;type(text)"`
Value string `orm:"column(value);null;type(text)"`
Optional bool `orm:"column(optional);null;type(integer)"`
AttrType string `orm:"column(attr_type);null;type(text)"`
Metadata string `orm:"column(metadata);null;type(text)"`
}
// SaveDeviceAttr save device attributes
func SaveDeviceAttr(o orm.Ormer, doc *DeviceAttr) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// insert data
// Using txOrm to execute SQL
_, e := txOrm.Insert(doc)
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when insert DeviceAttr data: %v", err)
return err
}
klog.V(4).Info("insert DeviceAttr data successfully")
return nil
}
// DeleteDeviceAttrByDeviceID delete device attr
func DeleteDeviceAttrByDeviceID(o orm.Ormer, deviceID string) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// delete data
// Using txOrm to execute SQL
_, e := txOrm.QueryTable(DeviceAttrTableName).Filter("deviceid", deviceID).Delete()
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when deleting Device data: %v", err)
return err
}
klog.V(4).Info("Delete Device data successfully")
return nil
}
// DeleteDeviceAttr delete device attr
func DeleteDeviceAttr(o orm.Ormer, deviceID string, name string) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// delete data
// Using txOrm to execute SQL
_, e := txOrm.QueryTable(DeviceAttrTableName).Filter("deviceid", deviceID).Filter("name", name).Delete()
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when deleting data: %v", err)
return err
}
klog.V(4).Infof("Delete Device attr successfully")
return nil
}
// UpdateDeviceAttrField update special field
func UpdateDeviceAttrField(deviceID string, name string, col string, value interface{}) error {
num, err := dbm.DBAccess.QueryTable(DeviceAttrTableName).Filter("deviceid", deviceID).Filter("name", name).Update(map[string]interface{}{col: value})
klog.V(4).Infof("Update affected Num: %d, %s", num, err)
return err
}
// UpdateDeviceAttrFields update special fields
func UpdateDeviceAttrFields(o orm.Ormer, deviceID string, name string, cols map[string]interface{}) error {
num, err := o.QueryTable(DeviceAttrTableName).Filter("deviceid", deviceID).Filter("name", name).Update(cols)
klog.V(4).Infof("Update affected Num: %d, %s", num, err)
return err
}
// QueryDeviceAttr query Device
func QueryDeviceAttr(key string, condition string) (*[]DeviceAttr, error) {
attrs := new([]DeviceAttr)
_, err := dbm.DBAccess.QueryTable(DeviceAttrTableName).Filter(key, condition).All(attrs)
if err != nil {
return nil, err
}
return attrs, nil
}
// DeviceDelete the struct for deleting device
type DeviceDelete struct {
DeviceID string
Name string
}
// DeviceAttrUpdate the struct for updating device attr
type DeviceAttrUpdate struct {
DeviceID string
Name string
Cols map[string]interface{}
}
// UpdateDeviceAttrMulti update device attr multi
func UpdateDeviceAttrMulti(updates []DeviceAttrUpdate) error {
var err error
for _, update := range updates {
err = UpdateDeviceAttrFields(dbm.DBAccess, update.DeviceID, update.Name, update.Cols)
if err != nil {
return err
}
}
return nil
}
// DeviceAttrTrans transaction of device attr
func DeviceAttrTrans(adds []DeviceAttr, deletes []DeviceDelete, updates []DeviceAttrUpdate) error {
obm := dbm.DefaultOrmFunc()
var err error
for _, add := range adds {
err = SaveDeviceAttr(obm, &add)
if err != nil {
return err
}
}
for _, delete := range deletes {
err = DeleteDeviceAttr(obm, delete.DeviceID, delete.Name)
if err != nil {
return err
}
}
for _, update := range updates {
err = UpdateDeviceAttrFields(obm, update.DeviceID, update.Name, update.Cols)
if err != nil {
return err
}
}
return err
}
package dtclient
import (
"context"
"github.com/beego/beego/v2/client/orm"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
// DeviceTwin the struct of device twin
type DeviceTwin struct {
ID int64 `orm:"column(id);size(64);auto;pk"`
DeviceID string `orm:"column(deviceid); null; type(text)"`
Name string `orm:"column(name);null;type(text)"`
Description string `orm:"column(description);null;type(text)"`
Expected string `orm:"column(expected);null;type(text)"`
Actual string `orm:"column(actual);null;type(text)"`
ExpectedMeta string `orm:"column(expected_meta);null;type(text)"`
ActualMeta string `orm:"column(actual_meta);null;type(text)"`
ExpectedVersion string `orm:"column(expected_version);null;type(text)"`
ActualVersion string `orm:"column(actual_version);null;type(text)"`
Optional bool `orm:"column(optional);null;type(integer)"`
AttrType string `orm:"column(attr_type);null;type(text)"`
Metadata string `orm:"column(metadata);null;type(text)"`
}
// SaveDeviceTwin save device twin
func SaveDeviceTwin(o orm.Ormer, doc *DeviceTwin) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// insert data
// Using txOrm to execute SQL
_, e := txOrm.Insert(doc)
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when insert DeviceTwin data: %v", err)
return err
}
klog.V(4).Info("insert DeviceTwin data successfully")
return nil
}
// DeleteDeviceTwinByDeviceID delete device twin
func DeleteDeviceTwinByDeviceID(o orm.Ormer, deviceID string) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// Delete data
// Using txOrm to execute SQL
_, e := txOrm.QueryTable(DeviceTwinTableName).Filter("deviceid", deviceID).Delete()
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when deleting Device data: %v", err)
return err
}
klog.V(4).Info("Delete Device data successfully")
return nil
}
// DeleteDeviceTwin delete device twin
func DeleteDeviceTwin(o orm.Ormer, deviceID string, name string) error {
err := o.DoTx(func(_ context.Context, txOrm orm.TxOrmer) error {
// Delete data
// Using txOrm to execute SQL
_, e := txOrm.QueryTable(DeviceTwinTableName).Filter("deviceid", deviceID).Filter("name", name).Delete()
// if e != nil the transaction will be rollback
// or it will be committed
return e
})
if err != nil {
klog.Errorf("Something wrong when deleting Device Twin data: %v", err)
return err
}
klog.V(4).Info("Delete Device Twin data successfully")
return nil
}
// UpdateDeviceTwinField update special field
func UpdateDeviceTwinField(deviceID string, name string, col string, value interface{}) error {
num, err := dbm.DBAccess.QueryTable(DeviceTwinTableName).Filter("deviceid", deviceID).Filter("name", name).Update(map[string]interface{}{col: value})
klog.V(4).Infof("Update affected Num: %d, %s", num, err)
return err
}
// UpdateDeviceTwinFields update special fields
func UpdateDeviceTwinFields(o orm.Ormer, deviceID string, name string, cols map[string]interface{}) error {
num, err := o.QueryTable(DeviceTwinTableName).Filter("deviceid", deviceID).Filter("name", name).Update(cols)
klog.V(4).Infof("Update affected Num: %d, %v", num, err)
return err
}
// QueryDeviceTwin query Device
func QueryDeviceTwin(key string, condition string) (*[]DeviceTwin, error) {
twin := new([]DeviceTwin)
_, err := dbm.DBAccess.QueryTable(DeviceTwinTableName).Filter(key, condition).All(twin)
if err != nil {
return nil, err
}
return twin, nil
}
// DeviceTwinUpdate the struct for updating device twin
type DeviceTwinUpdate struct {
DeviceID string
Name string
Cols map[string]interface{}
}
// UpdateDeviceTwinMulti update device twin multi
func UpdateDeviceTwinMulti(updates []DeviceTwinUpdate) error {
for _, update := range updates {
err := UpdateDeviceTwinFields(dbm.DBAccess, update.DeviceID, update.Name, update.Cols)
if err != nil {
return err
}
}
return nil
}
// DeviceTwinTrans transaction of device twin
func DeviceTwinTrans(adds []DeviceTwin, deletes []DeviceDelete, updates []DeviceTwinUpdate) error {
obm := dbm.DefaultOrmFunc()
var err error
for _, add := range adds {
err = SaveDeviceTwin(obm, &add)
if err != nil {
return err
}
}
for _, delete := range deletes {
err = DeleteDeviceTwin(obm, delete.DeviceID, delete.Name)
if err != nil {
return err
}
}
for _, update := range updates {
err = UpdateDeviceTwinFields(obm, update.DeviceID, update.Name, update.Cols)
if err != nil {
return err
}
}
return err
}
package dtclient
import (
"github.com/beego/beego/v2/client/orm"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core"
)
const (
//DeviceTableName device table
DeviceTableName = "device"
//DeviceAttrTableName device table
DeviceAttrTableName = "device_attr"
//DeviceTwinTableName device table
DeviceTwinTableName = "device_twin"
)
// InitDBTable create table
func InitDBTable(module core.Module) {
klog.Infof("Begin to register %v db model", module.Name())
if !module.Enable() {
klog.Infof("Module %s is disabled, DB meta for it will not be registered", module.Name())
return
}
orm.RegisterModel(new(Device))
orm.RegisterModel(new(DeviceAttr))
orm.RegisterModel(new(DeviceTwin))
}
package dtcommon
import (
"encoding/json"
"errors"
"fmt"
"reflect"
"regexp"
"strconv"
"strings"
"google.golang.org/protobuf/types/known/anypb"
"google.golang.org/protobuf/types/known/wrapperspb"
"k8s.io/klog/v2"
"github.com/kubeedge/api/apis/devices/v1beta1"
pb "github.com/kubeedge/api/apis/dmi/v1beta1"
"github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/constants"
)
// ValidateValue validate value type
func ValidateValue(valueType string, value string) error {
switch valueType {
case "":
valueType = constants.DataTypeString
return nil
case constants.DataTypeString:
return nil
case constants.DataTypeInt, constants.DataTypeInteger:
_, err := strconv.ParseInt(value, 10, 64)
if err != nil {
return errors.New("the value is not int or integer")
}
return nil
case constants.DataTypeFloat:
_, err := strconv.ParseFloat(value, 64)
if err != nil {
return errors.New("the value is not float")
}
return nil
case constants.DataTypeBoolean:
if strings.Compare(value, "true") != 0 && strings.Compare(value, "false") != 0 {
return errors.New("the bool value must be true or false")
}
return nil
case TypeDeleted:
return nil
default:
return errors.New("the value type is not allowed")
}
}
// ValidateTwinKey validate twin key
func ValidateTwinKey(key string) bool {
pattern := "^[a-zA-Z0-9-_.,:/@#]{1,128}$"
match, _ := regexp.MatchString(pattern, key)
return match
}
// ValidateTwinValue validate twin value
func ValidateTwinValue(value string) bool {
pattern := "^[a-zA-Z0-9-_.,:/@#]{1,512}$"
match, _ := regexp.MatchString(pattern, value)
return match
}
func ConvertDevice(device *v1beta1.Device) (*pb.Device, error) {
if device == nil {
return nil, errors.New("device cannot be nil")
}
data, err := json.Marshal(device)
if err != nil {
klog.Errorf("fail to marshal device %s with err: %v", device.Name, err)
return nil, err
}
var edgeDevice pb.Device
err = json.Unmarshal(data, &edgeDevice)
if err != nil {
klog.Errorf("fail to unmarshal device %s with err: %v", device.Name, err)
return nil, err
}
if device.Spec.Protocol.ConfigData != nil && device.Spec.Protocol.ConfigData.Data != nil {
configAnyData := make(map[string]*anypb.Any)
for k, v := range device.Spec.Protocol.ConfigData.Data {
anyValue, err := dataToAny(v)
if err != nil {
return nil, fmt.Errorf("failed to convert protocol config data: %v", err)
}
configAnyData[k] = anyValue
}
edgeDevice.Spec.Protocol.ConfigData.Data = configAnyData
}
var edgePropertyVisitors []*pb.DeviceProperty
for i := range device.Spec.Properties {
property, err := convertDeviceProperty(&device.Spec.Properties[i])
if err != nil {
return nil, fmt.Errorf("failed to convert property: %v", err)
}
edgePropertyVisitors = append(edgePropertyVisitors, property)
}
edgeDevice.Spec.Properties = edgePropertyVisitors
edgeDevice.Name = device.Name
if device.Spec.DeviceModelRef != nil {
edgeDevice.Spec.DeviceModelReference = device.Spec.DeviceModelRef.Name
}
edgeDevice.Namespace = device.Namespace
return &edgeDevice, nil
}
func convertDeviceProperty(prop *v1beta1.DeviceProperty) (*pb.DeviceProperty, error) {
if prop == nil {
return nil, errors.New("property cannot be nil")
}
item := new(pb.DeviceProperty)
propertyData, err := json.Marshal(prop)
if err != nil {
return nil, fmt.Errorf("failed to marshal property: %v", err)
}
err = json.Unmarshal(propertyData, item)
if prop.Visitors.ConfigData != nil && prop.Visitors.ConfigData.Data != nil {
configAnyData := make(map[string]*anypb.Any)
for k, v := range prop.Visitors.ConfigData.Data {
anyValue, err := dataToAny(v)
if err != nil {
return nil, fmt.Errorf("failed to convert visitor config data: %v", err)
}
configAnyData[k] = anyValue
}
item.Visitors.ConfigData.Data = configAnyData
}
return item, nil
}
func ConvertDeviceModel(model *v1beta1.DeviceModel) (*pb.DeviceModel, error) {
data, err := json.Marshal(model)
if err != nil {
klog.Errorf("fail to marshal device model %s with err: %v", model.Name, err)
return nil, err
}
var edgeDeviceModel pb.DeviceModel
err = json.Unmarshal(data, &edgeDeviceModel)
if err != nil {
klog.Errorf("fail to unmarshal device model %s with err: %v", model.Name, err)
return nil, err
}
edgeDeviceModel.Name = model.Name
edgeDeviceModel.Namespace = model.Namespace
return &edgeDeviceModel, nil
}
func dataToAny(v interface{}) (*anypb.Any, error) {
switch m := v.(type) {
case string:
strWrapper := wrapperspb.String(m)
anyStr, err := anypb.New(strWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyStr, nil
case int8:
intWrapper := wrapperspb.Int32(int32(m))
anyInt, err := anypb.New(intWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyInt, nil
case int16:
intWrapper := wrapperspb.Int32(int32(m))
anyInt, err := anypb.New(intWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyInt, nil
case int32:
intWrapper := wrapperspb.Int32(m)
anyInt, err := anypb.New(intWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyInt, nil
case int64:
intWrapper := wrapperspb.Int64(m)
anyInt, err := anypb.New(intWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyInt, nil
case int:
intWrapper := wrapperspb.Int32(int32(m))
anyInt, err := anypb.New(intWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyInt, nil
case float64:
floatWrapper := wrapperspb.Float(float32(m))
anyFloat, err := anypb.New(floatWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyFloat, nil
case float32:
floatWrapper := wrapperspb.Float(float32(m))
anyFloat, err := anypb.New(floatWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyFloat, nil
case bool:
boolWrapper := wrapperspb.Bool(m)
anyBool, err := anypb.New(boolWrapper)
if err != nil {
klog.Errorf("anypb new error: %v", err)
return nil, err
}
return anyBool, nil
default:
return nil, fmt.Errorf("%v does not support converting to any", reflect.TypeOf(v))
}
}
package dtcontext
import (
"context"
"errors"
"strings"
"sync"
"time"
//"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
deviceconfig "github.com/kubeedge/kubeedge/edge/pkg/devicetwin/config"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
// DTContext context for devicetwin
type DTContext struct {
GroupID string
NodeName string
CommChan map[string]chan interface{}
ConfirmChan chan interface{}
ConfirmMap *sync.Map
ModulesHealth *sync.Map
ModulesContext *context.Context
DeviceList *sync.Map
DeviceMutex *sync.Map
Mutex *sync.RWMutex
// DBConn *dtclient.Conn
State string
}
// InitDTContext init dtcontext
func InitDTContext() (*DTContext, error) {
return &DTContext{
GroupID: "",
NodeName: deviceconfig.Get().NodeName,
CommChan: make(map[string]chan interface{}),
ConfirmChan: make(chan interface{}, 1000),
ConfirmMap: &sync.Map{},
ModulesHealth: &sync.Map{},
DeviceList: &sync.Map{},
DeviceMutex: &sync.Map{},
Mutex: &sync.RWMutex{},
State: dtcommon.Disconnected,
}, nil
}
// CommTo communicate
func (dtc *DTContext) CommTo(dtmName string, content interface{}) error {
if v, exist := dtc.CommChan[dtmName]; exist {
v <- content
return nil
}
return errors.New("Not found chan to communicate")
}
// HeartBeat heartbeat to dtcontroller
func (dtc *DTContext) HeartBeat(dtmName string, content interface{}) error {
if strings.Compare(content.(string), "ping") == 0 {
dtc.ModulesHealth.Store(dtmName, time.Now().Unix())
//klog.V(3).Infof("%s is healthy %v", dtmName, time.Now().Unix())
} else if strings.Compare(content.(string), "stop") == 0 {
//klog.Infof("%s stop", dtmName)
return errors.New("stop")
}
return nil
}
// GetMutex get mutex
func (dtc *DTContext) GetMutex(deviceID string) (*sync.Mutex, bool) {
v, mutexExist := dtc.DeviceMutex.Load(deviceID)
if !mutexExist {
//klog.Errorf("GetMutex device %s not exist", deviceID)
return nil, false
}
mutex, isMutex := v.(*sync.Mutex)
if isMutex {
return mutex, true
}
return nil, false
}
// Lock get the lock of the device
func (dtc *DTContext) Lock(deviceID string) bool {
deviceMutex, ok := dtc.GetMutex(deviceID)
if ok {
dtc.Mutex.RLock()
deviceMutex.Lock()
return true
}
return false
}
// Unlock remove the lock of the device
func (dtc *DTContext) Unlock(deviceID string) bool {
deviceMutex, ok := dtc.GetMutex(deviceID)
if ok {
deviceMutex.Unlock()
dtc.Mutex.RUnlock()
return true
}
return false
}
// LockAll get all lock
func (dtc *DTContext) LockAll() {
dtc.Mutex.Lock()
}
// UnlockAll get all lock
func (dtc *DTContext) UnlockAll() {
dtc.Mutex.Unlock()
}
// IsDeviceExist judge device is exist
func (dtc *DTContext) IsDeviceExist(deviceID string) bool {
_, ok := dtc.DeviceList.Load(deviceID)
return ok
}
// GetDevice get device
func (dtc *DTContext) GetDevice(deviceID string) (*dttype.Device, bool) {
d, ok := dtc.DeviceList.Load(deviceID)
if ok {
if device, isDevice := d.(*dttype.Device); isDevice {
return device, true
}
return nil, false
}
return nil, false
}
// Send send result
func (dtc *DTContext) Send(identity string, action string, module string, msg *model.Message) error {
dtMsg := &dttype.DTMessage{
Action: action,
Identity: identity,
Type: module,
Msg: msg}
return dtc.CommTo(module, dtMsg)
}
// BuildModelMessage build mode messages
func (dtc *DTContext) BuildModelMessage(group string, parentID string, resource string, operation string, content interface{}) *model.Message {
msg := model.NewMessage(parentID)
msg.BuildRouter(modules.TwinGroup, group, resource, operation)
msg.Content = content
return msg
}
package dtmanager
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
"github.com/google/uuid"
//"k8s.io/klog/v2"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
connect "github.com/kubeedge/kubeedge/edge/pkg/common/cloudconnection"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
var (
//ActionCallBack map for action to callback
ActionCallBack map[string]CallBack
)
func init() {
initActionCallBack()
}
// CommWorker deal app response event
type CommWorker struct {
Worker
Group string
}
// Start worker
func (cw CommWorker) Start() {
for {
select {
case msg, ok := <-cw.ReceiverChan:
//klog.V(2).Info("receive msg commModule")
if !ok {
return
}
if dtMsg, isDTMessage := msg.(*dttype.DTMessage); isDTMessage {
if fn, exist := ActionCallBack[dtMsg.Action]; exist {
err := fn(cw.DTContexts, dtMsg.Identity, dtMsg.Msg)
if err != nil {
//klog.Errorf("CommModule deal %s event failed: %v", dtMsg.Action, err)
}
} else {
//klog.Errorf("CommModule deal %s event failed, not found callback", dtMsg.Action)
}
}
case <-time.After(time.Duration(60) * time.Second):
cw.checkConfirm(cw.DTContexts)
case v, ok := <-cw.HeartBeatChan:
if !ok {
return
}
if err := cw.DTContexts.HeartBeat(cw.Group, v); err != nil {
return
}
}
}
}
func initActionCallBack() {
ActionCallBack = make(map[string]CallBack)
ActionCallBack[dtcommon.SendToCloud] = dealSendToCloud
ActionCallBack[dtcommon.SendToEdge] = dealSendToEdge
ActionCallBack[dtcommon.LifeCycle] = dealLifeCycle
ActionCallBack[dtcommon.Confirm] = dealConfirm
}
func dealSendToEdge(_ *dtcontext.DTContext, _ string, msg interface{}) error {
message, ok := msg.(*model.Message)
if !ok {
return fmt.Errorf("msg type is %T and not Message type", msg)
}
beehiveContext.Send(dtcommon.EventHubModule, *message)
return nil
}
func dealSendToCloud(context *dtcontext.DTContext, _ string, msg interface{}) error {
if strings.Compare(context.State, dtcommon.Disconnected) == 0 {
//klog.Infof("Disconnected with cloud, not send msg to cloud")
return nil
}
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
beehiveContext.Send(dtcommon.HubModule, *message)
msgID := message.GetID()
context.ConfirmMap.Store(msgID, &dttype.DTMessage{Msg: message, Action: dtcommon.SendToCloud, Type: dtcommon.CommModule})
return nil
}
func dealLifeCycle(context *dtcontext.DTContext, _ string, msg interface{}) error {
//klog.V(2).Info("CONNECTED EVENT")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
connectedInfo, _ := message.Content.(string)
if strings.Compare(connectedInfo, connect.CloudConnected) == 0 {
if strings.Compare(context.State, dtcommon.Disconnected) == 0 {
err := detailRequest(context)
if err != nil {
//klog.Errorf("detail request: %v", err)
return err
}
}
context.State = dtcommon.Connected
} else if strings.Compare(connectedInfo, connect.CloudDisconnected) == 0 {
context.State = dtcommon.Disconnected
}
return nil
}
func dealConfirm(context *dtcontext.DTContext, _ string, msg interface{}) error {
//klog.V(2).Info("CONFIRM EVENT")
value, ok := msg.(*model.Message)
if ok {
parentMsgID := value.GetParentID()
//klog.Infof("CommModule deal confirm msgID %s", parentMsgID)
context.ConfirmMap.Delete(parentMsgID)
} else {
return errors.New("CommModule deal confirm, type not correct")
}
return nil
}
func detailRequest(context *dtcontext.DTContext) error {
getDetail := dttype.GetDetailNode{
EventType: "group_membership_event",
EventID: uuid.New().String(),
Operation: "detail",
GroupID: context.NodeName,
TimeStamp: time.Now().UnixNano() / 1000000}
getDetailJSON, marshalErr := json.Marshal(getDetail)
if marshalErr != nil {
//klog.Errorf("Marshal request error while request detail, err: %#v", marshalErr)
return marshalErr
}
message := context.BuildModelMessage("resource", "", "membership/detail", "get", string(getDetailJSON))
//klog.V(2).Info("Request detail")
msgID := message.GetID()
context.ConfirmMap.Store(msgID, &dttype.DTMessage{Msg: message, Action: dtcommon.SendToCloud, Type: dtcommon.CommModule})
beehiveContext.Send(dtcommon.HubModule, *message)
return nil
}
func (cw CommWorker) checkConfirm(context *dtcontext.DTContext) {
//klog.V(2).Info("CheckConfirm")
context.ConfirmMap.Range(func(key interface{}, value interface{}) bool {
dtmsg, ok := value.(*dttype.DTMessage)
//klog.V(2).Info("has msg")
if !ok {
//klog.Warningf("confirm map key %s 's value is not the *DTMessage type", key.(string))
return true
}
//klog.V(2).Info("redo task due to no recv")
if fn, exist := ActionCallBack[dtmsg.Action]; exist {
if err := fn(cw.DTContexts, dtmsg.Identity, dtmsg.Msg); err != nil {
//klog.Errorf("CommModule deal %s event failed: %v", dtmsg.Action, err)
}
} else {
//klog.Errorf("CommModule deal %s event failed, not found callback", dtmsg.Action)
}
return true
})
}
package dtmanager
import (
"encoding/json"
"errors"
"fmt"
"strings"
"time"
//"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
messagepkg "github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
var (
//deviceActionCallBack map for action to callback
deviceActionCallBack map[string]CallBack
)
// DeviceWorker deal device event
type DeviceWorker struct {
Worker
Group string
}
// Start worker
func (dw DeviceWorker) Start() {
initDeviceActionCallBack()
for {
select {
case msg, ok := <-dw.ReceiverChan:
if !ok {
return
}
if dtMsg, isDTMessage := msg.(*dttype.DTMessage); isDTMessage {
if fn, exist := deviceActionCallBack[dtMsg.Action]; exist {
err := fn(dw.DTContexts, dtMsg.Identity, dtMsg.Msg)
if err != nil {
//klog.Errorf("DeviceModule deal %s event failed: %v", dtMsg.Action, err)
}
} else {
//klog.Errorf("DeviceModule deal %s event failed, not found callback", dtMsg.Action)
}
}
case v, ok := <-dw.HeartBeatChan:
if !ok {
return
}
if err := dw.DTContexts.HeartBeat(dw.Group, v); err != nil {
return
}
}
}
}
func initDeviceActionCallBack() {
deviceActionCallBack = make(map[string]CallBack)
deviceActionCallBack[dtcommon.DeviceUpdated] = dealDeviceAttrUpdate
deviceActionCallBack[dtcommon.DeviceStateUpdate] = dealDeviceStateUpdate
}
func dealDeviceStateUpdate(context *dtcontext.DTContext, resource string, msg interface{}) error {
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
updatedDevice, err := dttype.UnmarshalDeviceUpdate(message.Content.([]byte))
if err != nil {
//klog.Errorf("Unmarshal device info failed, err: %#v", err)
return err
}
deviceID := resource
defer context.Unlock(deviceID)
context.Lock(deviceID)
doc, docExist := context.DeviceList.Load(deviceID)
if !docExist {
return nil
}
device, ok := doc.(*dttype.Device)
if !ok {
return nil
}
// state refers to definition in mappers-go/pkg/common/const.go
state := strings.ToLower(updatedDevice.State)
switch state {
case dtcommon.DeviceStatusOnline, dtcommon.DeviceStatusOffline, dtcommon.DeviceStatusOK,
dtcommon.DeviceStatusUnknown, dtcommon.DeviceStatusUnhealthy:
default:
return nil
}
var lastOnline string
if state == dtcommon.DeviceStatusOnline || state == dtcommon.DeviceStatusOK {
lastOnline = time.Now().UTC().Format(time.RFC3339)
}
for i := 1; i <= dtcommon.RetryTimes; i++ {
err = dtclient.UpdateDeviceFields(
device.ID,
map[string]interface{}{
"last_online": lastOnline,
"state": updatedDevice.State,
})
if err == nil {
break
}
time.Sleep(dtcommon.RetryInterval)
}
if err != nil {
return err
}
device.State = updatedDevice.State
device.LastOnline = lastOnline
payload, err := dttype.BuildDeviceCloudMsgState(dttype.BuildBaseMessage(), *device)
if err != nil {
return err
}
topic := dtcommon.DeviceETPrefix + device.ID + dtcommon.DeviceETStateUpdateResultSuffix
err = context.Send(device.ID,
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, payload))
if err != nil {
// TODO: handle error
//klog.Error(err)
}
msgResource := "device/" + device.ID + dtcommon.DeviceETStateUpdateSuffix
err = context.Send(deviceID,
dtcommon.SendToCloud,
dtcommon.CommModule,
context.BuildModelMessage("resource", "", msgResource, model.UpdateOperation, string(payload)))
if err != nil {
// TODO: handle error
//klog.Error(err)
}
return nil
}
func dealDeviceAttrUpdate(context *dtcontext.DTContext, resource string, msg interface{}) error {
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
updatedDevice, err := dttype.UnmarshalDeviceUpdate(message.Content.([]byte))
if err != nil {
//klog.Errorf("Unmarshal device info failed, err: %#v", err)
return err
}
deviceID := resource
context.Lock(deviceID)
if _, err = UpdateDeviceAttr(context, deviceID, updatedDevice.Attributes,
dttype.BaseMessage{EventID: updatedDevice.EventID}, 0); err != nil {
// TODO: handle error
//klog.Error(err)
}
context.Unlock(deviceID)
return nil
}
// UpdateDeviceAttr update device attributes
func UpdateDeviceAttr(context *dtcontext.DTContext, deviceID string, attributes map[string]*dttype.MsgAttr, baseMessage dttype.BaseMessage, dealType int) (interface{}, error) {
//klog.Infof("Begin to update attributes of the device %s", deviceID)
var err error
doc, docExist := context.DeviceList.Load(deviceID)
if !docExist {
return nil, nil
}
Device, ok := doc.(*dttype.Device)
if !ok {
return nil, nil
}
dealAttrResult := DealMsgAttr(context, Device.ID, attributes, dealType)
if dealAttrResult.Err != nil {
return nil, nil
}
add, deviceAttrDelete, update, result := dealAttrResult.Add, dealAttrResult.Delete, dealAttrResult.Update, dealAttrResult.Result
if len(add) != 0 || len(deviceAttrDelete) != 0 || len(update) != 0 {
for i := 1; i <= dtcommon.RetryTimes; i++ {
err = dtclient.DeviceAttrTrans(add, deviceAttrDelete, update)
if err == nil {
break
}
time.Sleep(dtcommon.RetryInterval)
}
now := time.Now().UnixNano() / 1e6
baseMessage.Timestamp = now
if err != nil {
if err := SyncDeviceFromSqlite(context, deviceID); err != nil {
// TODO: handle error
//klog.Error(err)
}
//klog.Errorf("Update device failed due to writing sql error: %v", err)
} else {
//klog.Infof("Send update attributes of device %s event to edge app", deviceID)
payload, err := dttype.BuildDeviceAttrUpdate(baseMessage, result)
if err != nil {
//todo
//klog.Errorf("Build device attribute update failed: %v", err)
}
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.DeviceETUpdatedSuffix
err = context.Send(deviceID, dtcommon.SendToEdge, dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, payload))
if err != nil {
// TODO: handle error
//klog.Error(err)
}
}
}
return nil, nil
}
// DealMsgAttr get diff,0:update, 1:detail
func DealMsgAttr(context *dtcontext.DTContext, deviceID string, msgAttrs map[string]*dttype.MsgAttr, dealType int) dttype.DealAttrResult {
device, ok := context.GetDevice(deviceID)
if !ok {
return dttype.DealAttrResult{
Err: fmt.Errorf("can not get deviceID %s in DealMsgAttr", deviceID),
}
}
attrs := device.Attributes
if attrs == nil {
device.Attributes = make(map[string]*dttype.MsgAttr)
attrs = device.Attributes
}
add := make([]dtclient.DeviceAttr, 0)
deletes := make([]dtclient.DeviceDelete, 0)
update := make([]dtclient.DeviceAttrUpdate, 0)
result := make(map[string]*dttype.MsgAttr)
for key, msgAttr := range msgAttrs {
if attr, exist := attrs[key]; exist {
if msgAttr == nil && dealType == 0 {
if *attr.Optional {
deletes = append(deletes, dtclient.DeviceDelete{DeviceID: deviceID, Name: key})
result[key] = nil
delete(attrs, key)
}
continue
}
isChange := false
cols := make(map[string]interface{})
result[key] = &dttype.MsgAttr{}
if strings.Compare(attr.Value, msgAttr.Value) != 0 {
attr.Value = msgAttr.Value
cols["value"] = msgAttr.Value
result[key].Value = msgAttr.Value
isChange = true
}
if msgAttr.Metadata != nil {
msgMetaJSON, _ := json.Marshal(msgAttr.Metadata)
attrMetaJSON, _ := json.Marshal(attr.Metadata)
if strings.Compare(string(msgMetaJSON), string(attrMetaJSON)) != 0 {
cols["attr_type"] = msgAttr.Metadata.Type
meta := dttype.CopyMsgAttr(msgAttr)
attr.Metadata = meta.Metadata
msgAttr.Metadata.Type = ""
metaJSON, _ := json.Marshal(msgAttr.Metadata)
cols["metadata"] = string(metaJSON)
msgAttr.Metadata.Type = cols["attr_type"].(string)
result[key].Metadata = meta.Metadata
isChange = true
}
}
if msgAttr.Optional != nil {
if *msgAttr.Optional != *attr.Optional && *attr.Optional {
optional := *msgAttr.Optional
cols["optional"] = optional
attr.Optional = &optional
result[key].Optional = &optional
isChange = true
}
}
if isChange {
update = append(update, dtclient.DeviceAttrUpdate{DeviceID: deviceID, Name: key, Cols: cols})
} else {
delete(result, key)
}
} else {
deviceAttr := dttype.MsgAttrToDeviceAttr(key, msgAttr)
deviceAttr.DeviceID = deviceID
deviceAttr.Value = msgAttr.Value
if msgAttr.Optional != nil {
optional := *msgAttr.Optional
deviceAttr.Optional = optional
}
if msgAttr.Metadata != nil {
//todo
deviceAttr.AttrType = msgAttr.Metadata.Type
msgAttr.Metadata.Type = ""
metaJSON, _ := json.Marshal(msgAttr.Metadata)
msgAttr.Metadata.Type = deviceAttr.AttrType
deviceAttr.Metadata = string(metaJSON)
}
add = append(add, deviceAttr)
attrs[key] = msgAttr
result[key] = msgAttr
}
}
if dealType > 0 {
for key := range attrs {
if _, exist := msgAttrs[key]; !exist {
deletes = append(deletes, dtclient.DeviceDelete{DeviceID: deviceID, Name: key})
result[key] = nil
}
}
for _, v := range deletes {
delete(attrs, v.Name)
}
}
return dttype.DealAttrResult{Add: add, Delete: deletes, Update: update, Result: result, Err: nil}
}
/*
Copyright 2022 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dtmanager
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"k8s.io/klog/v2"
"github.com/kubeedge/api/apis/devices/v1beta1"
pb "github.com/kubeedge/api/apis/dmi/v1beta1"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/devicecontroller/constants"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dmiclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dmiserver"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
"github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao"
"github.com/kubeedge/kubeedge/pkg/util"
)
// DMIWorker deal dmi event
type DMIWorker struct {
Worker
Group string
dmiCache *dmiserver.DMICache
//dmiActionCallBack map for action to callback
dmiActionCallBack map[string]CallBack
}
func (dw *DMIWorker) init() {
dw.dmiCache = &dmiserver.DMICache{
MapperMu: &sync.Mutex{},
DeviceMu: &sync.Mutex{},
DeviceModelMu: &sync.Mutex{},
MapperList: make(map[string]*pb.MapperInfo),
DeviceList: make(map[string]*v1beta1.Device),
DeviceModelList: make(map[string]*v1beta1.DeviceModel),
}
dw.initDMIActionCallBack()
dw.initDeviceModelInfoFromDB()
dw.initDeviceInfoFromDB()
dw.initDeviceMapperInfoFromDB()
}
// Start worker
func (dw DMIWorker) Start() {
klog.Infoln("dmi worker start")
dw.init()
go dmiserver.StartDMIServer(dw.dmiCache)
for {
select {
case msg, ok := <-dw.ReceiverChan:
if !ok {
return
}
if dtMsg, isDTMessage := msg.(*dttype.DTMessage); isDTMessage {
if fn, exist := dw.dmiActionCallBack[dtMsg.Action]; exist {
err := fn(dw.DTContexts, dtMsg.Identity, dtMsg.Msg)
if err != nil {
klog.Errorf("DMIModule deal %s event failed: %v", dtMsg.Action, err)
}
} else {
klog.Errorf("DMIModule deal %s event failed, not found callback", dtMsg.Action)
}
}
case v, ok := <-dw.HeartBeatChan:
if !ok {
return
}
if err := dw.DTContexts.HeartBeat(dw.Group, v); err != nil {
return
}
}
}
}
func (dw *DMIWorker) initDMIActionCallBack() {
dw.dmiActionCallBack = make(map[string]CallBack)
dw.dmiActionCallBack[dtcommon.MetaDeviceOperation] = dw.dealMetaDeviceOperation
}
func (dw *DMIWorker) dealMetaDeviceOperation(_ *dtcontext.DTContext, _ string, msg interface{}) error {
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
resources := strings.Split(message.Router.Resource, "/")
if len(resources) != 3 {
return fmt.Errorf("wrong resources %s", message.Router.Resource)
}
var device v1beta1.Device
var dm v1beta1.DeviceModel
switch resources[1] {
case constants.ResourceTypeDevice:
err := json.Unmarshal(message.Content.([]byte), &device)
if err != nil {
return fmt.Errorf("invalid message content with err: %+v", err)
}
deviceID := util.GetResourceID(device.Namespace, device.Name)
switch message.GetOperation() {
case model.InsertOperation:
dw.dmiCache.DeviceMu.Lock()
dw.dmiCache.DeviceList[deviceID] = &device
dw.dmiCache.DeviceMu.Unlock()
err = dmiclient.DMIClientsImp.RegisterDevice(&device)
if err != nil {
klog.Errorf("add device %s failed with err: %v", device.Name, err)
return err
}
case model.DeleteOperation:
err = dmiclient.DMIClientsImp.RemoveDevice(&device)
if err != nil {
klog.Errorf("delete device %s failed with err: %v", device.Name, err)
return err
}
dw.dmiCache.DeviceMu.Lock()
delete(dw.dmiCache.DeviceList, deviceID)
dw.dmiCache.DeviceMu.Unlock()
case model.UpdateOperation:
dw.dmiCache.DeviceMu.Lock()
dw.dmiCache.DeviceList[deviceID] = &device
dw.dmiCache.DeviceMu.Unlock()
err = dmiclient.DMIClientsImp.UpdateDevice(&device)
if err != nil {
klog.Errorf("udpate device %s failed with err: %v", device.Name, err)
return err
}
default:
klog.Warningf("unsupported operation %s", message.GetOperation())
}
case constants.ResourceTypeDeviceModel:
err := json.Unmarshal(message.Content.([]byte), &dm)
if err != nil {
return fmt.Errorf("invalid message content with err: %+v", err)
}
dmID := util.GetResourceID(dm.Namespace, dm.Name)
switch message.GetOperation() {
case model.InsertOperation:
dw.dmiCache.DeviceModelMu.Lock()
dw.dmiCache.DeviceModelList[dmID] = &dm
dw.dmiCache.DeviceModelMu.Unlock()
err = dmiclient.DMIClientsImp.CreateDeviceModel(&dm)
if err != nil {
klog.Errorf("add device model %s failed with err: %v", dm.Name, err)
return err
}
case model.DeleteOperation:
err = dmiclient.DMIClientsImp.RemoveDeviceModel(&dm)
if err != nil {
klog.Errorf("delete device model %s failed with err: %v", dm.Name, err)
return err
}
dw.dmiCache.DeviceModelMu.Lock()
delete(dw.dmiCache.DeviceModelList, dmID)
dw.dmiCache.DeviceModelMu.Unlock()
case model.UpdateOperation:
dw.dmiCache.DeviceModelMu.Lock()
dw.dmiCache.DeviceModelList[dmID] = &dm
dw.dmiCache.DeviceModelMu.Unlock()
err = dmiclient.DMIClientsImp.UpdateDeviceModel(&dm)
if err != nil {
klog.Errorf("update device model %s failed with err: %v", dm.Name, err)
return err
}
default:
klog.Warningf("unsupported operation %s", message.GetOperation())
}
default:
klog.Warningf("unsupported resource type %s", resources[3])
}
return nil
}
func (dw *DMIWorker) initDeviceModelInfoFromDB() {
metas, err := dao.QueryMeta("type", constants.ResourceTypeDeviceModel)
if err != nil {
klog.Errorf("fail to init device model info from db with err: %v", err)
return
}
for _, meta := range *metas {
deviceModel := v1beta1.DeviceModel{}
if err := json.Unmarshal([]byte(meta), &deviceModel); err != nil {
klog.Errorf("fail to unmarshal device model info from db with err: %v", err)
return
}
deviceModelID := util.GetResourceID(deviceModel.Namespace, deviceModel.Name)
dw.dmiCache.DeviceModelMu.Lock()
dw.dmiCache.DeviceModelList[deviceModelID] = &deviceModel
dw.dmiCache.DeviceModelMu.Unlock()
}
klog.Infoln("success to init device model info from db")
}
func (dw *DMIWorker) initDeviceInfoFromDB() {
metas, err := dao.QueryMeta("type", constants.ResourceTypeDevice)
if err != nil {
klog.Errorf("fail to init device info from db with err: %v", err)
return
}
for _, meta := range *metas {
device := v1beta1.Device{}
if err := json.Unmarshal([]byte(meta), &device); err != nil {
klog.Errorf("fail to unmarshal device info from db with err: %v", err)
return
}
deviceID := util.GetResourceID(device.Namespace, device.Name)
dw.dmiCache.DeviceMu.Lock()
dw.dmiCache.DeviceList[deviceID] = &device
dw.dmiCache.DeviceMu.Unlock()
}
klog.Infoln("success to init device info from db")
}
func (dw *DMIWorker) initDeviceMapperInfoFromDB() {
metas, err := dao.QueryMeta("type", constants.ResourceTypeDeviceMapper)
if err != nil {
klog.Errorf("fail to init device mapper info from db with err: %v", err)
return
}
for _, meta := range *metas {
deviceMapper := pb.MapperInfo{}
if err := json.Unmarshal([]byte(meta), &deviceMapper); err != nil {
klog.Errorf("fail to unmarshal device mapper info from db with err: %v", err)
return
}
dw.dmiCache.MapperMu.Lock()
dw.dmiCache.MapperList[deviceMapper.Name] = &deviceMapper
dw.dmiCache.MapperMu.Unlock()
}
klog.Infoln("success to init device mapper info from db")
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package dtmanager
import (
"github.com/kubeedge/beehive/pkg/core/model"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
)
var fuzzActions = map[int]string {
0: "dealTwinUpdate",
1: "dealTwinGet",
2: "dealTwinSync",
3: "dealDeviceAttrUpdate",
4: "dealDeviceStateUpdate",
5: "dealSendToCloud",
6: "dealSendToEdge",
7: "dealLifeCycle",
8: "dealConfirm",
9: "dealMembershipGet",
10: "dealMembershipUpdate",
11: "dealMembershipDetail",
}
func FuzzdealTwinActions(data []byte) int {
f := fuzz.NewConsumer(data)
device, err := f.GetString()
if err != nil {
return 0
}
content, err := f.GetBytes()
if err != nil {
return 0
}
actionType, err := f.GetInt()
if err != nil {
return 0
}
msg := &model.Message{
Content: content,
}
context, _ := dtcontext.InitDTContext()
switch fuzzActions[actionType%len(fuzzActions)] {
case "dealTwinUpdate":
dealTwinUpdate(context, device, msg)
case "dealTwinGet":
dealTwinGet(context, device, msg)
case "dealTwinSync":
dealTwinSync(context, device, msg)
case "dealDeviceAttrUpdate":
dealDeviceAttrUpdate(context, device, msg)
case "dealDeviceStateUpdate":
dealDeviceStateUpdate(context, device, msg)
case "dealSendToCloud":
dealSendToCloud(context, device, msg)
case "dealSendToEdge":
dealSendToEdge(context, device, msg)
case "dealLifeCycle":
dealLifeCycle(context, device, msg)
case "dealConfirm":
dealConfirm(context, device, msg)
case "dealMembershipGet":
dealMembershipGet(context, device, msg)
case "dealMembershipUpdate":
dealMembershipUpdate(context, device, msg)
case "dealMembershipDetail":
dealMembershipDetail(context, device, msg)
}
return 1
}
package dtmanager
import (
"errors"
"fmt"
"strings"
"sync"
"time"
//"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
messagepkg "github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
var (
//memActionCallBack map for action to callback
memActionCallBack map[string]CallBack
)
// MemWorker deal membership event
type MemWorker struct {
Worker
Group string
}
// Start worker
func (mw MemWorker) Start() {
initMemActionCallBack()
for {
select {
case msg, ok := <-mw.ReceiverChan:
if !ok {
return
}
if dtMsg, isDTMessage := msg.(*dttype.DTMessage); isDTMessage {
if fn, exist := memActionCallBack[dtMsg.Action]; exist {
err := fn(mw.DTContexts, dtMsg.Identity, dtMsg.Msg)
if err != nil {
//klog.Errorf("MemModule deal %s event failed: %v", dtMsg.Action, err)
}
} else {
//klog.Errorf("MemModule deal %s event failed, not found callback", dtMsg.Action)
}
}
case v, ok := <-mw.HeartBeatChan:
if !ok {
return
}
if err := mw.DTContexts.HeartBeat(mw.Group, v); err != nil {
return
}
}
}
}
func initMemActionCallBack() {
memActionCallBack = make(map[string]CallBack)
memActionCallBack[dtcommon.MemGet] = dealMembershipGet
memActionCallBack[dtcommon.MemUpdated] = dealMembershipUpdate
memActionCallBack[dtcommon.MemDetailResult] = dealMembershipDetail
}
func getRemoveList(context *dtcontext.DTContext, devices []dttype.Device) []dttype.Device {
var toRemove []dttype.Device
context.DeviceList.Range(func(key interface{}, value interface{}) bool {
isExist := false
for _, v := range devices {
if strings.Compare(v.ID, key.(string)) == 0 {
isExist = true
break
}
}
if !isExist {
toRemove = append(toRemove, dttype.Device{ID: key.(string)})
}
return true
})
return toRemove
}
func dealMembershipDetail(context *dtcontext.DTContext, _ string, msg interface{}) error {
//klog.Info("Deal node detail info")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
contentData, ok := message.Content.([]byte)
if !ok {
return errors.New("assertion failed")
}
devices, err := dttype.UnmarshalMembershipDetail(contentData)
if err != nil {
//klog.Errorf("Unmarshal membership info failed , err: %#v", err)
return err
}
baseMessage := dttype.BaseMessage{EventID: devices.EventID}
defer context.UnlockAll()
context.LockAll()
var toRemove []dttype.Device
isDelta := false
addDevice(context, devices.Devices, baseMessage, isDelta)
toRemove = getRemoveList(context, devices.Devices)
if len(toRemove) != 0 {
removeDevice(context, toRemove, baseMessage, isDelta)
}
//klog.Info("Deal node detail info successful")
return nil
}
func dealMembershipUpdate(context *dtcontext.DTContext, _ string, msg interface{}) error {
//klog.Infof("Membership event")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
contentData, ok := message.Content.([]byte)
if !ok {
return errors.New("assertion failed")
}
updateEdgeGroups, err := dttype.UnmarshalMembershipUpdate(contentData)
if err != nil {
//klog.Errorf("Unmarshal membership info failed , err: %#v", err)
return err
}
baseMessage := dttype.BaseMessage{EventID: updateEdgeGroups.EventID}
if len(updateEdgeGroups.AddDevices) > 0 {
//add device
addDevice(context, updateEdgeGroups.AddDevices, baseMessage, false)
}
if len(updateEdgeGroups.RemoveDevices) > 0 {
// delete device
removeDevice(context, updateEdgeGroups.RemoveDevices, baseMessage, false)
}
return nil
}
func dealMembershipGet(context *dtcontext.DTContext, _ string, msg interface{}) error {
//klog.Info("MEMBERSHIP EVENT")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
contentData, ok := message.Content.([]byte)
if !ok {
return errors.New("assertion failed")
}
return dealMembershipGetInner(context, contentData)
}
// addDevice add device to the edge group
func addDevice(context *dtcontext.DTContext, toAdd []dttype.Device, baseMessage dttype.BaseMessage, delta bool) {
//klog.Info("Add devices to edge group")
if !delta {
baseMessage.EventID = ""
}
if len(toAdd) == 0 {
return
}
dealType := 0
if !delta {
dealType = 1
}
for _, device := range toAdd {
//if device has existed, step out
deviceInstance, isDeviceExist := context.GetDevice(device.ID)
if isDeviceExist {
if delta {
//klog.Errorf("Add device %s failed, has existed", device.ID)
continue
}
if _, err := UpdateDeviceAttr(context, device.ID, device.Attributes, baseMessage, dealType); err != nil {
// TODO: handle err
//klog.Error(err)
}
if err := DealDeviceTwin(context, device.ID, baseMessage.EventID, device.Twin, dealType); err != nil {
// TODO: handle err
//klog.Error(err)
}
//todo sync twin
continue
}
var deviceMutex sync.Mutex
context.DeviceMutex.Store(device.ID, &deviceMutex)
if delta {
context.Lock(device.ID)
}
deviceInstance = &dttype.Device{ID: device.ID, Name: device.Name, Description: device.Description, State: device.State}
context.DeviceList.Store(device.ID, deviceInstance)
//write to sqlite
var err error
adds := make([]dtclient.Device, 0)
addAttr := make([]dtclient.DeviceAttr, 0)
addTwin := make([]dtclient.DeviceTwin, 0)
adds = append(adds, dtclient.Device{
ID: device.ID,
Name: device.Name,
Description: device.Description,
State: device.State})
for i := 1; i <= dtcommon.RetryTimes; i++ {
err = dtclient.AddDeviceTrans(adds, addAttr, addTwin)
if err == nil {
break
}
time.Sleep(dtcommon.RetryInterval)
}
if err != nil {
//klog.Errorf("Add device %s failed due to some error ,err: %#v", device.ID, err)
context.DeviceList.Delete(device.ID)
context.Unlock(device.ID)
continue
//todo
}
if device.Twin != nil {
//klog.Infof("Add device twin during first adding device %s", device.ID)
if err := DealDeviceTwin(context, device.ID, baseMessage.EventID, device.Twin, dealType); err != nil {
// TODO: handle err
//klog.Error(err)
}
}
if device.Attributes != nil {
//klog.Infof("Add device attr during first adding device %s", device.ID)
if _, err := UpdateDeviceAttr(context, device.ID, device.Attributes, baseMessage, dealType); err != nil {
// TODO: handle err
//klog.Error(err)
}
}
topic := dtcommon.MemETPrefix + context.NodeName + dtcommon.MemETUpdateSuffix
baseMessage := dttype.BuildBaseMessage()
addDeviceDevices := make([]dttype.Device, 0)
addDeviceDevices = append(addDeviceDevices, device)
addDeviceResult := dttype.MembershipUpdate{BaseMessage: baseMessage, AddDevices: addDeviceDevices}
result, err := dttype.MarshalMembershipUpdate(addDeviceResult)
if err != nil {
//klog.Errorf("add device %s failed, marshal membership err: %s", device.ID, err)
} else {
err := context.Send("", dtcommon.SendToEdge, dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, result))
if err != nil {
// TODO: handle err
//klog.Error(err)
}
}
if delta {
context.Unlock(device.ID)
}
}
}
// removeDevice remove device from the edge group
func removeDevice(context *dtcontext.DTContext, toRemove []dttype.Device, baseMessage dttype.BaseMessage, delta bool) {
//klog.Info("Begin to remove devices")
if !delta && baseMessage.EventID != "" {
baseMessage.EventID = ""
}
for _, device := range toRemove {
//update sqlite
_, deviceExist := context.GetDevice(device.ID)
if !deviceExist {
//klog.Errorf("Remove device %s failed, not existed", device.ID)
continue
}
if delta {
context.Lock(device.ID)
}
deletes := make([]string, 0)
deletes = append(deletes, device.ID)
for i := 1; i <= dtcommon.RetryTimes; i++ {
err := dtclient.DeleteDeviceTrans(deletes)
if err != nil {
//klog.Errorf("Delete document of device %s failed at %d time, err: %#v", device.ID, i, err)
} else {
//klog.Infof("Delete document of device %s successful", device.ID)
break
}
time.Sleep(dtcommon.RetryInterval)
}
//todo
context.DeviceList.Delete(device.ID)
context.DeviceMutex.Delete(device.ID)
if delta {
context.Unlock(device.ID)
}
topic := dtcommon.MemETPrefix + context.NodeName + dtcommon.MemETUpdateSuffix
baseMessage := dttype.BuildBaseMessage()
RemoveDevices := make([]dttype.Device, 0)
RemoveDevices = append(RemoveDevices, device)
deleteResult := dttype.MembershipUpdate{BaseMessage: baseMessage, RemoveDevices: RemoveDevices}
result, err := dttype.MarshalMembershipUpdate(deleteResult)
if err != nil {
//klog.Errorf("Remove device %s failed, marshal membership err: %s", device.ID, err)
continue
}
err = context.Send("", dtcommon.SendToEdge, dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, result))
if err != nil {
// TODO: handle err
//klog.Error(err)
}
//klog.Infof("Remove device %s successful", device.ID)
}
}
// dealMembershipGetInner deal get membership event
func dealMembershipGetInner(context *dtcontext.DTContext, payload []byte) error {
//klog.Info("Deal getting membership event")
result := []byte("")
edgeGet, err := dttype.UnmarshalBaseMessage(payload)
para := dttype.Parameter{}
now := time.Now().UnixNano() / 1e6
if err != nil {
//klog.Errorf("Unmarshal get membership info %s failed , err: %#v", string(payload), err)
para.Code = dtcommon.BadRequestCode
para.Reason = fmt.Sprintf("Unmarshal get membership info %s failed , err: %#v", string(payload), err)
var jsonErr error
result, jsonErr = dttype.BuildErrorResult(para)
if jsonErr != nil {
//klog.Errorf("Unmarshal error result error, err: %v", jsonErr)
}
} else {
para.EventID = edgeGet.EventID
var devices []*dttype.Device
context.DeviceList.Range(func(key interface{}, value interface{}) bool {
device, ok := value.(*dttype.Device)
if !ok {
//klog.Warningf("deviceList found invalid key-value pair, key: %s, value: %#v", key, value)
} else {
devices = append(devices, device)
}
return true
})
payload, err = dttype.BuildMembershipGetResult(dttype.BaseMessage{EventID: edgeGet.EventID, Timestamp: now}, devices)
if err != nil {
//klog.Errorf("Marshal membership failed while deal get membership ,err: %#v", err)
} else {
result = payload
}
}
topic := dtcommon.MemETPrefix + context.NodeName + dtcommon.MemETGetResultSuffix
//klog.Info("Deal getting membership successful and send the result")
err = context.Send("",
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, result))
if err != nil {
return err
}
return nil
}
// SyncDeviceFromSqlite sync device from sqlite
func SyncDeviceFromSqlite(context *dtcontext.DTContext, deviceID string) error {
//klog.Infof("Sync device detail info from DB of device %s", deviceID)
_, exist := context.GetDevice(deviceID)
if !exist {
var deviceMutex sync.Mutex
context.DeviceMutex.Store(deviceID, &deviceMutex)
}
devices, err := dtclient.QueryDevice("id", deviceID)
if err != nil {
//klog.Errorf("query device failed, id: %s, err: %v", deviceID, err)
return err
}
if len(*devices) == 0 {
return errors.New("not found device")
}
dbDoc := (*devices)[0]
deviceAttr, err := dtclient.QueryDeviceAttr("deviceid", deviceID)
if err != nil {
//klog.Errorf("query device attr failed, id: %s, err: %v", deviceID, err)
return err
}
deviceTwin, err := dtclient.QueryDeviceTwin("deviceid", deviceID)
if err != nil {
//klog.Errorf("query device twin failed, id: %s, err: %v", deviceID, err)
return err
}
context.DeviceList.Store(deviceID, &dttype.Device{
ID: deviceID,
Name: dbDoc.Name,
Description: dbDoc.Description,
State: dbDoc.State,
LastOnline: dbDoc.LastOnline,
Attributes: dttype.DeviceAttrToMsgAttr(*deviceAttr),
Twin: dttype.DeviceTwinToMsgTwin(*deviceTwin)})
return nil
}
package dtmanager
import (
"encoding/json"
"errors"
"fmt"
"strings"
"sync"
"time"
//"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
messagepkg "github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
const (
//RestDealType update from mqtt
RestDealType = 0
//SyncDealType update from cloud sync
SyncDealType = 1
//DetailDealType detail update from cloud
DetailDealType = 2
//SyncTwinDeleteDealType twin delete when sync
SyncTwinDeleteDealType = 3
//DealActual deal actual
DealActual = 1
//DealExpected deal expected
DealExpected = 0
stringType = "string"
)
var (
//twinActionCallBack map for action to callback
twinActionCallBack map[string]CallBack
initTwinActionCallBackOnce sync.Once
)
// TwinWorker deal twin event
type TwinWorker struct {
Worker
Group string
}
// Start worker
func (tw TwinWorker) Start() {
initTwinActionCallBack()
for {
select {
case msg, ok := <-tw.ReceiverChan:
if !ok {
return
}
if dtMsg, isDTMessage := msg.(*dttype.DTMessage); isDTMessage {
if fn, exist := twinActionCallBack[dtMsg.Action]; exist {
err := fn(tw.DTContexts, dtMsg.Identity, dtMsg.Msg)
if err != nil {
//klog.Errorf("TwinModule deal %s event failed", dtMsg.Action)
}
} else {
//klog.Errorf("TwinModule deal %s event failed, not found callback", dtMsg.Action)
}
}
case v, ok := <-tw.HeartBeatChan:
if !ok {
return
}
if err := tw.DTContexts.HeartBeat(tw.Group, v); err != nil {
return
}
}
}
}
func initTwinActionCallBack() {
initTwinActionCallBackOnce.Do(func() {
twinActionCallBack = make(map[string]CallBack)
twinActionCallBack[dtcommon.TwinUpdate] = dealTwinUpdate
twinActionCallBack[dtcommon.TwinGet] = dealTwinGet
twinActionCallBack[dtcommon.TwinCloudSync] = dealTwinSync
})
}
func dealTwinSync(context *dtcontext.DTContext, resource string, msg interface{}) error {
//klog.Info("Twin Sync EVENT")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
result := []byte("")
content, ok := message.Content.([]byte)
if !ok {
return errors.New("invalid message content")
}
msgTwin, err := dttype.UnmarshalDeviceTwinUpdate(content)
if err != nil {
//klog.Errorf("Unmarshal update request body failed, err: %#v", err)
if err := dealUpdateResult(context, "", "", dtcommon.BadRequestCode,
errors.New("unmarshal update request body failed, Please check the request"), result); err != nil {
// TODO: handle error
//klog.Error(err)
}
return err
}
//klog.Infof("Begin to update twin of the device %s", resource)
eventID := msgTwin.EventID
context.Lock(resource)
if err := DealDeviceTwin(context, resource, eventID, msgTwin.Twin, SyncDealType); err != nil {
// TODO: handle error
//klog.Error(err)
}
context.Unlock(resource)
//todo send ack
return nil
}
func dealTwinGet(context *dtcontext.DTContext, resource string, msg interface{}) error {
//klog.Info("Twin Get EVENT")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
content, ok := message.Content.([]byte)
if !ok {
return errors.New("invalid message content")
}
if err := DealGetTwin(context, resource, content); err != nil {
// TODO: handle error
//klog.Error(err)
}
return nil
}
func dealTwinUpdate(context *dtcontext.DTContext, resource string, msg interface{}) error {
//klog.Info("Twin Update EVENT")
message, ok := msg.(*model.Message)
if !ok {
return errors.New("msg not Message type")
}
content, ok := message.Content.([]byte)
if !ok {
return errors.New("invalid message content")
}
context.Lock(resource)
Updated(context, resource, content)
context.Unlock(resource)
return nil
}
// Updated update the snapshot
func Updated(context *dtcontext.DTContext, deviceID string, payload []byte) {
result := []byte("")
msg, err := dttype.UnmarshalDeviceTwinUpdate(payload)
if err != nil {
//klog.Errorf("Unmarshal update request body failed, err: %#v", err)
if err := dealUpdateResult(context, "", "", dtcommon.BadRequestCode, err, result); err != nil {
// TODO: handle error
//klog.Error(err)
}
return
}
//klog.Infof("Begin to update twin of the device %s", deviceID)
eventID := msg.EventID
if err := DealDeviceTwin(context, deviceID, eventID, msg.Twin, RestDealType); err != nil {
// TODO: handle error
//klog.Error(err)
}
}
// DealDeviceTwin deal device twin
func DealDeviceTwin(context *dtcontext.DTContext, deviceID string, eventID string, msgTwin map[string]*dttype.MsgTwin, dealType int) error {
//klog.Infof("Begin to deal device twin of the device %s", deviceID)
now := time.Now().UnixNano() / 1e6
result := []byte("")
device, isExist := context.GetDevice(deviceID)
if !isExist {
//klog.Errorf("Update twin rejected due to the device %s is not existed", deviceID)
if err := dealUpdateResult(context, deviceID, eventID, dtcommon.NotFoundCode,
errors.New("update rejected due to the device is not existed"), result); err != nil {
// TODO: handle error
//klog.Error(err)
}
return errors.New("update rejected due to the device is not existed")
}
content := msgTwin
var err error
if content == nil {
//klog.Errorf("Update twin of device %s error, key:twin does not exist", deviceID)
err = dttype.ErrorUpdate
if err := dealUpdateResult(context, deviceID, eventID, dtcommon.BadRequestCode, err, result); err != nil {
// TODO: handle error
//klog.Error(err)
}
return err
}
dealTwinResult := DealMsgTwin(context, deviceID, content, dealType)
add, deletes, update := dealTwinResult.Add, dealTwinResult.Delete, dealTwinResult.Update
if dealType == RestDealType && dealTwinResult.Err != nil {
if err := SyncDeviceFromSqlite(context, deviceID); err != nil {
// TODO: handle error
//klog.Error(err)
}
err = dealTwinResult.Err
updateResult, _ := dttype.BuildDeviceTwinResult(dttype.BaseMessage{EventID: eventID, Timestamp: now}, dealTwinResult.Result, 0)
if err := dealUpdateResult(context, deviceID, eventID, dtcommon.BadRequestCode, err, updateResult); err != nil {
// TODO: handle error
//klog.Error(err)
}
return err
}
if len(add) != 0 || len(deletes) != 0 || len(update) != 0 {
for i := 1; i <= dtcommon.RetryTimes; i++ {
err = dtclient.DeviceTwinTrans(add, deletes, update)
if err == nil {
break
}
time.Sleep(dtcommon.RetryInterval)
}
if err != nil {
if err := SyncDeviceFromSqlite(context, deviceID); err != nil {
// TODO: handle error
//klog.Error(err)
}
//klog.Errorf("Update device twin failed due to writing sql error: %v", err)
}
}
if dealType == RestDealType {
updateResult, _ := dttype.BuildDeviceTwinResult(dttype.BaseMessage{EventID: eventID, Timestamp: now}, dealTwinResult.Result, dealType)
if err := dealUpdateResult(context, deviceID, eventID, dtcommon.InternalErrorCode, err, updateResult); err != nil {
// TODO: handle error
//klog.Error(err)
}
if err != nil { // The error returned by dtclient.DeviceTwinTrans()
return err
}
}
if len(dealTwinResult.Document) > 0 {
if err := dealDocument(context, deviceID, dttype.BaseMessage{EventID: eventID, Timestamp: now}, dealTwinResult.Document); err != nil {
// TODO: handle error
//klog.Error(err)
}
}
delta, ok := dttype.BuildDeviceTwinDelta(dttype.BuildBaseMessage(), device.Twin)
if ok {
if err := dealDelta(context, deviceID, delta); err != nil {
// TODO: handle error
//klog.Error(err)
}
}
if len(dealTwinResult.SyncResult) > 0 {
if err := dealSyncResult(context, deviceID, dttype.BuildBaseMessage(), dealTwinResult.SyncResult); err != nil {
// TODO: handle error
//klog.Error(err)
}
}
return nil
}
// dealUpdateResult build update result and send result, if success send the current state
func dealUpdateResult(context *dtcontext.DTContext, deviceID string, eventID string, code int, err error, payload []byte) error {
//klog.Infof("Deal update result of device %s: Build and send result", deviceID)
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.TwinETUpdateResultSuffix
reason := ""
para := dttype.Parameter{
EventID: eventID,
Code: code,
Reason: reason}
result := []byte("")
var jsonErr error
if err == nil {
result = payload
} else {
para.Reason = err.Error()
result, jsonErr = dttype.BuildErrorResult(para)
if jsonErr != nil {
//klog.Errorf("Unmarshal error result of device %s error, err: %v", deviceID, jsonErr)
return jsonErr
}
}
//klog.Infof("Deal update result of device %s: send result", deviceID)
return context.Send("",
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, result))
}
// dealDelta send delta
func dealDelta(context *dtcontext.DTContext, deviceID string, payload []byte) error {
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.TwinETDeltaSuffix
//klog.Infof("Deal delta of device %s: send delta", deviceID)
return context.Send("",
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, payload))
}
// dealSyncResult build and send sync result, is delta update
func dealSyncResult(context *dtcontext.DTContext, deviceID string, baseMessage dttype.BaseMessage, twin map[string]*dttype.MsgTwin) error {
//klog.Infof("Deal sync result of device %s: sync with cloud", deviceID)
resource := "device/" + deviceID + dtcommon.TwinETEdgeSyncSuffix
return context.Send("",
dtcommon.SendToCloud,
dtcommon.CommModule,
context.BuildModelMessage("resource", "", resource, model.UpdateOperation, dttype.DeviceTwinResult{BaseMessage: baseMessage, Twin: twin}))
}
// dealDocument build document and save current state as last state, update sqlite
func dealDocument(context *dtcontext.DTContext, deviceID string, baseMessage dttype.BaseMessage, twinDocument map[string]*dttype.TwinDoc) error {
//klog.Infof("Deal document of device %s: build and send document", deviceID)
payload, _ := dttype.BuildDeviceTwinDocument(baseMessage, twinDocument)
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.TwinETDocumentSuffix
//klog.Infof("Deal document of device %s: send document", deviceID)
return context.Send("",
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, payload))
}
// DealGetTwin deal get twin event
func DealGetTwin(context *dtcontext.DTContext, deviceID string, payload []byte) error {
//klog.Info("Deal the event of getting twin")
msg := []byte("")
para := dttype.Parameter{}
edgeGet, err := dttype.UnmarshalBaseMessage(payload)
if err != nil {
//klog.Errorf("Unmarshal twin info %s failed , err: %#v", string(payload), err)
para.Code = dtcommon.BadRequestCode
para.Reason = fmt.Sprintf("Unmarshal twin info %s failed , err: %#v", string(payload), err)
var jsonErr error
msg, jsonErr = dttype.BuildErrorResult(para)
if jsonErr != nil {
//klog.Errorf("Unmarshal error result error, err: %v", jsonErr)
return jsonErr
}
} else {
para.EventID = edgeGet.EventID
doc, exist := context.GetDevice(deviceID)
if !exist {
//klog.Errorf("Device %s not found while getting twin", deviceID)
para.Code = dtcommon.NotFoundCode
para.Reason = fmt.Sprintf("Device %s not found while getting twin", deviceID)
var jsonErr error
msg, jsonErr = dttype.BuildErrorResult(para)
if jsonErr != nil {
//klog.Errorf("Unmarshal error result error, err: %v", jsonErr)
return jsonErr
}
} else {
now := time.Now().UnixNano() / 1e6
var err error
msg, err = dttype.BuildDeviceTwinResult(dttype.BaseMessage{EventID: edgeGet.EventID, Timestamp: now}, doc.Twin, RestDealType)
if err != nil {
//klog.Errorf("Build state while deal get twin err: %#v", err)
para.Code = dtcommon.InternalErrorCode
para.Reason = fmt.Sprintf("Build state while deal get twin err: %#v", err)
var jsonErr error
msg, jsonErr = dttype.BuildErrorResult(para)
if jsonErr != nil {
//klog.Errorf("Unmarshal error result error, err: %v", jsonErr)
return jsonErr
}
}
}
}
topic := dtcommon.DeviceETPrefix + deviceID + dtcommon.TwinETGetResultSuffix
//klog.Infof("Deal the event of getting twin of device %s: send result ", deviceID)
return context.Send("",
dtcommon.SendToEdge,
dtcommon.CommModule,
context.BuildModelMessage(modules.BusGroup, "", topic, messagepkg.OperationPublish, msg))
}
// dealtype 0:update ,2:cloud_update,1:detail result,3:deleted
func dealVersion(version *dttype.TwinVersion, reqVersion *dttype.TwinVersion, dealType int) (bool, error) {
if dealType == RestDealType {
version.EdgeVersion = version.EdgeVersion + 1
} else if dealType >= SyncDealType {
if reqVersion == nil {
if dealType == SyncTwinDeleteDealType {
return true, nil
}
return false, errors.New("version not allowed be nil while syncing")
}
if version.CloudVersion > reqVersion.CloudVersion {
return false, errors.New("version not allowed")
}
if version.EdgeVersion > reqVersion.EdgeVersion {
return false, errors.New("not allowed to sync due to version conflict")
}
version.CloudVersion = reqVersion.CloudVersion
version.EdgeVersion = reqVersion.EdgeVersion
}
return true, nil
}
func dealTwinDelete(returnResult *dttype.DealTwinResult, deviceID string, key string, twin *dttype.MsgTwin, msgTwin *dttype.MsgTwin, dealType int) {
document := returnResult.Document
document[key] = &dttype.TwinDoc{}
copytwin := dttype.CopyMsgTwin(twin, true)
document[key].LastState = ©twin
cols := make(map[string]interface{})
syncResult := returnResult.SyncResult
syncResult[key] = &dttype.MsgTwin{}
update := returnResult.Update
isChange := false
if msgTwin == nil && dealType == RestDealType && *twin.Optional || dealType >= SyncDealType && strings.Compare(msgTwin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
if twin.Metadata != nil && strings.Compare(twin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
return
}
if dealType != RestDealType {
dealType = SyncTwinDeleteDealType
}
hasTwinExpected := true
if twin.ExpectedVersion == nil {
twin.ExpectedVersion = &dttype.TwinVersion{}
hasTwinExpected = false
}
if hasTwinExpected {
expectedVersion := twin.ExpectedVersion
var msgTwinExpectedVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinExpectedVersion = msgTwin.ExpectedVersion
}
ok, _ := dealVersion(expectedVersion, msgTwinExpectedVersion, dealType)
if !ok {
if dealType != RestDealType {
copySync := dttype.CopyMsgTwin(twin, false)
syncResult[key] = ©Sync
delete(document, key)
returnResult.SyncResult = syncResult
return
}
} else {
expectedVersionJSON, _ := json.Marshal(expectedVersion)
cols["expected_version"] = string(expectedVersionJSON)
cols["attr_type"] = dtcommon.TypeDeleted
cols["expected_meta"] = nil
cols["expected"] = nil
if twin.Expected == nil {
twin.Expected = &dttype.TwinValue{}
}
twin.Expected.Value = nil
twin.Expected.Metadata = nil
twin.ExpectedVersion = expectedVersion
twin.Metadata = &dttype.TypeMetadata{Type: dtcommon.TypeDeleted}
if dealType == RestDealType {
copySync := dttype.CopyMsgTwin(twin, false)
syncResult[key] = ©Sync
}
document[key].CurrentState = nil
isChange = true
}
}
hasTwinActual := true
if twin.ActualVersion == nil {
twin.ActualVersion = &dttype.TwinVersion{}
hasTwinActual = false
}
if hasTwinActual {
actualVersion := twin.ActualVersion
var msgTwinActualVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinActualVersion = msgTwin.ActualVersion
}
ok, _ := dealVersion(actualVersion, msgTwinActualVersion, dealType)
if !ok {
if dealType != RestDealType {
copySync := dttype.CopyMsgTwin(twin, false)
syncResult[key] = ©Sync
delete(document, key)
returnResult.SyncResult = syncResult
return
}
} else {
actualVersionJSON, _ := json.Marshal(actualVersion)
cols["actual_version"] = string(actualVersionJSON)
cols["attr_type"] = dtcommon.TypeDeleted
cols["actual_meta"] = nil
cols["actual"] = nil
if twin.Actual == nil {
twin.Actual = &dttype.TwinValue{}
}
twin.Actual.Value = nil
twin.Actual.Metadata = nil
twin.ActualVersion = actualVersion
twin.Metadata = &dttype.TypeMetadata{Type: dtcommon.TypeDeleted}
if dealType == RestDealType {
copySync := dttype.CopyMsgTwin(twin, false)
syncResult[key] = ©Sync
}
document[key].CurrentState = nil
isChange = true
}
}
}
if isChange {
update = append(update, dtclient.DeviceTwinUpdate{DeviceID: deviceID, Name: key, Cols: cols})
returnResult.Update = update
if dealType == RestDealType {
returnResult.Result[key] = nil
returnResult.SyncResult = syncResult
} else {
delete(syncResult, key)
}
returnResult.Document = document
} else {
delete(document, key)
delete(syncResult, key)
}
}
//0:expected ,1 :actual
func isTwinValueDiff(twin *dttype.MsgTwin, msgTwin *dttype.MsgTwin, dealType int) (bool, error) {
hasTwin := false
hasMsgTwin := false
twinValue := twin.Expected
msgTwinValue := msgTwin.Expected
if twinValue != nil {
hasTwin = true
}
if dealType == DealActual {
twinValue = twin.Actual
msgTwinValue = msgTwin.Actual
hasTwin = true
}
if msgTwinValue != nil {
hasMsgTwin = true
}
valueType := stringType
if strings.Compare(twin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
if msgTwin.Metadata != nil {
valueType = msgTwin.Metadata.Type
}
} else {
valueType = twin.Metadata.Type
}
if hasMsgTwin {
if hasTwin {
err := dtcommon.ValidateValue(valueType, *msgTwinValue.Value)
if err != nil {
return false, err
}
return true, nil
}
return true, nil
}
return false, nil
}
func dealTwinCompare(returnResult *dttype.DealTwinResult, deviceID string, key string, twin *dttype.MsgTwin, msgTwin *dttype.MsgTwin, dealType int) error {
//klog.Info("dealtwincompare")
now := time.Now().UnixNano() / 1e6
document := returnResult.Document
document[key] = &dttype.TwinDoc{}
copytwin := dttype.CopyMsgTwin(twin, true)
document[key].LastState = ©twin
if strings.Compare(twin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
document[key].LastState = nil
}
cols := make(map[string]interface{})
syncResult := returnResult.SyncResult
syncResult[key] = &dttype.MsgTwin{}
update := returnResult.Update
isChange := false
isSyncAllow := true
if msgTwin == nil {
return nil
}
expectedOk, expectedErr := isTwinValueDiff(twin, msgTwin, DealExpected)
if expectedOk {
value := msgTwin.Expected.Value
meta := dttype.ValueMetadata{Timestamp: now}
if twin.ExpectedVersion == nil {
twin.ExpectedVersion = &dttype.TwinVersion{}
}
version := twin.ExpectedVersion
var msgTwinExpectedVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinExpectedVersion = msgTwin.ExpectedVersion
}
ok, err := dealVersion(version, msgTwinExpectedVersion, dealType)
if !ok {
// if reject the sync, set the syncResult and then send the edge_updated msg
if dealType != RestDealType {
syncResult[key].Expected = &dttype.TwinValue{Value: twin.Expected.Value, Metadata: twin.Expected.Metadata}
syncResult[key].ExpectedVersion = &dttype.TwinVersion{CloudVersion: twin.ExpectedVersion.CloudVersion, EdgeVersion: twin.ExpectedVersion.EdgeVersion}
syncOptional := *twin.Optional
syncResult[key].Optional = &syncOptional
metaJSON, _ := json.Marshal(twin.Metadata)
var meta dttype.TypeMetadata
if err := json.Unmarshal(metaJSON, &meta); err != nil {
// TODO: handle error
//klog.Error(err)
}
syncResult[key].Metadata = &meta
isSyncAllow = false
} else {
returnResult.Err = err
return err
}
} else {
metaJSON, _ := json.Marshal(meta)
versionJSON, _ := json.Marshal(version)
cols["expected"] = value
cols["expected_meta"] = string(metaJSON)
cols["expected_version"] = string(versionJSON)
if twin.Expected == nil {
twin.Expected = &dttype.TwinValue{}
}
twin.Expected.Value = value
twin.Expected.Metadata = &meta
twin.ExpectedVersion = version
// if rest update, set the syncResult and send the edge_updated msg
if dealType == RestDealType {
syncResult[key].Expected = &dttype.TwinValue{Value: value, Metadata: &meta}
syncResult[key].ExpectedVersion = &dttype.TwinVersion{CloudVersion: version.CloudVersion, EdgeVersion: version.EdgeVersion}
syncOptional := *twin.Optional
syncResult[key].Optional = &syncOptional
metaJSON, _ := json.Marshal(twin.Metadata)
var meta dttype.TypeMetadata
if err := json.Unmarshal(metaJSON, &meta); err != nil {
// TODO: handle error
//klog.Error(err)
}
syncResult[key].Metadata = &meta
}
isChange = true
}
} else {
if expectedErr != nil && dealType == RestDealType {
returnResult.Err = expectedErr
return expectedErr
}
}
actualOk, actualErr := isTwinValueDiff(twin, msgTwin, DealActual)
if actualOk && isSyncAllow {
value := msgTwin.Actual.Value
meta := dttype.ValueMetadata{Timestamp: now}
if twin.ActualVersion == nil {
twin.ActualVersion = &dttype.TwinVersion{}
}
version := twin.ActualVersion
var msgTwinActualVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinActualVersion = msgTwin.ActualVersion
}
ok, err := dealVersion(version, msgTwinActualVersion, dealType)
if !ok {
if dealType != RestDealType {
syncResult[key].Actual = &dttype.TwinValue{Value: twin.Actual.Value, Metadata: twin.Actual.Metadata}
syncResult[key].ActualVersion = &dttype.TwinVersion{CloudVersion: twin.ActualVersion.CloudVersion, EdgeVersion: twin.ActualVersion.EdgeVersion}
syncOptional := *twin.Optional
syncResult[key].Optional = &syncOptional
metaJSON, _ := json.Marshal(twin.Metadata)
var meta dttype.TypeMetadata
if err := json.Unmarshal(metaJSON, &meta); err != nil {
// TODO: handle error
//klog.Error(err)
}
syncResult[key].Metadata = &meta
isSyncAllow = false
} else {
returnResult.Err = err
return err
}
} else {
metaJSON, _ := json.Marshal(meta)
versionJSON, _ := json.Marshal(version)
cols["actual"] = value
cols["actual_meta"] = string(metaJSON)
cols["actual_version"] = string(versionJSON)
if twin.Actual == nil {
twin.Actual = &dttype.TwinValue{}
}
twin.Actual.Value = value
twin.Actual.Metadata = &meta
twin.ActualVersion = version
if dealType == RestDealType {
syncResult[key].Actual = &dttype.TwinValue{Value: msgTwin.Actual.Value, Metadata: &meta}
syncOptional := *twin.Optional
syncResult[key].Optional = &syncOptional
metaJSON, _ := json.Marshal(twin.Metadata)
var meta dttype.TypeMetadata
if err := json.Unmarshal(metaJSON, &meta); err != nil {
// TODO: handle error
//klog.Error(err)
}
syncResult[key].Metadata = &meta
syncResult[key].ActualVersion = &dttype.TwinVersion{CloudVersion: version.CloudVersion, EdgeVersion: version.EdgeVersion}
}
isChange = true
}
} else {
if actualErr != nil && dealType == RestDealType {
returnResult.Err = actualErr
return actualErr
}
}
if isSyncAllow {
if msgTwin.Optional != nil {
if *msgTwin.Optional != *twin.Optional && *twin.Optional {
optional := *msgTwin.Optional
cols["optional"] = optional
twin.Optional = &optional
syncOptional := *twin.Optional
syncResult[key].Optional = &syncOptional
isChange = true
}
}
// if update the deleted twin, allow to update attr_type
if msgTwin.Metadata != nil {
msgMetaJSON, _ := json.Marshal(msgTwin.Metadata)
twinMetaJSON, _ := json.Marshal(twin.Metadata)
if strings.Compare(string(msgMetaJSON), string(twinMetaJSON)) != 0 {
meta := dttype.CopyMsgTwin(msgTwin, true)
meta.Metadata.Type = ""
metaJSON, _ := json.Marshal(meta.Metadata)
cols["metadata"] = string(metaJSON)
if strings.Compare(twin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
cols["attr_type"] = msgTwin.Metadata.Type
twin.Metadata.Type = msgTwin.Metadata.Type
var meta dttype.TypeMetadata
if err := json.Unmarshal(msgMetaJSON, &meta); err != nil {
// TODO: handle error
//klog.Error(err)
}
syncResult[key].Metadata = &meta
}
isChange = true
}
} else {
if strings.Compare(twin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
twin.Metadata = &dttype.TypeMetadata{Type: stringType}
cols["attr_type"] = stringType
syncResult[key].Metadata = twin.Metadata
isChange = true
}
}
}
if isChange {
update = append(update, dtclient.DeviceTwinUpdate{DeviceID: deviceID, Name: key, Cols: cols})
returnResult.Update = update
current := dttype.CopyMsgTwin(twin, true)
document[key].CurrentState = ¤t
returnResult.Document = document
if dealType == RestDealType {
copyResult := dttype.CopyMsgTwin(syncResult[key], true)
returnResult.Result[key] = ©Result
returnResult.SyncResult = syncResult
} else {
if !isSyncAllow {
returnResult.SyncResult = syncResult
} else {
delete(syncResult, key)
}
}
} else {
if dealType == RestDealType {
delete(document, key)
delete(syncResult, key)
} else {
delete(document, key)
if !isSyncAllow {
returnResult.SyncResult = syncResult
} else {
delete(syncResult, key)
}
}
}
return nil
}
func dealTwinAdd(returnResult *dttype.DealTwinResult, deviceID string, key string, twins map[string]*dttype.MsgTwin, msgTwin *dttype.MsgTwin, dealType int) error {
now := time.Now().UnixNano() / 1e6
document := returnResult.Document
document[key] = &dttype.TwinDoc{}
document[key].LastState = nil
if msgTwin == nil {
return errors.New("the request body is wrong")
}
deviceTwin := dttype.MsgTwinToDeviceTwin(key, msgTwin)
deviceTwin.DeviceID = deviceID
syncResult := returnResult.SyncResult
syncResult[key] = &dttype.MsgTwin{}
isChange := false
//add deleted twin when syncing from cloud: add version
if dealType != RestDealType && strings.Compare(msgTwin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
if msgTwin.ExpectedVersion != nil {
versionJSON, _ := json.Marshal(msgTwin.ExpectedVersion)
deviceTwin.ExpectedVersion = string(versionJSON)
}
if msgTwin.ActualVersion != nil {
versionJSON, _ := json.Marshal(msgTwin.ActualVersion)
deviceTwin.ActualVersion = string(versionJSON)
}
}
if msgTwin.Expected != nil {
version := &dttype.TwinVersion{}
var msgTwinExpectedVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinExpectedVersion = msgTwin.ExpectedVersion
}
ok, err := dealVersion(version, msgTwinExpectedVersion, dealType)
if !ok {
// not match
if dealType == RestDealType {
returnResult.Err = err
return err
}
// reject add twin
return nil
}
// value type default string
valueType := stringType
if msgTwin.Metadata != nil {
valueType = msgTwin.Metadata.Type
}
err = dtcommon.ValidateValue(valueType, *msgTwin.Expected.Value)
if err == nil {
meta := dttype.ValueMetadata{Timestamp: now}
metaJSON, _ := json.Marshal(meta)
versionJSON, _ := json.Marshal(version)
deviceTwin.ExpectedMeta = string(metaJSON)
deviceTwin.ExpectedVersion = string(versionJSON)
deviceTwin.Expected = *msgTwin.Expected.Value
isChange = true
} else {
delete(document, key)
delete(syncResult, key)
// reject add twin, if rest add return the err, while sync add return nil
if dealType == RestDealType {
returnResult.Err = err
return err
}
return nil
}
}
if msgTwin.Actual != nil {
version := &dttype.TwinVersion{}
var msgTwinActualVersion *dttype.TwinVersion
if dealType != RestDealType {
msgTwinActualVersion = msgTwin.ActualVersion
}
ok, err := dealVersion(version, msgTwinActualVersion, dealType)
if !ok {
if dealType == RestDealType {
returnResult.Err = err
return err
}
return nil
}
valueType := stringType
if msgTwin.Metadata != nil {
valueType = msgTwin.Metadata.Type
}
err = dtcommon.ValidateValue(valueType, *msgTwin.Actual.Value)
if err == nil {
meta := dttype.ValueMetadata{Timestamp: now}
metaJSON, _ := json.Marshal(meta)
versionJSON, _ := json.Marshal(version)
deviceTwin.ActualMeta = string(metaJSON)
deviceTwin.ActualVersion = string(versionJSON)
deviceTwin.Actual = *msgTwin.Actual.Value
isChange = true
} else {
delete(document, key)
delete(syncResult, key)
if dealType == RestDealType {
returnResult.Err = err
return err
}
return nil
}
}
//add the optional of twin
if msgTwin.Optional != nil {
optional := *msgTwin.Optional
deviceTwin.Optional = optional
isChange = true
} else {
deviceTwin.Optional = true
isChange = true
}
//add the metadata of the twin
if msgTwin.Metadata != nil {
//todo
deviceTwin.AttrType = msgTwin.Metadata.Type
msgTwin.Metadata.Type = ""
metaJSON, _ := json.Marshal(msgTwin.Metadata)
deviceTwin.Metadata = string(metaJSON)
msgTwin.Metadata.Type = deviceTwin.AttrType
isChange = true
} else {
deviceTwin.AttrType = stringType
isChange = true
}
if isChange {
twins[key] = dttype.DeviceTwinToMsgTwin([]dtclient.DeviceTwin{deviceTwin})[key]
add := returnResult.Add
add = append(add, deviceTwin)
returnResult.Add = add
copytwin := dttype.CopyMsgTwin(twins[key], true)
if strings.Compare(twins[key].Metadata.Type, dtcommon.TypeDeleted) == 0 {
document[key].CurrentState = nil
} else {
document[key].CurrentState = ©twin
}
returnResult.Document = document
copySync := dttype.CopyMsgTwin(twins[key], false)
syncResult[key] = ©Sync
if dealType == RestDealType {
copyResult := dttype.CopyMsgTwin(syncResult[key], true)
returnResult.Result[key] = ©Result
returnResult.SyncResult = syncResult
} else {
delete(syncResult, key)
}
} else {
delete(document, key)
delete(syncResult, key)
}
return nil
}
// DealMsgTwin get diff while updating twin
func DealMsgTwin(context *dtcontext.DTContext, deviceID string, msgTwins map[string]*dttype.MsgTwin, dealType int) dttype.DealTwinResult {
add := make([]dtclient.DeviceTwin, 0)
deletes := make([]dtclient.DeviceDelete, 0)
update := make([]dtclient.DeviceTwinUpdate, 0)
result := make(map[string]*dttype.MsgTwin)
syncResult := make(map[string]*dttype.MsgTwin)
document := make(map[string]*dttype.TwinDoc)
returnResult := dttype.DealTwinResult{Add: add,
Delete: deletes,
Update: update,
Result: result,
SyncResult: syncResult,
Document: document,
Err: nil}
device, ok := context.GetDevice(deviceID)
if !ok {
//klog.Error("invalid device id")
return dttype.DealTwinResult{Add: add,
Delete: deletes,
Update: update,
Result: result,
SyncResult: syncResult,
Document: document,
Err: errors.New("invalid device id")}
}
twins := device.Twin
if twins == nil {
device.Twin = make(map[string]*dttype.MsgTwin)
twins = device.Twin
}
var err error
for key, msgTwin := range msgTwins {
if twin, exist := twins[key]; exist {
if dealType >= 1 && msgTwin != nil && (msgTwin.Metadata == nil) {
//klog.Info("Not found metadata of twin")
}
if msgTwin == nil && dealType == 0 || dealType >= 1 && strings.Compare(msgTwin.Metadata.Type, dtcommon.TypeDeleted) == 0 {
dealTwinDelete(&returnResult, deviceID, key, twin, msgTwin, dealType)
continue
}
err = dealTwinCompare(&returnResult, deviceID, key, twin, msgTwin, dealType)
if err != nil {
return returnResult
}
} else {
err = dealTwinAdd(&returnResult, deviceID, key, twins, msgTwin, dealType)
if err != nil {
return returnResult
}
}
}
context.DeviceList.Store(deviceID, device)
return returnResult
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dtmanager
import (
"encoding/json"
"errors"
"reflect"
"sync"
"testing"
"time"
"github.com/beego/beego/v2/client/orm"
"github.com/golang/mock/gomock"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
"github.com/kubeedge/kubeedge/pkg/testtools"
)
var (
deviceA = "DeviceA"
deviceB = "DeviceB"
deviceC = "DeviceC"
event1 = "Event1"
key1 = "key1"
typeInt = "int"
typeString = "string"
valueType = "value"
)
// sendMsg sends message to receiverChannel and heartbeatChannel
func (tw TwinWorker) sendMsg(msg *dttype.DTMessage, msgHeart string, actionType string, contentType interface{}) {
if tw.ReceiverChan != nil {
msg.Action = actionType
msg.Msg.Content = contentType
tw.ReceiverChan <- msg
}
if tw.HeartBeatChan != nil {
tw.HeartBeatChan <- msgHeart
}
}
// receiveMsg receives message from the commChannel
func receiveMsg(commChannel chan interface{}, message *dttype.DTMessage) {
msg, ok := <-commChannel
if !ok {
klog.Errorf("No message received from communication channel")
return
}
*message = *msg.(*dttype.DTMessage)
}
// twinValueFunc returns a new TwinValue
func twinValueFunc() *dttype.TwinValue {
var twinValue dttype.TwinValue
value := valueType
valueMetaData := &dttype.ValueMetadata{Timestamp: time.Now().UnixNano() / 1e6}
twinValue.Value = &value
twinValue.Metadata = valueMetaData
return &twinValue
}
// keyTwinUpdateFunc returns a new DeviceTwinUpdate
func keyTwinUpdateFunc() dttype.DeviceTwinUpdate {
var keyTwinUpdate dttype.DeviceTwinUpdate
twinKey := make(map[string]*dttype.MsgTwin)
twinKey[key1] = &dttype.MsgTwin{
Expected: twinValueFunc(),
Actual: twinValueFunc(),
Metadata: &dttype.TypeMetadata{Type: "nil"},
}
keyTwinUpdate.Twin = twinKey
keyTwinUpdate.BaseMessage = dttype.BaseMessage{EventID: event1}
return keyTwinUpdate
}
// twinWorkerFunc returns a new TwinWorker
func twinWorkerFunc(receiverChannel chan interface{}, confirmChannel chan interface{}, heartBeatChannel chan interface{}, context dtcontext.DTContext, group string) TwinWorker {
return TwinWorker{
Worker{
receiverChannel,
confirmChannel,
heartBeatChannel,
&context,
},
group,
}
}
// contextFunc returns a new DTContext
func contextFunc(deviceID string) dtcontext.DTContext {
context := dtcontext.DTContext{
DeviceList: &sync.Map{},
DeviceMutex: &sync.Map{},
Mutex: &sync.RWMutex{},
}
var testMutex sync.Mutex
context.DeviceMutex.Store(deviceID, &testMutex)
var device dttype.Device
context.DeviceList.Store(deviceID, &device)
return context
}
// msgTypeFunc returns a new Message
func msgTypeFunc(content interface{}) *model.Message {
return &model.Message{
Content: content,
}
}
// TestStart is function to test Start
func TestStart(t *testing.T) {
keyTwinUpdate := keyTwinUpdateFunc()
contentKeyTwin, _ := json.Marshal(keyTwinUpdate)
commChan := make(map[string]chan interface{})
commChannel := make(chan interface{})
commChan[dtcommon.CommModule] = commChannel
context := dtcontext.DTContext{
DeviceList: &sync.Map{},
DeviceMutex: &sync.Map{},
Mutex: &sync.RWMutex{},
CommChan: commChan,
ModulesHealth: &sync.Map{},
}
var testMutex sync.Mutex
context.DeviceMutex.Store(deviceB, &testMutex)
msgAttr := make(map[string]*dttype.MsgAttr)
device := dttype.Device{
ID: "id1",
Name: deviceB,
Attributes: msgAttr,
Twin: keyTwinUpdate.Twin,
}
context.DeviceList.Store(deviceB, &device)
msg := &dttype.DTMessage{
Msg: &model.Message{
Header: model.MessageHeader{
ID: "id1",
ParentID: "pid1",
Timestamp: 0,
Sync: false,
},
Router: model.MessageRoute{
Source: "source",
Resource: "resource",
Group: "group",
Operation: "op",
},
Content: contentKeyTwin,
},
Action: dtcommon.TwinGet,
Type: dtcommon.CommModule,
}
msgHeartPing := "ping"
msgHeartStop := "stop"
receiverChannel := make(chan interface{})
heartbeatChannel := make(chan interface{})
tests := []struct {
name string
tw TwinWorker
actionType string
contentType interface{}
msgType string
}{
{
name: "TestStart(): Case 1: ReceiverChan case when error is nil",
tw: twinWorkerFunc(receiverChannel, nil, nil, context, ""),
actionType: dtcommon.TwinGet,
contentType: contentKeyTwin,
},
{
name: "TestStart(): Case 2: ReceiverChan case error log; TwinModule deal event failed, not found callback",
tw: twinWorkerFunc(receiverChannel, nil, nil, context, ""),
actionType: dtcommon.SendToEdge,
contentType: contentKeyTwin,
},
{
name: "TestStart(): Case 3: ReceiverChan case error log; TwinModule deal event failed",
tw: twinWorkerFunc(receiverChannel, nil, nil, context, ""),
actionType: dtcommon.TwinGet,
},
{
name: "TestStart(): Case 4: HeartBeatChan case when error is nil",
tw: twinWorkerFunc(nil, nil, heartbeatChannel, context, "Group1"),
msgType: msgHeartPing,
},
{
name: "TestStart(): Case 5: HeartBeatChan case when error is not nil",
tw: twinWorkerFunc(nil, nil, heartbeatChannel, context, "Group1"),
msgType: msgHeartStop,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
go test.tw.sendMsg(msg, test.msgType, test.actionType, test.contentType)
go test.tw.Start()
time.Sleep(100 * time.Millisecond)
message := &dttype.DTMessage{}
go receiveMsg(commChannel, message)
time.Sleep(100 * time.Millisecond)
if (test.tw.ReceiverChan != nil) && !reflect.DeepEqual(message.Identity, msg.Identity) && !reflect.DeepEqual(message.Type, msg.Type) {
t.Errorf("DTManager.TestStart() case failed: got = %v, Want = %v", message, msg)
}
if _, exist := context.ModulesHealth.Load("Group1"); test.tw.HeartBeatChan != nil && !exist {
t.Errorf("DTManager.TestStart() case failed: HeartBeatChan received no string")
}
})
}
}
// TestDealTwinSync is function to test dealTwinSync
func TestDealTwinSync(t *testing.T) {
content, contentKeyTwin, context := generateTwinContent(event1, deviceB)
tests := CasesMsgWorkerStr{
{
name: "TestDealTwinSync(): Case 1: msg not Message type",
context: &dtcontext.DTContext{},
msg: model.Message{
Content: dttype.BaseMessage{EventID: event1},
},
wantErr: errors.New("msg not Message type"),
},
{
name: "TestDealTwinSync(): Case 2: invalid message content",
context: &dtcontext.DTContext{},
msg: msgTypeFunc(dttype.BaseMessage{EventID: event1}),
wantErr: errors.New("invalid message content"),
},
{
name: "TestDealTwinSync(): Case 3: Unmarshal update request body failed",
context: &dtcontext.DTContext{},
msg: msgTypeFunc(content),
wantErr: dttype.ErrorUpdate,
},
{
name: "TestDealTwinSync(): Case 4: Success case",
context: &context,
resource: deviceB,
msg: msgTypeFunc(contentKeyTwin),
wantErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := dealTwinSync(test.context, test.resource, test.msg); !reflect.DeepEqual(err, test.wantErr) {
t.Errorf("DTManager.TestDealTwinSync() case failed: got = %v, Want = %v", err, test.wantErr)
}
})
}
}
// TestDealTwinGet is function to test dealTwinGet
func TestDealTwinGet(t *testing.T) {
_, contentKeyTwin, context := generateTwinContent(event1, deviceB)
tests := CasesMsgWorkerStr{
{
name: "TestDealTwinGet(): Case 1: msg not Message type",
context: &dtcontext.DTContext{},
msg: model.Message{
Content: dttype.BaseMessage{EventID: event1},
},
wantErr: errors.New("msg not Message type"),
},
{
name: "TestDealTwinGet(): Case 2: invalid message content",
context: &dtcontext.DTContext{},
msg: msgTypeFunc(dttype.BaseMessage{EventID: event1}),
wantErr: errors.New("invalid message content"),
},
{
name: "TestDealTwinGet(): Case 3: Success; Unmarshal twin info fails in DealGetTwin()",
context: &context,
resource: deviceB,
msg: msgTypeFunc([]byte("")),
wantErr: nil,
},
{
name: "TestDealTwinGet(): Case 4: Success; Device not found while getting twin in DealGetTwin()",
context: &context,
resource: deviceB,
msg: msgTypeFunc(contentKeyTwin),
wantErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := dealTwinGet(test.context, test.resource, test.msg); !reflect.DeepEqual(err, test.wantErr) {
t.Errorf("DTManager.TestDealTwinGet() case failed: got = %v, Want = %v", err, test.wantErr)
}
})
}
}
// TestDealTwinUpdate is function to test dealTwinUpdate
func TestDealTwinUpdate(t *testing.T) {
content, contentKeyTwin, context := generateTwinContent(event1, deviceB)
tests := CasesMsgWorkerStr{
{
name: "TestDealTwinUpdate(): Case 1: msg not Message type",
context: &dtcontext.DTContext{},
msg: model.Message{
Content: dttype.BaseMessage{EventID: event1},
},
wantErr: errors.New("msg not Message type"),
},
{
name: "TestDealTwinUpdate(): Case 2: invalid message content",
context: &dtcontext.DTContext{},
msg: msgTypeFunc(dttype.BaseMessage{EventID: event1}),
wantErr: errors.New("invalid message content"),
},
{
name: "TestDealTwinUpdate(): Case 3: Success; Unmarshal update request body fails in Updated()",
context: &context,
resource: deviceB,
msg: msgTypeFunc(content),
wantErr: nil,
},
{
name: "TestDealTwinUpdate(): Case 4: Success; Begin to update twin of the device in Updated()",
context: &context,
resource: deviceA,
msg: msgTypeFunc(contentKeyTwin),
wantErr: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := dealTwinUpdate(test.context, test.resource, test.msg); !reflect.DeepEqual(err, test.wantErr) {
t.Errorf("DTManager.TestDealTwinUpdate() case failed: got = %v, Want = %v", err, test.wantErr)
}
})
}
}
// TestDealDeviceTwin is function to test DealDeviceTwin
func TestDealDeviceTwin(t *testing.T) {
mockOrmer, mockQuerySeter := testtools.InitOrmerMock(t)
str := typeString
optionTrue := true
msgTwin := make(map[string]*dttype.MsgTwin)
msgTwin[key1] = &dttype.MsgTwin{
Expected: twinValueFunc(),
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
}
contextDeviceB := contextFunc(deviceB)
twinDeviceB := make(map[string]*dttype.MsgTwin)
twinDeviceB[deviceB] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
}
deviceBTwin := dttype.Device{
Twin: twinDeviceB,
}
contextDeviceB.DeviceList.Store(deviceB, &deviceBTwin)
contextDeviceC := dtcontext.DTContext{
DeviceList: &sync.Map{},
DeviceMutex: &sync.Map{},
Mutex: &sync.RWMutex{},
}
var testMutex sync.Mutex
contextDeviceC.DeviceMutex.Store(deviceC, &testMutex)
twinDeviceC := make(map[string]*dttype.MsgTwin)
twinDeviceC[deviceC] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
}
deviceCTwin := dttype.Device{Twin: twinDeviceC}
contextDeviceC.DeviceList.Store(deviceC, &deviceCTwin)
tests := []struct {
name string
context *dtcontext.DTContext
deviceID string
eventID string
msgTwin map[string]*dttype.MsgTwin
dealType int
err error
filterReturn orm.QuerySeter
allReturnInt int64
allReturnErr error
queryTableReturn orm.QuerySeter
beginReturn orm.TxOrmer
rollbackNums int
beginNums int
commitNums int
filterNums int
insertNums int
deleteNums int
updateNums int
queryNums int
}{
{
name: "TestDealDeviceTwin(): Case 1: msgTwin is nil",
context: &contextDeviceB,
deviceID: deviceB,
dealType: RestDealType,
err: dttype.ErrorUpdate,
rollbackNums: 0,
beginNums: 1,
commitNums: 1,
filterNums: 0,
insertNums: 1,
deleteNums: 0,
updateNums: 0,
queryNums: 0,
},
{
name: "TestDealDeviceTwin(): Case 2: Success Case",
context: &contextDeviceC,
deviceID: deviceC,
msgTwin: msgTwin,
dealType: RestDealType,
err: nil,
filterReturn: mockQuerySeter,
allReturnInt: int64(1),
allReturnErr: nil,
queryTableReturn: mockQuerySeter,
rollbackNums: 0,
beginNums: 1,
commitNums: 1,
filterNums: 0,
insertNums: 1,
deleteNums: 0,
updateNums: 0,
queryNums: 0,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockOrmer.EXPECT().DoTx(gomock.Any()).Return(test.allReturnErr).Times(test.insertNums)
mockOrmer.EXPECT().DoTx(gomock.Any()).Return(test.allReturnErr).Times(test.deleteNums)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(test.queryNums)
if err := DealDeviceTwin(test.context, test.deviceID, test.eventID, test.msgTwin, test.dealType); !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealDeviceTwin() case failed: got = %v, Want = %v", err, test.err)
}
})
}
}
// TestDealDeviceTwinResult is function to test DealDeviceTwin when dealTwinResult.Err is not nil
func TestDealDeviceTwinResult(t *testing.T) {
mockOrmer, mockQuerySeter := testtools.InitOrmerMock(t)
str := typeString
optionTrue := true
value := valueType
msgTwinValue := make(map[string]*dttype.MsgTwin)
msgTwinValue[deviceB] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &value},
Metadata: &dttype.TypeMetadata{Type: "nil"},
}
contextDeviceA := contextFunc(deviceB)
twinDeviceA := make(map[string]*dttype.MsgTwin)
twinDeviceA[deviceA] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
}
deviceATwin := dttype.Device{Twin: twinDeviceA}
contextDeviceA.DeviceList.Store(deviceA, &deviceATwin)
tests := []struct {
name string
context *dtcontext.DTContext
deviceID string
eventID string
msgTwin map[string]*dttype.MsgTwin
dealType int
err error
filterReturn orm.QuerySeter
allReturnInt int64
allReturnErr error
queryTableReturn orm.QuerySeter
}{
{
name: "TestDealDeviceTwinResult(): dealTwinResult error",
context: &contextDeviceA,
deviceID: deviceB,
msgTwin: msgTwinValue,
dealType: RestDealType,
err: errors.New("the value type is not allowed"),
filterReturn: mockQuerySeter,
allReturnInt: int64(1),
allReturnErr: nil,
queryTableReturn: mockQuerySeter,
},
}
fakeDevice := new([]dtclient.Device)
fakeDeviceArray := make([]dtclient.Device, 1)
fakeDeviceArray[0] = dtclient.Device{ID: deviceB}
fakeDevice = &fakeDeviceArray
fakeDeviceAttr := new([]dtclient.DeviceAttr)
fakeDeviceAttrArray := make([]dtclient.DeviceAttr, 1)
fakeDeviceAttrArray[0] = dtclient.DeviceAttr{DeviceID: deviceB}
fakeDeviceAttr = &fakeDeviceAttrArray
fakeDeviceTwin := new([]dtclient.DeviceTwin)
fakeDeviceTwinArray := make([]dtclient.DeviceTwin, 1)
fakeDeviceTwinArray[0] = dtclient.DeviceTwin{DeviceID: deviceB}
fakeDeviceTwin = &fakeDeviceTwinArray
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDevice).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDeviceAttr).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDeviceTwin).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
if err := DealDeviceTwin(test.context, test.deviceID, test.eventID, test.msgTwin, test.dealType); !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealDeviceTwinResult() case failed: got = %v, Want = %v", err, test.err)
}
})
}
}
// TestDealDeviceTwinTrans is function to test DealDeviceTwin when DeviceTwinTrans() return error
func TestDealDeviceTwinTrans(t *testing.T) {
mockOrmer, mockQuerySeter := testtools.InitOrmerMock(t)
str := typeString
optionTrue := true
msgTwin := make(map[string]*dttype.MsgTwin)
msgTwin[key1] = &dttype.MsgTwin{
Expected: twinValueFunc(),
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
}
contextDeviceB := contextFunc(deviceB)
twinDeviceB := make(map[string]*dttype.MsgTwin)
twinDeviceB[deviceB] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
}
deviceBTwin := dttype.Device{Twin: twinDeviceB}
contextDeviceB.DeviceList.Store(deviceB, &deviceBTwin)
tests := []struct {
name string
context *dtcontext.DTContext
deviceID string
eventID string
msgTwin map[string]*dttype.MsgTwin
dealType int
err error
filterReturn orm.QuerySeter
insertReturnInt int64
insertReturnErr error
deleteReturnInt int64
deleteReturnErr error
updateReturnInt int64
updateReturnErr error
allReturnInt int64
allReturnErr error
queryTableReturn orm.QuerySeter
}{
{
name: "TestDealDeviceTwinTrans(): DeviceTwinTrans error",
context: &contextDeviceB,
deviceID: deviceB,
msgTwin: msgTwin,
dealType: RestDealType,
err: errors.New("failed DB Operation"),
filterReturn: mockQuerySeter,
insertReturnInt: int64(1),
insertReturnErr: errors.New("failed DB Operation"),
deleteReturnInt: int64(1),
deleteReturnErr: nil,
updateReturnInt: int64(1),
updateReturnErr: nil,
allReturnInt: int64(1),
allReturnErr: nil,
queryTableReturn: mockQuerySeter,
},
}
fakeDevice := new([]dtclient.Device)
fakeDeviceArray := make([]dtclient.Device, 1)
fakeDeviceArray[0] = dtclient.Device{ID: deviceB}
fakeDevice = &fakeDeviceArray
fakeDeviceAttr := new([]dtclient.DeviceAttr)
fakeDeviceAttrArray := make([]dtclient.DeviceAttr, 1)
fakeDeviceAttrArray[0] = dtclient.DeviceAttr{DeviceID: deviceB}
fakeDeviceAttr = &fakeDeviceAttrArray
fakeDeviceTwin := new([]dtclient.DeviceTwin)
fakeDeviceTwinArray := make([]dtclient.DeviceTwin, 1)
fakeDeviceTwinArray[0] = dtclient.DeviceTwin{DeviceID: deviceB}
fakeDeviceTwin = &fakeDeviceTwinArray
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
mockOrmer.EXPECT().DoTx(gomock.Any()).Return(test.insertReturnErr).Times(5)
mockOrmer.EXPECT().DoTx(gomock.Any()).Return(test.deleteReturnErr).Times(0)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(0)
mockQuerySeter.EXPECT().Update(gomock.Any()).Return(test.updateReturnInt, test.updateReturnErr).Times(0)
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDevice).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDeviceAttr).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
mockQuerySeter.EXPECT().All(gomock.Any()).SetArg(0, *fakeDeviceTwin).Return(test.allReturnInt, test.allReturnErr).Times(1)
mockQuerySeter.EXPECT().Filter(gomock.Any(), gomock.Any()).Return(test.filterReturn).Times(1)
mockOrmer.EXPECT().QueryTable(gomock.Any()).Return(test.queryTableReturn).Times(1)
if err := DealDeviceTwin(test.context, test.deviceID, test.eventID, test.msgTwin, test.dealType); !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealDeviceTwinTrans() case failed: got = %v, Want = %v", err, test.err)
}
})
}
}
// TestDealVersion is function to test dealVersion
func TestDealVersion(t *testing.T) {
twinCloudEdgeVersion := dttype.TwinVersion{
CloudVersion: 1,
EdgeVersion: 1,
}
twinCloudVersion := dttype.TwinVersion{
CloudVersion: 1,
EdgeVersion: 0,
}
tests := []struct {
name string
version *dttype.TwinVersion
reqVersion *dttype.TwinVersion
dealType int
errorWant bool
err error
}{
{
name: "TestDealVersion(): Case 1: dealType=3",
version: &dttype.TwinVersion{},
dealType: SyncTwinDeleteDealType,
errorWant: true,
err: nil,
},
{
name: "TestDealVersion(): Case 2: dealType>=1 && version.EdgeVersion>reqVersion.EdgeVersion",
version: &twinCloudEdgeVersion,
reqVersion: &twinCloudVersion,
dealType: SyncDealType,
errorWant: false,
err: errors.New("not allowed to sync due to version conflict"),
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
got, err := dealVersion(test.version, test.reqVersion, test.dealType)
if !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealVersion() case failed: got = %v, Want = %v", err, test.err)
return
}
if !reflect.DeepEqual(got, test.errorWant) {
t.Errorf("DTManager.TestDealVersion() case failed: got = %v, want %v", got, test.errorWant)
}
})
}
}
// TestDealTwinDelete is function to test dealTwinDelete
func TestDealTwinDelete(t *testing.T) {
optionTrue := true
optionFalse := false
str := typeString
doc := make(map[string]*dttype.TwinDoc)
doc[key1] = &dttype.TwinDoc{}
sync := make(map[string]*dttype.MsgTwin)
sync[key1] = &dttype.MsgTwin{
Expected: twinValueFunc(),
Actual: twinValueFunc(),
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
}
result := make(map[string]*dttype.MsgTwin)
result[key1] = &dttype.MsgTwin{}
tests := []struct {
name string
returnResult *dttype.DealTwinResult
deviceID string
key string
twin *dttype.MsgTwin
msgTwin *dttype.MsgTwin
dealType int
want *dttype.DealTwinResult
}{
{
name: "TestDealTwinDelete(): Case 1: msgTwin is not nil; isChange is false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
want: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
},
{
name: "TestDealTwinDelete(): Case 2: hasTwinExpected is true; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ExpectedVersion: &dttype.TwinVersion{
CloudVersion: 1,
},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{
CloudVersion: 0,
},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
want: &dttype.DealTwinResult{
Document: make(map[string]*dttype.TwinDoc),
SyncResult: map[string]*dttype.MsgTwin{
key1: {
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ExpectedVersion: &dttype.TwinVersion{
CloudVersion: 1,
},
},
},
Result: result,
},
},
{
name: "TestDealTwinDelete(): Case 3: hasTwinActual is true; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ActualVersion: &dttype.TwinVersion{
CloudVersion: 1,
},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{
CloudVersion: 0,
},
},
dealType: SyncDealType,
want: &dttype.DealTwinResult{
Document: make(map[string]*dttype.TwinDoc),
SyncResult: map[string]*dttype.MsgTwin{
key1: {
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ActualVersion: &dttype.TwinVersion{
CloudVersion: 1,
},
ExpectedVersion: &dttype.TwinVersion{},
},
},
Result: result,
},
},
{
name: "TestDealTwinDelete(): Case 4: hasTwinExpected is true; hasTwinActual is true",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: RestDealType,
want: &dttype.DealTwinResult{
Document: doc,
SyncResult: map[string]*dttype.MsgTwin{
key1: {
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
Expected: &dttype.TwinValue{
Value: nil,
Metadata: nil,
},
ExpectedVersion: &dttype.TwinVersion{
CloudVersion: 0,
EdgeVersion: 1,
},
ActualVersion: &dttype.TwinVersion{
CloudVersion: 0,
EdgeVersion: 1,
},
Actual: &dttype.TwinValue{
Value: nil,
Metadata: nil,
},
},
},
Result: map[string]*dttype.MsgTwin{
key1: nil,
},
Update: []dtclient.DeviceTwinUpdate{{
DeviceID: deviceA,
Name: key1,
Cols: map[string]interface{}{
"attr_type": dtcommon.TypeDeleted,
"expected_meta": nil,
"expected": nil,
"actual_meta": nil,
"actual": nil,
"actual_version": "{\"cloud\": 0,\"edge\": 1}",
"expected_version": "{\"cloud\": 0,\"edge\": 1}",
},
}},
},
},
{
name: "TestDealTwinDelete(): Case 5: hasTwinExpected is true; hasTwinActual is false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeString,
},
ExpectedVersion: &dttype.TwinVersion{},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
want: &dttype.DealTwinResult{
Document: doc,
SyncResult: map[string]*dttype.MsgTwin{
key1: nil,
},
Update: []dtclient.DeviceTwinUpdate{{
DeviceID: deviceA,
Name: key1,
Cols: map[string]interface{}{
"attr_type": dtcommon.TypeDeleted,
"expected_meta": nil,
"expected": nil,
"expected_version": "{\"cloud\": 0,\"edge\": 0}",
},
}},
Result: map[string]*dttype.MsgTwin{
key1: nil,
},
},
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
dealTwinDelete(test.returnResult, test.deviceID, test.key, test.twin, test.msgTwin, test.dealType)
if (test.returnResult.SyncResult[key1] != nil || test.want.SyncResult[key1] != nil) && !reflect.DeepEqual(*test.returnResult.SyncResult[key1].ExpectedVersion, *test.want.SyncResult[key1].ExpectedVersion) {
t.Errorf("DTManager.TestDealTwinDelete() case failed: SyncResult got = %+v, Want = %+v", *test.returnResult.SyncResult[key1].ExpectedVersion, *test.want.SyncResult[key1].ExpectedVersion)
}
})
}
}
// TestDealTwinCompare is function to test dealTwinCompare
func TestDealTwinCompare(t *testing.T) {
optionTrue := true
optionFalse := false
str := typeString
doc := make(map[string]*dttype.TwinDoc)
doc[key1] = &dttype.TwinDoc{}
sync := make(map[string]*dttype.MsgTwin)
sync[key1] = &dttype.MsgTwin{
Expected: twinValueFunc(),
Actual: twinValueFunc(),
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
}
result := make(map[string]*dttype.MsgTwin)
result[key1] = &dttype.MsgTwin{}
tests := []struct {
name string
returnResult *dttype.DealTwinResult
deviceID string
key string
twin *dttype.MsgTwin
msgTwin *dttype.MsgTwin
dealType int
err error
}{
{
name: "TestDealTwinCompare(): Case 1: msgTwin nil",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
},
dealType: RestDealType,
err: nil,
},
{
name: "TestDealTwinCompare(): Case 2: actualOk is false; actualErr is not nil",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
},
msgTwin: &dttype.MsgTwin{
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{Type: typeInt},
},
dealType: RestDealType,
err: errors.New("the value is not int or integer"),
},
{
name: "TestDealTwinCompare(): Case 3: expectedOk is true; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: typeString},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{Type: typeInt},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinCompare(): Case 4: actualOk is true; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
},
msgTwin: &dttype.MsgTwin{
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{Type: typeString},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinCompare(): Case 5: expectedOk is true; actualOk is true",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
},
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{Value: &str},
Actual: &dttype.TwinValue{Value: &str},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{Type: typeString},
ActualVersion: &dttype.TwinVersion{},
},
dealType: RestDealType,
err: nil,
},
{
name: "TestDealTwinCompare(): Case 6: expectedOk is false; actualOk is false",
returnResult: &dttype.DealTwinResult{Document: doc, SyncResult: sync, Result: result},
deviceID: deviceA,
key: key1,
twin: &dttype.MsgTwin{
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{Type: dtcommon.TypeDeleted},
},
msgTwin: &dttype.MsgTwin{
Optional: &optionFalse,
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := dealTwinCompare(test.returnResult, test.deviceID, test.key, test.twin, test.msgTwin, test.dealType); !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealTwinCompare() case failed: got = %+v, Want = %+v", err, test.err)
}
})
}
}
// TestDealTwinAdd is function to test dealTwinAdd
func TestDealTwinAdd(t *testing.T) {
optionTrue := true
str := typeString
doc := make(map[string]*dttype.TwinDoc)
doc[key1] = &dttype.TwinDoc{}
sync := make(map[string]*dttype.MsgTwin)
sync[key1] = &dttype.MsgTwin{}
result := make(map[string]*dttype.MsgTwin)
result[key1] = &dttype.MsgTwin{}
twinDelete := make(map[string]*dttype.MsgTwin)
twinDelete[key1] = &dttype.MsgTwin{
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
}
twinInt := make(map[string]*dttype.MsgTwin)
twinInt[key1] = &dttype.MsgTwin{
Metadata: &dttype.TypeMetadata{
Type: typeInt,
},
}
tests := []struct {
name string
returnResult *dttype.DealTwinResult
deviceID string
key string
twins map[string]*dttype.MsgTwin
msgTwin *dttype.MsgTwin
dealType int
err error
}{
{
name: "TestDealTwinAdd(): Case 1: msgTwin nil",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
dealType: RestDealType,
err: errors.New("the request body is wrong"),
},
{
name: "TestDealTwinAdd(): Case 2: msgTwin.Expected is not nil; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinAdd(): Case 3: msgTwin.Expected is not nil; ValidateValue() returns error",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeInt,
},
ExpectedVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinAdd(): Case 4: msgTwin.Actual is not nil; dealVersion() returns false",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinAdd(): Case 5: msgTwin.Actual is not nil; ValidateValue() returns error; dealType=0",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeInt,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: RestDealType,
err: errors.New("the value is not int or integer"),
},
{
name: "TestDealTwinAdd(): Case 6: msgTwin.Actual is not nil; ValidateValue() returns error; dealType=1",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: typeInt,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
{
name: "TestDealTwinAdd(): Case 7: msgTwin.Expected is nil; msgTwin.Actual is nil",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinInt,
msgTwin: &dttype.MsgTwin{
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: RestDealType,
err: nil,
},
{
name: "TestDealTwinAdd(): Case 8: msgTwin.Expected is not nil; msgTwin.Actual is not nil",
returnResult: &dttype.DealTwinResult{
Document: doc,
SyncResult: sync,
Result: result,
},
deviceID: deviceA,
key: key1,
twins: twinDelete,
msgTwin: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
ExpectedVersion: &dttype.TwinVersion{},
ActualVersion: &dttype.TwinVersion{},
},
dealType: SyncDealType,
err: nil,
},
}
for _, test := range tests {
t.Run(test.name, func(t *testing.T) {
if err := dealTwinAdd(test.returnResult, test.deviceID, test.key, test.twins, test.msgTwin, test.dealType); !reflect.DeepEqual(err, test.err) {
t.Errorf("DTManager.TestDealTwinAdd() case failed: got = %+v, Want = %+v", err, test.err)
}
})
}
}
// TestDealMsgTwin is function to test DealMsgTwin
func TestDealMsgTwin(t *testing.T) {
value := valueType
str := typeString
optionTrue := true
optionFalse := false
add := make([]dtclient.DeviceTwin, 0)
deletes := make([]dtclient.DeviceDelete, 0)
update := make([]dtclient.DeviceTwinUpdate, 0)
result := make(map[string]*dttype.MsgTwin)
syncResult := make(map[string]*dttype.MsgTwin)
syncResultDevice := make(map[string]*dttype.MsgTwin)
syncResultDevice[deviceA] = &dttype.MsgTwin{}
document := make(map[string]*dttype.TwinDoc)
documentDevice := make(map[string]*dttype.TwinDoc)
documentDevice[deviceA] = &dttype.TwinDoc{LastState: nil}
documentDeviceTwin := make(map[string]*dttype.TwinDoc)
documentDeviceTwin[deviceA] = &dttype.TwinDoc{
LastState: &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
},
}
msgTwin := make(map[string]*dttype.MsgTwin)
msgTwin[deviceB] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &value,
},
Metadata: &dttype.TypeMetadata{
Type: "nil",
},
}
msgTwinDevice := make(map[string]*dttype.MsgTwin)
msgTwinDevice[deviceA] = nil
msgTwinDeviceTwin := make(map[string]*dttype.MsgTwin)
msgTwinDeviceTwin[deviceA] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionFalse,
Metadata: &dttype.TypeMetadata{
Type: typeInt,
},
ActualVersion: &dttype.TwinVersion{},
}
context := contextFunc(deviceB)
twin := make(map[string]*dttype.MsgTwin)
twin[deviceA] = &dttype.MsgTwin{
Expected: &dttype.TwinValue{
Value: &str,
},
Actual: &dttype.TwinValue{
Value: &str,
},
Optional: &optionTrue,
Metadata: &dttype.TypeMetadata{
Type: dtcommon.TypeDeleted,
},
}
device := dttype.Device{Twin: twin}
context.DeviceList.Store(deviceA, &device)
tests := []struct {
name string
context *dtcontext.DTContext
deviceID string
msgTwins map[string]*dttype.MsgTwin
dealType int
want dttype.DealTwinResult
}{
{
name: "TestDealMsgTwin(): Case1: invalid device id",
context: &context,
deviceID: deviceC,
msgTwins: msgTwin,
dealType: RestDealType,
want: dttype.DealTwinResult{
Add: add,
Delete: deletes,
Update: update,
Result: result,
SyncResult: syncResult,
Document: document,
Err: errors.New("invalid device id"),
},
},
{
name: "TestDealMsgTwin(): Case 2: dealTwinCompare error",
context: &context,
deviceID: deviceA,
msgTwins: msgTwinDeviceTwin,
dealType: RestDealType,
want: dttype.DealTwinResult{
Add: add,
Delete: deletes,
Update: update,
Result: result,
SyncResult: syncResultDevice,
Document: documentDevice,
Err: errors.New("the value is not int or integer"),
},
},
{
name: "TestDealMsgTwin(): Case 3: Success case",
context: &context,
deviceID: deviceA,
msgTwins: msgTwinDevice,
dealType: RestDealType,
want: dttype.DealTwinResult{
Add: add,
Delete: deletes,
Update: update,
Result: result,
SyncResult: syncResultDevice,
Document: documentDeviceTwin,
},
},
}
for _, tt := range tests {
t.Run(tt.name, func(t *testing.T) {
if got := DealMsgTwin(tt.context, tt.deviceID, tt.msgTwins, tt.dealType); !reflect.DeepEqual(got, tt.want) {
t.Errorf("DTManager.DealMsgTwin() case failed: got = %+v, want = %+v", got, tt.want)
}
})
}
}
func generateTwinContent(eventID, deviceID string) ([]byte, []byte, dtcontext.DTContext) {
content, _ := json.Marshal(dttype.DeviceTwinUpdate{BaseMessage: dttype.BaseMessage{EventID: eventID}})
contentKeyTwin, _ := json.Marshal(keyTwinUpdateFunc())
context := contextFunc(deviceID)
return content, contentKeyTwin, context
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dtmanager
import (
"time"
"github.com/kubeedge/beehive/pkg/common"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcontext"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dttype"
)
const (
Delay = 10 * time.Millisecond
MaxRetries = 5
Identity = "identity"
Message = "message"
Msg = "msg"
Action = "action"
TestAction = "testAction"
ActionPresent = "StartTest-ActionPresentInActionCallback"
ActionNotPresent = "StartTest-ActionNotPresentInActionCallback"
PingHeartBeat = "StartTest-PingInHeartBeatChannel"
StopHeartBeat = "StartTest-StopInHeartBeatChannel"
Group = "group"
)
// CaseWorkerStr is case struct of worker
type CaseWorkerStr struct {
name string
Worker Worker
}
// CaseHeartBeatWorkerStr is case struct of worker for heartbeat
type CaseHeartBeatWorkerStr struct {
name string
Worker Worker
Group string
}
type CasesMsgWorkerStr []struct {
name string
context *dtcontext.DTContext
resource string
msg interface{}
wantErr error
}
// GenerateReceiveChanAction generates receive channel action
func GenerateReceiveChanAction(action, identity, id, content string) chan interface{} {
channel := make(chan interface{}, 1)
channel <- &dttype.DTMessage{
Action: action,
Identity: identity,
Msg: &model.Message{
Header: model.MessageHeader{
ID: id,
},
Content: content,
},
}
return channel
}
// GenerateStartActionCase generates start action case
func GenerateStartActionCase(name string, channelPresent chan interface{}) CaseWorkerStr {
dtContextStateConnected, _ := dtcontext.InitDTContext()
dtContextStateConnected.State = dtcommon.Connected
return CaseWorkerStr{
name: name,
Worker: Worker{
ReceiverChan: channelPresent,
DTContexts: dtContextStateConnected,
},
}
}
// GenerateHeartBeatCase generates heart beat action case
func GenerateHeartBeatCase(name, group string, channel chan interface{}) CaseHeartBeatWorkerStr {
beehiveContext.InitContext([]string{common.MsgCtxTypeChannel})
dtContexts, _ := dtcontext.InitDTContext()
return CaseHeartBeatWorkerStr{
name: name,
Worker: Worker{
ReceiverChan: channel,
DTContexts: dtContexts,
},
Group: group,
}
}
func generateMessageAttributes() map[string]*dttype.MsgAttr {
messageAttributes := make(map[string]*dttype.MsgAttr)
optional := true
msgattr := &dttype.MsgAttr{
Value: "ON",
Optional: &optional,
Metadata: &dttype.TypeMetadata{
Type: "device",
},
}
messageAttributes["DeviceA"] = msgattr
return messageAttributes
}
package dttype
import (
"encoding/json"
"errors"
"strings"
"time"
"github.com/google/uuid"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
)
// Device the struct of device
type Device struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
State string `json:"state,omitempty"`
LastOnline string `json:"last_online,omitempty"`
Attributes map[string]*MsgAttr `json:"attributes,omitempty"`
Twin map[string]*MsgTwin `json:"twin,omitempty"`
}
// DeviceCloudMsg used to synchronize device data to the cloud
type DeviceCloudMsg struct {
ID string `json:"id,omitempty"`
Name string `json:"name,omitempty"`
Description string `json:"description,omitempty"`
State string `json:"state,omitempty"`
LastOnlineTime string `json:"lastOnlineTime,omitempty"`
Attributes map[string]*MsgAttr `json:"attributes,omitempty"`
Twin map[string]*MsgTwin `json:"twin,omitempty"`
}
// BaseMessage the base struct of event message
type BaseMessage struct {
EventID string `json:"event_id"`
Timestamp int64 `json:"timestamp"`
}
var ErrorUnmarshal = errors.New("Unmarshal update request body failed, please check the request")
var ErrorUpdate = errors.New("Update twin error, key:twin does not exist")
var ErrorKey = errors.New("The key of twin must only include upper or lowercase letters, number, english, and special letter - _ . , : / @ # and the length of key should be less than 128 bytes")
var ErrorValue = errors.New("The value of twin must only include upper or lowercase letters, number, english, and special letter - _ . , : / @ # and the length of value should be less than 512 bytes")
// SetEventID set event id
func (bs *BaseMessage) SetEventID(eventID string) {
bs.EventID = eventID
}
// BuildBaseMessage build base msg
func BuildBaseMessage() BaseMessage {
now := time.Now().UnixNano() / 1e6
return BaseMessage{
EventID: uuid.New().String(),
Timestamp: now}
}
// Parameter container para
type Parameter struct {
EventID string
Code int
Reason string
}
// Result the struct of Result for sending
type Result struct {
BaseMessage
Code int `json:"code,omitempty"`
Reason string `json:"reason,omitempty"`
}
// MembershipDetail the struct of membership detail
type MembershipDetail struct {
BaseMessage
Devices []Device `json:"devices"`
}
// MembershipUpdate the struct of membership update
type MembershipUpdate struct {
BaseMessage
AddDevices []Device `json:"added_devices"`
RemoveDevices []Device `json:"removed_devices"`
}
// MarshalMembershipUpdate marshal membership update
func MarshalMembershipUpdate(result MembershipUpdate) ([]byte, error) {
for i := range result.AddDevices {
if result.AddDevices[i].Twin != nil {
for k, v := range result.AddDevices[i].Twin {
if v.Metadata != nil && strings.Compare(v.Metadata.Type, dtcommon.TypeDeleted) == 0 {
result.AddDevices[i].Twin[k] = nil
}
v.ActualVersion = nil
v.ExpectedVersion = nil
}
}
}
for i := range result.RemoveDevices {
if result.RemoveDevices[i].Twin != nil {
for k, v := range result.RemoveDevices[i].Twin {
if v.Metadata != nil && strings.Compare(v.Metadata.Type, dtcommon.TypeDeleted) == 0 {
result.RemoveDevices[i].Twin[k] = nil
}
v.ActualVersion = nil
v.ExpectedVersion = nil
}
}
}
resultJSON, err := json.Marshal(result)
return resultJSON, err
}
// MsgAttr the struct of device attr
type MsgAttr struct {
Value string `json:"value"`
Optional *bool `json:"optional,omitempty"`
Metadata *TypeMetadata `json:"metadata,omitempty"`
}
// MsgTwin the struct of device twin
type MsgTwin struct {
Expected *TwinValue `json:"expected,omitempty"`
Actual *TwinValue `json:"actual,omitempty"`
Optional *bool `json:"optional,omitempty"`
Metadata *TypeMetadata `json:"metadata,omitempty"`
ExpectedVersion *TwinVersion `json:"expected_version,omitempty"`
ActualVersion *TwinVersion `json:"actual_version,omitempty"`
}
// TwinValue the struct of twin value
type TwinValue struct {
Value *string `json:"value,omitempty"`
Metadata *ValueMetadata `json:"metadata,omitempty"`
}
// TwinVersion twin version
type TwinVersion struct {
CloudVersion int64 `json:"cloud"`
EdgeVersion int64 `json:"edge"`
}
// TypeMetadata the meta of value type
type TypeMetadata struct {
Type string `json:"type,omitempty"`
}
// ValueMetadata the meta of value
type ValueMetadata struct {
Timestamp int64 `json:"timestamp,omitempty"`
}
// UpdateCloudVersion update cloud version
func (tv *TwinVersion) UpdateCloudVersion() {
tv.CloudVersion = tv.CloudVersion + 1
}
// UpdateEdgeVersion update edge version while dealing edge update
func (tv *TwinVersion) UpdateEdgeVersion() {
tv.EdgeVersion = tv.EdgeVersion + 1
}
// CompareWithCloud compare with cloud vershon while dealing cloud update req
func (tv TwinVersion) CompareWithCloud(tvCloud TwinVersion) bool {
return tvCloud.EdgeVersion >= tv.EdgeVersion
}
// UpdateCloudVersion update cloud version
func UpdateCloudVersion(version string) (string, error) {
var twinversion TwinVersion
err := json.Unmarshal([]byte(version), &twinversion)
if err != nil {
return "", err
}
twinversion.UpdateCloudVersion()
result, err := json.Marshal(twinversion)
if err != nil {
return "", err
}
return string(result), nil
}
// UpdateEdgeVersion update Edge version
func UpdateEdgeVersion(version string) (string, error) {
var twinversion TwinVersion
err := json.Unmarshal([]byte(version), &twinversion)
if err != nil {
return "", err
}
twinversion.UpdateEdgeVersion()
result, err := json.Marshal(twinversion)
if err != nil {
return "", err
}
return string(result), nil
}
// CompareVersion compare cloud version
func CompareVersion(cloudversion string, edgeversion string) bool {
var twincloudversion TwinVersion
err := json.Unmarshal([]byte(cloudversion), &twincloudversion)
if err != nil {
return false
}
var twinedgeversion TwinVersion
err1 := json.Unmarshal([]byte(edgeversion), &twinedgeversion)
if err1 != nil {
return false
}
return twinedgeversion.CompareWithCloud(twincloudversion)
}
// ConnectedInfo connected info
type ConnectedInfo struct {
EventType string `json:"event_type"`
TimeStamp int64 `json:"timestamp"`
}
// UnmarshalConnectedInfo unmarshal connected info
func UnmarshalConnectedInfo(payload []byte) (ConnectedInfo, error) {
var connectedInfo ConnectedInfo
err := json.Unmarshal(payload, &connectedInfo)
if err != nil {
return connectedInfo, err
}
return connectedInfo, nil
}
// DeviceTwinDocument the struct of twin document
type DeviceTwinDocument struct {
BaseMessage
Twin map[string]*TwinDoc `json:"twin"`
}
// TwinDoc the struct of twin document
type TwinDoc struct {
LastState *MsgTwin `json:"last"`
CurrentState *MsgTwin `json:"current"`
}
// DeviceTwinUpdate the struct of device twin update
type DeviceTwinUpdate struct {
BaseMessage
Twin map[string]*MsgTwin `json:"twin"`
}
// UnmarshalDeviceTwinDocument unmarshal device twin document
func UnmarshalDeviceTwinDocument(payload []byte) (*DeviceTwinDocument, error) {
var deviceTwinUpdate DeviceTwinDocument
err := json.Unmarshal(payload, &deviceTwinUpdate)
if err != nil {
return &deviceTwinUpdate, err
}
return &deviceTwinUpdate, nil
}
// UnmarshalDeviceTwinUpdate unmarshal device twin update
func UnmarshalDeviceTwinUpdate(payload []byte) (*DeviceTwinUpdate, error) {
var deviceTwinUpdate DeviceTwinUpdate
err := json.Unmarshal(payload, &deviceTwinUpdate)
if err != nil {
return &deviceTwinUpdate, ErrorUnmarshal
}
if deviceTwinUpdate.Twin == nil {
return &deviceTwinUpdate, ErrorUpdate
}
for key, value := range deviceTwinUpdate.Twin {
match := dtcommon.ValidateTwinKey(key)
if !match {
return &deviceTwinUpdate, ErrorKey
}
if value != nil {
if value.Expected != nil {
if value.Expected.Value != nil {
if *value.Expected.Value != "" {
match := dtcommon.ValidateTwinValue(*value.Expected.Value)
if !match {
return &deviceTwinUpdate, ErrorValue
}
}
}
}
if value.Actual != nil {
if value.Actual.Value != nil {
if *value.Actual.Value != "" {
match := dtcommon.ValidateTwinValue(*value.Actual.Value)
if !match {
return &deviceTwinUpdate, ErrorValue
}
}
}
}
}
}
return &deviceTwinUpdate, nil
}
// DealTwinResult the result of dealing twin
type DealTwinResult struct {
Add []dtclient.DeviceTwin
Delete []dtclient.DeviceDelete
Update []dtclient.DeviceTwinUpdate
Result map[string]*MsgTwin
SyncResult map[string]*MsgTwin
Document map[string]*TwinDoc
Err error
}
// DealAttrResult the result of dealing attr
type DealAttrResult struct {
Add []dtclient.DeviceAttr
Delete []dtclient.DeviceDelete
Update []dtclient.DeviceAttrUpdate
Result map[string]*MsgAttr
Err error
}
package dttype
import (
"encoding/json"
"strings"
"time"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtclient"
"github.com/kubeedge/kubeedge/edge/pkg/devicetwin/dtcommon"
)
// UnmarshalMembershipDetail Unmarshal membershipdetail
func UnmarshalMembershipDetail(payload []byte) (*MembershipDetail, error) {
var membershipDetail MembershipDetail
err := json.Unmarshal(payload, &membershipDetail)
if err != nil {
return nil, err
}
return &membershipDetail, nil
}
// UnmarshalMembershipUpdate Unmarshal membershipupdate
func UnmarshalMembershipUpdate(payload []byte) (*MembershipUpdate, error) {
var membershipUpdate MembershipUpdate
err := json.Unmarshal(payload, &membershipUpdate)
if err != nil {
return nil, err
}
return &membershipUpdate, nil
}
// UnmarshalBaseMessage Unmarshal get
func UnmarshalBaseMessage(payload []byte) (*BaseMessage, error) {
var get BaseMessage
err := json.Unmarshal(payload, &get)
if err != nil {
return nil, err
}
return &get, nil
}
// DeviceAttrToMsgAttr deviceattr to msgattr
func DeviceAttrToMsgAttr(deviceAttrs []dtclient.DeviceAttr) map[string]*MsgAttr {
msgAttrs := make(map[string]*MsgAttr, len(deviceAttrs))
for _, attr := range deviceAttrs {
optional := attr.Optional
msgAttrs[attr.Name] = &MsgAttr{
Value: attr.Value,
Optional: &optional,
Metadata: &TypeMetadata{Type: attr.AttrType}}
}
return msgAttrs
}
// DeviceTwinToMsgTwin devicetwin contains meta and version to msgtwin,
func DeviceTwinToMsgTwin(deviceTwins []dtclient.DeviceTwin) map[string]*MsgTwin {
msgTwins := make(map[string]*MsgTwin, len(deviceTwins))
for _, twin := range deviceTwins {
var expectedMeta ValueMetadata
var actualMeta ValueMetadata
var expectedVersion TwinVersion
var actualVersion TwinVersion
optional := twin.Optional
expected := twin.Expected
actual := twin.Actual
msgTwin := &MsgTwin{
Optional: &optional,
Metadata: &TypeMetadata{Type: twin.AttrType}}
if expected != "" {
expectedValue := &TwinValue{Value: &expected}
if twin.ExpectedMeta != "" {
if err := json.Unmarshal([]byte(twin.ExpectedMeta), &expectedMeta); err != nil {
// TODO: handle error
klog.Error(err)
}
expectedValue.Metadata = &expectedMeta
}
msgTwin.Expected = expectedValue
}
if actual != "" {
actualValue := &TwinValue{Value: &actual}
if twin.ActualMeta != "" {
if err := json.Unmarshal([]byte(twin.ActualMeta), &actualMeta); err != nil {
// TODO: handle error
klog.Error(err)
}
actualValue.Metadata = &actualMeta
}
msgTwin.Actual = actualValue
}
if twin.ExpectedVersion != "" {
if err := json.Unmarshal([]byte(twin.ExpectedVersion), &expectedVersion); err != nil {
// TODO: handle error
klog.Error(err)
}
msgTwin.ExpectedVersion = &expectedVersion
}
if twin.ActualVersion != "" {
if err := json.Unmarshal([]byte(twin.ActualVersion), &actualVersion); err != nil {
// TODO: handle error
klog.Error(err)
}
msgTwin.ActualVersion = &actualVersion
}
msgTwins[twin.Name] = msgTwin
}
return msgTwins
}
// MsgAttrToDeviceAttr msgattr to deviceattr
func MsgAttrToDeviceAttr(name string, msgAttr *MsgAttr) dtclient.DeviceAttr {
attrType := "string"
if msgAttr.Metadata != nil {
attrType = msgAttr.Metadata.Type
}
optional := true
if msgAttr.Optional != nil {
optional = *msgAttr.Optional
}
return dtclient.DeviceAttr{
Name: name,
AttrType: attrType,
Optional: optional}
}
// CopyMsgTwin copy msg twin
func CopyMsgTwin(msgTwin *MsgTwin, noVersion bool) MsgTwin {
var result MsgTwin
payload, _ := json.Marshal(msgTwin)
if err := json.Unmarshal(payload, &result); err != nil {
// TODO: handle error
klog.Error(err)
}
if noVersion {
result.ActualVersion = nil
result.ExpectedVersion = nil
}
return result
}
// CopyMsgAttr copy msg attr
func CopyMsgAttr(msgAttr *MsgAttr) MsgAttr {
var result MsgAttr
payload, _ := json.Marshal(msgAttr)
if err := json.Unmarshal(payload, &result); err != nil {
// TODO: handle error
klog.Error(err)
}
return result
}
// MsgTwinToDeviceTwin msgtwin convert to devicetwin
func MsgTwinToDeviceTwin(name string, msgTwin *MsgTwin) dtclient.DeviceTwin {
optional := true
if msgTwin.Optional != nil {
optional = *msgTwin.Optional
}
attrType := "string"
if msgTwin.Metadata != nil {
attrType = msgTwin.Metadata.Type
}
return dtclient.DeviceTwin{
Name: name,
AttrType: attrType,
Optional: optional}
}
// DeviceMsg the struct of device state msg
type DeviceMsg struct {
BaseMessage
DeviceCloudMsg DeviceCloudMsg `json:"device"`
}
// BuildDeviceCloudMsgState build the msg
func BuildDeviceCloudMsgState(baseMessage BaseMessage, device Device) ([]byte, error) {
result := DeviceMsg{
BaseMessage: baseMessage,
DeviceCloudMsg: DeviceCloudMsg{
Name: device.Name,
State: device.State,
LastOnlineTime: device.LastOnline}}
payload, err := json.Marshal(result)
if err != nil {
return []byte(""), err
}
return payload, nil
}
// DeviceAttrUpdate the struct of device attr update msg
type DeviceAttrUpdate struct {
BaseMessage
Attributes map[string]*MsgAttr `json:"attributes"`
}
// BuildDeviceAttrUpdate build the DeviceAttrUpdate
func BuildDeviceAttrUpdate(baseMessage BaseMessage, attrs map[string]*MsgAttr) ([]byte, error) {
result := DeviceAttrUpdate{BaseMessage: baseMessage, Attributes: attrs}
payload, err := json.Marshal(result)
if err != nil {
return []byte(""), err
}
return payload, nil
}
// MembershipGetResult membership get result
type MembershipGetResult struct {
BaseMessage
Devices []Device `json:"devices"`
}
// BuildMembershipGetResult build membership
func BuildMembershipGetResult(baseMessage BaseMessage, devices []*Device) ([]byte, error) {
result := make([]Device, 0, len(devices))
for _, v := range devices {
result = append(result, Device{
ID: v.ID,
Name: v.Name,
Description: v.Description,
State: v.State,
LastOnline: v.LastOnline,
Attributes: v.Attributes})
}
payload, err := json.Marshal(MembershipGetResult{BaseMessage: baseMessage, Devices: result})
if err != nil {
return []byte(""), err
}
return payload, nil
}
// DeviceTwinResult device get result
type DeviceTwinResult struct {
BaseMessage
Twin map[string]*MsgTwin `json:"twin"`
}
// BuildDeviceTwinResult build device twin result, 0:get,1:update,2:sync
func BuildDeviceTwinResult(baseMessage BaseMessage, twins map[string]*MsgTwin, dealType int) ([]byte, error) {
result := make(map[string]*MsgTwin)
if dealType == 0 {
for k, v := range twins {
if v == nil {
result[k] = nil
continue
}
if v.Metadata != nil && strings.Compare(v.Metadata.Type, dtcommon.TypeDeleted) == 0 {
continue
}
twin := *v
twin.ActualVersion = nil
twin.ExpectedVersion = nil
result[k] = &twin
}
} else {
result = twins
}
payload, err := json.Marshal(DeviceTwinResult{BaseMessage: baseMessage, Twin: result})
if err != nil {
return []byte(""), err
}
return payload, nil
}
// BuildErrorResult build error result
func BuildErrorResult(para Parameter) ([]byte, error) {
result := Result{BaseMessage: BaseMessage{Timestamp: time.Now().UnixNano() / 1e6,
EventID: para.EventID},
Code: para.Code,
Reason: para.Reason}
errorResult, err := json.Marshal(result)
if err != nil {
return []byte(""), err
}
return errorResult, nil
}
// DeviceUpdate device update
type DeviceUpdate struct {
BaseMessage
State string `json:"state,omitempty"`
Attributes map[string]*MsgAttr `json:"attributes"`
}
// UnmarshalDeviceUpdate unmarshal device update
func UnmarshalDeviceUpdate(payload []byte) (*DeviceUpdate, error) {
var get DeviceUpdate
err := json.Unmarshal(payload, &get)
if err != nil {
return nil, err
}
return &get, nil
}
// DeviceTwinDelta devicetwin
type DeviceTwinDelta struct {
BaseMessage
Twin map[string]*MsgTwin `json:"twin"`
Delta map[string]string `json:"delta"`
}
// BuildDeviceTwinDelta build device twin delta
func BuildDeviceTwinDelta(baseMessage BaseMessage, twins map[string]*MsgTwin) ([]byte, bool) {
result := make(map[string]*MsgTwin, len(twins))
delta := make(map[string]string)
for k, v := range twins {
if v.Metadata != nil && strings.Compare(v.Metadata.Type, dtcommon.TypeDeleted) == 0 {
continue
}
var expectedValue, actualValue string
if v.Expected != nil && v.Expected.Value != nil {
expectedValue = *v.Expected.Value
}
if expectedValue == "" {
continue
}
if v.Actual != nil && v.Actual.Value != nil {
actualValue = *v.Actual.Value
}
if expectedValue != actualValue {
delta[k] = expectedValue
}
twin := *v
twin.ActualVersion = nil
twin.ExpectedVersion = nil
result[k] = &twin
}
payload, err := json.Marshal(DeviceTwinDelta{BaseMessage: baseMessage, Twin: result, Delta: delta})
if err != nil {
return []byte(""), false
}
if len(delta) > 0 {
return payload, true
}
return payload, false
}
// BuildDeviceTwinDocument build device twin document
func BuildDeviceTwinDocument(baseMessage BaseMessage, twins map[string]*TwinDoc) ([]byte, bool) {
payload, err := json.Marshal(DeviceTwinDocument{BaseMessage: baseMessage, Twin: twins})
if err != nil {
return []byte(""), false
}
return payload, true
}
package util
import (
"crypto/tls"
"crypto/x509"
"errors"
"os"
"time"
MQTT "github.com/eclipse/paho.mqtt.golang"
"k8s.io/klog/v2"
eventconfig "github.com/kubeedge/kubeedge/edge/pkg/eventbus/config"
)
var (
// TokenWaitTime to wait
TokenWaitTime = 120 * time.Second
// Loop connect to wait
LoopConnectPeriord = 5 * time.Second
)
// CheckKeyExist check dis info format
func CheckKeyExist(keys []string, disinfo map[string]interface{}) error {
for _, v := range keys {
_, ok := disinfo[v]
if !ok {
klog.Errorf("key: %s not found", v)
return errors.New("key not found")
}
}
return nil
}
// CheckClientToken checks token is right
func CheckClientToken(token MQTT.Token) (bool, error) {
if token.Wait() && token.Error() != nil {
return false, token.Error()
}
return true, nil
}
// PathExist check file exists or not
func PathExist(path string) bool {
_, err := os.Stat(path)
return err == nil || os.IsExist(err)
}
// HubClientInit create mqtt client config
func HubClientInit(server, clientID, username, password string) *MQTT.ClientOptions {
opts := MQTT.NewClientOptions().AddBroker(server).SetClientID(clientID).SetCleanSession(true)
if username != "" {
opts.SetUsername(username)
if password != "" {
opts.SetPassword(password)
}
}
klog.V(4).Infof("Start to set TLS configuration for MQTT client")
tlsConfig := &tls.Config{}
if eventconfig.Config.TLS.Enable {
cert, err := tls.LoadX509KeyPair(eventconfig.Config.TLS.TLSMqttCertFile, eventconfig.Config.TLS.TLSMqttPrivateKeyFile)
if err != nil {
klog.Errorf("Failed to load x509 key pair: %v", err)
return nil
}
caCert, err := os.ReadFile(eventconfig.Config.TLS.TLSMqttCAFile)
if err != nil {
klog.Errorf("Failed to read TLSMqttCAFile")
return nil
}
pool := x509.NewCertPool()
if ok := pool.AppendCertsFromPEM(caCert); !ok {
klog.Errorf("Cannot parse the certificates")
return nil
}
tlsConfig = &tls.Config{
RootCAs: pool,
Certificates: []tls.Certificate{cert},
InsecureSkipVerify: false,
}
} else {
tlsConfig = &tls.Config{InsecureSkipVerify: true, ClientAuth: tls.NoClientCert}
}
opts.SetTLSConfig(tlsConfig)
klog.V(4).Infof("set TLS configuration for MQTT client successfully")
return opts
}
// LoopConnect connect to mqtt server
func LoopConnect(clientID string, client MQTT.Client) {
for {
klog.Infof("start connect to mqtt server with client id: %s", clientID)
token := client.Connect()
klog.Infof("client %s isconnected: %v", clientID, client.IsConnected())
if rs, err := CheckClientToken(token); !rs {
klog.Errorf("connect error: %v", err)
} else {
return
}
time.Sleep(LoopConnectPeriord)
}
}
package config
import (
"sync"
"github.com/kubeedge/api/apis/componentconfig/edgecore/v1alpha2"
)
var Config Configure
var once sync.Once
type Configure struct {
v1alpha2.EventBus
NodeName string
}
func InitConfigure(eventbus *v1alpha2.EventBus, nodeName string) {
once.Do(func() {
Config = Configure{
EventBus: *eventbus,
NodeName: nodeName,
}
})
}
/*
Copyright 2020 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package dao
import (
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
const (
SubTopicsName = "sub_topics"
)
type SubTopics struct {
Topic string `orm:"column(topic); type(text); pk"`
}
// InsertTopics insert sub_topics
func InsertTopics(topic string) error {
_, err := dbm.DBAccess.Raw("INSERT OR REPLACE INTO sub_topics (topic) VALUES (?)", topic).Exec()
klog.V(4).Infof("INSERT result %v", err)
return err
}
// DeleteTopicsByKey delete sub_topics by key
func DeleteTopicsByKey(key string) error {
num, err := dbm.DBAccess.QueryTable(SubTopicsName).Filter("topic", key).Delete()
klog.V(4).Infof("Delete affected Num: %d, %v", num, err)
return err
}
// QueryAllTopics return all sub_topics, if no error, SubTopics not null
func QueryAllTopics() (*[]string, error) {
event := new([]SubTopics)
_, err := dbm.DBAccess.QueryTable(SubTopicsName).All(event)
if err != nil {
return nil, err
}
var result []string
for _, v := range *event {
result = append(result, v.Topic)
}
return &result, nil
}
package mqtt
import (
"fmt"
"strconv"
"time"
MQTT "github.com/eclipse/paho.mqtt.golang"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/eventbus/common/util"
"github.com/kubeedge/kubeedge/edge/pkg/eventbus/dao"
)
const UploadTopic = "SYS/dis/upload_records"
var (
// MQTTHub client
MQTTHub *Client
// GroupID stands for group id
GroupID string
// ConnectedTopic to send connect event
ConnectedTopic = "$hw/events/connected/%s"
// DisconnectedTopic to send disconnect event
DisconnectedTopic = "$hw/events/disconnected/%s"
// MemberGet to get membership device
MemberGet = "$hw/events/edgeGroup/%s/membership/get"
// MemberGetRes to get membership device
MemberGetRes = "$hw/events/edgeGroup/%s/membership/get/result"
// MemberDetail which edge-client should be pub when service start
MemberDetail = "$hw/events/edgeGroup/%s/membership/detail"
// MemberDetailRes MemberDetail topic resp
MemberDetailRes = "$hw/events/edgeGroup/%s/membership/detail/result"
// MemberUpdate updating of the twin
MemberUpdate = "$hw/events/edgeGroup/%s/membership/updated"
// GroupUpdate updates a edgegroup
GroupUpdate = "$hw/events/edgeGroup/%s/updated"
// GroupAuthGet get temperary aksk from cloudhub
GroupAuthGet = "$hw/events/edgeGroup/%s/authInfo/get"
// GroupAuthGetRes temperary aksk from cloudhub
GroupAuthGetRes = "$hw/events/edgeGroup/%s/authInfo/get/result"
// SubTopics which edge-client should be sub
SubTopics = []string{
"$hw/events/upload/#",
"$hw/events/device/+/+/state/update",
"$hw/events/device/+/+/twin/+",
"$hw/events/node/+/membership/get",
UploadTopic,
"+/user/#",
}
)
// Client struct
type Client struct {
MQTTUrl string
PubClientID string
SubClientID string
Username string
Password string
PubCli MQTT.Client
SubCli MQTT.Client
}
// AccessInfo that deliver between edge-hub and cloud-hub
type AccessInfo struct {
Name string `json:"name"`
Type string `json:"type"`
Topic string `json:"topic"`
Content []byte `json:"content"`
}
func onPubConnectionLost(_ MQTT.Client, err error) {
klog.Errorf("onPubConnectionLost with error: %v", err)
go MQTTHub.InitPubClient()
}
func onSubConnectionLost(_ MQTT.Client, err error) {
klog.Errorf("onSubConnectionLost with error: %v", err)
go MQTTHub.InitSubClient()
}
func onSubConnect(client MQTT.Client) {
for _, t := range SubTopics {
token := client.Subscribe(t, 1, OnSubMessageReceived)
if rs, err := util.CheckClientToken(token); !rs {
klog.Errorf("edge-hub-cli subscribe topic: %s, %v", t, err)
return
}
klog.Infof("edge-hub-cli subscribe topic to %s", t)
}
topics, err := dao.QueryAllTopics()
if err != nil {
klog.Errorf("list edge-hub-cli-topics failed: %v", err)
return
}
if len(*topics) <= 0 {
klog.Infof("list edge-hub-cli-topics status, no record, skip sync")
return
}
for _, t := range *topics {
token := client.Subscribe(t, 1, OnSubMessageReceived)
if rs, err := util.CheckClientToken(token); !rs {
klog.Errorf("edge-hub-cli subscribe topic: %s, %v", t, err)
return
}
klog.Infof("edge-hub-cli subscribe topic to %s", t)
}
}
// OnSubMessageReceived msg received callback
func OnSubMessageReceived(_ MQTT.Client, msg MQTT.Message) {
klog.Infof("OnSubMessageReceived receive msg from topic: %s", msg.Topic())
NewMessageMux().Dispatch(msg.Topic(), msg.Payload())
}
// InitSubClient init sub client
func (mq *Client) InitSubClient() {
timeStr := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
right := len(timeStr)
if right > 10 {
right = 10
}
// if SubClientID is NOT set, we need to generate it by ourselves.
if mq.SubClientID == "" {
mq.SubClientID = fmt.Sprintf("hub-client-sub-%s", timeStr[0:right])
}
subOpts := util.HubClientInit(mq.MQTTUrl, mq.SubClientID, mq.Username, mq.Password)
subOpts.OnConnect = onSubConnect
subOpts.AutoReconnect = false
subOpts.OnConnectionLost = onSubConnectionLost
mq.SubCli = MQTT.NewClient(subOpts)
util.LoopConnect(mq.SubClientID, mq.SubCli)
klog.Info("finish hub-client sub")
}
// InitPubClient init pub client
func (mq *Client) InitPubClient() {
timeStr := strconv.FormatInt(time.Now().UnixNano()/1e6, 10)
right := len(timeStr)
if right > 10 {
right = 10
}
// if PubClientID is NOT set, we need to generate it by ourselves.
if mq.PubClientID == "" {
mq.PubClientID = fmt.Sprintf("hub-client-pub-%s", timeStr[0:right])
}
pubOpts := util.HubClientInit(mq.MQTTUrl, mq.PubClientID, mq.Username, mq.Password)
pubOpts.OnConnectionLost = onPubConnectionLost
pubOpts.AutoReconnect = false
mq.PubCli = MQTT.NewClient(pubOpts)
util.LoopConnect(mq.PubClientID, mq.PubCli)
klog.Info("finish hub-client pub")
}
package mqtt
import (
"bytes"
"fmt"
"regexp"
"strings"
"k8s.io/klog/v2"
)
type HandlerFunc func(topic string, payload []byte)
// MessageMuxEntry message mux entry
type MessageMuxEntry struct {
pattern *MessagePattern
handlerFunc HandlerFunc
}
// MessagePattern message pattern
type MessagePattern struct {
resExpr *MessageExpression
resource string
operation string
}
// MessageExpression message expression
type MessageExpression struct {
Matcher *regexp.Regexp
VarNames []string
VarCount int
}
// MessageMux message mux
type MessageMux struct {
muxEntry []*MessageMuxEntry
}
// NewExpression new expression
func NewExpression() *MessageExpression {
return &MessageExpression{}
}
// GetExpression get expression
func (exp *MessageExpression) GetExpression(topic string) *MessageExpression {
var buffer bytes.Buffer
var varNames []string
var varCount int
buffer.WriteString("^")
if strings.HasPrefix(topic, "/") {
buffer.WriteString("/")
}
fields := strings.Split(strings.Trim(topic, "/"), "/")
for _, field := range fields {
if field == "" {
continue
}
if strings.HasPrefix(field, "{") {
colon := strings.Index(field, ":")
var varName string
if colon != -1 {
varName = strings.TrimSpace(field[1:colon])
paramExpr := strings.TrimSpace(field[colon+1 : len(field)-1])
if paramExpr == "*" { // special case
buffer.WriteString("(.*)")
} else {
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr))
}
} else {
varName = strings.TrimSpace(field[1 : len(field)-1])
buffer.WriteString("([^/]+?)")
}
varNames = append(varNames, varName)
varCount++
} else {
buffer.WriteString(regexp.QuoteMeta(field))
}
buffer.WriteString("/")
}
expression := strings.TrimRight(buffer.String(), "/") + "(/.*)?$"
compiled, err := regexp.Compile(expression)
if err != nil {
klog.Errorf("failed to compile resource, error: %+v", err)
return nil
}
return &MessageExpression{
Matcher: compiled,
VarNames: varNames,
VarCount: varCount,
}
}
var mux MessageMux
// NewMessageMux new message mux
func NewMessageMux() *MessageMux {
return &mux
}
// NewPattern new pattern
func NewPattern(resource string) *MessagePattern {
expression := NewExpression()
resExpr := expression.GetExpression(resource)
if resExpr == nil {
klog.Errorf("bad resource for expression: %s", resource)
return nil
}
return &MessagePattern{
resource: resource,
resExpr: resExpr,
}
}
// NewEntry new entry
func NewEntry(pattern *MessagePattern, handle func(topic string, payload []byte)) *MessageMuxEntry {
return &MessageMuxEntry{
pattern: pattern,
handlerFunc: handle,
}
}
// Match /path/{param}/sub
func (pattern *MessagePattern) Match(topic string) bool {
return pattern.resExpr.Matcher.Match([]byte(topic))
}
// Entry mux := NewMessageMux(ctx, module)
// mux.Entry(NewPattern(res).Op(opr), handle))
func (mux *MessageMux) Entry(pattern *MessagePattern, handle func(topic string, payload []byte)) *MessageMux {
entry := NewEntry(pattern, handle)
mux.muxEntry = append(mux.muxEntry, entry)
return mux
}
func (mux *MessageMux) Dispatch(topic string, payload []byte) {
for _, entry := range mux.muxEntry {
matched := entry.pattern.Match(topic)
if !matched {
continue
}
entry.handlerFunc(topic, payload)
return
}
handleUploadTopic(topic, payload)
}
// RegisterMsgHandler register handler for message if topic is matched in pattern
// for "$hw/events/device/+/twin/+", "$hw/events/node/+/membership/get", send to twin
// for other, send to hub
// for "SYS/dis/upload_records", no need to base64 topic
func RegisterMsgHandler() {
mux.Entry(NewPattern("$hw/events/device/"), handleDeviceTwin)
mux.Entry(NewPattern("$hw/events/node/"), handleDeviceTwin)
mux.Entry(NewPattern("SYS/dis/upload_records"), handleUploadTopic)
}
package mqtt
import (
"encoding/base64"
"fmt"
"k8s.io/klog/v2"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
beehiveModel "github.com/kubeedge/beehive/pkg/core/model"
messagepkg "github.com/kubeedge/kubeedge/edge/pkg/common/message"
"github.com/kubeedge/kubeedge/edge/pkg/common/modules"
)
// handleDevice for topic "$hw/events/device/+/twin/+", "$hw/events/node/+/membership/get"
func handleDeviceTwin(topic string, payload []byte) {
target := modules.TwinGroup
resource := base64.URLEncoding.EncodeToString([]byte(topic))
// routing key will be $hw.<project_id>.events.user.bus.response.cluster.<cluster_id>.node.<node_id>.<base64_topic>
message := beehiveModel.NewMessage("").BuildRouter(modules.BusGroup, modules.UserGroup,
resource, messagepkg.OperationResponse).FillBody(string(payload))
klog.Info(fmt.Sprintf("Received msg from mqttserver, deliver to %s with resource %s", target, message.GetResource()))
beehiveContext.SendToGroup(target, *message)
}
// handleUploadTopic for topic "SYS/dis/upload_records"
func handleUploadTopic(topic string, payload []byte) {
target := modules.HubGroup
message := beehiveModel.NewMessage("").BuildRouter(modules.BusGroup, modules.UserGroup,
topic, beehiveModel.UploadOperation).FillBody(string(payload))
klog.Info(fmt.Sprintf("Received msg from mqttserver, deliver to %s with resource %s", target, message.GetResource()))
beehiveContext.SendToGroup(target, *message)
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package mqtt
import (
"fmt"
"time"
"github.com/256dpi/gomqtt/broker"
"github.com/256dpi/gomqtt/client"
"github.com/256dpi/gomqtt/packet"
"github.com/256dpi/gomqtt/transport"
fuzz "github.com/AdaLogics/go-fuzz-headers"
)
func FuzzMqttPublish(data []byte) int {
f := fuzz.NewConsumer(data)
var topic string
customString, err := f.GetBool()
if err != nil {
return 0
}
if customString {
topic, err = f.GetString()
if err != nil {
return 0
}
} else {
topic = "test"
}
payload, err := f.GetBytes()
if err != nil {
return 0
}
server, err := transport.Launch("tcp://localhost:8080")
if err != nil {
return 0
}
done := make(chan struct{})
backend := broker.NewMemoryBackend()
backend.Logger = func(e broker.LogEvent, c *broker.Client, pkt packet.Generic, msg *packet.Message, err error) {
if e == broker.LostConnection {
close(done)
}
}
engine := broker.NewEngine(backend)
engine.Accept(server)
c := client.New()
wait := make(chan struct{})
c.Callback = func(msg *packet.Message, err error) error {
if err != nil {
return fmt.Errorf("CallBack error %s", err.Error())
}
close(wait)
return nil
}
cf, err := c.Connect(client.NewConfig("tcp://localhost:8080"))
if err != nil {
return 0
}
err = cf.Wait(10 * time.Second)
if err != nil {
return 0
}
sf, err := c.Subscribe(topic, 0)
if err != nil {
return 0
}
err = sf.Wait(10 * time.Second)
if err != nil {
return 0
}
pf, err := c.Publish(topic, payload, 0, false)
if err != nil {
return 0
}
err = pf.Wait(10 * time.Second)
if err != nil {
return 0
}
<-wait
err = c.Disconnect()
if err != nil {
return 0
}
<-done
err = server.Close()
if err != nil {
return 0
}
engine.Close()
return 1
}
/*
Copyright 2019 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package mqtt
import (
"github.com/256dpi/gomqtt/broker"
"github.com/256dpi/gomqtt/packet"
"github.com/256dpi/gomqtt/topic"
"github.com/256dpi/gomqtt/transport"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/eventbus/dao"
)
// Server serve as an internal mqtt broker.
type Server struct {
// Internal mqtt url
url string
// Used to save and match topic, it is thread-safe tree.
tree *topic.Tree
// A server accepts incoming connections.
server transport.Server
// A MemoryBackend stores all in memory.
backend *broker.MemoryBackend
// Qos has three types: QOSAtMostOnce, QOSAtLeastOnce, QOSExactlyOnce.
// now we use QOSAtMostOnce as default.
qos int
// If set retain to true, the topic message will be saved in memory and
// the future subscribers will receive the msg whose subscriptions match
// its topic.
// If set retain to false, then will do nothing.
retain bool
// A sessionQueueSize will default to 100
sessionQueueSize int
}
// NewMqttServer create an internal mqtt server.
func NewMqttServer(sqz int, url string, retain bool, qos int) *Server {
return &Server{
sessionQueueSize: sqz,
url: url,
tree: topic.NewStandardTree(),
retain: retain,
qos: qos,
}
}
// Run launch a server and accept connections.
func (m *Server) Run() error {
var err error
m.server, err = transport.Launch(m.url)
if err != nil {
klog.Errorf("Launch transport failed %v", err)
return err
}
m.backend = broker.NewMemoryBackend()
m.backend.SessionQueueSize = m.sessionQueueSize
m.backend.Logger = func(event broker.LogEvent, client *broker.Client, pkt packet.Generic, msg *packet.Message, err error) {
if event == broker.MessagePublished {
if len(m.tree.Match(msg.Topic)) > 0 {
m.onSubscribe(msg)
}
}
}
engine := broker.NewEngine(m.backend)
engine.Accept(m.server)
return nil
}
// onSubscribe will be called if the topic is matched in topic tree.
func (m *Server) onSubscribe(msg *packet.Message) {
klog.Infof("OnSubscribe recevie msg from topic: %s", msg.Topic)
NewMessageMux().Dispatch(msg.Topic, msg.Payload)
}
// InitInternalTopics sets internal topics to server by default.
func (m *Server) InitInternalTopics() {
for _, v := range SubTopics {
m.tree.Set(v, packet.Subscription{Topic: v, QOS: packet.QOS(m.qos)})
klog.Infof("Subscribe internal topic to %s", v)
}
topics, err := dao.QueryAllTopics()
if err != nil {
klog.Errorf("list edge-hub-cli-topics failed: %v", err)
return
}
if len(*topics) <= 0 {
klog.Infof("list edge-hub-cli-topics status, no record, skip sync")
return
}
for _, t := range *topics {
m.tree.Set(t, packet.Subscription{Topic: t, QOS: packet.QOS(m.qos)})
klog.Infof("Subscribe internal topic to %s", t)
}
}
// SetTopic set the topic to internal mqtt broker.
func (m *Server) SetTopic(topic string) {
m.tree.Set(topic, packet.Subscription{Topic: topic, QOS: packet.QOSAtMostOnce})
}
// RemoveTopic remove the topic from internal mqtt broker.
func (m *Server) RemoveTopic(topic string) {
m.tree.Remove(topic, packet.Subscription{Topic: topic, QOS: packet.QOSAtMostOnce})
}
// Publish will dispatch topic msg to its subscribers directly.
func (m *Server) Publish(topic string, payload []byte) {
client := &broker.Client{}
msg := &packet.Message{
Topic: topic,
Retain: m.retain,
Payload: payload,
QOS: packet.QOS(m.qos),
}
if err := m.backend.Publish(client, msg, nil); err != nil {
// TODO: handle error
klog.Error(err)
}
}
package dao
import (
"fmt"
"strings"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
// constant metatable name reference
const (
MetaTableName = "meta"
)
// Meta metadata object
type Meta struct {
Key string `orm:"column(key); size(256); pk"`
Type string `orm:"column(type); size(32)"`
Value string `orm:"column(value); null; type(text)"`
}
// SaveMeta save meta to db
func SaveMeta(meta *Meta) error {
num, err := dbm.DBAccess.Insert(meta)
klog.V(4).Infof("Insert affected Num: %d, %v", num, err)
if err == nil || IsNonUniqueNameError(err) {
return nil
}
return err
}
// IsNonUniqueNameError tests if the error returned by sqlite is unique.
// It will check various sqlite versions.
func IsNonUniqueNameError(err error) bool {
str := err.Error()
if strings.HasSuffix(str, "are not unique") || strings.Contains(str, "UNIQUE constraint failed") || strings.HasSuffix(str, "constraint failed") {
return true
}
return false
}
// DeleteMetaByKey delete meta by key
func DeleteMetaByKey(key string) error {
num, err := dbm.DBAccess.QueryTable(MetaTableName).Filter("key", key).Delete()
klog.V(4).Infof("Delete affected Num: %d, %v", num, err)
return err
}
// DeleteMetaByKeyAndPodUID delete meta by key and podUID
func DeleteMetaByKeyAndPodUID(key, podUID string) (int64, error) {
sqlStr := fmt.Sprintf("DELETE FROM meta WHERE key = '%s' and value LIKE '%%%s%%'", key, podUID)
res, err := dbm.DBAccess.Raw(sqlStr).Exec()
if err != nil {
klog.Errorf("delete pod by key %s and podUID %s failed, err: %v", key, podUID, err)
return 0, err
}
return res.RowsAffected()
}
// UpdateMeta update meta
func UpdateMeta(meta *Meta) error {
num, err := dbm.DBAccess.Update(meta) // will update all field
klog.V(4).Infof("Update affected Num: %d, %v", num, err)
return err
}
// InsertOrUpdate insert or update meta
func InsertOrUpdate(meta *Meta) error {
_, err := dbm.DBAccess.Raw("INSERT OR REPLACE INTO meta (key, type, value) VALUES (?,?,?)", meta.Key, meta.Type, meta.Value).Exec() // will update all field
klog.V(4).Infof("Update result %v", err)
return err
}
// UpdateMetaField update special field
func UpdateMetaField(key string, col string, value interface{}) error {
num, err := dbm.DBAccess.QueryTable(MetaTableName).Filter("key", key).Update(map[string]interface{}{col: value})
klog.V(4).Infof("Update affected Num: %d, %v", num, err)
return err
}
// UpdateMetaFields update special fields
func UpdateMetaFields(key string, cols map[string]interface{}) error {
num, err := dbm.DBAccess.QueryTable(MetaTableName).Filter("key", key).Update(cols)
klog.V(4).Infof("Update affected Num: %d, %v", num, err)
return err
}
// QueryMeta return only meta's value, if no error, Meta not null
func QueryMeta(key string, condition string) (*[]string, error) {
meta := new([]Meta)
_, err := dbm.DBAccess.QueryTable(MetaTableName).Filter(key, condition).All(meta)
if err != nil {
return nil, err
}
var result []string
for _, v := range *meta {
result = append(result, v.Value)
}
return &result, nil
}
// QueryAllMeta return all meta, if no error, Meta not null
func QueryAllMeta(key string, condition string) (*[]Meta, error) {
meta := new([]Meta)
_, err := dbm.DBAccess.QueryTable(MetaTableName).Filter(key, condition).All(meta)
if err != nil {
return nil, err
}
return meta, nil
}
package v2
import (
"github.com/beego/beego/v2/client/orm"
"k8s.io/apimachinery/pkg/runtime/schema"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
// constant metatable name reference
const (
NewMetaTableName = "meta_v2"
// column name
KEY = "Key"
GVR = "GroupVersionResource"
NS = "Namespace"
NAME = "Name"
RV = "ResourceVersion"
NullNamespace = "null"
GroupCore = "core"
NullName = "null"
)
// MetaV2 record k8s api object
type MetaV2 struct {
// Key is the primary key of a line record, format like k8s obj key in etcd:
// /Group/Version/Resources/Namespace/Name
//0/1 /2 /3 /4 /5
// /core/v1/pods/{namespaces}/{name} normal obj
// /core/v1/pods/{namespaces} List obj
// /extensions/v1beta1/ingresses/{namespaces}/{name} normal obj
// /storage.k8s.io/v1beta1/csidrivers/null/{name} cluster scope obj
Key string `orm:"column(key); size(256); pk"`
// GroupVersionResource are set buy gvr.String() like "/v1, Resource=endpoints"
GroupVersionResource string `orm:"column(groupversionresource); size(256);"`
// Namespace is the namespace of an api object, and set as metadata.namespace
Namespace string `orm:"column(namespace); size(256)"`
// Name is the name of api object, and set as metadata.name
Name string `orm:"column(name); size(256)"`
// ResourceVersion is the resource version of the obj, and set as metadata.resourceVersion
ResourceVersion uint64 `orm:"column(resourceversion); size(256)"`
// Value is the api object in json format
// TODO: change to []byte
Value string `orm:"column(value); null; type(text)"`
}
// List a slice of raw data by Group Version Resource Namespace Name
func RawMetaByGVRNN(gvr schema.GroupVersionResource, namespace string, name string) (*[]MetaV2, error) {
objs := new([]MetaV2)
var err error
// TODO: use getCondition
//cond := getCondition(gvr,namespace,name)
//klog.Infof("cond:%+v",cond)
//_,err = dbm.DBAccess.QueryTable(NewMetaTableName).SetCond(cond).All(objs)
if gvr.Empty() {
_, err = dbm.DBAccess.QueryTable(NewMetaTableName).All(objs)
} else {
switch namespace {
case NullNamespace, "":
switch name {
case NullName, "":
_, err = dbm.DBAccess.QueryTable(NewMetaTableName).Filter(GVR, gvr.String()).All(objs)
default:
_, err = dbm.DBAccess.QueryTable(NewMetaTableName).Filter(GVR, gvr.String()).Filter(NAME, name).All(objs)
}
default:
switch name {
case NullName, "":
_, err = dbm.DBAccess.QueryTable(NewMetaTableName).Filter(GVR, gvr.String()).Filter(NS, namespace).All(objs)
default:
_, err = dbm.DBAccess.QueryTable(NewMetaTableName).Filter(GVR, gvr.String()).Filter(NS, namespace).Filter(NAME, name).All(objs)
}
}
}
if err != nil {
return nil, err
}
return objs, nil
}
func getCondition(gvr schema.GroupVersionResource, namespace string, name string) *orm.Condition {
cond := orm.NewCondition()
cond.And(GVR, gvr.String())
if namespace != NullNamespace {
cond.And(NS, namespace)
}
if name != NullName {
cond.And(NAME, name)
}
return cond
}
/*
Copyright 2025 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package v2
import (
"encoding/json"
"fmt"
"strings"
"github.com/beego/beego/v2/client/orm"
"k8s.io/apimachinery/pkg/runtime/schema"
operationv1alpha1 "github.com/kubeedge/api/apis/operations/v1alpha1"
operationv1alpha2 "github.com/kubeedge/api/apis/operations/v1alpha2"
"github.com/kubeedge/kubeedge/common/types"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
var (
MetaGVRUpgrade = schema.GroupVersionResource{
Group: operationv1alpha2.GroupName,
Version: operationv1alpha2.Version,
Resource: "nodeupgradejobspecs",
}
// Deprecated: For compatibility with v1alpha1 version, It will be removed in v1.23
MetaGVRUpgradeV1alpha1 = schema.GroupVersionResource{
Group: operationv1alpha1.GroupName,
Version: operationv1alpha1.Version,
Resource: "nodeupgradejobtaskrequests",
}
)
// Upgrade is a dao wrapper for node upgrade job record.
// It's used to store information needed to continue upgrading after the upgrade is confirmed.
// Whether complete information about node jobs needs to be stored is another matter.
type Upgrade struct {
db orm.Ormer
}
func NewUpgrade() *Upgrade {
return &Upgrade{
db: dbm.DBAccess,
}
}
// Generates the key for meta_v2 table. The format is:
// /{group}/{version}/{resource}/{jobname}/nodes/{nodename}
func (dao *Upgrade) key(jobname, nodename string) string {
items := []string{
"/" + MetaGVRUpgrade.Group, // Start with '/'
MetaGVRUpgrade.Version,
MetaGVRUpgrade.Resource,
jobname,
"nodes",
nodename,
}
return strings.Join(items, "/")
}
// Get returns jobname, nodename and NodeUpgradeJobSpec that query by GVR.
// The return values jobname and nodename are parsed from 'meta_v2.key'.
// The NodeUpgradeJobSpec is parsed from 'meta_v2.value'.
// Usually, only 1 or 0 rows of data should be queried by GVR.
func (dao *Upgrade) Get() (string, string, *operationv1alpha2.NodeUpgradeJobSpec, error) {
row, err := onlyOneUpgradeRowByGVR(dao.db, MetaGVRUpgrade.String())
if err != nil {
return "", "", nil, err
}
if row == nil {
return "", "", nil, nil
}
arrs := strings.Split(row.Key, "/")
jobname, nodename := arrs[len(arrs)-3], arrs[len(arrs)-1]
if row.Value == "" {
return jobname, nodename, nil, nil
}
var spec operationv1alpha2.NodeUpgradeJobSpec
if err := json.Unmarshal([]byte(row.Value), &spec); err != nil {
return jobname, nodename, nil, fmt.Errorf("failed to unmarshal metav2 value to NodeTaskRequest, err: %v", err)
}
return jobname, nodename, &spec, nil
}
// Save saves jobname, nodename and NodeUpgradeJobSpec to metav2 table.
// A node will only retain one piece of upgrade data.
func (dao *Upgrade) Save(jobname, nodename string, spec *operationv1alpha2.NodeUpgradeJobSpec) error {
// Cleaning up historical nodeupgradejobtaskrequests datas
if err := dao.Delete(); err != nil {
return err
}
buff, err := json.Marshal(spec)
if err != nil {
return fmt.Errorf("failed to marshal NodeTaskRequest to json, err: %v", err)
}
meta := MetaV2{
Key: dao.key(jobname, nodename),
Name: jobname,
Namespace: NullNamespace,
GroupVersionResource: MetaGVRUpgrade.String(),
Value: string(buff),
}
if _, err := dao.db.Insert(&meta); err != nil {
return fmt.Errorf("failed to save NodeTaskRequest to metav2, err: %v", err)
}
return nil
}
// Delete deletes nodeupgradejobspecs resources from metav2 table.
func (dao *Upgrade) Delete() error {
row, err := onlyOneUpgradeRowByGVR(dao.db, MetaGVRUpgrade.String())
if err != nil {
return err
}
if row == nil {
return nil
}
if _, err := dao.db.Delete(row); err != nil {
return fmt.Errorf("failed to delete NodeTaskRequest by key %s, err: %v", row.Key, err)
}
return nil
}
// onlyOneUpgradeRowByGVR returns the first row of data that query by GVR.
func onlyOneUpgradeRowByGVR(db orm.Ormer, gvr string) (*MetaV2, error) {
var rows []MetaV2
if _, err := db.QueryTable(NewMetaTableName).
Filter(GVR, gvr).All(&rows); err != nil {
return nil, fmt.Errorf("failed to query metav2 by %s GVR, err: %v", gvr, err)
}
if len(rows) == 0 {
return nil, nil
}
return &rows[0], nil
}
// UpgradeV1alpha1 is a dao wrapper for v1alpha1 node upgrade job record.
// It's used to store information needed to continue upgrading after the upgrade is confirmed.
// Deprecated: For compatibility with v1alpha1 version, It will be removed in v1.23
type UpgradeV1alpha1 struct {
db orm.Ormer
}
func NewUpgradeV1alpha1() *UpgradeV1alpha1 {
return &UpgradeV1alpha1{
db: dbm.DBAccess,
}
}
// Generates the key for meta_v2 table. The format is:
// /{group}/{version}/{resource}/{taskID}
func (dao *UpgradeV1alpha1) key(taskID string) string {
items := []string{
"/" + MetaGVRUpgradeV1alpha1.Group, // Start with '/'
MetaGVRUpgradeV1alpha1.Version,
MetaGVRUpgradeV1alpha1.Resource,
taskID,
}
return strings.Join(items, "/")
}
// Get returns the NodeTaskRequest that query by GVR and parsed from 'meta_v2.value'.
// Usually, only 1 or 0 rows of data should be queried by GVR.
func (dao *UpgradeV1alpha1) Get() (*types.NodeTaskRequest, error) {
row, err := onlyOneUpgradeRowByGVR(dao.db, MetaGVRUpgradeV1alpha1.String())
if err != nil {
return nil, err
}
if row == nil || row.Value == "" {
return nil, nil
}
var req types.NodeTaskRequest
if err := json.Unmarshal([]byte(row.Value), &req); err != nil {
return nil, fmt.Errorf("failed to unmarshal metav2 value to NodeTaskRequest, err: %v", err)
}
return &req, nil
}
// Save saves the NodeTaskRequest to metav2 table.
// A node will only retain one piece of upgrade data.
func (dao *UpgradeV1alpha1) Save(request *types.NodeTaskRequest) error {
// Cleaning up historical nodeupgradejobtaskrequests datas
if err := dao.Delete(); err != nil {
return err
}
buff, err := json.Marshal(request)
if err != nil {
return fmt.Errorf("failed to marshal NodeTaskRequest to json, err: %v", err)
}
meta := MetaV2{
Key: dao.key(request.TaskID),
Name: request.TaskID,
Namespace: NullNamespace,
GroupVersionResource: MetaGVRUpgradeV1alpha1.String(),
Value: string(buff),
}
if _, err := dao.db.Insert(&meta); err != nil {
return fmt.Errorf("failed to save NodeTaskRequest to metav2, err: %v", err)
}
return nil
}
// Delete deletes nodeupgradejobtaskrequests resources from metav2 table.
func (dao *UpgradeV1alpha1) Delete() error {
row, err := onlyOneUpgradeRowByGVR(dao.db, MetaGVRUpgradeV1alpha1.String())
if err != nil {
return err
}
if row == nil {
return nil
}
if _, err := dao.db.Delete(row); err != nil {
return fmt.Errorf("failed to delete NodeTaskRequest by key %s, err: %v", row.Key, err)
}
return nil
}
/*
Copyright 2021 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package metaserver
import (
"context"
"crypto/sha256"
"encoding/json"
"fmt"
"sync"
"time"
apierrors "k8s.io/apimachinery/pkg/api/errors"
metainternalversion "k8s.io/apimachinery/pkg/apis/meta/internalversion"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
"k8s.io/apimachinery/pkg/runtime/schema"
"k8s.io/klog/v2"
beehiveContext "github.com/kubeedge/beehive/pkg/core/context"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/cloud/pkg/common/messagelayer"
)
// Application record the resources that are in applying for requesting to be transferred down from the cloud, please:
// 0.use Agent.Generate to generate application
// 1.use Agent.Apply to apply application( generate msg and send it to cloud dynamiccontroller)
type Application struct {
// ID is the SHA256 checksum generated from request information
ID string
// The following field defines the Application request information
// Key format: group version resource namespaces name
Key string
Verb ApplicationVerb
Nodename string
Option []byte
ReqBody []byte
Subresource string
// The following field defines the Application response result
RespBody []byte
Status ApplicationStatus
Reason string // why in this status
Error apierrors.StatusError
ctx context.Context // to end app.Wait
cancel context.CancelFunc
// count the number of current citations
count uint64
countLock *sync.Mutex
// Timestamp record the last closing time of application, only make sense when count == 0
Timestamp time.Time
}
func NewApplication(ctx context.Context, key string, verb ApplicationVerb, nodename, subresource string, option interface{}, reqBody interface{}) (*Application, error) {
var v1 metav1.ListOptions
if internal, ok := option.(metainternalversion.ListOptions); ok {
err := metainternalversion.Convert_internalversion_ListOptions_To_v1_ListOptions(&internal, &v1, nil)
if err != nil {
// error here won't happen, log in case
klog.Errorf("failed to transfer internalListOption to v1ListOption, force set to empty")
}
option = v1
}
ctx2, cancel := context.WithCancel(ctx)
app := &Application{
Key: key,
Verb: verb,
Nodename: nodename,
Subresource: subresource,
Status: PreApplying,
Option: ToBytes(option),
ReqBody: ToBytes(reqBody),
ctx: ctx2,
cancel: cancel,
count: 0,
countLock: &sync.Mutex{},
Timestamp: time.Time{},
}
app.Add()
return app, nil
}
func (a *Application) Identifier() string {
if a.ID != "" {
return a.ID
}
b := []byte(a.Nodename)
b = append(b, []byte(a.Key)...)
b = append(b, []byte(a.Verb)...)
b = append(b, a.Option...)
b = append(b, a.ReqBody...)
b = append(b, []byte(a.Subresource)...)
a.ID = fmt.Sprintf("%x", sha256.Sum256(b))
return a.ID
}
func (a *Application) String() string {
return fmt.Sprintf("(NodeName=%v;Key=%v;Verb=%v;Status=%v;Reason=%v)", a.Nodename, a.Key, a.Verb, a.Status, a.Reason)
}
func (a *Application) ReqContent() interface{} {
return a.ReqBody
}
func (a *Application) RespContent() interface{} {
return a.RespBody
}
// OptionTo convert application option. Remember `i` must be a pointer to the initialized variable
func (a *Application) OptionTo(i interface{}) error {
err := json.Unmarshal(a.Option, i)
if err != nil {
return fmt.Errorf("failed to parse Option bytes, %v", err)
}
return nil
}
func (a *Application) ReqBodyTo(i interface{}) error {
err := json.Unmarshal(a.ReqBody, i)
if err != nil {
return fmt.Errorf("failed to parse ReqBody bytes, %v", err)
}
return nil
}
func (a *Application) RespBodyTo(i interface{}) error {
err := json.Unmarshal(a.RespBody, i)
if err != nil {
return fmt.Errorf("failed to parse RespBody bytes, %v", err)
}
return nil
}
func (a *Application) GVR() schema.GroupVersionResource {
gvr, _, _ := ParseKey(a.Key)
return gvr
}
func (a *Application) Namespace() string {
_, ns, _ := ParseKey(a.Key)
return ns
}
func (a *Application) Cancel() {
if a.cancel != nil {
a.cancel()
}
}
func (a *Application) GetStatus() ApplicationStatus {
return a.Status
}
// Wait the result of application after it is applied by application agent
func (a *Application) Wait() {
if a.ctx != nil {
<-a.ctx.Done()
}
}
func (a *Application) Reset() {
if a.ctx != nil && a.cancel != nil {
a.cancel()
}
a.ctx, a.cancel = context.WithCancel(beehiveContext.GetContext())
a.Reason = ""
a.RespBody = []byte{}
}
func (a *Application) Add() {
a.countLock.Lock()
a.count++
a.countLock.Unlock()
}
func (a *Application) getCount() uint64 {
a.countLock.Lock()
c := a.count
a.countLock.Unlock()
return c
}
// Close must be called when applicant no longer using application
func (a *Application) Close() {
a.countLock.Lock()
defer a.countLock.Unlock()
if a.count == 0 {
return
}
a.Timestamp = time.Now()
a.count--
if a.count == 0 {
a.Status = Completed
}
}
func (a *Application) LastCloseTime() time.Time {
a.countLock.Lock()
defer a.countLock.Unlock()
if a.count == 0 && !a.Timestamp.IsZero() {
return a.Timestamp
}
return time.Time{}
}
func ToBytes(i interface{}) (bytes []byte) {
if i == nil {
return
}
if bytes, ok := i.([]byte); ok {
return bytes
}
var err error
if bytes, err = json.Marshal(i); err != nil {
klog.Errorf("marshal content to []byte failed, err: %v", err)
}
return
}
// extract application in message's Content
func MsgToApplication(msg model.Message) (*Application, error) {
var app = new(Application)
contentData, err := msg.GetContentData()
if err != nil {
return nil, err
}
err = json.Unmarshal(contentData, app)
if err != nil {
return nil, err
}
nodeID, err := messagelayer.GetNodeID(msg)
if err != nil {
nodeID = app.Nodename
}
app.Nodename = nodeID
return app, nil
}
// MsgToApplications extract applications in message's Content
func MsgToApplications(msg model.Message) (map[string]Application, error) {
contentData, err := msg.GetContentData()
if err != nil {
return nil, err
}
applications := make(map[string]Application)
err = json.Unmarshal(contentData, &applications)
if err != nil {
return nil, err
}
return applications, nil
}
package metaserver
import (
"context"
"fmt"
"strings"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apimachinery/pkg/runtime/schema"
apirequest "k8s.io/apiserver/pkg/endpoints/request"
"k8s.io/klog/v2"
v2 "github.com/kubeedge/kubeedge/edge/pkg/metamanager/dao/v2"
"github.com/kubeedge/kubeedge/pkg/metaserver/util"
)
func KeyFunc(obj runtime.Object) string {
key, err := KeyFuncObj(obj)
if err != nil {
klog.Errorf("failed to parse key from an obj:%v", err)
return ""
}
return key
}
// KeyFuncObj generated key from obj
func KeyFuncObj(obj runtime.Object) (string, error) {
accessor, err := meta.Accessor(obj)
if err != nil {
return "", err
}
objKind := obj.GetObjectKind()
gvk := objKind.GroupVersionKind()
if gvk.Empty() {
return "", fmt.Errorf("could not get group/version/kind information in obj")
}
group := gvk.Group
version := gvk.Version
resource := util.UnsafeKindToResource(gvk.Kind)
namespace := accessor.GetNamespace()
name := accessor.GetName()
if group == "" {
group = v2.GroupCore
}
if namespace == "" {
namespace = v2.NullNamespace
}
key := fmt.Sprintf("/%s/%s/%s/%s/%s", group, version, resource, namespace, name)
return key, nil
}
// KeyFuncReq generate key from req context
func KeyFuncReq(ctx context.Context, _ string) (string, error) {
info, ok := apirequest.RequestInfoFrom(ctx)
if !ok {
return "", fmt.Errorf("no request info in context")
}
if !info.IsResourceRequest {
return info.Path, nil
}
group := ""
switch info.APIPrefix {
case "api":
group = v2.GroupCore
case "apis":
if info.APIGroup == "" {
return "", fmt.Errorf("failed to get key from request info")
}
group = info.APIGroup
default:
return "", fmt.Errorf("failed to get key from request info")
}
version := info.APIVersion
resource := info.Resource
namespace := info.Namespace
name := info.Name
// if the request resource is namespaces, set the ns to null in the key
if resource == "namespaces" {
namespace = v2.NullNamespace
}
if namespace == "" {
namespace = v2.NullNamespace
}
if name == "" {
name = v2.NullName
}
key := fmt.Sprintf("/%s/%s/%s/%s/%s", group, version, resource, namespace, name)
return key, nil
}
func KeyRootFunc(ctx context.Context) string {
key, err := KeyFuncReq(ctx, "")
if err != nil {
panic("fail to get list key!")
}
return key
}
// ParseKey parse key to group/version/resource, namespace, name
// Now key format is like below:
// 0/1 /2 /3 /4 /5
//
// /core/v1/pods/{namespaces}/{name}
//
// 0/1 /2/3
//
// /app/v1/deployments
//
// 0/1 /2 /3
//
// /core/v1/endpoints
//
// Remember that ParseKey is not responsible for verifying the validity of the content,
// for example, gvr in key /app/v1111/endpoint will be parsed as {Group:"app", Version:"v1111", Resource:"endpoint"}
func ParseKey(key string) (gvr schema.GroupVersionResource, namespace string, name string) {
sl := key
for strings.HasSuffix(sl, "/") {
sl = strings.TrimSuffix(sl, "/")
}
if sl == "" {
return
}
slices := strings.Split(sl, "/")
length := len(slices)
if len(slices) == 0 || slices[0] != "" {
// klog.Errorf("[metaserver]failed to parse key: format error, %v",key)
return
}
var (
groupIndex = 1
versionIndex = 2
resourceIndex = 3
namespaceIndex = 4
nameIndex = 5
)
IndexCheck(length, &groupIndex, &versionIndex, &resourceIndex, &namespaceIndex, &nameIndex)
group := slices[groupIndex]
if group == v2.GroupCore {
group = ""
}
gv, err := schema.ParseGroupVersion(group + "/" + slices[versionIndex])
if err != nil {
klog.Error(err)
return
}
gvr = gv.WithResource(slices[resourceIndex])
namespace = slices[namespaceIndex]
if namespace == v2.NullNamespace {
namespace = ""
}
name = slices[nameIndex]
if name == v2.NullName {
name = ""
}
return gvr, namespace, name
}
// force set index to 0 if out of range
func IndexCheck(length int, indexes ...*int) {
for _, index := range indexes {
if *index >= length {
*index = 0
}
}
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package metaserver
func FuzzParseKey(data []byte) int {
_, _, _ = ParseKey(string(data))
return 1
}
package util
import (
"context"
"strings"
"golang.org/x/text/cases"
"golang.org/x/text/language"
"k8s.io/apimachinery/pkg/api/meta"
"k8s.io/apimachinery/pkg/apis/meta/v1/unstructured"
"k8s.io/apimachinery/pkg/fields"
"k8s.io/apimachinery/pkg/labels"
"k8s.io/apimachinery/pkg/runtime"
"k8s.io/apiserver/pkg/storage"
"k8s.io/client-go/kubernetes/scheme"
"k8s.io/klog/v2"
beehiveModel "github.com/kubeedge/beehive/pkg/core/model"
)
// MetaType is generally consisted of apiversion, kind like:
// {
// apiVersion: apps/v1
// kind: Deployments
// }
// TODO: support crd
func SetMetaType(obj runtime.Object) error {
accessor, err := meta.Accessor(obj)
if err != nil {
return err
}
//gvr,_,_ := apiserverlite.ParseKey(accessor.GetSelfLink())
kinds, _, err := scheme.Scheme.ObjectKinds(obj)
if err != nil {
return err
}
gvk := kinds[0]
obj.GetObjectKind().SetGroupVersionKind(gvk)
klog.V(6).Infof("[metaserver]successfully set MetaType for obj %v, %+v", obj.GetObjectKind(), accessor.GetName())
return nil
}
// Sometimes, we need guess kind according to resource:
// 1. In most cases, is like pods to Pod,
// 2. In some unusual cases, requires special treatment like endpoints to Endpoints
func UnsafeResourceToKind(r string) string {
if len(r) == 0 {
return r
}
unusualResourceToKind := map[string]string{
"endpoints": "Endpoints",
"endpointslices": "EndpointSlice",
"nodes": "Node",
"namespaces": "Namespace",
"services": "Service",
"podstatus": "PodStatus",
"nodestatus": "NodeStatus",
"customresourcedefinitions": "CustomResourceDefinition",
"customresourcedefinitionlist": "CustomResourceDefinitionList",
}
if v, isUnusual := unusualResourceToKind[r]; isUnusual {
return v
}
caser := cases.Title(language.Und)
k := caser.String(r)
switch {
case strings.HasSuffix(k, "ies"):
return strings.TrimSuffix(k, "ies") + "y"
case strings.HasSuffix(k, "ses"):
return strings.TrimSuffix(k, "es")
case strings.HasSuffix(k, "s"):
return strings.TrimSuffix(k, "s")
}
return k
}
func UnsafeKindToResource(k string) string {
if len(k) == 0 {
return k
}
unusualKindToResource := map[string]string{
"Endpoints": "endpoints",
"PodStatus": "podstatus",
"NodeStatus": "nodestatus",
"CustomResourceDefinition": "customresourcedefinitions",
"CustomResourceDefinitionList": "customresourcedefinitionlist",
}
if v, isUnusual := unusualKindToResource[k]; isUnusual {
return v
}
r := strings.ToLower(k)
switch string(r[len(r)-1]) {
case "s":
return r + "es"
case "y":
return strings.TrimSuffix(r, "y") + "ies"
}
return r + "s"
}
func UnstructuredAttr(obj runtime.Object) (labels.Set, fields.Set, error) {
switch obj.GetObjectKind().GroupVersionKind().Kind {
case "Pod":
metadata, err := meta.Accessor(obj)
if err != nil {
return nil, nil, err
}
setMap := make(fields.Set)
if metadata.GetName() != "" {
setMap["metadata.name"] = metadata.GetName()
}
if metadata.GetNamespace() != "" {
setMap["metadata.namespaces"] = metadata.GetNamespace()
}
unstrObj, ok := obj.(*unstructured.Unstructured)
if ok {
value, found, err := unstructured.NestedString(unstrObj.Object, "spec", "nodeName")
if found && err == nil {
setMap["spec.nodeName"] = value
}
}
return metadata.GetLabels(), setMap, nil
default:
return storage.DefaultNamespaceScopedAttr(obj)
}
}
// GetMessageUID returns the UID of the object in message
func GetMessageAPIVersion(msg *beehiveModel.Message) string {
obj, ok := msg.Content.(runtime.Object)
if ok {
return obj.GetObjectKind().GroupVersionKind().GroupVersion().String()
}
return ""
}
// GetMessageUID returns the UID of the object in message
func GetMessageResourceType(msg *beehiveModel.Message) string {
obj, ok := msg.Content.(runtime.Object)
if ok {
return obj.GetObjectKind().GroupVersionKind().Kind
}
return ""
}
type key int
const (
// applicationKey is the context key for the application.
applicationIDKey key = iota
)
// WithApplicationID returns a copy of parent in which the applicationID value is set
func WithApplicationID(parent context.Context, appID string) context.Context {
return context.WithValue(parent, applicationIDKey, appID)
}
// ApplicationIDFrom returns the value of the ApplicationID key on the ctx
func ApplicationIDFrom(ctx context.Context) (string, bool) {
applicationID, ok := ctx.Value(applicationIDKey).(string)
return applicationID, ok
}
// ApplicationIDValue returns the value of the applicationID key on the ctx, or the empty string if none
func ApplicationIDValue(ctx context.Context) string {
applicationID, _ := ApplicationIDFrom(ctx)
return applicationID
}
/*
Copyright 2025 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package message
import (
"encoding/json"
"fmt"
"strings"
operationsv1alpha2 "github.com/kubeedge/api/apis/operations/v1alpha2"
)
// FormatNodeUpgradeJobExtend formats the node upgrade job extend.
func FormatNodeUpgradeJobExtend(fromVer, toVer string) string {
return strings.Join([]string{fromVer, toVer}, ",")
}
// ParseNodeUpgradeJobExtend parses the node upgrade job extend.
func ParseNodeUpgradeJobExtend(extend string) (fromVer, toVer string, err error) {
parts := strings.Split(extend, ",")
if len(parts) != 2 {
return "", "", fmt.Errorf("invalid node upgrade job extend: %s", extend)
}
return parts[0], parts[1], nil
}
func FormatImagePrePullJobExtend(statusItems []operationsv1alpha2.ImageStatus) (string, error) {
bff, err := json.Marshal(statusItems)
if err != nil {
return "", err
}
return string(bff), nil
}
func ParseImagePrePullJobExtend(extend string) ([]operationsv1alpha2.ImageStatus, error) {
var statusItems []operationsv1alpha2.ImageStatus
if err := json.Unmarshal([]byte(extend), &statusItems); err != nil {
return nil, err
}
return statusItems, nil
}
/*
Copyright 2025 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package message
import (
"errors"
"strings"
"k8s.io/klog/v2"
operationsv1alpha2 "github.com/kubeedge/api/apis/operations/v1alpha2"
"github.com/kubeedge/kubeedge/common/constants"
)
const (
OperationUpdateNodeActionStatus = "UpdateNodeActionStatus"
)
// UpstreamMessage defines the upstream message content of the node job.
type UpstreamMessage struct {
// Action defines the action of the node job.
Action string `json:"action"`
// Succ defines whether the action is successful.
Succ bool `json:"succ"`
// FinishTime defines the finish time of the action.
FinishTime string `json:"finishTime"`
// Reason defines error message.
Reason string `json:"reason"`
// Extend uses to stored serializable string. Some node actions may do multiple things,
// this field can store the Extend infos for cloud parsing.
Extend string `json:"extend"`
}
// Resource defines the message resource of the node job.
type Resource struct {
// APIVersion defines the group/version of the node job resource
APIVersion string
// ResourceType defines the node job resource of Kubernetes.(e.g., nodeupgradejob, imagepulljob, etc.)
ResourceType string
// JobName defines the name of the node job job resource.
JobName string
// NodeName defines the name of the node.
NodeName string
}
// Check checks the resource fields.
func (r Resource) Check() error {
if r.APIVersion == "" {
return errors.New("the APIVersion field must not be blank")
}
if r.ResourceType == "" {
return errors.New("the ResourceType field must not be blank")
}
if r.JobName == "" {
return errors.New("the job name field must not be blank")
}
if r.NodeName == "" {
return errors.New("the node name field must not be blank")
}
return nil
}
// String returns resource that satisfy the message resource format
// {apiversion}/{resource_type}/{job_name}/node/{node_name}.
// It is best to use Check method to verify fields first.
func (r Resource) String() string {
return strings.Join([]string{r.APIVersion, r.ResourceType, r.JobName, "node", r.NodeName}, constants.ResourceSep)
}
// IsNodeJobResource returns whether the resource is a node job resource.
func IsNodeJobResource(r string) bool {
return strings.HasPrefix(r, operationsv1alpha2.SchemeGroupVersion.String())
}
// ParseResource parse the node job resource from the message resource.
// It is best to use IsResource function to judge first:
//
// if IsNodeJobResource(resstr) {
// res := ParseResource(resstr)
// }
func ParseResource(resource string) Resource {
parts := strings.Split(resource, constants.ResourceSep)
if len(parts) != 6 {
klog.Warningf("invalid nodetask resource format: %s", resource)
return Resource{}
}
return Resource{
APIVersion: parts[0] + "/" + parts[1], // {group}/{version}
ResourceType: parts[2],
JobName: parts[3],
NodeName: parts[5],
}
}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stream
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
"k8s.io/klog/v2"
)
type EdgedAttachConnection struct {
ReadChan chan *Message `json:"-"`
Stop chan struct{} `json:"-"`
MessID uint64
URL url.URL `json:"url"`
Header http.Header `json:"header"`
Method string `json:"method"`
}
func (ah *EdgedAttachConnection) CreateConnectMessage() (*Message, error) {
data, err := json.Marshal(ah)
if err != nil {
return nil, err
}
return NewMessage(ah.MessID, MessageTypeAttachConnect, data), nil
}
func (ah *EdgedAttachConnection) GetMessageID() uint64 {
return ah.MessID
}
func (ah *EdgedAttachConnection) String() string {
return fmt.Sprintf("EDGE_ATTACH_CONNECTOR Message MessageID %v", ah.MessID)
}
func (ah *EdgedAttachConnection) CacheTunnelMessage(msg *Message) {
ah.ReadChan <- msg
}
func (ah *EdgedAttachConnection) CloseReadChannel() {
close(ah.ReadChan)
}
func (ah *EdgedAttachConnection) CleanChannel() {
for {
select {
case <-ah.Stop:
default:
return
}
}
}
func (ah *EdgedAttachConnection) receiveFromCloudStream(con net.Conn, stop chan struct{}) {
for message := range ah.ReadChan {
switch message.MessageType {
case MessageTypeRemoveConnect:
klog.V(6).Infof("%s receive remove client id %v", ah.String(), message.ConnectID)
stop <- struct{}{}
case MessageTypeData:
_, err := con.Write(message.Data)
klog.V(6).Infof("%s receive attach %v data ", ah.String(), message.Data)
if err != nil {
klog.Errorf("failed to write, err: %v", err)
}
}
}
klog.V(6).Infof("%s read channel closed", ah.String())
}
func (ah *EdgedAttachConnection) write2CloudStream(tunnel SafeWriteTunneler, con net.Conn, stop chan struct{}) {
defer func() {
stop <- struct{}{}
}()
var data [256]byte
for {
n, err := con.Read(data[:])
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("%v failed to read attach data, err:%v", ah.String(), err)
}
return
}
msg := NewMessage(ah.MessID, MessageTypeData, data[:n])
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v failed to write to tunnel, msg: %+v, err: %v", ah.String(), msg, err)
return
}
klog.V(6).Infof("%v write attach data %v", ah.String(), data[:n])
}
}
func (ah *EdgedAttachConnection) Serve(tunnel SafeWriteTunneler) error {
tripper, err := spdy.NewRoundTripper(nil)
if err != nil {
return fmt.Errorf("failed to creates a new tripper, err: %v", err)
}
req, err := http.NewRequest(ah.Method, ah.URL.String(), nil)
if err != nil {
return fmt.Errorf("failed to create attach request, err: %v", err)
}
req.Header = ah.Header
con, err := tripper.Dial(req)
if err != nil {
klog.Errorf("failed to dial, err: %v", err)
return err
}
defer con.Close()
go ah.receiveFromCloudStream(con, ah.Stop)
defer func() {
for retry := 0; retry < 3; retry++ {
msg := NewMessage(ah.MessID, MessageTypeRemoveConnect, nil)
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v send %s message error %v", ah, msg.MessageType, err)
} else {
break
}
}
}()
go ah.write2CloudStream(tunnel, con, ah.Stop)
<-ah.Stop
klog.V(6).Infof("receive stop signal, so stop attach scan ...")
return nil
}
var _ EdgedConnection = &EdgedAttachConnection{}
package stream
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"net/url"
"k8s.io/apimachinery/pkg/util/httpstream/spdy"
"k8s.io/klog/v2"
)
type EdgedExecConnection struct {
ReadChan chan *Message `json:"-"`
Stop chan struct{} `json:"-"`
MessID uint64
URL url.URL `json:"url"`
Header http.Header `json:"header"`
Method string `json:"method"`
}
func (e *EdgedExecConnection) CreateConnectMessage() (*Message, error) {
data, err := json.Marshal(e)
if err != nil {
return nil, err
}
return NewMessage(e.MessID, MessageTypeExecConnect, data), nil
}
func (e *EdgedExecConnection) GetMessageID() uint64 {
return e.MessID
}
func (e *EdgedExecConnection) String() string {
return fmt.Sprintf("EDGE_EXEC_CONNECTOR Message MessageID %v", e.MessID)
}
func (e *EdgedExecConnection) CacheTunnelMessage(msg *Message) {
e.ReadChan <- msg
}
func (e *EdgedExecConnection) CloseReadChannel() {
close(e.ReadChan)
}
func (e *EdgedExecConnection) CleanChannel() {
for {
select {
case <-e.Stop:
default:
return
}
}
}
type responder struct{}
func (r *responder) Error(w http.ResponseWriter, _ *http.Request, err error) {
klog.Errorf("failed to proxy request: %v", err)
http.Error(w, err.Error(), http.StatusInternalServerError)
}
func (e *EdgedExecConnection) receiveFromCloudStream(con net.Conn, stop chan struct{}) {
for message := range e.ReadChan {
switch message.MessageType {
case MessageTypeRemoveConnect:
klog.V(6).Infof("%s receive remove client id %v", e.String(), message.ConnectID)
stop <- struct{}{}
case MessageTypeData:
_, err := con.Write(message.Data)
klog.V(6).Infof("%s receive exec %v data ", e.String(), message.Data)
if err != nil {
klog.Errorf("failed to write, err: %v", err)
}
}
}
klog.V(6).Infof("%s read channel closed", e.String())
}
func (e *EdgedExecConnection) write2CloudStream(tunnel SafeWriteTunneler, con net.Conn, stop chan struct{}) {
defer func() {
stop <- struct{}{}
}()
var data [256]byte
for {
n, err := con.Read(data[:])
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("%v failed to read exec data, err:%v", e.String(), err)
}
return
}
msg := NewMessage(e.MessID, MessageTypeData, data[:n])
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v failed to write to tunnel, msg: %+v, err: %v", e.String(), msg, err)
return
}
klog.V(6).Infof("%v write exec data %v", e.String(), data[:n])
}
}
func (e *EdgedExecConnection) Serve(tunnel SafeWriteTunneler) error {
tripper, err := spdy.NewRoundTripper(nil)
if err != nil {
return fmt.Errorf("failed to creates a new tripper, err: %v", err)
}
req, err := http.NewRequest(e.Method, e.URL.String(), nil)
if err != nil {
return fmt.Errorf("failed to create exec request, err: %v", err)
}
req.Header = e.Header
con, err := tripper.Dial(req)
if err != nil {
klog.Errorf("failed to dial, err: %v", err)
return err
}
defer con.Close()
go e.receiveFromCloudStream(con, e.Stop)
defer func() {
for retry := 0; retry < 3; retry++ {
msg := NewMessage(e.MessID, MessageTypeRemoveConnect, nil)
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v send %s message error %v", e, msg.MessageType, err)
} else {
break
}
}
}()
go e.write2CloudStream(tunnel, con, e.Stop)
<-e.Stop
klog.V(6).Infof("receive stop signal, so stop exec scan ...")
return nil
}
var _ EdgedConnection = &EdgedExecConnection{}
/*
Copyright 2020 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stream
import (
"bufio"
"encoding/json"
"errors"
"fmt"
"io"
"net/http"
"net/url"
"k8s.io/klog/v2"
)
type EdgedLogsConnection struct {
ReadChan chan *Message `json:"-"`
Stop chan struct{} `json:"-"`
MessID uint64 // message id
URL url.URL `json:"url"`
Header http.Header `json:"header"`
}
func (l *EdgedLogsConnection) GetMessageID() uint64 {
return l.MessID
}
func (l *EdgedLogsConnection) CacheTunnelMessage(msg *Message) {
l.ReadChan <- msg
}
func (l *EdgedLogsConnection) CloseReadChannel() {
close(l.ReadChan)
}
func (l *EdgedLogsConnection) CleanChannel() {
for {
select {
case <-l.Stop:
default:
return
}
}
}
func (l *EdgedLogsConnection) CreateConnectMessage() (*Message, error) {
data, err := json.Marshal(l)
if err != nil {
return nil, err
}
return NewMessage(l.MessID, MessageTypeLogsConnect, data), nil
}
func (l *EdgedLogsConnection) String() string {
return fmt.Sprintf("EDGE_LOGS_CONNECTOR Message MessageID %v", l.MessID)
}
func (l *EdgedLogsConnection) receiveFromCloudStream(stop chan struct{}) {
for mess := range l.ReadChan {
if mess.MessageType == MessageTypeRemoveConnect {
klog.Infof("receive remove client id %v", mess.ConnectID)
stop <- struct{}{}
}
}
klog.V(6).Infof("%s read channel closed", l.String())
}
func (l *EdgedLogsConnection) write2CloudStream(tunnel SafeWriteTunneler, reader bufio.Reader, stop chan struct{}) {
defer func() {
stop <- struct{}{}
}()
var data [256]byte
for {
n, err := reader.Read(data[:])
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("%v failed to write log data, err:%v", l.String(), err)
}
return
}
msg := NewMessage(l.MessID, MessageTypeData, data[:n])
err = tunnel.WriteMessage(msg)
if err != nil {
klog.Errorf("write tunnel message %v error", msg)
return
}
klog.V(4).Infof("%v write logs %v", l.String(), string(data[:n]))
}
}
func (l *EdgedLogsConnection) Serve(tunnel SafeWriteTunneler) error {
//connect edged
client := http.Client{}
req, err := http.NewRequest(http.MethodGet, l.URL.String(), nil)
if err != nil {
klog.Errorf("create new logs request error %v", err)
return err
}
req.Header = l.Header
resp, err := client.Do(req)
if err != nil {
klog.Errorf("request logs error %v", err)
return err
}
defer resp.Body.Close()
reader := bufio.NewReader(resp.Body)
go l.receiveFromCloudStream(l.Stop)
defer func() {
for retry := 0; retry < 3; retry++ {
msg := NewMessage(l.MessID, MessageTypeRemoveConnect, nil)
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v send %s message error %v", l, msg.MessageType, err)
} else {
break
}
}
}()
go l.write2CloudStream(tunnel, *reader, l.Stop)
<-l.Stop
klog.Infof("receive stop signal, so stop logs scan ...")
return nil
}
var _ EdgedConnection = &EdgedLogsConnection{}
/*
Copyright 2020 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stream
import (
"bufio"
"encoding/json"
"fmt"
"net/http"
"net/url"
"k8s.io/klog/v2"
)
type EdgedMetricsConnection struct {
ReadChan chan *Message `json:"-"`
Stop chan struct{} `json:"-"`
MessID uint64 // message id
URL url.URL `json:"url"`
Header http.Header `json:"header"`
}
func (ms *EdgedMetricsConnection) GetMessageID() uint64 {
return ms.MessID
}
func (ms *EdgedMetricsConnection) CacheTunnelMessage(msg *Message) {
ms.ReadChan <- msg
}
func (ms *EdgedMetricsConnection) CloseReadChannel() {
close(ms.ReadChan)
}
func (ms *EdgedMetricsConnection) CleanChannel() {
for {
select {
case <-ms.Stop:
default:
return
}
}
}
func (ms *EdgedMetricsConnection) CreateConnectMessage() (*Message, error) {
data, err := json.Marshal(ms)
if err != nil {
return nil, err
}
return NewMessage(ms.MessID, MessageTypeMetricConnect, data), nil
}
func (ms *EdgedMetricsConnection) String() string {
return fmt.Sprintf("EDGE_METRICS_CONNECTOR Message MessageID %v", ms.MessID)
}
func (ms *EdgedMetricsConnection) receiveFromCloudStream(stop chan struct{}) {
for mess := range ms.ReadChan {
if mess.MessageType == MessageTypeRemoveConnect {
klog.Infof("receive remove client id %v", mess.ConnectID)
stop <- struct{}{}
}
}
klog.V(6).Infof("%s read channel closed", ms.String())
}
func (ms *EdgedMetricsConnection) write2CloudStream(tunnel SafeWriteTunneler, resp *http.Response, stop chan struct{}) {
defer func() {
stop <- struct{}{}
}()
scan := bufio.NewScanner(resp.Body)
for scan.Scan() {
// 10 = \n
msg := NewMessage(ms.MessID, MessageTypeData, append(scan.Bytes(), 10))
err := tunnel.WriteMessage(msg)
if err != nil {
klog.Errorf("write tunnel message %v error", msg)
return
}
klog.V(4).Infof("%v write metrics data %v", ms.String(), string(scan.Bytes()))
}
}
func (ms *EdgedMetricsConnection) Serve(tunnel SafeWriteTunneler) error {
//connect edged
client := http.Client{}
req, err := http.NewRequest(http.MethodGet, ms.URL.String(), nil)
if err != nil {
klog.Errorf("create new metrics request error %v", err)
return err
}
req.Header = ms.Header
// Since current tunnel implementation only support Text message,
// we should force Accept-Encoding to identity to avoid any compression.
// For example, user may pass accept-encoding: gzip in header.
// TODO: luogangyi
// When we support binary message, we can remove this setting.
req.Header.Set("accept-encoding", "identity")
resp, err := client.Do(req)
if err != nil {
klog.Errorf("request metrics error %v", err)
return err
}
defer resp.Body.Close()
go ms.receiveFromCloudStream(ms.Stop)
defer func() {
for retry := 0; retry < 3; retry++ {
msg := NewMessage(ms.MessID, MessageTypeRemoveConnect, nil)
if err := tunnel.WriteMessage(msg); err != nil {
klog.Errorf("%v send %s message error %v", ms, msg.MessageType, err)
} else {
break
}
}
}()
go ms.write2CloudStream(tunnel, resp, ms.Stop)
<-ms.Stop
klog.Infof("receive stop signal, so stop metrics scan ...")
return nil
}
var _ EdgedConnection = &EdgedMetricsConnection{}
/*
Copyright 2020 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stream
import (
"bufio"
"encoding/binary"
"fmt"
"io"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/common/constants"
)
type MessageType uint64
func (m MessageType) String() string {
switch m {
case MessageTypeLogsConnect:
return "LOGS_CONNECT"
case MessageTypeExecConnect:
return "EXEC_CONNECT"
case MessageTypeAttachConnect:
return "ATTACH_CONNECT"
case MessageTypeMetricConnect:
return "METRIC_CONNECT"
case MessageTypeData:
return "DATA"
case MessageTypeRemoveConnect:
return "REMOVE_CONNECT"
}
return "UNKNOWN"
}
type Message struct {
// ConnectID indicate the apiserver connection id
ConnectID uint64
MessageType MessageType
Data []byte
}
func NewMessage(id uint64, messType MessageType, data []byte) *Message {
return &Message{
ConnectID: id,
MessageType: messType,
Data: data,
}
}
func (m *Message) Bytes() []byte {
// connectID + MessageType + Data
buf, offset := make([]byte, 16), 0
offset += binary.PutUvarint(buf[offset:], m.ConnectID)
offset += binary.PutUvarint(buf[offset:], uint64(m.MessageType))
return append(buf[0:offset], m.Data...)
}
func (m *Message) String() string {
return fmt.Sprintf("MESSAGE: connectID %v messageType %s", m.ConnectID, m.MessageType)
}
func ReadMessageFromTunnel(r io.Reader) (*Message, error) {
buf := bufio.NewReader(r)
connectID, err := binary.ReadUvarint(buf)
if err != nil {
return nil, err
}
messageType, err := binary.ReadUvarint(buf)
if err != nil {
return nil, err
}
data, err := io.ReadAll(io.LimitReader(buf, constants.MaxRespBodyLength))
if err != nil {
return nil, err
}
klog.V(6).Infof("Receive Tunnel message connectID %d messageType %s data:%v string:[%v]",
connectID, MessageType(messageType), data, string(data))
return &Message{
ConnectID: connectID,
MessageType: MessageType(messageType),
Data: data,
}, nil
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package stream
import "bytes"
func FuzzReadMessageFromTunnel(data []byte) int {
_, _ = ReadMessageFromTunnel(bytes.NewReader(data))
return 1
}
/*
Copyright 2020 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package stream
import (
"io"
"sync"
"time"
"github.com/gorilla/websocket"
)
type SafeWriteTunneler interface {
WriteMessage(message *Message) error
WriteControl(messageType int, data []byte, deadline time.Time) error
NextReader() (messageType int, r io.Reader, err error)
io.Closer
}
type DefaultTunnel struct {
lock *sync.Mutex
con *websocket.Conn
}
func (t *DefaultTunnel) WriteControl(messageType int, data []byte, deadline time.Time) (e error) {
t.lock.Lock()
e = t.con.WriteControl(messageType, data, deadline)
t.lock.Unlock()
return
}
func (t *DefaultTunnel) Close() error {
return t.con.Close()
}
func (t *DefaultTunnel) NextReader() (messageType int, r io.Reader, err error) {
return t.con.NextReader()
}
func (t *DefaultTunnel) WriteMessage(m *Message) (e error) {
t.lock.Lock()
e = t.con.WriteMessage(websocket.TextMessage, m.Bytes())
t.lock.Unlock()
return
}
func NewDefaultTunnel(con *websocket.Conn) *DefaultTunnel {
return &DefaultTunnel{
lock: &sync.Mutex{},
con: con,
}
}
var _ SafeWriteTunneler = &DefaultTunnel{}
/*
Copyright 2023 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package testtools
import (
"testing"
"github.com/beego/beego/v2/client/orm"
"github.com/golang/mock/gomock"
"github.com/kubeedge/kubeedge/edge/mocks/beego"
"github.com/kubeedge/kubeedge/edge/pkg/common/dbm"
)
func InitOrmerMock(t *testing.T) (*beego.MockOrmer, *beego.MockQuerySeter) {
//Initialize Global Variables (Mocks)
// ormerMock is mocked Ormer implementation
var ormerMock *beego.MockOrmer
// querySeterMock is mocked QuerySeter implementation
var querySeterMock *beego.MockQuerySeter
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
ormerMock = beego.NewMockOrmer(mockCtrl)
querySeterMock = beego.NewMockQuerySeter(mockCtrl)
dbm.DBAccess = ormerMock
dbm.DefaultOrmFunc = func() orm.Ormer {
return ormerMock
}
return ormerMock, querySeterMock
}
/*
Copyright 2024 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"fmt"
"net"
"github.com/vishvananda/netlink"
"golang.org/x/sys/unix"
"k8s.io/klog/v2"
)
type DummyDeviceManager struct {
netlink.Handle
}
func NewDummyDeviceManager() *DummyDeviceManager {
return &DummyDeviceManager{netlink.Handle{}}
}
// EnsureDummyDevice ensure dummy device exist
func (d *DummyDeviceManager) EnsureDummyDevice(devName string) (bool, error) {
_, err := d.LinkByName(devName)
if err == nil {
// found dummy device
return true, nil
}
klog.Warningf("No dummy device %s, link it", devName)
dummy := &netlink.Dummy{
LinkAttrs: netlink.LinkAttrs{Name: devName},
}
return false, d.LinkAdd(dummy)
}
// DeleteDummyDevice delete dummy device.
func (d *DummyDeviceManager) DeleteDummyDevice(devName string) error {
link, err := d.LinkByName(devName)
if err != nil {
_, ok := err.(netlink.LinkNotFoundError)
if ok {
return nil
}
return fmt.Errorf("failed to delete a non-exist dummy device %s: %v", devName, err)
}
dummy, ok := link.(*netlink.Dummy)
if !ok {
return fmt.Errorf("expect dummy device, got device type: %s", link.Type())
}
return d.LinkDel(dummy)
}
// ListBindAddress list all IP addresses which are bound in a given interface
func (d *DummyDeviceManager) ListBindAddress(devName string) ([]string, error) {
dev, err := d.LinkByName(devName)
if err != nil {
return nil, fmt.Errorf("failed to get interface: %s, err: %v", devName, err)
}
addrs, err := d.AddrList(dev, 0)
if err != nil {
return nil, fmt.Errorf("failed to list bound address of interface %s, err: %v", devName, err)
}
var ips []string
for _, addr := range addrs {
ips = append(ips, addr.IP.String())
}
return ips, nil
}
// EnsureAddressBind checks if address is bound to the interface, if not, binds it. If the address is already bound, return true.
func (d *DummyDeviceManager) EnsureAddressBind(address, devName string) (bool, error) {
dev, err := d.LinkByName(devName)
if err != nil {
return false, fmt.Errorf("failed to get interface: %s, err: %v", devName, err)
}
addr := net.ParseIP(address)
if addr == nil {
return false, fmt.Errorf("failed to parse ip address: %s", address)
}
if err := d.AddrAdd(dev, &netlink.Addr{IPNet: netlink.NewIPNet(addr)}); err != nil {
// "EEXIST" will be returned if the address is already bound to device
if err == unix.EEXIST {
return true, nil
}
return false, fmt.Errorf("failed to bind address %s to interface %s, err: %v", address, devName, err)
}
return false, nil
}
/*
Copyright 2018 The KubeEdge Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package util
import (
"bytes"
"fmt"
"net"
"os"
"os/exec"
"path/filepath"
"strings"
utilnet "k8s.io/apimachinery/pkg/util/net"
nodeutil "k8s.io/component-helpers/node/util"
"k8s.io/kubernetes/pkg/apis/core/validation"
"github.com/kubeedge/kubeedge/common/constants"
)
func GetLocalIP(hostName string) (string, error) {
var ipAddr net.IP
var err error
// fix https://github.com/kubeedge/kubeedge/issues/6023
// LookupIP maybe get docker0 ip's or other cni ip's
// maybe get default route interface ip's can be safter
// if get default ip fails then should use LookupIP
// ignore when ChooseHostInterface fails
ipAddr, err = utilnet.ChooseHostInterface()
if err == nil {
return ipAddr.String(), nil
}
// if ChooseHostInterface fails we can use LookupIP
addrs, _ := net.LookupIP(hostName)
for _, addr := range addrs {
if err := ValidateNodeIP(addr); err != nil {
continue
}
if addr.To4() != nil {
ipAddr = addr
break
}
if ipAddr == nil && addr.To16() != nil {
ipAddr = addr
}
}
if ipAddr == nil {
return "", fmt.Errorf("can not get any useable node IP")
}
return ipAddr.String(), nil
}
// ValidateNodeIP validates given node IP belongs to the current host
func ValidateNodeIP(nodeIP net.IP) error {
// Honor IP limitations set in setNodeStatus()
if nodeIP.To4() == nil && nodeIP.To16() == nil {
return fmt.Errorf("nodeIP must be a valid IP address")
}
if nodeIP.IsLoopback() {
return fmt.Errorf("nodeIP can't be loopback address")
}
if nodeIP.IsMulticast() {
return fmt.Errorf("nodeIP can't be a multicast address")
}
if nodeIP.IsLinkLocalUnicast() {
return fmt.Errorf("nodeIP can't be a link-local unicast address")
}
if nodeIP.IsUnspecified() {
return fmt.Errorf("nodeIP can't be an all zeros address")
}
addrs, err := net.InterfaceAddrs()
if err != nil {
return err
}
for _, addr := range addrs {
var ip net.IP
switch v := addr.(type) {
case *net.IPNet:
ip = v.IP
case *net.IPAddr:
ip = v.IP
}
if ip != nil && ip.Equal(nodeIP) {
return nil
}
}
return fmt.Errorf("node IP: %q not found in the host's network interfaces", nodeIP.String())
}
// Command executes command and returns output
func Command(name string, arg []string) (string, error) {
cmd := exec.Command(name, arg...)
ret, err := cmd.Output()
if err != nil {
return string(ret), err
}
return strings.Trim(string(ret), "\n"), nil
}
// GetCurPath returns filepath
func GetCurPath() string {
file, _ := exec.LookPath(os.Args[0])
path, _ := filepath.Abs(file)
rst := filepath.Dir(path)
return rst
}
func SpliceErrors(errors []error) string {
if len(errors) == 0 {
return ""
}
var stb strings.Builder
stb.WriteString("[\n")
for _, err := range errors {
stb.WriteString(fmt.Sprintf(" %s\n", err.Error()))
}
stb.WriteString("]\n")
return stb.String()
}
// GetHostname returns a reasonable hostname
func GetHostname() string {
hostnameOverride, err := nodeutil.GetHostname("")
if err != nil {
return constants.DefaultHostnameOverride
}
msgs := validation.ValidateNodeName(hostnameOverride, false)
if len(msgs) > 0 {
return constants.DefaultHostnameOverride
}
return hostnameOverride
}
// ConcatStrings use bytes.buffer to concatenate string variable
func ConcatStrings(ss ...string) string {
var bff bytes.Buffer
for _, s := range ss {
bff.WriteString(s)
}
return bff.String()
}
// GetResourceID return resource ID
func GetResourceID(namespace, name string) string {
return namespace + "/" + name
}
/*
Copyright 2014 The Kubernetes Authors.
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
*/
package version
import (
"fmt"
"runtime"
apimachineryversion "k8s.io/apimachinery/pkg/version"
)
// Get returns the overall codebase version. It's for detecting
// what code a binary was built from.
func Get() apimachineryversion.Info {
// These variables typically come from -ldflags settings and in
// their absence fallback to the settings in pkg/version/base.go
return apimachineryversion.Info{
Major: gitMajor,
Minor: gitMinor,
GitVersion: gitVersion,
GitCommit: gitCommit,
GitTreeState: gitTreeState,
BuildDate: buildDate,
GoVersion: runtime.Version(),
Compiler: runtime.Compiler,
Platform: fmt.Sprintf("%s/%s", runtime.GOOS, runtime.GOARCH),
}
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/kubeedge/kubeedge/pkg/viaduct/pkg/mux (interfaces: Handler)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
model "github.com/kubeedge/beehive/pkg/core/model"
mux "github.com/kubeedge/kubeedge/pkg/viaduct/pkg/mux"
reflect "reflect"
)
// MockHandler is a mock of Handler interface
type MockHandler struct {
ctrl *gomock.Controller
recorder *MockHandlerMockRecorder
}
// MockHandlerMockRecorder is the mock recorder for MockHandler
type MockHandlerMockRecorder struct {
mock *MockHandler
}
// NewMockHandler creates a new mock instance
func NewMockHandler(ctrl *gomock.Controller) *MockHandler {
mock := &MockHandler{ctrl: ctrl}
mock.recorder = &MockHandlerMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockHandler) EXPECT() *MockHandlerMockRecorder {
return m.recorder
}
// ServeConn mocks base method
func (m *MockHandler) ServeConn(arg0 *model.Message, arg1 mux.ResponseWriter) {
m.ctrl.T.Helper()
m.ctrl.Call(m, "ServeConn", arg0, arg1)
}
// ServeConn indicates an expected call of ServeConn
func (mr *MockHandlerMockRecorder) ServeConn(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ServeConn", reflect.TypeOf((*MockHandler)(nil).ServeConn), arg0, arg1)
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/lucas-clemente/quic-go (interfaces: Session)
// Package mocks is a generated GoMock package.
package mocks
import (
"context"
"github.com/golang/mock/gomock"
quic_go "github.com/lucas-clemente/quic-go"
"net"
"reflect"
)
// MockSession is a mock of Session interface
type MockSession struct {
ctrl *gomock.Controller
recorder *MockSessionMockRecorder
}
// MockSessionMockRecorder is the mock recorder for MockSession
type MockSessionMockRecorder struct {
mock *MockSession
}
// NewMockSession creates a new mock instance
func NewMockSession(ctrl *gomock.Controller) *MockSession {
mock := &MockSession{ctrl: ctrl}
mock.recorder = &MockSessionMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockSession) EXPECT() *MockSessionMockRecorder {
return m.recorder
}
// AcceptStream mocks base method
func (m *MockSession) AcceptStream() (quic_go.Stream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AcceptStream")
ret0, _ := ret[0].(quic_go.Stream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AcceptStream indicates an expected call of AcceptStream
func (mr *MockSessionMockRecorder) AcceptStream() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptStream", reflect.TypeOf((*MockSession)(nil).AcceptStream))
}
// AcceptUniStream mocks base method
func (m *MockSession) AcceptUniStream() (quic_go.ReceiveStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "AcceptUniStream")
ret0, _ := ret[0].(quic_go.ReceiveStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// AcceptUniStream indicates an expected call of AcceptUniStream
func (mr *MockSessionMockRecorder) AcceptUniStream() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "AcceptUniStream", reflect.TypeOf((*MockSession)(nil).AcceptUniStream))
}
// Close mocks base method
func (m *MockSession) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close
func (mr *MockSessionMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockSession)(nil).Close))
}
// CloseWithError mocks base method
func (m *MockSession) CloseWithError(arg0 quic_go.ErrorCode, arg1 error) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CloseWithError", arg0, arg1)
ret0, _ := ret[0].(error)
return ret0
}
// CloseWithError indicates an expected call of CloseWithError
func (mr *MockSessionMockRecorder) CloseWithError(arg0, arg1 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CloseWithError", reflect.TypeOf((*MockSession)(nil).CloseWithError), arg0, arg1)
}
// ConnectionState mocks base method
func (m *MockSession) ConnectionState() quic_go.ConnectionState {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "ConnectionState")
ret0, _ := ret[0].(quic_go.ConnectionState)
return ret0
}
// ConnectionState indicates an expected call of ConnectionState
func (mr *MockSessionMockRecorder) ConnectionState() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "ConnectionState", reflect.TypeOf((*MockSession)(nil).ConnectionState))
}
// Context mocks base method
func (m *MockSession) Context() context.Context {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Context")
ret0, _ := ret[0].(context.Context)
return ret0
}
// Context indicates an expected call of Context
func (mr *MockSessionMockRecorder) Context() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockSession)(nil).Context))
}
// LocalAddr mocks base method
func (m *MockSession) LocalAddr() net.Addr {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "LocalAddr")
ret0, _ := ret[0].(net.Addr)
return ret0
}
// LocalAddr indicates an expected call of LocalAddr
func (mr *MockSessionMockRecorder) LocalAddr() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "LocalAddr", reflect.TypeOf((*MockSession)(nil).LocalAddr))
}
// OpenStream mocks base method
func (m *MockSession) OpenStream() (quic_go.Stream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenStream")
ret0, _ := ret[0].(quic_go.Stream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenStream indicates an expected call of OpenStream
func (mr *MockSessionMockRecorder) OpenStream() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenStream", reflect.TypeOf((*MockSession)(nil).OpenStream))
}
// OpenStreamSync mocks base method
func (m *MockSession) OpenStreamSync() (quic_go.Stream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenStreamSync")
ret0, _ := ret[0].(quic_go.Stream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenStreamSync indicates an expected call of OpenStreamSync
func (mr *MockSessionMockRecorder) OpenStreamSync() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenStreamSync", reflect.TypeOf((*MockSession)(nil).OpenStreamSync))
}
// OpenUniStream mocks base method
func (m *MockSession) OpenUniStream() (quic_go.SendStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenUniStream")
ret0, _ := ret[0].(quic_go.SendStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenUniStream indicates an expected call of OpenUniStream
func (mr *MockSessionMockRecorder) OpenUniStream() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenUniStream", reflect.TypeOf((*MockSession)(nil).OpenUniStream))
}
// OpenUniStreamSync mocks base method
func (m *MockSession) OpenUniStreamSync() (quic_go.SendStream, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "OpenUniStreamSync")
ret0, _ := ret[0].(quic_go.SendStream)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// OpenUniStreamSync indicates an expected call of OpenUniStreamSync
func (mr *MockSessionMockRecorder) OpenUniStreamSync() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "OpenUniStreamSync", reflect.TypeOf((*MockSession)(nil).OpenUniStreamSync))
}
// RemoteAddr mocks base method
func (m *MockSession) RemoteAddr() net.Addr {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "RemoteAddr")
ret0, _ := ret[0].(net.Addr)
return ret0
}
// RemoteAddr indicates an expected call of RemoteAddr
func (mr *MockSessionMockRecorder) RemoteAddr() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "RemoteAddr", reflect.TypeOf((*MockSession)(nil).RemoteAddr))
}
// Code generated by MockGen. DO NOT EDIT.
// Source: github.com/lucas-clemente/quic-go (interfaces: Stream)
// Package mocks is a generated GoMock package.
package mocks
import (
"context"
"github.com/golang/mock/gomock"
quic_go "github.com/lucas-clemente/quic-go"
"reflect"
"time"
)
// MockStream is a mock of Stream interface
type MockStream struct {
ctrl *gomock.Controller
recorder *MockStreamMockRecorder
}
// MockStreamMockRecorder is the mock recorder for MockStream
type MockStreamMockRecorder struct {
mock *MockStream
}
// NewMockStream creates a new mock instance
func NewMockStream(ctrl *gomock.Controller) *MockStream {
mock := &MockStream{ctrl: ctrl}
mock.recorder = &MockStreamMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockStream) EXPECT() *MockStreamMockRecorder {
return m.recorder
}
// CancelRead mocks base method
func (m *MockStream) CancelRead(arg0 quic_go.ErrorCode) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CancelRead", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CancelRead indicates an expected call of CancelRead
func (mr *MockStreamMockRecorder) CancelRead(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelRead", reflect.TypeOf((*MockStream)(nil).CancelRead), arg0)
}
// CancelWrite mocks base method
func (m *MockStream) CancelWrite(arg0 quic_go.ErrorCode) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "CancelWrite", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// CancelWrite indicates an expected call of CancelWrite
func (mr *MockStreamMockRecorder) CancelWrite(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "CancelWrite", reflect.TypeOf((*MockStream)(nil).CancelWrite), arg0)
}
// Close mocks base method
func (m *MockStream) Close() error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Close")
ret0, _ := ret[0].(error)
return ret0
}
// Close indicates an expected call of Close
func (mr *MockStreamMockRecorder) Close() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Close", reflect.TypeOf((*MockStream)(nil).Close))
}
// Context mocks base method
func (m *MockStream) Context() context.Context {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Context")
ret0, _ := ret[0].(context.Context)
return ret0
}
// Context indicates an expected call of Context
func (mr *MockStreamMockRecorder) Context() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Context", reflect.TypeOf((*MockStream)(nil).Context))
}
// Read mocks base method
func (m *MockStream) Read(arg0 []byte) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Read", arg0)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Read indicates an expected call of Read
func (mr *MockStreamMockRecorder) Read(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockStream)(nil).Read), arg0)
}
// SetDeadline mocks base method
func (m *MockStream) SetDeadline(arg0 time.Time) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetDeadline", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetDeadline indicates an expected call of SetDeadline
func (mr *MockStreamMockRecorder) SetDeadline(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetDeadline", reflect.TypeOf((*MockStream)(nil).SetDeadline), arg0)
}
// SetReadDeadline mocks base method
func (m *MockStream) SetReadDeadline(arg0 time.Time) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetReadDeadline", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetReadDeadline indicates an expected call of SetReadDeadline
func (mr *MockStreamMockRecorder) SetReadDeadline(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetReadDeadline", reflect.TypeOf((*MockStream)(nil).SetReadDeadline), arg0)
}
// SetWriteDeadline mocks base method
func (m *MockStream) SetWriteDeadline(arg0 time.Time) error {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "SetWriteDeadline", arg0)
ret0, _ := ret[0].(error)
return ret0
}
// SetWriteDeadline indicates an expected call of SetWriteDeadline
func (mr *MockStreamMockRecorder) SetWriteDeadline(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "SetWriteDeadline", reflect.TypeOf((*MockStream)(nil).SetWriteDeadline), arg0)
}
// StreamID mocks base method
func (m *MockStream) StreamID() quic_go.StreamID {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "StreamID")
ret0, _ := ret[0].(quic_go.StreamID)
return ret0
}
// StreamID indicates an expected call of StreamID
func (mr *MockStreamMockRecorder) StreamID() *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "StreamID", reflect.TypeOf((*MockStream)(nil).StreamID))
}
// Write mocks base method
func (m *MockStream) Write(arg0 []byte) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Write", arg0)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Write indicates an expected call of Write
func (mr *MockStreamMockRecorder) Write(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockStream)(nil).Write), arg0)
}
// Code generated by MockGen. DO NOT EDIT.
// Source: io (interfaces: Reader)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockReader is a mock of Reader interface
type MockReader struct {
ctrl *gomock.Controller
recorder *MockReaderMockRecorder
}
// MockReaderMockRecorder is the mock recorder for MockReader
type MockReaderMockRecorder struct {
mock *MockReader
}
// NewMockReader creates a new mock instance
func NewMockReader(ctrl *gomock.Controller) *MockReader {
mock := &MockReader{ctrl: ctrl}
mock.recorder = &MockReaderMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockReader) EXPECT() *MockReaderMockRecorder {
return m.recorder
}
// Read mocks base method
func (m *MockReader) Read(arg0 []byte) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Read", arg0)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Read indicates an expected call of Read
func (mr *MockReaderMockRecorder) Read(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Read", reflect.TypeOf((*MockReader)(nil).Read), arg0)
}
// Code generated by MockGen. DO NOT EDIT.
// Source: io (interfaces: Writer)
// Package mocks is a generated GoMock package.
package mocks
import (
gomock "github.com/golang/mock/gomock"
reflect "reflect"
)
// MockWriter is a mock of Writer interface
type MockWriter struct {
ctrl *gomock.Controller
recorder *MockWriterMockRecorder
}
// MockWriterMockRecorder is the mock recorder for MockWriter
type MockWriterMockRecorder struct {
mock *MockWriter
}
// NewMockWriter creates a new mock instance
func NewMockWriter(ctrl *gomock.Controller) *MockWriter {
mock := &MockWriter{ctrl: ctrl}
mock.recorder = &MockWriterMockRecorder{mock}
return mock
}
// EXPECT returns an object that allows the caller to indicate expected use
func (m *MockWriter) EXPECT() *MockWriterMockRecorder {
return m.recorder
}
// Write mocks base method
func (m *MockWriter) Write(arg0 []byte) (int, error) {
m.ctrl.T.Helper()
ret := m.ctrl.Call(m, "Write", arg0)
ret0, _ := ret[0].(int)
ret1, _ := ret[1].(error)
return ret0, ret1
}
// Write indicates an expected call of Write
func (mr *MockWriterMockRecorder) Write(arg0 interface{}) *gomock.Call {
mr.mock.ctrl.T.Helper()
return mr.mock.ctrl.RecordCallWithMethodType(mr.mock, "Write", reflect.TypeOf((*MockWriter)(nil).Write), arg0)
}
package conn
import (
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/lane"
)
type responseWriter struct {
Type string
Van interface{}
}
// write response
func (r *responseWriter) WriteResponse(msg *model.Message, content interface{}) {
response := msg.NewRespByMessage(msg, content)
err := lane.NewLane(r.Type, r.Van).WriteMessage(response)
if err != nil {
klog.Errorf("failed to write response, error: %+v", err)
}
}
// write error
func (r *responseWriter) WriteError(msg *model.Message, errMsg string) {
response := model.NewErrorMessage(msg, errMsg)
err := lane.NewLane(r.Type, r.Van).WriteMessage(response)
if err != nil {
klog.Errorf("failed to write error, error: %+v", err)
}
}
package conn
import (
"io"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/mux"
)
// connection options
type ConnectionOptions struct {
// the protocol type that the connection based on
ConnType string
// connection or session object for each kind of protocol
Base interface{}
// control lane
CtrlLane interface{}
// connect stat
State *ConnectionState
// the message route to
Handler mux.Handler
// package type
// only used by websocket mode
ConnUse api.UseType
// consumer for raw data
Consumer io.Writer
// auto route into entries
AutoRoute bool
// OnReadTransportErr
OnReadTransportErr func(nodeID, projectID string)
}
// get connection interface by ConnTye
func NewConnection(opts *ConnectionOptions) Connection {
switch opts.ConnType {
case api.ProtocolTypeQuic:
return NewQuicConn(opts)
case api.ProtocolTypeWS:
return NewWSConn(opts)
}
klog.Errorf("bad connection type(%s)", opts.ConnType)
return nil
}
package conn
import (
"encoding/json"
"errors"
"fmt"
"io"
"net"
"net/http"
"sync"
"time"
"github.com/lucas-clemente/quic-go"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/comm"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/fifo"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/keeper"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/lane"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/mux"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/smgr"
)
var (
// TODO:
autoFree = false
)
// QuicConnection the connection based on quic protocol
type QuicConnection struct {
writeDeadline time.Time
readDeadline time.Time
session smgr.Session
handler mux.Handler
ctrlLan lane.Lane
state *ConnectionState
streamManager *smgr.StreamManager
consumer io.Writer
connUse api.UseType
syncKeeper *keeper.SyncKeeper
messageFifo *fifo.MessageFifo
autoRoute bool
OnReadTransportErr func(nodeID, projectID string)
locker sync.Mutex
}
// NewQuicConn new quic connection
func NewQuicConn(options *ConnectionOptions) *QuicConnection {
quicSession := options.Base.(quic.Session)
return &QuicConnection{
session: smgr.Session{Sess: quicSession},
handler: options.Handler,
ctrlLan: options.CtrlLane.(lane.Lane),
state: options.State,
connUse: options.ConnUse,
syncKeeper: keeper.NewSyncKeeper(),
consumer: options.Consumer,
autoRoute: options.AutoRoute,
messageFifo: fifo.NewMessageFifo(),
OnReadTransportErr: options.OnReadTransportErr,
streamManager: smgr.NewStreamManager(smgr.NumStreamsMax, autoFree, quicSession),
}
}
// process header message
func (conn *QuicConnection) headerMessage(msg *model.Message) error {
if msg == nil {
klog.Errorf("nil message error")
return fmt.Errorf("nil message error")
}
headers := make(http.Header)
err := json.Unmarshal(msg.GetContent().([]byte), &headers)
if err != nil {
klog.Errorf("failed to unmarshal header, error: %+v", err)
return err
}
conn.state.Headers = headers
return nil
}
// read control message from control lan and process
func (conn *QuicConnection) serveControlLan() {
var msg model.Message
for {
// read control message
err := conn.ctrlLan.ReadMessage(&msg)
if err != nil {
klog.Error("failed read control message")
return
}
// feedback the response
resp := msg.NewRespByMessage(&msg, comm.RespTypeAck)
err = conn.ctrlLan.WriteMessage(resp)
if err != nil {
klog.Errorf("failed to send response back, error:%+v", err)
return
}
}
}
// ServeSession accept steams from remote peer
// then, receive messages from the steam
func (conn *QuicConnection) serveSession() {
for {
stream, err := conn.session.AcceptStream()
if err != nil {
// close the session
klog.Warningf("accept stream error(%+v) or the session has been closed",
err)
// close local session
_ = conn.Close()
if conn.OnReadTransportErr != nil {
conn.OnReadTransportErr(conn.state.Headers.Get("node_id"),
conn.state.Headers.Get("project_id"))
}
return
}
conn.streamManager.AddStream(stream)
// auto route to mux or raw data consumer
conn.dispatch(stream)
}
}
// dispatch the message
func (conn *QuicConnection) dispatch(stream *smgr.Stream) {
if stream.UseType == api.UseTypeMessage {
go conn.handleMessage(stream)
} else if stream.UseType == api.UseTypeStream {
go conn.handleRawData(stream)
} else {
klog.Warningf("bad stream use type(%s), ignore", stream.UseType)
}
}
// ServeConn start control lan and session loop
func (conn *QuicConnection) ServeConn() {
go conn.serveControlLan()
conn.serveSession()
}
func (conn *QuicConnection) Read(raw []byte) (int, error) {
// get stream from pool
stream, err := conn.streamManager.GetStream(api.UseTypeStream, false, nil)
if err != nil {
// close the session
klog.Warningf("accept stream error(%+v) or the session has been closed", err)
return 0, err
}
defer conn.streamManager.ReleaseStream(api.UseTypeStream, stream)
return lane.NewLane(api.ProtocolTypeQuic, stream).Read(raw)
}
// open stream and dispatch message for the new stream
func (conn *QuicConnection) openStreamSync(streamUse api.UseType, autoDispatch bool) (*smgr.Stream, error) {
stream, err := conn.session.OpenStreamSync(streamUse)
if err != nil {
klog.Errorf("failed to open stream, error: %+v", err)
return stream, err
}
// start dispatch for the new stream
if autoDispatch {
conn.dispatch(stream)
}
return stream, err
}
// accept stream and dispatch message for the new stream
func (conn *QuicConnection) acceptStream(_ api.UseType, autoDispatch bool) (*smgr.Stream, error) {
stream, err := conn.session.AcceptStream()
if err != nil {
klog.Errorf("failed to accept stream, error: %+v", err)
return stream, err
}
// start dispatch for the new stream
if autoDispatch {
conn.dispatch(stream)
}
return stream, err
}
// Write write raw data into stream
func (conn *QuicConnection) Write(raw []byte) (int, error) {
conn.locker.Lock()
defer conn.locker.Unlock()
stream, err := conn.streamManager.GetStream(api.UseTypeStream, false, conn.openStreamSync)
if err != nil {
klog.Errorf("failed to acquire stream sync, error:%+v", err)
return 0, err
}
defer conn.streamManager.ReleaseStream(api.UseTypeStream, stream)
return lane.NewLane(api.ProtocolTypeQuic, stream).Write(raw)
}
func (conn *QuicConnection) handleRawData(stream *smgr.Stream) {
if conn.consumer == nil {
klog.Warning("bad raw data consumer")
return
}
if !conn.autoRoute {
return
}
_, err := io.Copy(conn.consumer, lane.NewLane(api.ProtocolTypeQuic, stream.Stream))
if err != nil {
klog.Errorf("failed to copy data, error: %+v", err)
return
}
}
// read message from stream and route the message to mux
func (conn *QuicConnection) handleMessage(stream *smgr.Stream) {
msg := &model.Message{}
for {
err := lane.NewLane(api.ProtocolTypeQuic, stream.Stream).ReadMessage(msg)
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("failed to read message, error: %+v", err)
}
conn.streamManager.FreeStream(stream)
return
}
// to check whether the message is a response or not
if matched := conn.syncKeeper.MatchAndNotify(*msg); matched {
continue
}
// put the messages into fifo and wait for reading
if !conn.autoRoute {
conn.messageFifo.Put(msg)
continue
}
// user do not set message handle, use the default mux
if conn.handler == nil {
// use default mux
conn.handler = mux.MuxDefault
}
conn.handler.ServeConn(&mux.MessageRequest{
Header: conn.state.Headers,
PeerCertificates: conn.state.PeerCertificates,
Message: msg,
}, &responseWriter{
Type: api.ProtocolTypeQuic,
Van: stream.Stream,
})
}
}
// Close will cancel write and read
// close the session
func (conn *QuicConnection) Close() error {
conn.state.State = api.StatDisconnected
conn.streamManager.Destroy()
return conn.session.Close()
}
// WriteMessageSync write sync message
// please set write deadline before WriteMessageSync called
func (conn *QuicConnection) WriteMessageSync(msg *model.Message) (*model.Message, error) {
if conn.session.Sess == nil {
klog.Error("bad connection session")
return nil, fmt.Errorf("bad connection session")
}
conn.locker.Lock()
defer conn.locker.Unlock()
stream, err := conn.streamManager.GetStream(api.UseTypeMessage, true, conn.openStreamSync)
if err != nil {
klog.Errorf("failed to acquire stream sync, error:%+v", err)
return nil, fmt.Errorf("failed to acquire stream sync, error:%+v", err)
}
defer conn.streamManager.ReleaseStream(api.UseTypeMessage, stream)
lane := lane.NewLane(api.ProtocolTypeQuic, stream)
_ = lane.SetWriteDeadline(conn.writeDeadline)
msg.Header.Sync = true
err = lane.WriteMessage(msg)
if err != nil {
return nil, err
}
// receive response
response, err := conn.syncKeeper.WaitResponse(msg, conn.writeDeadline)
return &response, nil
}
// WriteMessageAsync send async message
func (conn *QuicConnection) WriteMessageAsync(msg *model.Message) error {
if conn.session.Sess == nil {
klog.Error("bad connection session")
return fmt.Errorf("bad connection session")
}
conn.locker.Lock()
defer conn.locker.Unlock()
stream, err := conn.streamManager.GetStream(api.UseTypeMessage, true, conn.openStreamSync)
if err != nil {
klog.Errorf("failed to acquire stream sync, error:%+v", err)
return fmt.Errorf("failed to acquire stream sync, error:%+v", err)
}
defer conn.streamManager.ReleaseStream(api.UseTypeMessage, stream)
lane := lane.NewLane(api.ProtocolTypeQuic, stream)
_ = lane.SetWriteDeadline(conn.writeDeadline)
msg.Header.Sync = false
return lane.WriteMessage(msg)
}
// ReadMessage read message from fifo
// it will blocked when no message received
func (conn *QuicConnection) ReadMessage(msg *model.Message) error {
return conn.messageFifo.Get(msg)
}
func (conn *QuicConnection) SetReadDeadline(t time.Time) error {
conn.readDeadline = t
return nil
}
func (conn *QuicConnection) SetWriteDeadline(t time.Time) error {
conn.writeDeadline = t
return nil
}
func (conn *QuicConnection) RemoteAddr() net.Addr {
return conn.session.Sess.RemoteAddr()
}
func (conn *QuicConnection) LocalAddr() net.Addr {
return conn.session.Sess.LocalAddr()
}
func (conn *QuicConnection) ConnectionState() ConnectionState {
return *conn.state
}
package conn
import (
"errors"
"io"
"net"
"sync"
"time"
"github.com/gorilla/websocket"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/comm"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/fifo"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/keeper"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/lane"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/mux"
)
type WSConnection struct {
WriteDeadline time.Time
ReadDeadline time.Time
handler mux.Handler
wsConn *websocket.Conn
state *ConnectionState
syncKeeper *keeper.SyncKeeper
connUse api.UseType
consumer io.Writer
autoRoute bool
messageFifo *fifo.MessageFifo
locker sync.Mutex
OnReadTransportErr func(nodeID, projectID string)
}
func NewWSConn(options *ConnectionOptions) *WSConnection {
return &WSConnection{
wsConn: options.Base.(*websocket.Conn),
handler: options.Handler,
syncKeeper: keeper.NewSyncKeeper(),
state: options.State,
connUse: options.ConnUse,
autoRoute: options.AutoRoute,
messageFifo: fifo.NewMessageFifo(),
OnReadTransportErr: options.OnReadTransportErr,
}
}
// ServeConn start to receive message from connection
func (conn *WSConnection) ServeConn() {
switch conn.connUse {
case api.UseTypeMessage:
go conn.handleMessage()
case api.UseTypeStream:
go conn.handleRawData()
case api.UseTypeShare:
klog.Error("don't support share in websocket")
}
}
func (conn *WSConnection) filterControlMessage(msg *model.Message) bool {
// check control message
operation := msg.GetOperation()
if operation != comm.ControlTypeConfig &&
operation != comm.ControlTypePing &&
operation != comm.ControlTypePong {
return false
}
// feedback the response
resp := msg.NewRespByMessage(msg, comm.RespTypeAck)
conn.locker.Lock()
err := lane.NewLane(api.ProtocolTypeWS, conn.wsConn).WriteMessage(resp)
conn.locker.Unlock()
if err != nil {
klog.Errorf("failed to send response back, error:%+v", err)
}
return true
}
func (conn *WSConnection) handleRawData() {
if conn.consumer == nil {
klog.Warning("bad consumer for raw data")
return
}
if !conn.autoRoute {
return
}
// TODO: support control message processing in raw data mode
_, err := io.Copy(conn.consumer, lane.NewLane(api.ProtocolTypeQuic, conn.wsConn))
if err != nil {
klog.Errorf("failed to copy data, error: %+v", err)
conn.state.State = api.StatDisconnected
conn.wsConn.Close()
return
}
}
func (conn *WSConnection) handleMessage() {
for {
msg := &model.Message{}
err := lane.NewLane(api.ProtocolTypeWS, conn.wsConn).ReadMessage(msg)
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("failed to read message, error: %+v", err)
}
conn.state.State = api.StatDisconnected
_ = conn.wsConn.Close()
if conn.OnReadTransportErr != nil {
conn.OnReadTransportErr(conn.state.Headers.Get("node_id"),
conn.state.Headers.Get("project_id"))
}
return
}
// filter control message
if filtered := conn.filterControlMessage(msg); filtered {
continue
}
// to check whether the message is a response or not
if matched := conn.syncKeeper.MatchAndNotify(*msg); matched {
continue
}
// put the messages into fifo and wait for reading
if !conn.autoRoute {
conn.messageFifo.Put(msg)
continue
}
if conn.handler == nil {
// use default mux
conn.handler = mux.MuxDefault
}
conn.handler.ServeConn(&mux.MessageRequest{
Header: conn.state.Headers,
PeerCertificates: conn.state.PeerCertificates,
Message: msg,
}, &responseWriter{
Type: api.ProtocolTypeWS,
Van: conn.wsConn,
})
}
}
func (conn *WSConnection) SetReadDeadline(t time.Time) error {
conn.ReadDeadline = t
return nil
}
func (conn *WSConnection) SetWriteDeadline(t time.Time) error {
conn.WriteDeadline = t
return nil
}
func (conn *WSConnection) Read(raw []byte) (int, error) {
return lane.NewLane(api.ProtocolTypeWS, conn.wsConn).Read(raw)
}
func (conn *WSConnection) Write(raw []byte) (int, error) {
return lane.NewLane(api.ProtocolTypeWS, conn.wsConn).Write(raw)
}
func (conn *WSConnection) WriteMessageAsync(msg *model.Message) error {
lane := lane.NewLane(api.ProtocolTypeWS, conn.wsConn)
_ = lane.SetWriteDeadline(conn.WriteDeadline)
msg.Header.Sync = false
conn.locker.Lock()
defer conn.locker.Unlock()
return lane.WriteMessage(msg)
}
func (conn *WSConnection) WriteMessageSync(msg *model.Message) (*model.Message, error) {
lane := lane.NewLane(api.ProtocolTypeWS, conn.wsConn)
// send msg
_ = lane.SetWriteDeadline(conn.WriteDeadline)
msg.Header.Sync = true
conn.locker.Lock()
err := lane.WriteMessage(msg)
conn.locker.Unlock()
if err != nil {
klog.Errorf("write message error(%+v)", err)
return nil, err
}
//receive response
response, err := conn.syncKeeper.WaitResponse(msg, conn.WriteDeadline)
return &response, err
}
func (conn *WSConnection) ReadMessage(msg *model.Message) error {
return conn.messageFifo.Get(msg)
}
func (conn *WSConnection) RemoteAddr() net.Addr {
return conn.wsConn.RemoteAddr()
}
func (conn *WSConnection) LocalAddr() net.Addr {
return conn.wsConn.LocalAddr()
}
func (conn *WSConnection) Close() error {
conn.messageFifo.Close()
return conn.wsConn.Close()
}
// get connection state
// TODO:
func (conn *WSConnection) ConnectionState() ConnectionState {
return *conn.state
}
package fifo
import (
"fmt"
"sync"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/comm"
)
type MessageFifo struct {
fifo chan model.Message
closeOnce sync.Once
}
// set the fifo capacity to MessageFiFoSizeMax
func NewMessageFifo() *MessageFifo {
return &MessageFifo{
fifo: make(chan model.Message, comm.MessageFiFoSizeMax),
}
}
// Put put the message into fifo
func (f *MessageFifo) Put(msg *model.Message) {
select {
case f.fifo <- *msg:
default:
// discard the old message
<-f.fifo
// push into fifo
f.fifo <- *msg
klog.Warning("too many message received, fifo overflow")
}
}
// Get get message from fifo
// this api is blocked when the fifo is empty
func (f *MessageFifo) Get(msg *model.Message) error {
var ok bool
*msg, ok = <-f.fifo
if !ok {
return fmt.Errorf("the fifo is broken")
}
return nil
}
func (f *MessageFifo) Close() {
f.closeOnce.Do(func() {
close(f.fifo)
})
}
package filter
import (
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
)
type Func func(*model.Message) error
type MessageFilter struct {
Filters []Func
Index int
}
func (filter *MessageFilter) AddFilterFunc(filterFunc Func) {
filter.Filters = append(filter.Filters, filterFunc)
}
func (filter *MessageFilter) ProcessFilter(msg *model.Message) error {
for _, filterFunc := range filter.Filters {
err := filterFunc(msg)
if err != nil {
klog.Warningf("the message(%s) have been filtered", msg.GetID())
return err
}
}
return nil
}
package keeper
import (
"fmt"
"sync"
"time"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
)
type SyncKeeper struct {
keeper sync.Map
}
func NewSyncKeeper() *SyncKeeper {
return &SyncKeeper{}
}
func (k *SyncKeeper) sendToKeepChannel(msg model.Message) error {
obj, exist := k.keeper.Load(msg.GetParentID())
if !exist {
klog.Errorf("failed to get sync keeper channel, message id:%s", msg.GetID())
return fmt.Errorf("failed to get sync keeper channel")
}
channel := obj.(chan model.Message)
select {
case channel <- msg:
default:
klog.Error("keeper channel is full")
return fmt.Errorf("keeper channel is full")
}
return nil
}
func (k *SyncKeeper) addKeepChannel(msgID string) chan model.Message {
channel := make(chan model.Message)
k.keeper.Store(msgID, channel)
return channel
}
func (k *SyncKeeper) deleteKeepChannel(msgID string) {
k.keeper.Delete(msgID)
}
func (k *SyncKeeper) Match(msg model.Message) bool {
_, exist := k.keeper.Load(msg.GetParentID())
return exist
}
func (k *SyncKeeper) MatchAndNotify(msg model.Message) bool {
if matched := k.Match(msg); !matched {
return false
}
if err := k.sendToKeepChannel(msg); err != nil {
klog.Errorf("failed to send to keep channel, error:%+v", err)
}
return true
}
func (k *SyncKeeper) WaitResponse(msg *model.Message, deadline time.Time) (model.Message, error) {
msgID := msg.GetID()
channel := k.addKeepChannel(msgID)
timer := time.NewTimer(time.Until(deadline))
select {
case resp := <-channel:
timer.Stop()
k.keeper.Delete(msgID)
return resp, nil
case <-timer.C:
klog.Warningf("wait response timeout, message id:%s", msgID)
k.keeper.Delete(msgID)
return model.Message{}, fmt.Errorf("wait response timeout, message id:%s", msgID)
}
}
package lane
import (
"time"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
)
type Lane interface {
SetReadDeadline(t time.Time) error
SetWriteDeadline(t time.Time) error
ReadMessage(msg *model.Message) error
WriteMessage(msg *model.Message) error
Read(raw []byte) (int, error)
Write(raw []byte) (int, error)
}
func NewLane(protoType string, van interface{}) Lane {
switch protoType {
case api.ProtocolTypeQuic:
return NewQuicLane(van)
case api.ProtocolTypeWS:
return NewWSLaneWithoutPack(van)
}
klog.Errorf("bad protocol type(%s)", protoType)
return nil
}
// Copyright 2022 ADA Logics Ltd
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
//
package lane
import (
"fmt"
fuzz "github.com/AdaLogics/go-fuzz-headers"
"github.com/golang/mock/gomock"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/mocks"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/packer"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/translator"
)
var mockFuzzStream *mocks.MockStream
var errorFuzzReturn error
// initMocks is function to initialize mocks.
func initMocksForFuzzing(t *FuzzTester) {
mockCtrl := gomock.NewController(t)
defer mockCtrl.Finish()
mockFuzzStream = mocks.NewMockStream(mockCtrl)
}
func FuzzLaneReadMessage(data []byte) int {
f := fuzz.NewConsumer(data)
msg := &model.Message{}
err := f.GenerateStruct(msg)
if err != nil {
return 0
}
bytesMsg, err := translator.NewTran().Encode(msg)
if err != nil {
return 0
}
header := packer.PackageHeader{Version: 0011, PayloadLen: (uint32(len(bytesMsg)))}
headerBuffer := make([]byte, 0)
header.Pack(&headerBuffer)
t := &FuzzTester{}
initMocksForFuzzing(t)
l := &QuicLane{
stream: mockFuzzStream,
}
mockFuzzStream.EXPECT().Read(gomock.Any()).Return(0, errorFuzzReturn).Times(0)
callFirst := mockFuzzStream.EXPECT().Read(gomock.Any()).SetArg(0, headerBuffer).Return(packer.HeaderSize, nil).Times(1)
mockFuzzStream.EXPECT().Read(gomock.Any()).SetArg(0, bytesMsg).Return(len(bytesMsg), nil).Times(1).After(callFirst)
err = l.ReadMessage(msg)
if err != nil {
}
return 1
}
type FuzzTester struct {
}
func (ft *FuzzTester) Cleanup(func()) {}
func (ft *FuzzTester) Setenv(kev, value string) {}
func (ft *FuzzTester) Error(args ...interface{}) {}
func (ft *FuzzTester) Errorf(format string, args ...interface{}) {}
func (ft *FuzzTester) Fail() {}
func (ft *FuzzTester) FailNow() {}
func (ft *FuzzTester) Failed() bool { return true }
func (ft *FuzzTester) Fatal(args ...interface{}) { panic("Fatal panic from fuzzer") }
func (ft *FuzzTester) Fatalf(format string, args ...interface{}) {
panic(fmt.Sprintf("Fatal panic from fuzzer: %s %+v\n", format, args))
}
func (ft *FuzzTester) Helper() {}
func (ft *FuzzTester) Log(args ...interface{}) {}
func (ft *FuzzTester) Logf(format string, args ...interface{}) {}
func (ft *FuzzTester) Name() string { return "fuzz" }
func (ft *FuzzTester) Parallel() {}
func (ft *FuzzTester) Skip(args ...interface{}) {}
func (ft *FuzzTester) SkipNow() {}
func (ft *FuzzTester) Skipf(format string, args ...interface{}) {}
func (ft *FuzzTester) Skipped() bool { return true }
func (ft *FuzzTester) TempDir() string { return "/tmp" }
package lane
import (
"time"
"github.com/lucas-clemente/quic-go"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/packer"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/translator"
)
type QuicLane struct {
writeDeadline time.Time
readDeadline time.Time
stream quic.Stream
}
func NewQuicLane(van interface{}) *QuicLane {
if stream, ok := van.(quic.Stream); ok {
return &QuicLane{stream: stream}
}
klog.Error("oops! bad type of van")
return nil
}
func (l *QuicLane) ReadMessage(msg *model.Message) error {
rawData, err := packer.NewReader(l.stream).Read()
if err != nil {
return err
}
err = translator.NewTran().Decode(rawData, msg)
if err != nil {
klog.Error("failed to decode message")
return err
}
return nil
}
func (l *QuicLane) WriteMessage(msg *model.Message) error {
rawData, err := translator.NewTran().Encode(msg)
if err != nil {
klog.Error("failed to encode message")
return err
}
_, err = packer.NewWriter(l.stream).Write(rawData)
return err
}
func (l *QuicLane) Read(raw []byte) (int, error) {
return l.stream.Read(raw)
}
func (l *QuicLane) Write(raw []byte) (int, error) {
return l.stream.Write(raw)
}
func (l *QuicLane) SetReadDeadline(t time.Time) error {
l.readDeadline = t
return l.stream.SetReadDeadline(t)
}
func (l *QuicLane) SetWriteDeadline(t time.Time) error {
l.writeDeadline = t
return l.stream.SetWriteDeadline(t)
}
package lane
import (
"errors"
"io"
"time"
"github.com/gorilla/websocket"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/packer"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/translator"
)
type WSLane struct {
writeDeadline time.Time
readDeadline time.Time
conn *websocket.Conn
}
func NewWSLane(van interface{}) *WSLane {
if wsConn, ok := van.(*websocket.Conn); ok {
return &WSLane{conn: wsConn}
}
klog.Error("oops! bad type of van")
return nil
}
func (l *WSLane) Read(p []byte) (int, error) {
_, msgData, err := l.conn.ReadMessage()
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("read message error(%+v)", err)
}
return len(msgData), err
}
p = append(p[:0], msgData...)
return len(msgData), err
}
func (l *WSLane) ReadMessage(msg *model.Message) error {
rawData, err := packer.NewReader(l).Read()
if err != nil {
return err
}
err = translator.NewTran().Decode(rawData, msg)
if err != nil {
klog.Error("failed to decode message")
return err
}
return nil
}
func (l *WSLane) Write(p []byte) (int, error) {
err := l.conn.WriteMessage(websocket.BinaryMessage, p)
if err != nil {
klog.Errorf("write websocket message error(%+v)", err)
return len(p), err
}
return len(p), err
}
func (l *WSLane) WriteMessage(msg *model.Message) error {
rawData, err := translator.NewTran().Encode(msg)
if err != nil {
klog.Error("failed to encode message")
return err
}
_, err = packer.NewWriter(l).Write(rawData)
return err
}
func (l *WSLane) SetReadDeadline(t time.Time) error {
l.readDeadline = t
return l.conn.SetReadDeadline(t)
}
func (l *WSLane) SetWriteDeadline(t time.Time) error {
l.writeDeadline = t
return l.conn.SetWriteDeadline(t)
}
package lane
import (
"errors"
"io"
"time"
"github.com/gorilla/websocket"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
)
type WSLaneWithoutPack struct {
writeDeadline time.Time
readDeadline time.Time
conn *websocket.Conn
}
func NewWSLaneWithoutPack(van interface{}) *WSLaneWithoutPack {
if wsConn, ok := van.(*websocket.Conn); ok {
return &WSLaneWithoutPack{conn: wsConn}
}
klog.Error("oops! bad type of van")
return nil
}
func (l *WSLaneWithoutPack) Read(p []byte) (int, error) {
_, msgData, err := l.conn.ReadMessage()
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Errorf("read message error(%+v)", err)
}
return len(msgData), err
}
p = append(p[:0], msgData...)
return len(msgData), err
}
func (l *WSLaneWithoutPack) ReadMessage(msg *model.Message) error {
return l.conn.ReadJSON(msg)
}
func (l *WSLaneWithoutPack) Write(p []byte) (int, error) {
err := l.conn.WriteMessage(websocket.BinaryMessage, p)
if err != nil {
klog.Errorf("write websocket message error(%+v)", err)
return len(p), err
}
return len(p), err
}
func (l *WSLaneWithoutPack) WriteMessage(msg *model.Message) error {
return l.conn.WriteJSON(msg)
}
func (l *WSLaneWithoutPack) SetReadDeadline(t time.Time) error {
l.readDeadline = t
return l.conn.SetReadDeadline(t)
}
func (l *WSLaneWithoutPack) SetWriteDeadline(t time.Time) error {
l.writeDeadline = t
return l.conn.SetWriteDeadline(t)
}
package mux
type HandlerFunc func(*MessageContainer, ResponseWriter)
type MessageMuxEntry struct {
pattern *MessagePattern
handleFunc HandlerFunc
}
func NewEntry(pattern *MessagePattern, handle func(*MessageContainer, ResponseWriter)) *MessageMuxEntry {
return &MessageMuxEntry{
pattern: pattern,
handleFunc: handle,
}
}
func (entry *MessageMuxEntry) Pattern(pattern *MessagePattern) *MessageMuxEntry {
entry.pattern = pattern
return entry
}
func (entry *MessageMuxEntry) Handle(handle func(*MessageContainer, ResponseWriter)) *MessageMuxEntry {
entry.handleFunc = handle
return entry
}
package mux
import (
"bytes"
"fmt"
"regexp"
"strings"
"k8s.io/klog/v2"
)
type MessageExpression struct {
VarNames []string
VarCount int
Matcher *regexp.Regexp
}
func NewExpression() *MessageExpression {
return &MessageExpression{}
}
func (exp *MessageExpression) GetExpression(resource string) *MessageExpression {
var buffer bytes.Buffer
var varNames []string
var varCount int
if resource == "*" {
compiled, _ := regexp.Compile("(/.*)?$")
return &MessageExpression{
Matcher: compiled,
}
}
buffer.WriteString("^")
if strings.HasPrefix(resource, "/") {
buffer.WriteString("/")
}
fields := strings.Split(strings.Trim(resource, "/"), "/")
for _, field := range fields {
if field == "" {
continue
}
if strings.HasPrefix(field, "{") {
colon := strings.Index(field, ":")
var varName string
if colon != -1 {
varName = strings.TrimSpace(field[1:colon])
paramExpr := strings.TrimSpace(field[colon+1 : len(field)-1])
if paramExpr == "*" {
buffer.WriteString("(.*)")
} else {
buffer.WriteString(fmt.Sprintf("(%s)", paramExpr))
}
} else {
varName = strings.TrimSpace(field[1 : len(field)-1])
buffer.WriteString("([^/]+?)")
}
varNames = append(varNames, varName)
varCount++
} else {
buffer.WriteString(regexp.QuoteMeta(field))
}
buffer.WriteString("/")
}
expression := strings.TrimRight(buffer.String(), "/") + "(/.*)?$"
compiled, err := regexp.Compile(expression)
if err != nil {
klog.Errorf("failed to compile resource expression(%s)", expression)
return nil
}
return &MessageExpression{
Matcher: compiled,
VarCount: varCount,
VarNames: varNames,
}
}
package mux
import (
"crypto/x509"
"fmt"
"net/http"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/filter"
)
type ResponseWriter interface {
WriteResponse(msg *model.Message, content interface{})
WriteError(msg *model.Message, err string)
}
type Handler interface {
ServeConn(req *MessageRequest, writer ResponseWriter)
}
type MessageRequest struct {
Header http.Header
PeerCertificates []*x509.Certificate
Message *model.Message
}
type MessageContainer struct {
Header http.Header
PeerCertificates []*x509.Certificate
Message *model.Message
parameters map[string]string
}
type MessageMux struct {
filter *filter.MessageFilter
muxEntry []*MessageMuxEntry
}
var (
MuxDefault = NewMessageMux()
)
func (c *MessageContainer) Parameter(name string) string {
return c.parameters[name]
}
func NewMessageMux() *MessageMux {
return &MessageMux{}
}
func (mux *MessageMux) Entry(pattern *MessagePattern, handle func(*MessageContainer, ResponseWriter)) *MessageMux {
entry := NewEntry(pattern, handle)
mux.muxEntry = append(mux.muxEntry, entry)
return mux
}
func (mux *MessageMux) extractParameters(expression *MessageExpression, resource string) map[string]string {
parameters := make(map[string]string)
matches := expression.Matcher.FindStringSubmatch(resource)
for index := 1; index < len(matches); index++ {
if len(expression.VarNames) >= index {
parameters[expression.VarNames[index-1]] = matches[index]
}
}
return parameters
}
func (mux *MessageMux) wrapMessage(req *MessageRequest, params map[string]string) *MessageContainer {
return &MessageContainer{
Message: req.Message,
parameters: params,
Header: req.Header,
PeerCertificates: req.PeerCertificates,
}
}
func (mux *MessageMux) dispatch(req *MessageRequest, writer ResponseWriter) error {
for _, entry := range mux.muxEntry {
// select entry
matched := entry.pattern.Match(req.Message)
if !matched {
continue
}
// extract parameters
parameters := mux.extractParameters(entry.pattern.resExpr, req.Message.GetResource())
// wrap message
container := mux.wrapMessage(req, parameters)
// call user handle of entry
entry.handleFunc(container, writer)
return nil
}
return fmt.Errorf("failed to found entry for message")
}
func (mux *MessageMux) AddFilter(filter *filter.MessageFilter) {
mux.filter = filter
}
func (mux *MessageMux) processFilter(req *MessageRequest) error {
if mux.filter != nil {
return mux.filter.ProcessFilter(req.Message)
}
return nil
}
func (mux *MessageMux) ServeConn(req *MessageRequest, writer ResponseWriter) {
err := mux.processFilter(req)
if err != nil {
return
}
_ = mux.dispatch(req, writer)
}
func Entry(pattern *MessagePattern, handle func(*MessageContainer, ResponseWriter)) *MessageMux {
MuxDefault.Entry(pattern, handle)
return MuxDefault
}
package mux
import (
"strings"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
)
type MessagePattern struct {
resource string
operation string
resExpr *MessageExpression
}
func NewPattern(resource string) *MessagePattern {
expression := NewExpression()
resExpr := expression.GetExpression(resource)
if resExpr == nil {
klog.Errorf("bad resource(%s) for expression", resource)
return nil
}
return &MessagePattern{
resource: resource,
resExpr: resExpr,
}
}
func (pattern *MessagePattern) Res(resource string) *MessagePattern {
pattern.resource = resource
return pattern
}
func (pattern *MessagePattern) Op(operation string) *MessagePattern {
pattern.operation = operation
return pattern
}
func (pattern *MessagePattern) matchOp(message *model.Message) bool {
if message == nil {
klog.Errorf("nil message can't be matched, operation: %s", pattern.operation)
return false
}
return strings.Compare(pattern.operation, message.GetOperation()) == 0 ||
strings.Compare(pattern.operation, "*") == 0
}
func (pattern *MessagePattern) Match(message *model.Message) bool {
if message == nil {
klog.Errorf("nil message can't be matched, resource: %s", pattern.resource)
return false
}
return pattern.resExpr.Matcher.Match([]byte(message.GetResource())) && pattern.matchOp(message)
}
package packer
const (
// package type definition
Message PackageType = 0x01
Stream PackageType = 0x02
UserDefined PackageType = 0x04
// flags
FlagCompressed = 0x80
// the len of magic sequence
VersionSize = 4
PackageTypeSize = 1
PackageFlagsSize = 1
PayloadLenSize = 4
HeaderSize = VersionSize + PackageTypeSize + PackageFlagsSize + PayloadLenSize
// filed offsets
VersionOffset = 0
PackageTypeOffset = VersionSize
FlagsOffset = VersionSize + PackageTypeSize
PayloadLenOffset = VersionSize + PackageTypeSize + PackageFlagsSize
)
type PackageType uint8
type PackageHeader struct {
// major_version: Version << 24
// minor_version: Version << 16
// fix_version: Version << 8
// Version => {major_version} | {minor_version} | {fix_version}
Version uint32
// the package type
// message package: 0x01
// stream package: 0x02
// user-defined package: 0x04
PackageType PackageType
// flags
Flags uint8
// payload length
// the size of package payload
PayloadLen uint32
}
// new package
func NewPackageHeader(packageType PackageType) *PackageHeader {
return &PackageHeader{
PackageType: packageType,
Version: makeUpVersion(MajorVersion, MinorVersion, FixVersion),
}
}
// set version
func (h *PackageHeader) SetVersion(version uint32) *PackageHeader {
h.Version = version
return h
}
// get version
func (h *PackageHeader) GetVersion() uint32 {
return h.Version
}
// set payload len
func (h *PackageHeader) SetPayloadLen(payloadLen uint32) *PackageHeader {
h.PayloadLen = payloadLen
return h
}
// get payload len of package
func (h *PackageHeader) GetPayloadLen() uint32 {
return h.PayloadLen
}
// set package type
func (h *PackageHeader) SetPackageType(packageType PackageType) *PackageHeader {
h.PackageType = packageType
return h
}
// get package type
func (h *PackageHeader) GetPackageType() PackageType {
return h.PackageType
}
// set package flags
func (h *PackageHeader) SetFlags(flags uint8) *PackageHeader {
h.Flags = flags
return h
}
// get package flags
func (h *PackageHeader) GetFlags() uint8 {
return h.Flags
}
package packer
// do packing
func (h *PackageHeader) Pack(buffer *[]byte) {
*buffer = append(*buffer,
byte(h.Version>>24),
byte(h.Version>>16),
byte(h.Version>>8),
byte(h.Version),
byte(h.PackageType),
byte(h.Flags),
byte(h.PayloadLen>>24),
byte(h.PayloadLen>>16),
byte(h.PayloadLen>>8),
byte(h.PayloadLen))
}
// do unpacking
func (h *PackageHeader) Unpack(header []byte) {
h.Version = uint32(header[VersionOffset])<<24 |
uint32(header[VersionOffset+1])<<16 |
uint32(header[VersionOffset+2])<<8 |
uint32(header[VersionOffset+3])
h.PackageType = PackageType(header[PackageTypeOffset])
h.Flags = uint8(header[FlagsOffset])
h.PayloadLen = uint32(header[PayloadLenOffset])<<24 |
uint32(header[PayloadLenOffset+1])<<16 |
uint32(header[PayloadLenOffset+2])<<8 |
uint32(header[PayloadLenOffset+3])
}
package packer
import (
"errors"
"fmt"
"io"
"k8s.io/klog/v2"
)
type Reader struct {
reader io.Reader
}
func NewReader(r io.Reader) *Reader {
return &Reader{reader: r}
}
// Read message raw data from reader
// steps:
// 1)read the package header
// 2)unpack the package header and get the payload length
// 3)read the payload
func (r *Reader) Read() ([]byte, error) {
if r.reader == nil {
klog.Error("bad io reader")
return nil, fmt.Errorf("bad io reader")
}
headerBuffer := make([]byte, HeaderSize)
_, err := io.ReadFull(r.reader, headerBuffer)
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Error("failed to read package header from buffer")
}
return nil, err
}
header := PackageHeader{}
header.Unpack(headerBuffer)
payloadBuffer := make([]byte, header.PayloadLen)
_, err = io.ReadFull(r.reader, payloadBuffer)
if err != nil {
if !errors.Is(err, io.EOF) {
klog.Error("failed to read payload from buffer")
}
return nil, err
}
return payloadBuffer, nil
}
package packer
const (
// pakcage version
MajorVersion = 1
MinorVersion = 1
FixVersion = 1
)
// make up version
func makeUpVersion(major, minor, fix uint8) uint32 {
return uint32(major)<<24 | uint32(minor)<<16 | uint32(fix)<<8
}
// break down version
func breadDownVersion(version uint32) (uint8, uint8, uint8) {
return uint8(version >> 24), uint8(version >> 16), uint8(version >> 8)
}
package packer
import (
"fmt"
"io"
"k8s.io/klog/v2"
)
type Writer struct {
writer io.Writer
}
// new Writer instance
func NewWriter(w io.Writer) *Writer {
return &Writer{writer: w}
}
// Write message raw data
// steps:
// 1) packer the package header
// 2) write header
// 3) write message raw data
func (w *Writer) Write(data []byte) (int, error) {
if w.writer == nil {
klog.Error("bad io writer")
return 0, fmt.Errorf("bad io writer")
}
// packing header
header := NewPackageHeader(Message)
header.SetPayloadLen(uint32(len(data)))
var headerBuffer []byte
header.Pack(&headerBuffer)
// write header
_, err := w.writer.Write(headerBuffer)
if err != nil {
klog.Error("failed to write header")
return 0, err
}
// write payload
_, err = w.writer.Write(data)
if err != nil {
klog.Error("failed to write payload")
return 0, err
}
return len(data), nil
}
// Code generated by protoc-gen-go. DO NOT EDIT.
// source: message.proto
package message
import (
fmt "fmt"
math "math"
proto "github.com/golang/protobuf/proto"
)
// Reference imports to suppress errors if they are not otherwise used.
var _ = proto.Marshal
var _ = fmt.Errorf
var _ = math.Inf
// This is a compile-time assertion to ensure that this generated file
// is compatible with the proto package it is being compiled against.
// A compilation error at this line likely means your copy of the
// proto package needs to be updated.
const _ = proto.ProtoPackageIsVersion3 // please upgrade the proto package
type MessageRouter struct {
// where the message come from
Source string `protobuf:"bytes,1,opt,name=Source,proto3" json:"Source,omitempty"`
// where the message will send to
Group string `protobuf:"bytes,2,opt,name=Group,proto3" json:"Group,omitempty"`
// what's the operation on resource
Operaion string `protobuf:"bytes,3,opt,name=Operaion,proto3" json:"Operaion,omitempty"`
// what's the resource want to operate
Resouce string `protobuf:"bytes,4,opt,name=Resouce,proto3" json:"Resouce,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *MessageRouter) Reset() { *m = MessageRouter{} }
func (m *MessageRouter) String() string { return proto.CompactTextString(m) }
func (*MessageRouter) ProtoMessage() {}
func (*MessageRouter) Descriptor() ([]byte, []int) {
return fileDescriptor_33c57e4bae7b9afd, []int{0}
}
func (m *MessageRouter) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MessageRouter.Unmarshal(m, b)
}
func (m *MessageRouter) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MessageRouter.Marshal(b, m, deterministic)
}
func (m *MessageRouter) XXX_Merge(src proto.Message) {
xxx_messageInfo_MessageRouter.Merge(m, src)
}
func (m *MessageRouter) XXX_Size() int {
return xxx_messageInfo_MessageRouter.Size(m)
}
func (m *MessageRouter) XXX_DiscardUnknown() {
xxx_messageInfo_MessageRouter.DiscardUnknown(m)
}
var xxx_messageInfo_MessageRouter proto.InternalMessageInfo
func (m *MessageRouter) GetSource() string {
if m != nil {
return m.Source
}
return ""
}
func (m *MessageRouter) GetGroup() string {
if m != nil {
return m.Group
}
return ""
}
func (m *MessageRouter) GetOperaion() string {
if m != nil {
return m.Operaion
}
return ""
}
func (m *MessageRouter) GetResouce() string {
if m != nil {
return m.Resouce
}
return ""
}
type MessageHeader struct {
// the message uuid
ID string `protobuf:"bytes,1,opt,name=MessageID,proto3" json:"MessageID,omitempty"`
// the response message parent id must be same with the message received
// please use NewRespByMessage to new response message
ParentID string `protobuf:"bytes,2,opt,name=ParentID,proto3" json:"ParentID,omitempty"`
// the time of creating
Timestamp int64 `protobuf:"zigzag64,3,opt,name=Timestamp,proto3" json:"Timestamp,omitempty"`
// the flag will be set in send sync
Sync bool `protobuf:"varint,4,opt,name=Sync,proto3" json:"Sync,omitempty"`
// message type
MessageType string `protobuf:"bytes,5,opt,name=MessageType,proto3" json:"MessageType,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *MessageHeader) Reset() { *m = MessageHeader{} }
func (m *MessageHeader) String() string { return proto.CompactTextString(m) }
func (*MessageHeader) ProtoMessage() {}
func (*MessageHeader) Descriptor() ([]byte, []int) {
return fileDescriptor_33c57e4bae7b9afd, []int{1}
}
func (m *MessageHeader) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_MessageHeader.Unmarshal(m, b)
}
func (m *MessageHeader) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_MessageHeader.Marshal(b, m, deterministic)
}
func (m *MessageHeader) XXX_Merge(src proto.Message) {
xxx_messageInfo_MessageHeader.Merge(m, src)
}
func (m *MessageHeader) XXX_Size() int {
return xxx_messageInfo_MessageHeader.Size(m)
}
func (m *MessageHeader) XXX_DiscardUnknown() {
xxx_messageInfo_MessageHeader.DiscardUnknown(m)
}
var xxx_messageInfo_MessageHeader proto.InternalMessageInfo
func (m *MessageHeader) GetID() string {
if m != nil {
return m.ID
}
return ""
}
func (m *MessageHeader) GetParentID() string {
if m != nil {
return m.ParentID
}
return ""
}
func (m *MessageHeader) GetTimestamp() int64 {
if m != nil {
return m.Timestamp
}
return 0
}
func (m *MessageHeader) GetSync() bool {
if m != nil {
return m.Sync
}
return false
}
func (m *MessageHeader) GetMessageType() string {
if m != nil {
return m.MessageType
}
return ""
}
type Message struct {
Header *MessageHeader `protobuf:"bytes,1,opt,name=header,proto3" json:"header,omitempty"`
Router *MessageRouter `protobuf:"bytes,2,opt,name=router,proto3" json:"router,omitempty"`
Content []byte `protobuf:"bytes,3,opt,name=Content,proto3" json:"Content,omitempty"`
XXX_NoUnkeyedLiteral struct{} `json:"-"`
XXX_unrecognized []byte `json:"-"`
XXX_sizecache int32 `json:"-"`
}
func (m *Message) Reset() { *m = Message{} }
func (m *Message) String() string { return proto.CompactTextString(m) }
func (*Message) ProtoMessage() {}
func (*Message) Descriptor() ([]byte, []int) {
return fileDescriptor_33c57e4bae7b9afd, []int{2}
}
func (m *Message) XXX_Unmarshal(b []byte) error {
return xxx_messageInfo_Message.Unmarshal(m, b)
}
func (m *Message) XXX_Marshal(b []byte, deterministic bool) ([]byte, error) {
return xxx_messageInfo_Message.Marshal(b, m, deterministic)
}
func (m *Message) XXX_Merge(src proto.Message) {
xxx_messageInfo_Message.Merge(m, src)
}
func (m *Message) XXX_Size() int {
return xxx_messageInfo_Message.Size(m)
}
func (m *Message) XXX_DiscardUnknown() {
xxx_messageInfo_Message.DiscardUnknown(m)
}
var xxx_messageInfo_Message proto.InternalMessageInfo
func (m *Message) GetHeader() *MessageHeader {
if m != nil {
return m.Header
}
return nil
}
func (m *Message) GetRouter() *MessageRouter {
if m != nil {
return m.Router
}
return nil
}
func (m *Message) GetContent() []byte {
if m != nil {
return m.Content
}
return nil
}
func init() {
proto.RegisterType((*MessageRouter)(nil), "message.MessageRouter")
proto.RegisterType((*MessageHeader)(nil), "message.MessageHeader")
proto.RegisterType((*Message)(nil), "message.Message")
}
func init() { proto.RegisterFile("message.proto", fileDescriptor_33c57e4bae7b9afd) }
var fileDescriptor_33c57e4bae7b9afd = []byte{
// 261 bytes of a gzipped FileDescriptorProto
0x1f, 0x8b, 0x08, 0x00, 0x00, 0x00, 0x00, 0x00, 0x02, 0xff, 0x6c, 0x91, 0xb1, 0x4e, 0xc3, 0x30,
0x10, 0x86, 0x95, 0xd0, 0x26, 0xed, 0x95, 0x32, 0x9c, 0x50, 0x65, 0x21, 0x86, 0x2a, 0x13, 0x53,
0x06, 0x78, 0x04, 0x22, 0x41, 0x06, 0x04, 0x72, 0xfb, 0x02, 0x26, 0x9c, 0xa0, 0x43, 0x6c, 0xcb,
0x76, 0x86, 0xcc, 0x3c, 0x00, 0xaf, 0x8c, 0x72, 0x71, 0x02, 0x48, 0x6c, 0xfe, 0xee, 0x7e, 0xdd,
0xff, 0xeb, 0x37, 0x6c, 0x5b, 0xf2, 0x5e, 0xbd, 0x53, 0x69, 0x9d, 0x09, 0x06, 0xf3, 0x88, 0x85,
0x87, 0xed, 0xd3, 0xf8, 0x94, 0xa6, 0x0b, 0xe4, 0x70, 0x07, 0xd9, 0xc1, 0x74, 0xae, 0x21, 0x91,
0xec, 0x93, 0x9b, 0xb5, 0x8c, 0x84, 0x97, 0xb0, 0x7c, 0x70, 0xa6, 0xb3, 0x22, 0xe5, 0xf1, 0x08,
0x78, 0x05, 0xab, 0x67, 0x4b, 0x4e, 0x9d, 0x8c, 0x16, 0x67, 0xbc, 0x98, 0x19, 0x05, 0xe4, 0x92,
0xbc, 0xe9, 0x1a, 0x12, 0x0b, 0x5e, 0x4d, 0x58, 0x7c, 0x25, 0xb3, 0xeb, 0x23, 0xa9, 0x37, 0x72,
0x78, 0x01, 0x69, 0x5d, 0x45, 0xc7, 0xb4, 0xae, 0x86, 0xbb, 0x2f, 0xca, 0x91, 0x0e, 0x75, 0x15,
0x0d, 0x67, 0xc6, 0x6b, 0x58, 0x1f, 0x4f, 0x2d, 0xf9, 0xa0, 0x5a, 0xcb, 0xa6, 0x28, 0x7f, 0x06,
0x88, 0xb0, 0x38, 0xf4, 0xba, 0x61, 0xcb, 0x95, 0xe4, 0x37, 0xee, 0x61, 0x13, 0xed, 0x8e, 0xbd,
0x25, 0xb1, 0xe4, 0x83, 0xbf, 0x47, 0xc5, 0x67, 0x02, 0x79, 0x64, 0x2c, 0x21, 0xfb, 0xe0, 0x54,
0x9c, 0x67, 0x73, 0xbb, 0x2b, 0xa7, 0xee, 0xfe, 0x64, 0x96, 0x51, 0x35, 0xe8, 0x1d, 0x77, 0xc7,
0x49, 0xff, 0xd1, 0x8f, 0xcd, 0xca, 0xa8, 0x1a, 0x7a, 0xb9, 0x37, 0x3a, 0x90, 0x0e, 0x9c, 0xfe,
0x5c, 0x4e, 0xf8, 0x9a, 0xf1, 0xe7, 0xdc, 0x7d, 0x07, 0x00, 0x00, 0xff, 0xff, 0xd1, 0xe5, 0x28,
0xb7, 0xad, 0x01, 0x00, 0x00,
}
package smgr
import (
"io"
"github.com/lucas-clemente/quic-go"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
)
// wrapper for session manager
type Stream struct {
// the use type of stream only be stream or message
UseType api.UseType
// quic stream
Stream quic.Stream
}
type Session struct {
Sess quic.Session
}
func (s *Session) OpenStreamSync(streamUse api.UseType) (*Stream, error) {
stream, err := s.Sess.OpenStreamSync()
if err != nil {
klog.Errorf("failed to open stream, error: %+v", err)
return nil, err
}
// TODO: add write timeout
_, err = stream.Write([]byte(streamUse))
if err != nil {
klog.Errorf("write stream type, error: %+v", err)
return nil, err
}
return &Stream{
UseType: streamUse,
Stream: stream,
}, nil
}
func (s *Session) AcceptStream() (*Stream, error) {
stream, err := s.Sess.AcceptStream()
if err != nil {
klog.Errorf("failed to accept stream, error: %+v", err)
return nil, err
}
// TODO: add read timeout
typeBytes := make([]byte, api.UseLen)
_, err = io.ReadFull(stream, typeBytes)
if err != nil {
klog.Errorf("read stream type, error: %+v", err)
return nil, err
}
klog.Infof("receive a stream(%s)", string(typeBytes))
return &Stream{
UseType: api.UseType(typeBytes),
Stream: stream,
}, nil
}
func (s *Session) Close() error {
return s.Sess.Close()
}
package smgr
import (
"fmt"
"sync"
"github.com/lucas-clemente/quic-go"
"k8s.io/klog/v2"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/api"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/comm"
)
const (
NumStreamsMax = 99
PoolStreamMinDefault = 2
ThresholdDefault = 0.8
)
// get stream from session
// AcceptStream or OpenStreamXX
type GetFuncEx func(api.UseType, bool) (*Stream, error)
type StreamManager struct {
NumStreamsMax int
Session *Session
messagePool PoolManager
binaryPool PoolManager
lock sync.RWMutex
}
type PoolManager struct {
idlePool streamPool
busyPool streamPool
lock sync.RWMutex
cond sync.Cond
autoFree bool
}
type streamPool struct {
streamMap map[quic.StreamID]*Stream
streamFiFo []*Stream
lock sync.RWMutex
}
func (pool *streamPool) addStream(stream *Stream) {
pool.lock.Lock()
defer pool.lock.Unlock()
pool.streamMap[stream.Stream.StreamID()] = stream
}
func (pool *streamPool) len() int {
pool.lock.RLock()
defer pool.lock.RUnlock()
return len(pool.streamMap)
}
func (pool *streamPool) delStream(stream quic.Stream) {
pool.lock.Lock()
defer pool.lock.Unlock()
delete(pool.streamMap, stream.StreamID())
}
func (pool *streamPool) getStream() *Stream {
pool.lock.Lock()
defer pool.lock.Unlock()
// get one stream randomly
var stream *Stream
for _, stream = range pool.streamMap {
break
}
if stream != nil {
delete(pool.streamMap, stream.Stream.StreamID())
}
return stream
}
// free stream by stream id
func (pool *streamPool) freeStream(s quic.Stream) bool {
pool.lock.Lock()
defer pool.lock.Unlock()
for streamID := range pool.streamMap {
if streamID == s.StreamID() {
delete(pool.streamMap, s.StreamID())
_ = s.CancelRead(quic.ErrorCode(comm.StatusCodeFreeStream))
s.Close()
_ = s.CancelWrite(quic.ErrorCode(comm.StatusCodeFreeStream))
return true
}
}
return false
}
func (pool *streamPool) destroyStreams() {
pool.lock.Lock()
defer pool.lock.Unlock()
for _, stream := range pool.streamMap {
_ = stream.Stream.CancelRead(quic.ErrorCode(comm.StatusCodeFreeStream))
stream.Stream.Close()
_ = stream.Stream.CancelWrite(quic.ErrorCode(comm.StatusCodeFreeStream))
}
pool.streamMap = make(map[quic.StreamID]*Stream)
}
func (mgr *PoolManager) len() int {
mgr.lock.RLock()
defer mgr.lock.RUnlock()
return mgr.idlePool.len() + mgr.busyPool.len()
}
func (mgr *PoolManager) acquireStream() (*Stream, error) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
// get a stream from idle pool
stream := mgr.idlePool.getStream()
if stream != nil {
// add into busy pool
mgr.busyPool.addStream(stream)
return stream, nil
}
return nil, fmt.Errorf("no stream existing")
}
func (mgr *PoolManager) addBusyStream(stream *Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
mgr.busyPool.addStream(stream)
}
func (mgr *PoolManager) addIdleStream(stream *Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
mgr.idlePool.addStream(stream)
mgr.cond.Signal()
}
func (mgr *PoolManager) availableOrWait() {
mgr.lock.Lock()
defer mgr.lock.Unlock()
if mgr.idlePool.len() <= 0 {
mgr.cond.Wait()
}
}
func (mgr *PoolManager) freeStream(stream quic.Stream) {
if freed := mgr.idlePool.freeStream(stream); !freed {
mgr.busyPool.freeStream(stream)
}
}
// check and free the idle streams when the idle number
// is more 80% when releaseStream called
func (mgr *PoolManager) checkThreshold() bool {
totalLen := mgr.idlePool.len() + mgr.busyPool.len()
klog.Infof("total: %v, idle: %v, busy: %v", totalLen, mgr.idlePool.len(), mgr.busyPool.len())
if totalLen >= PoolStreamMinDefault &&
mgr.idlePool.len() >= (int(float64(totalLen)*ThresholdDefault)+1) {
return true
}
return false
}
func (mgr *PoolManager) releaseStream(stream quic.Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
// delete the stream from busy pool
mgr.busyPool.delStream(stream)
// add into idle pool
mgr.idlePool.addStream(&Stream{
Stream: stream,
})
if mgr.autoFree {
overrun := mgr.checkThreshold()
if overrun {
klog.Info("start to free idle streams")
mgr.idlePool.destroyStreams()
}
}
mgr.cond.Signal()
}
func (mgr *PoolManager) Destroy() {
mgr.lock.Lock()
defer mgr.lock.Unlock()
mgr.idlePool.destroyStreams()
mgr.busyPool.destroyStreams()
}
func NewStreamManager(streamMax int, autoFree bool, session quic.Session) *StreamManager {
if streamMax <= 0 {
streamMax = NumStreamsMax
}
streamMgr := &StreamManager{
NumStreamsMax: streamMax,
Session: &Session{session},
messagePool: PoolManager{
idlePool: streamPool{
streamMap: make(map[quic.StreamID]*Stream),
},
busyPool: streamPool{
streamMap: make(map[quic.StreamID]*Stream),
},
autoFree: autoFree,
},
binaryPool: PoolManager{
idlePool: streamPool{
streamMap: make(map[quic.StreamID]*Stream),
},
busyPool: streamPool{
streamMap: make(map[quic.StreamID]*Stream),
},
autoFree: autoFree,
},
}
streamMgr.messagePool.cond.L = &streamMgr.messagePool.lock
streamMgr.binaryPool.cond.L = &streamMgr.binaryPool.lock
return streamMgr
}
func (mgr *StreamManager) getPoolManager(useType api.UseType) *PoolManager {
var poolMgr *PoolManager
switch useType {
case api.UseTypeMessage:
poolMgr = &mgr.messagePool
case api.UseTypeStream:
poolMgr = &mgr.binaryPool
default:
klog.Errorf("bad stream use type(%s)%s, ", useType, api.UseTypeMessage)
}
return poolMgr
}
func (mgr *StreamManager) ReleaseStream(useType api.UseType, stream quic.Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
// try to get stream from pool
poolMgr := mgr.getPoolManager(useType)
if poolMgr == nil {
return
}
poolMgr.releaseStream(stream)
}
func (mgr *StreamManager) AddStream(stream *Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
// try to get stream from pool
poolMgr := mgr.getPoolManager(stream.UseType)
if poolMgr == nil {
return
}
poolMgr.addIdleStream(stream)
}
func (mgr *StreamManager) GetStream(useType api.UseType, autoDispatch bool, getFuncEx GetFuncEx) (quic.Stream, error) {
mgr.lock.Lock()
// try to get stream from pool
poolMgr := mgr.getPoolManager(useType)
if poolMgr == nil {
mgr.lock.Unlock()
return nil, fmt.Errorf("bad stream use type(%s)", useType)
}
// get stream from stream pool
if getFuncEx == nil {
// wait if have no idle stream
mgr.lock.Unlock()
poolMgr.availableOrWait()
mgr.lock.Lock()
} else {
// check the max number of streams
total := mgr.binaryPool.len() + mgr.messagePool.len()
if total >= mgr.NumStreamsMax {
// if no available idle stream, block and wait
klog.Info("wait for idle stream")
mgr.lock.Unlock()
// check it has a available stream or wait for a stream
poolMgr.availableOrWait()
mgr.lock.Lock()
}
}
// acquire stream from current stream pool
stream, err := poolMgr.acquireStream()
if err == nil {
mgr.lock.Unlock()
return stream.Stream, err
}
mgr.lock.Unlock()
// failed to acquire stream
// return err if just want get stream from pool
if getFuncEx == nil {
return nil, fmt.Errorf("failed to get stream, error: %+v", err)
}
// try to get stream from session
stream, err = getFuncEx(useType, autoDispatch)
if err != nil {
klog.Warningf("get stream error(%+v)", err)
return nil, err
}
// add the new stream into pools
mgr.lock.Lock()
poolMgr.addBusyStream(stream)
mgr.lock.Unlock()
return stream.Stream, nil
}
func (mgr *StreamManager) FreeStream(stream *Stream) {
mgr.lock.Lock()
defer mgr.lock.Unlock()
// try to get stream from pool
poolMgr := mgr.getPoolManager(stream.UseType)
if poolMgr == nil {
return
}
poolMgr.freeStream(stream.Stream)
}
func (mgr *StreamManager) Destroy() {
mgr.lock.Lock()
defer mgr.lock.Unlock()
mgr.messagePool.Destroy()
mgr.binaryPool.Destroy()
}
package translator
import (
"encoding/json"
"fmt"
"github.com/golang/protobuf/proto"
"k8s.io/klog/v2"
"github.com/kubeedge/beehive/pkg/core/model"
"github.com/kubeedge/kubeedge/pkg/viaduct/pkg/protos/message"
)
type MessageTranslator struct {
}
func NewTran() *MessageTranslator {
return &MessageTranslator{}
}
func (t *MessageTranslator) protoToModel(src *message.Message, dst *model.Message) error {
dst.BuildHeader(src.Header.ID, src.Header.ParentID, int64(src.Header.Timestamp)).
BuildRouter(src.Router.Source, src.Router.Group, src.Router.Resouce, src.Router.Operaion).
FillBody(src.Content)
// TODO:
dst.Header.Sync = src.Header.Sync
return nil
}
func (t *MessageTranslator) modelToProto(src *model.Message, dst *message.Message) error {
dst.Header.ID = src.GetID()
dst.Header.ParentID = src.GetParentID()
dst.Header.Timestamp = int64(src.GetTimestamp())
dst.Header.Sync = src.IsSync()
dst.Router.Source = src.GetSource()
dst.Router.Group = src.GetGroup()
dst.Router.Resouce = src.GetResource()
dst.Router.Operaion = src.GetOperation()
if content := src.GetContent(); content != nil {
switch content := content.(type) {
case []byte:
dst.Content = content
case string:
dst.Content = []byte(content)
default:
bytes, err := json.Marshal(content)
if err != nil {
klog.Error("failed to marshal")
return err
}
dst.Content = bytes
}
}
return nil
}
func (t *MessageTranslator) Decode(raw []byte, msg interface{}) error {
modelMessage, ok := msg.(*model.Message)
if !ok {
return fmt.Errorf("bad msg type")
}
protoMessage := message.Message{}
err := proto.Unmarshal(raw, &protoMessage)
if err != nil {
klog.Error("failed to unmarshal payload")
return err
}
_ = t.protoToModel(&protoMessage, modelMessage)
return nil
}
func (t *MessageTranslator) Encode(msg interface{}) ([]byte, error) {
modelMessage, ok := msg.(*model.Message)
if !ok {
return nil, fmt.Errorf("bad msg type")
}
protoMessage := message.Message{
Header: &message.MessageHeader{},
Router: &message.MessageRouter{},
}
err := t.modelToProto(modelMessage, &protoMessage)
if err != nil {
klog.Error("failed to copy message")
return nil, err
}
msgBytes, err := proto.Marshal(&protoMessage)
if err != nil {
return nil, err
}
return msgBytes, nil
}