通过一个例子演示gNMI订阅服务的实现

通过gNMI来订阅服务器内存使用率信息,订阅形式为sample,每10秒向客户端推送一次,1分钟后自动关闭推送通道

gNMI和gRPC的关系

  • gRPC 是一个高性能、开源的远程过程调用(RPC)框架,它使用 Protocol Buffers(protobuf)作为接口定义语言(IDL)。
  • gNMI(gRPC Network Management Interface)是基于 gRPC 的网络管理接口
  • gNMI 是使用 gRPC 进行网络管理的一种具体实现。它定义了一组消息和服务,用于网络设备和网络管理系统之间的交互。
  • gNMI 提供了一种标准化的方式来配置、查询和订阅网络设备的状态和数据。
  • gNMI定义了自己的proto,用户也可以按照自己的需求做扩展

前期准备

  • 创建文件夹并初始化项目
admin@hpc-1:~/go$ mkdir my_gNMI
admin@hpc-1:~/go$ cd my_gNMI
admin@hpc-1:~/go/my_gNMI$ go mod init my_gNMI
  • 下载需要的包
admin@hpc-1:~/go/my_gNMI$ go get google.golang.org/grpc
admin@hpc-1:~/go/my_gNMI$ go get github.com/shirou/gopsutil/mem
admin@hpc-1:~/go/my_gNMI$ go get github.com/openconfig/gnmi/proto/gnmi
  • proto不必另外写了,就利用gnmi现有的proto,注意到定义了四种rpc,后续这四种rpc都必须定义
service gNMI {
  // Capabilities allows the client to retrieve the set of capabilities that
  // is supported by the target. This allows the target to validate the
  // service version that is implemented and retrieve the set of models that
  // the target supports. The models can then be specified in subsequent RPCs
  // to restrict the set of data that is utilized.
  // Reference: gNMI Specification Section 3.2
  rpc Capabilities(CapabilityRequest) returns (CapabilityResponse);
  // Retrieve a snapshot of data from the target. A Get RPC requests that the
  // target snapshots a subset of the data tree as specified by the paths
  // included in the message and serializes this to be returned to the
  // client using the specified encoding.
  // Reference: gNMI Specification Section 3.3
  rpc Get(GetRequest) returns (GetResponse);
  // Set allows the client to modify the state of data on the target. The
  // paths to modified along with the new values that the client wishes
  // to set the value to.
  // Reference: gNMI Specification Section 3.4
  rpc Set(SetRequest) returns (SetResponse);
  // Subscribe allows a client to request the target to send it values
  // of particular paths within the data tree. These values may be streamed
  // at a particular cadence (STREAM), sent one off on a long-lived channel
  // (POLL), or sent as a one-off retrieval (ONCE).
  // Reference: gNMI Specification Section 3.5
  rpc Subscribe(stream SubscribeRequest) returns (stream SubscribeResponse);
}

实现

  • 定义获取内存使用率的path:/system/memory
  • server的源码
centec@hpc-1:~/go/my_gNMI/server$ 
package main

import (
    "fmt"
    "log"
    "net"
    "time"
    "context"

    "github.com/shirou/gopsutil/mem"
    "github.com/openconfig/gnmi/proto/gnmi"
    "google.golang.org/grpc"
)

type server struct{}

//获取内存使用率的函数
func get_memory_util() (float64, error) {
    memory, err := mem.VirtualMemory()
    if err != nil {
        fmt.Println("Error:", err)
        return 0,err
    }
    usagePercent := memory.UsedPercent
    return usagePercent,nil
}

func (s *server) Capabilities(context.Context, *gnmi.CapabilityRequest) (*gnmi.CapabilityResponse, error) {
    // 返回服务器支持的 CapabilityResponse,只占位,具体实现先空着
    return &gnmi.CapabilityResponse{}, nil
}

func (s *server) Get(context.Context, *gnmi.GetRequest) (*gnmi.GetResponse, error) {
    // 返回服务器的 GetResponse,只占位,具体实现先空着
    return &gnmi.GetResponse{}, nil
}

func (s *server) Set(context.Context, *gnmi.SetRequest) (*gnmi.SetResponse, error) {
    // 处理 SetRequest 并返回 SetResponse,只占位,具体实现先空着
    return &gnmi.SetResponse{}, nil
}


func (s *server) Subscribe(stream gnmi.GNMI_SubscribeServer) error {
    // 记录客户端是否取消订阅
    ctx := stream.Context()
    done := ctx.Done()

    for {
        req, err := stream.Recv()
        if err != nil {
            return err
        }

        if req.GetSubscribe() != nil {
            // 处理订阅请求
            go handleSubscription(stream, done, req.GetSubscribe())
        }
    }
}

func handleSubscription(stream gnmi.GNMI_SubscribeServer, done <-chan struct{}, sub *gnmi.SubscriptionList) {

    var memoryUsage float64
    var err error
    // 获取采样间隔
    sampleInterval := sub.GetSubscription()[0].GetSampleInterval()

    for {
        select {
        case <-done:
            return
        default:
            // 构建更新消息
            memoryUsage, err = get_memory_util()
             if err != nil {
                 fmt.Println("Get memory util failure:", err)
                 return
             }
            update := &gnmi.SubscribeResponse_Update{
                //下面":"前面都在gnmi proto生成的代码中定义过的
                Update: &gnmi.Notification{
                    Timestamp: time.Now().UnixNano(),
                    Update: []*gnmi.Update{
                        {
                            Path: &gnmi.Path{
                                Elem: []*gnmi.PathElem{
                                    {
                                        Name: "system",
                                    },
                                    {
                                        Name: "memory",
                                    },
                                },
                            },
                            Val: &gnmi.TypedValue{
                                Value: &gnmi.TypedValue_DoubleVal{
                                    DoubleVal: memoryUsage,
                                },
                            },
                        },
                    },
                },
            }

            // 发送更新消息给客户端
            err := stream.Send(&gnmi.SubscribeResponse{
                Response: update,
            })
            if err != nil {
                log.Println("Failed to send update:", err)
                return
            }

            fmt.Println(sampleInterval)
            // 等待采样间隔
            <-time.After(time.Duration(sampleInterval) * time.Second)
        }
    }
}

func main() {
    lis, err := net.Listen("tcp", ":50051")
    if err != nil {
        log.Fatalf("Failed to listen: %v", err)
    }

    s := grpc.NewServer()
    gnmi.RegisterGNMIServer(s, &server{})

    log.Println("Server running on port 50051")
    if err := s.Serve(lis); err != nil {
        log.Fatalf("Failed to serve: %v", err)
    }
}
centec@hpc-1:~/go/my_gNMI/server$ 
  • client源码
centec@hpc-1:~/go/my_gNMI/client$ cat client.go 
package main

import (
    "context"
    "log"
    "time"

    "github.com/openconfig/gnmi/proto/gnmi"
    "google.golang.org/grpc"
)

func main() {
    conn, err := grpc.Dial("localhost:50051", grpc.WithInsecure())
    if err != nil {
        log.Fatalf("Failed to connect: %v", err)
    }
    defer conn.Close()

    c := gnmi.NewGNMIClient(conn)

    ctx, cancel := context.WithTimeout(context.Background(), 10*time.Hour)
    defer cancel()

    // 设置订阅参数
    subscribe := &gnmi.SubscriptionList{
        Subscription: []*gnmi.Subscription{
            {
                Path: &gnmi.Path{
                    Elem: []*gnmi.PathElem{
                        {
                            Name: "system",
                        },
                        {
                            Name: "memory",
                        },
                    },
                },
                Mode:           gnmi.SubscriptionMode_SAMPLE,
                SampleInterval: 10, // 设置采样间隔为 10 秒
            },
        },
        Mode: gnmi.SubscriptionList_STREAM,
    }

    // 创建订阅请求
    subReq := &gnmi.SubscribeRequest{
        Request: &gnmi.SubscribeRequest_Subscribe{
            Subscribe: subscribe,
        },
    }

    stream, err := c.Subscribe(ctx)
    if err != nil {
        log.Fatalf("Failed to subscribe: %v", err)
    }

    err = stream.Send(subReq)
    if err != nil {
        log.Fatalf("Failed to send subscribe request: %v", err)
    }

    // 启动一个go routine,等待 1 分钟后cancel context
    go func() {
        <-time.After(1 * time.Minute)
        cancel()
        log.Println("Unsubscribed")
    }()

    log.Println("Subscribed")

    // 接收服务器发送的更新消息
    for {
        res, err := stream.Recv()
        if err != nil {
            log.Fatalf("Failed to receive update: %v", err)
        }

        update := res.GetUpdate()
        if update != nil {
            // 处理接收到的更新消息
            log.Printf("Received update: %+v", update)
        }
    }
}
centec@hpc-1:~/go/my_gNMI/client$ 

验证

  • 启动server,保持一直运行
centec@hpc-1:~/go/my_gNMI/server$ go run server.go 
2024/04/25 17:19:02 Server running on port 50051
  • 另开一个终端,启动client,一分钟后自动cancel
centec@hpc-1:~/go/my_gNMI/client$ go run client.go 
2024/04/25 17:20:00 Subscribed
2024/04/25 17:20:00 Received update: timestamp:1714036800991972077  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.807159341843345}}
2024/04/25 17:20:11 Received update: timestamp:1714036811001969660  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.865417470678985}}
2024/04/25 17:20:21 Received update: timestamp:1714036821011881474  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.864675080036548}}
2024/04/25 17:20:31 Received update: timestamp:1714036831021878936  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.865183031528744}}
2024/04/25 17:20:41 Received update: timestamp:1714036841022197657  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.863620103860452}}
2024/04/25 17:20:51 Received update: timestamp:1714036851031841235  update:{path:{elem:{name:"system"}  elem:{name:"memory"}}  val:{double_val:25.86430388471533}}
2024/04/25 17:21:01 Unsubscribed
2024/04/25 17:21:01 Failed to receive update: rpc error: code = Canceled desc = context canceled
exit status 1
centec@hpc-1:~/go/my_gNMI/client$ 
  • 5
    点赞
  • 4
    收藏
    觉得还不错? 一键收藏
  • 0
    评论
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值