From 28df7c855784e784fb0e614c1cca0e565a7ef75f Mon Sep 17 00:00:00 2001 From: Ondrej Fabry Date: Mon, 22 Oct 2018 14:45:21 +0200 Subject: Introduce StatsAPI and it's initial implementation - this implementation is basically Go wrapper around VPP's vppapiclient C library Change-Id: I6f53dc3e228868834bf3a8a00c686ad05e22f3dd Signed-off-by: Ondrej Fabry --- .gitignore | 1 + adapter/adapter.go | 46 ----- adapter/mock/mock_adapter.go | 6 +- adapter/stats_api.go | 86 +++++++++ adapter/vpp_api.go | 47 +++++ adapter/vppapiclient/doc.go | 18 ++ adapter/vppapiclient/empty_adapter.go | 56 ------ adapter/vppapiclient/stat_client.go | 279 +++++++++++++++++++++++++++ adapter/vppapiclient/stat_client_stub.go | 45 +++++ adapter/vppapiclient/vppapiclient.go | 223 +++++++++++++++++++++ adapter/vppapiclient/vppapiclient_adapter.go | 212 -------------------- adapter/vppapiclient/vppapiclient_stub.go | 53 +++++ core/connection.go | 45 ++--- core/request_handler.go | 6 +- examples/cmd/stats-api/stats_api.go | 66 +++++++ examples/cmd/stats-client/stats_client.go | 9 + govpp.go | 32 +-- 17 files changed, 874 insertions(+), 356 deletions(-) delete mode 100644 adapter/adapter.go create mode 100644 adapter/stats_api.go create mode 100644 adapter/vpp_api.go create mode 100644 adapter/vppapiclient/doc.go delete mode 100644 adapter/vppapiclient/empty_adapter.go create mode 100644 adapter/vppapiclient/stat_client.go create mode 100644 adapter/vppapiclient/stat_client_stub.go create mode 100644 adapter/vppapiclient/vppapiclient.go delete mode 100644 adapter/vppapiclient/vppapiclient_adapter.go create mode 100644 adapter/vppapiclient/vppapiclient_stub.go create mode 100644 examples/cmd/stats-api/stats_api.go diff --git a/.gitignore b/.gitignore index c10a9ca..7ec04f8 100644 --- a/.gitignore +++ b/.gitignore @@ -7,6 +7,7 @@ cmd/binapi-generator/binapi-generator examples/cmd/perf-bench/perf-bench examples/cmd/simple-client/simple-client examples/cmd/stats-client/stats-client +examples/cmd/stats-api/stats-api # extras extras/libmemif/examples/gopacket/gopacket diff --git a/adapter/adapter.go b/adapter/adapter.go deleted file mode 100644 index aa34329..0000000 --- a/adapter/adapter.go +++ /dev/null @@ -1,46 +0,0 @@ -// Copyright (c) 2017 Cisco and/or its affiliates. -// -// 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 adapter - -import ( - "errors" -) - -// ErrNotImplemented is an error returned when missing implementation. -var ErrNotImplemented = errors.New("not implemented for this OS") - -// MsgCallback defines func signature for message callback. -type MsgCallback func(msgID uint16, data []byte) - -// VppAdapter provides connection to VPP. It is responsible for sending and receiving of binary-encoded messages to/from VPP. -type VppAdapter interface { - // Connect connects the process to VPP. - Connect() error - - // Disconnect disconnects the process from VPP. - Disconnect() - - // GetMsgID returns a runtime message ID for the given message name and CRC. - GetMsgID(msgName string, msgCrc string) (uint16, error) - - // SendMsg sends a binary-encoded message to VPP. - SendMsg(context uint32, data []byte) error - - // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP. - SetMsgCallback(cb MsgCallback) - - // WaitReady waits until adapter is ready. - WaitReady() error -} diff --git a/adapter/mock/mock_adapter.go b/adapter/mock/mock_adapter.go index cdf2081..9783651 100644 --- a/adapter/mock/mock_adapter.go +++ b/adapter/mock/mock_adapter.go @@ -37,7 +37,7 @@ const ( useReplyHandlers // use reply handler ) -// VppAdapter represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter. +// VppAPI represents a mock VPP adapter that can be used for unit/integration testing instead of the vppapiclient adapter. type VppAdapter struct { callback adapter.MsgCallback @@ -107,8 +107,8 @@ func (a *VppAdapter) Connect() error { } // Disconnect emulates disconnecting the process from VPP. -func (a *VppAdapter) Disconnect() { - // no op +func (a *VppAdapter) Disconnect() error { + return nil } // GetMsgNameByID returns message name for specified message ID. diff --git a/adapter/stats_api.go b/adapter/stats_api.go new file mode 100644 index 0000000..8815aae --- /dev/null +++ b/adapter/stats_api.go @@ -0,0 +1,86 @@ +package adapter + +// StatsAPI provides connection to VPP stats API. +type StatsAPI interface { + // Connect establishes client connection to the stats API. + Connect() error + + // Disconnect terminates client connection. + Disconnect() error + + // ListStats lists names for all stats. + ListStats(patterns ...string) (statNames []string, err error) + + // DumpStats dumps all stat entries. + DumpStats(patterns ...string) ([]*StatEntry, error) +} + +// StatType represents type of stat directory and simply +// defines what type of stat data is stored in the stat entry. +type StatType int + +const ( + _ StatType = iota + ScalarIndex + SimpleCounterVector + CombinedCounterVector + ErrorIndex +) + +func (d StatType) String() string { + switch d { + case ScalarIndex: + return "ScalarIndex" + case SimpleCounterVector: + return "SimpleCounterVector" + case CombinedCounterVector: + return "CombinedCounterVector" + case ErrorIndex: + return "ErrorIndex" + } + return "UnknownStatType" +} + +// StatEntry represents single stat entry. The type of stat stored in Data +// is defined by Type. +type StatEntry struct { + Name string + Type StatType + Data Stat +} + +// Counter represents simple counter with single value. +type Counter uint64 + +// CombinedCounter represents counter with two values, for packet count and bytes count. +type CombinedCounter struct { + Packets Counter + Bytes Counter +} + +// ScalarStat represents stat for ScalarIndex. +type ScalarStat float64 + +// ErrorStat represents stat for ErrorIndex. +type ErrorStat uint64 + +// SimpleCounterStat represents stat for SimpleCounterVector. +// The outer array represents workers and the inner array represents sw_if_index. +// Values should be aggregated per interface for every worker. +type SimpleCounterStat [][]Counter + +// CombinedCounterStat represents stat for CombinedCounterVector. +// The outer array represents workers and the inner array represents sw_if_index. +// Values should be aggregated per interface for every worker. +type CombinedCounterStat [][]CombinedCounter + +// Data represents some type of stat which is usually defined by StatType. +type Stat interface { + // isStat is unexported to limit implementations of Data interface to this package, + isStat() +} + +func (ScalarStat) isStat() {} +func (ErrorStat) isStat() {} +func (SimpleCounterStat) isStat() {} +func (CombinedCounterStat) isStat() {} diff --git a/adapter/vpp_api.go b/adapter/vpp_api.go new file mode 100644 index 0000000..7d14633 --- /dev/null +++ b/adapter/vpp_api.go @@ -0,0 +1,47 @@ +// Copyright (c) 2017 Cisco and/or its affiliates. +// +// 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 adapter + +import ( + "errors" +) + +// ErrNotImplemented is an error returned when missing implementation. +var ErrNotImplemented = errors.New("not implemented for this OS") + +// MsgCallback defines func signature for message callback. +type MsgCallback func(msgID uint16, data []byte) + +// VppAPI provides connection to VPP binary API. +// It is responsible for sending and receiving of binary-encoded messages to/from VPP. +type VppAPI interface { + // Connect connects the process to VPP. + Connect() error + + // Disconnect disconnects the process from VPP. + Disconnect() error + + // GetMsgID returns a runtime message ID for the given message name and CRC. + GetMsgID(msgName string, msgCrc string) (msgID uint16, err error) + + // SendMsg sends a binary-encoded message to VPP. + SendMsg(context uint32, data []byte) error + + // SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP. + SetMsgCallback(cb MsgCallback) + + // WaitReady waits until adapter is ready. + WaitReady() error +} diff --git a/adapter/vppapiclient/doc.go b/adapter/vppapiclient/doc.go new file mode 100644 index 0000000..6505498 --- /dev/null +++ b/adapter/vppapiclient/doc.go @@ -0,0 +1,18 @@ +// Copyright (c) 2018 Cisco and/or its affiliates. +// +// 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 vppapiclient is the default VPP adapter being used for +// the connection to VPP binary & stats API via shared memory. +// It is essentially Go wrapper for the VPP vppapiclient library written in C. +package vppapiclient diff --git a/adapter/vppapiclient/empty_adapter.go b/adapter/vppapiclient/empty_adapter.go deleted file mode 100644 index 7514048..0000000 --- a/adapter/vppapiclient/empty_adapter.go +++ /dev/null @@ -1,56 +0,0 @@ -// Copyright (c) 2017 Cisco and/or its affiliates. -// -// 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. - -// +build windows darwin - -/* - This is just an empty adapter that does nothing. It builds only on Windows and OSX, where the real - VPP API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX. -*/ - -package vppapiclient - -import ( - "git.fd.io/govpp.git/adapter" -) - -type vppAPIClientAdapter struct{} - -func NewVppAdapter(string) adapter.VppAdapter { - return &vppAPIClientAdapter{} -} - -func (a *vppAPIClientAdapter) Connect() error { - return adapter.ErrNotImplemented -} - -func (a *vppAPIClientAdapter) Disconnect() { - // no op -} - -func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) { - return 0, nil -} - -func (a *vppAPIClientAdapter) SendMsg(clientID uint32, data []byte) error { - return nil -} - -func (a *vppAPIClientAdapter) SetMsgCallback(cb adapter.MsgCallback) { - // no op -} - -func (a *vppAPIClientAdapter) WaitReady() error { - return nil -} diff --git a/adapter/vppapiclient/stat_client.go b/adapter/vppapiclient/stat_client.go new file mode 100644 index 0000000..5f3b932 --- /dev/null +++ b/adapter/vppapiclient/stat_client.go @@ -0,0 +1,279 @@ +// Copyright (c) 2018 Cisco and/or its affiliates. +// +// 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. + +// +build !windows,!darwin + +package vppapiclient + +/* +#cgo CFLAGS: -DPNG_DEBUG=1 +#cgo LDFLAGS: -lvppapiclient + +#include +#include +#include +#include +#include +#include + +static int +govpp_stat_connect(char *socket_name) +{ + return stat_segment_connect(socket_name); +} + +static void +govpp_stat_disconnect() +{ + stat_segment_disconnect(); +} + +static uint32_t* +govpp_stat_segment_ls(uint8_t ** pattern) +{ + return stat_segment_ls(pattern); +} + +static int +govpp_stat_segment_vec_len(void *vec) +{ + return stat_segment_vec_len(vec); +} + +static void +govpp_stat_segment_vec_free(void *vec) +{ + stat_segment_vec_free(vec); +} + +static char* +govpp_stat_segment_dir_index_to_name(uint32_t *dir, uint32_t index) +{ + return stat_segment_index_to_name(dir[index]); +} + +static stat_segment_data_t* +govpp_stat_segment_dump(uint32_t *counter_vec) +{ + return stat_segment_dump(counter_vec); +} + +static stat_segment_data_t +govpp_stat_segment_dump_index(stat_segment_data_t *data, int index) +{ + return data[index]; +} + +static int +govpp_stat_segment_data_type(stat_segment_data_t *data) +{ + return data->type; +} + +static double +govpp_stat_segment_data_get_scalar_value(stat_segment_data_t *data) +{ + return data->scalar_value; +} + +static double +govpp_stat_segment_data_get_error_value(stat_segment_data_t *data) +{ + return data->error_value; +} + +static uint64_t** +govpp_stat_segment_data_get_simple_counter(stat_segment_data_t *data) +{ + return data->simple_counter_vec; +} + +static uint64_t* +govpp_stat_segment_data_get_simple_counter_index(stat_segment_data_t *data, int index) +{ + return data->simple_counter_vec[index]; +} + +static uint64_t +govpp_stat_segment_data_get_simple_counter_index_value(stat_segment_data_t *data, int index, int index2) +{ + return data->simple_counter_vec[index][index2]; +} + +static vlib_counter_t** +govpp_stat_segment_data_get_combined_counter(stat_segment_data_t *data) +{ + return data->combined_counter_vec; +} + +static vlib_counter_t* +govpp_stat_segment_data_get_combined_counter_index(stat_segment_data_t *data, int index) +{ + return data->combined_counter_vec[index]; +} + +static uint64_t +govpp_stat_segment_data_get_combined_counter_index_packets(stat_segment_data_t *data, int index, int index2) +{ + return data->combined_counter_vec[index][index2].packets; +} + +static uint64_t +govpp_stat_segment_data_get_combined_counter_index_bytes(stat_segment_data_t *data, int index, int index2) +{ + return data->combined_counter_vec[index][index2].bytes; +} + +static void +govpp_stat_segment_data_free(stat_segment_data_t *data) +{ + stat_segment_data_free(data); +} + +static uint8_t** +govpp_stat_segment_string_vector(uint8_t ** string_vector, char *string) +{ + return stat_segment_string_vector(string_vector, string); +} +*/ +import "C" +import ( + "fmt" + "os" + "unsafe" + + "git.fd.io/govpp.git/adapter" +) + +var ( + // DefaultStatSocket is the default path for the VPP stat socket file. + DefaultStatSocket = "/run/vpp/stats.sock" +) + +// StatClient is the default implementation of StatsAPI. +type StatClient struct { + socketName string +} + +// NewStatClient returns new VPP stats API client. +func NewStatClient(socketName string) *StatClient { + return &StatClient{ + socketName: socketName, + } +} + +func (c *StatClient) Connect() error { + var sockName string + + if c.socketName == "" { + sockName = DefaultStatSocket + } else { + sockName = c.socketName + } + + rc := C.govpp_stat_connect(C.CString(sockName)) + if rc != 0 { + return fmt.Errorf("connecting to VPP stats API failed (rc=%v)", rc) + } + + return nil +} + +func (c *StatClient) Disconnect() error { + C.govpp_stat_disconnect() + return nil +} + +func (c *StatClient) ListStats(patterns ...string) (stats []string, err error) { + dir := C.govpp_stat_segment_ls(convertStringSlice(patterns)) + defer C.govpp_stat_segment_vec_free(unsafe.Pointer(dir)) + + l := C.govpp_stat_segment_vec_len(unsafe.Pointer(dir)) + for i := 0; i < int(l); i++ { + nameChar := C.govpp_stat_segment_dir_index_to_name(dir, C.uint32_t(i)) + stats = append(stats, C.GoString(nameChar)) + C.free(unsafe.Pointer(nameChar)) + } + + return stats, nil +} + +func (c *StatClient) DumpStats(patterns ...string) (stats []*adapter.StatEntry, err error) { + dir := C.govpp_stat_segment_ls(convertStringSlice(patterns)) + defer C.govpp_stat_segment_vec_free(unsafe.Pointer(dir)) + + dump := C.govpp_stat_segment_dump(dir) + defer C.govpp_stat_segment_data_free(dump) + + l := C.govpp_stat_segment_vec_len(unsafe.Pointer(dump)) + for i := 0; i < int(l); i++ { + v := C.govpp_stat_segment_dump_index(dump, C.int(i)) + nameChar := v.name + name := C.GoString(nameChar) + typ := adapter.StatType(C.govpp_stat_segment_data_type(&v)) + + stat := &adapter.StatEntry{ + Name: name, + Type: typ, + } + + switch typ { + case adapter.ScalarIndex: + stat.Data = adapter.ScalarStat(C.govpp_stat_segment_data_get_scalar_value(&v)) + + case adapter.ErrorIndex: + stat.Data = adapter.ErrorStat(C.govpp_stat_segment_data_get_error_value(&v)) + + case adapter.SimpleCounterVector: + length := int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_simple_counter(&v)))) + vector := make([][]adapter.Counter, length) + for k := 0; k < length; k++ { + for j := 0; j < int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_simple_counter_index(&v, _Ctype_int(k))))); j++ { + vector[k] = append(vector[k], adapter.Counter(C.govpp_stat_segment_data_get_simple_counter_index_value(&v, _Ctype_int(k), _Ctype_int(j)))) + } + } + stat.Data = adapter.SimpleCounterStat(vector) + + case adapter.CombinedCounterVector: + length := int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_combined_counter(&v)))) + vector := make([][]adapter.CombinedCounter, length) + for k := 0; k < length; k++ { + for j := 0; j < int(C.govpp_stat_segment_vec_len(unsafe.Pointer(C.govpp_stat_segment_data_get_combined_counter_index(&v, _Ctype_int(k))))); j++ { + vector[k] = append(vector[k], adapter.CombinedCounter{ + Packets: adapter.Counter(C.govpp_stat_segment_data_get_combined_counter_index_packets(&v, _Ctype_int(k), _Ctype_int(j))), + Bytes: adapter.Counter(C.govpp_stat_segment_data_get_combined_counter_index_bytes(&v, _Ctype_int(k), _Ctype_int(j))), + }) + } + } + stat.Data = adapter.CombinedCounterStat(vector) + + default: + fmt.Fprintf(os.Stderr, "invalid stat type: %v (%d)", typ, typ) + continue + + } + + stats = append(stats, stat) + } + + return stats, nil +} + +func convertStringSlice(strs []string) **C.uint8_t { + var arr **C.uint8_t + for _, str := range strs { + arr = C.govpp_stat_segment_string_vector(arr, C.CString(str)) + } + return arr +} diff --git a/adapter/vppapiclient/stat_client_stub.go b/adapter/vppapiclient/stat_client_stub.go new file mode 100644 index 0000000..24f7e13 --- /dev/null +++ b/adapter/vppapiclient/stat_client_stub.go @@ -0,0 +1,45 @@ +// Copyright (c) 2018 Cisco and/or its affiliates. +// +// 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. + +// +build windows darwin + +package vppapiclient + +import ( + "git.fd.io/govpp.git/adapter" +) + +// StatClient is just an stub adapter that does nothing. It builds only on Windows and OSX, where the real +// VPP stats API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX. +type StatClient struct{} + +func NewStatClient(socketName string) *StatClient { + return new(StatClient) +} + +func (*StatClient) Connect() error { + return adapter.ErrNotImplemented +} + +func (*StatClient) Disconnect() error { + return nil +} + +func (*StatClient) ListStats(patterns ...string) (statNames []string, err error) { + return nil, adapter.ErrNotImplemented +} + +func (*StatClient) DumpStats(patterns ...string) ([]*adapter.StatEntry, error) { + return nil, adapter.ErrNotImplemented +} diff --git a/adapter/vppapiclient/vppapiclient.go b/adapter/vppapiclient/vppapiclient.go new file mode 100644 index 0000000..34ad199 --- /dev/null +++ b/adapter/vppapiclient/vppapiclient.go @@ -0,0 +1,223 @@ +// Copyright (c) 2017 Cisco and/or its affiliates. +// +// 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. + +// +build !windows,!darwin + +package vppapiclient + +/* +#cgo CFLAGS: -DPNG_DEBUG=1 +#cgo LDFLAGS: -lvppapiclient + +#include +#include +#include +#include +#include + +extern void go_msg_callback(uint16_t msg_id, void* data, size_t size); + +typedef struct __attribute__((__packed__)) _req_header { + uint16_t msg_id; + uint32_t client_index; + uint32_t context; +} req_header_t; + +typedef struct __attribute__((__packed__)) _reply_header { + uint16_t msg_id; +} reply_header_t; + +static void +govpp_msg_callback(unsigned char *data, int size) +{ + reply_header_t *header = ((reply_header_t *)data); + go_msg_callback(ntohs(header->msg_id), data, size); +} + +static int +govpp_send(uint32_t context, void *data, size_t size) +{ + req_header_t *header = ((req_header_t *)data); + header->context = htonl(context); + return vac_write(data, size); +} + +static int +govpp_connect(char *shm) +{ + return vac_connect("govpp", shm, govpp_msg_callback, 32); +} + +static int +govpp_disconnect() +{ + return vac_disconnect(); +} + +static uint32_t +govpp_get_msg_index(char *name_and_crc) +{ + return vac_get_msg_index(name_and_crc); +} +*/ +import "C" + +import ( + "fmt" + "os" + "path/filepath" + "reflect" + "unsafe" + + "git.fd.io/govpp.git/adapter" + "github.com/fsnotify/fsnotify" +) + +const ( + // shmDir is a directory where shared memory is supposed to be created. + shmDir = "/dev/shm/" + // vppShmFile is a default name of the file in the shmDir. + vppShmFile = "vpe-api" +) + +// global VPP binary API client adapter context +var client *VppClient + +// VppClient is the default implementation of the VppAPI. +type VppClient struct { + shmPrefix string + msgCallback adapter.MsgCallback +} + +// NewVppClient returns a new VPP binary API client. +func NewVppClient(shmPrefix string) *VppClient { + return &VppClient{ + shmPrefix: shmPrefix, + } +} + +// Connect connects the process to VPP. +func (a *VppClient) Connect() error { + if client != nil { + return fmt.Errorf("already connected to binary API, disconnect first") + } + + var rc _Ctype_int + if a.shmPrefix == "" { + rc = C.govpp_connect(nil) + } else { + shm := C.CString(a.shmPrefix) + rc = C.govpp_connect(shm) + } + if rc != 0 { + return fmt.Errorf("connecting to VPP binary API failed (rc=%v)", rc) + } + + client = a + return nil +} + +// Disconnect disconnects the process from VPP. +func (a *VppClient) Disconnect() error { + client = nil + + rc := C.govpp_disconnect() + if rc != 0 { + return fmt.Errorf("disconnecting from VPP binary API failed (rc=%v)", rc) + } + + return nil +} + +// GetMsgID returns a runtime message ID for the given message name and CRC. +func (a *VppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) { + nameAndCrc := C.CString(msgName + "_" + msgCrc) + defer C.free(unsafe.Pointer(nameAndCrc)) + + msgID := uint16(C.govpp_get_msg_index(nameAndCrc)) + if msgID == ^uint16(0) { + // VPP does not know this message + return msgID, fmt.Errorf("unknown message: %v (crc: %v)", msgName, msgCrc) + } + + return msgID, nil +} + +// SendMsg sends a binary-encoded message to VPP. +func (a *VppClient) SendMsg(context uint32, data []byte) error { + rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data))) + if rc != 0 { + return fmt.Errorf("unable to send the message (rc=%v)", rc) + } + return nil +} + +// SetMsgCallback sets a callback function that will be called by the adapter +// whenever a message comes from VPP. +func (a *VppClient) SetMsgCallback(cb adapter.MsgCallback) { + a.msgCallback = cb +} + +// WaitReady blocks until shared memory for sending +// binary api calls is present on the file system. +func (a *VppClient) WaitReady() error { + var path string + + // join the path to the shared memory segment + if a.shmPrefix == "" { + path = filepath.Join(shmDir, vppShmFile) + } else { + path = filepath.Join(shmDir, a.shmPrefix+"-"+vppShmFile) + } + + // check if file at the path exists + if _, err := os.Stat(path); err == nil { + // file exists, we are ready + return nil + } else if !os.IsNotExist(err) { + return err + } + + // file does not exist, start watching folder + watcher, err := fsnotify.NewWatcher() + if err != nil { + return err + } + defer watcher.Close() + + if err := watcher.Add(shmDir); err != nil { + return err + } + + for { + ev := <-watcher.Events + if ev.Name == path { + if (ev.Op & fsnotify.Create) == fsnotify.Create { + // file was created, we are ready + break + } + } + } + + return nil +} + +//export go_msg_callback +func go_msg_callback(msgID C.uint16_t, data unsafe.Pointer, size C.size_t) { + // convert unsafe.Pointer to byte slice + sliceHeader := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)} + byteSlice := *(*[]byte)(unsafe.Pointer(sliceHeader)) + + client.msgCallback(uint16(msgID), byteSlice) +} diff --git a/adapter/vppapiclient/vppapiclient_adapter.go b/adapter/vppapiclient/vppapiclient_adapter.go deleted file mode 100644 index e62bccd..0000000 --- a/adapter/vppapiclient/vppapiclient_adapter.go +++ /dev/null @@ -1,212 +0,0 @@ -// Copyright (c) 2017 Cisco and/or its affiliates. -// -// 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. - -// +build !windows,!darwin - -// Package vppapiclient is the default VPP adapter being used for the connection with VPP via shared memory. -// It is based on the communication with the vppapiclient VPP library written in C via CGO. -package vppapiclient - -/* -#cgo CFLAGS: -DPNG_DEBUG=1 -#cgo LDFLAGS: -lvppapiclient - -#include -#include -#include -#include -#include - -extern void go_msg_callback(uint16_t msg_id, void* data, size_t size); - -typedef struct __attribute__((__packed__)) _req_header { - uint16_t msg_id; - uint32_t client_index; - uint32_t context; -} req_header_t; - -typedef struct __attribute__((__packed__)) _reply_header { - uint16_t msg_id; -} reply_header_t; - -static void -govpp_msg_callback (unsigned char *data, int size) -{ - reply_header_t *header = ((reply_header_t *)data); - go_msg_callback(ntohs(header->msg_id), data, size); -} - -static int -govpp_send(uint32_t context, void *data, size_t size) -{ - req_header_t *header = ((req_header_t *)data); - header->context = htonl(context); - return vac_write(data, size); -} - -static int -govpp_connect (char *shm) -{ - return vac_connect("govpp", shm, govpp_msg_callback, 32); -} - -static int -govvp_disconnect() -{ - return vac_disconnect(); -} - -static uint32_t -govpp_get_msg_index(char *name_and_crc) -{ - return vac_get_msg_index(name_and_crc); -} -*/ -import "C" - -import ( - "fmt" - "os" - "path/filepath" - "reflect" - "unsafe" - - "git.fd.io/govpp.git/adapter" - "github.com/fsnotify/fsnotify" -) - -const ( - // watchedFolder is a folder where vpp's shared memory is supposed to be created. - // File system events are monitored in this folder. - watchedFolder = "/dev/shm/" - // watchedFile is a default name of the file in the watchedFolder. Once the file is present, - // the vpp is ready to accept a new connection. - watchedFile = "vpe-api" -) - -// vppAPIClientAdapter is the opaque context of the adapter. -type vppAPIClientAdapter struct { - shmPrefix string - callback adapter.MsgCallback -} - -var vppClient *vppAPIClientAdapter // global vpp API client adapter context - -// NewVppAdapter returns a new vpp API client adapter. -func NewVppAdapter(shmPrefix string) adapter.VppAdapter { - return &vppAPIClientAdapter{ - shmPrefix: shmPrefix, - } -} - -// Connect connects the process to VPP. -func (a *vppAPIClientAdapter) Connect() error { - vppClient = a - var rc _Ctype_int - if a.shmPrefix == "" { - rc = C.govpp_connect(nil) - } else { - shm := C.CString(a.shmPrefix) - rc = C.govpp_connect(shm) - } - if rc != 0 { - return fmt.Errorf("unable to connect to VPP (error=%d)", rc) - } - return nil -} - -// Disconnect disconnects the process from VPP. -func (a *vppAPIClientAdapter) Disconnect() { - C.govvp_disconnect() -} - -// GetMsgID returns a runtime message ID for the given message name and CRC. -func (a *vppAPIClientAdapter) GetMsgID(msgName string, msgCrc string) (uint16, error) { - nameAndCrc := C.CString(msgName + "_" + msgCrc) - defer C.free(unsafe.Pointer(nameAndCrc)) - - msgID := uint16(C.govpp_get_msg_index(nameAndCrc)) - if msgID == ^uint16(0) { - // VPP does not know this message - return msgID, fmt.Errorf("unknown message: %v (crc: %v)", msgName, msgCrc) - } - - return msgID, nil -} - -// SendMsg sends a binary-encoded message to VPP. -func (a *vppAPIClientAdapter) SendMsg(context uint32, data []byte) error { - rc := C.govpp_send(C.uint32_t(context), unsafe.Pointer(&data[0]), C.size_t(len(data))) - if rc != 0 { - return fmt.Errorf("unable to send the message (error=%d)", rc) - } - return nil -} - -// SetMsgCallback sets a callback function that will be called by the adapter whenever a message comes from VPP. -func (a *vppAPIClientAdapter) SetMsgCallback(cb adapter.MsgCallback) { - a.callback = cb -} - -// WaitReady blocks until shared memory for sending -// binary api calls is present on the file system. -func (a *vppAPIClientAdapter) WaitReady() error { - // Path to the shared memory segment - var path string - if a.shmPrefix == "" { - path = filepath.Join(watchedFolder, watchedFile) - } else { - path = filepath.Join(watchedFolder, a.shmPrefix+"-"+watchedFile) - } - - // Watch folder if file does not exist yet - if !fileExists(path) { - watcher, err := fsnotify.NewWatcher() - if err != nil { - return err - } - defer watcher.Close() - - if err := watcher.Add(watchedFolder); err != nil { - return err - } - - for { - ev := <-watcher.Events - if ev.Name == path && (ev.Op&fsnotify.Create) == fsnotify.Create { - break - } - } - } - - return nil -} - -func fileExists(name string) bool { - if _, err := os.Stat(name); err != nil { - if os.IsNotExist(err) { - return false - } - } - return true -} - -//export go_msg_callback -func go_msg_callback(msgID C.uint16_t, data unsafe.Pointer, size C.size_t) { - // convert unsafe.Pointer to byte slice - slice := &reflect.SliceHeader{Data: uintptr(data), Len: int(size), Cap: int(size)} - byteArr := *(*[]byte)(unsafe.Pointer(slice)) - - vppClient.callback(uint16(msgID), byteArr) -} diff --git a/adapter/vppapiclient/vppapiclient_stub.go b/adapter/vppapiclient/vppapiclient_stub.go new file mode 100644 index 0000000..6de89a7 --- /dev/null +++ b/adapter/vppapiclient/vppapiclient_stub.go @@ -0,0 +1,53 @@ +// Copyright (c) 2017 Cisco and/or its affiliates. +// +// 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. + +// +build windows darwin + +package vppapiclient + +import ( + "git.fd.io/govpp.git/adapter" +) + +// VppClient is just an stub adapter that does nothing. It builds only on Windows and OSX, where the real +// VPP binary API client adapter does not build. Its sole purpose is to make the compiler happy on Windows and OSX. +type VppClient struct{} + +func NewVppAdapter(string) *VppClient { + return &VppClient{} +} + +func (a *VppClient) Connect() error { + return adapter.ErrNotImplemented +} + +func (a *VppClient) Disconnect() error { + return nil +} + +func (a *VppClient) GetMsgID(msgName string, msgCrc string) (uint16, error) { + return 0, nil +} + +func (a *VppClient) SendMsg(clientID uint32, data []byte) error { + return nil +} + +func (a *VppClient) SetMsgCallback(cb adapter.MsgCallback) { + // no op +} + +func (a *VppClient) WaitReady() error { + return adapter.ErrNotImplemented +} diff --git a/core/connection.go b/core/connection.go index 7d014ce..c4048f0 100644 --- a/core/connection.go +++ b/core/connection.go @@ -72,8 +72,9 @@ var ( // Connection represents a shared memory connection to VPP via vppAdapter. type Connection struct { - vpp adapter.VppAdapter // VPP adapter - connected uint32 // non-zero if the adapter is connected to VPP + vppClient adapter.VppAPI // VPP binary API client adapter + + vppConnected uint32 // non-zero if the adapter is connected to VPP codec *codec.MsgCodec // message codec msgIDs map[string]uint16 // map of message IDs indexed by message name + CRC @@ -93,24 +94,24 @@ type Connection struct { lastReply time.Time // time of the last received reply from VPP } -func newConnection(vpp adapter.VppAdapter) *Connection { +func newConnection(binapi adapter.VppAPI) *Connection { c := &Connection{ - vpp: vpp, + vppClient: binapi, codec: &codec.MsgCodec{}, msgIDs: make(map[string]uint16), msgMap: make(map[uint16]api.Message), channels: make(map[uint16]*Channel), subscriptions: make(map[uint16][]*subscriptionCtx), } - vpp.SetMsgCallback(c.msgCallback) + binapi.SetMsgCallback(c.msgCallback) return c } // Connect connects to VPP using specified VPP adapter and returns the connection handle. // This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed. -func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) { +func Connect(binapi adapter.VppAPI) (*Connection, error) { // create new connection handle - c, err := createConnection(vppAdapter) + c, err := createConnection(binapi) if err != nil { return nil, err } @@ -127,9 +128,9 @@ func Connect(vppAdapter adapter.VppAdapter) (*Connection, error) { // and ConnectionState channel. This call does not block until connection is established, it // returns immediately. The caller is supposed to watch the returned ConnectionState channel for // Connected/Disconnected events. In case of disconnect, the library will asynchronously try to reconnect. -func AsyncConnect(vppAdapter adapter.VppAdapter) (*Connection, chan ConnectionEvent, error) { +func AsyncConnect(binapi adapter.VppAPI) (*Connection, chan ConnectionEvent, error) { // create new connection handle - c, err := createConnection(vppAdapter) + c, err := createConnection(binapi) if err != nil { return nil, nil, err } @@ -150,14 +151,14 @@ func (c *Connection) Disconnect() { connLock.Lock() defer connLock.Unlock() - if c.vpp != nil { + if c.vppClient != nil { c.disconnectVPP() } conn = nil } // newConnection returns new connection handle. -func createConnection(vppAdapter adapter.VppAdapter) (*Connection, error) { +func createConnection(binapi adapter.VppAPI) (*Connection, error) { connLock.Lock() defer connLock.Unlock() @@ -165,7 +166,7 @@ func createConnection(vppAdapter adapter.VppAdapter) (*Connection, error) { return nil, errors.New("only one connection per process is supported") } - conn = newConnection(vppAdapter) + conn = newConnection(binapi) return conn, nil } @@ -175,19 +176,19 @@ func (c *Connection) connectVPP() error { log.Debug("Connecting to VPP..") // blocking connect - if err := c.vpp.Connect(); err != nil { + if err := c.vppClient.Connect(); err != nil { return err } log.Debugf("Connected to VPP.") if err := c.retrieveMessageIDs(); err != nil { - c.vpp.Disconnect() + c.vppClient.Disconnect() return fmt.Errorf("VPP is incompatible: %v", err) } // store connected state - atomic.StoreUint32(&c.connected, 1) + atomic.StoreUint32(&c.vppConnected, 1) return nil } @@ -272,7 +273,7 @@ func (c *Connection) retrieveMessageIDs() (err error) { msgs := api.GetAllMessages() for name, msg := range msgs { - msgID, err := c.vpp.GetMsgID(msg.GetMessageName(), msg.GetCrcString()) + msgID, err := c.vppClient.GetMsgID(msg.GetMessageName(), msg.GetCrcString()) if err != nil { return err } @@ -296,14 +297,14 @@ func (c *Connection) retrieveMessageIDs() (err error) { // fallback for control ping when vpe package is not imported if c.pingReqID == 0 { - c.pingReqID, err = c.vpp.GetMsgID(msgControlPing.GetMessageName(), msgControlPing.GetCrcString()) + c.pingReqID, err = c.vppClient.GetMsgID(msgControlPing.GetMessageName(), msgControlPing.GetCrcString()) if err != nil { return err } addMsg(c.pingReqID, msgControlPing) } if c.pingReplyID == 0 { - c.pingReplyID, err = c.vpp.GetMsgID(msgControlPingReply.GetMessageName(), msgControlPingReply.GetCrcString()) + c.pingReplyID, err = c.vppClient.GetMsgID(msgControlPingReply.GetMessageName(), msgControlPingReply.GetCrcString()) if err != nil { return err } @@ -315,8 +316,8 @@ func (c *Connection) retrieveMessageIDs() (err error) { // disconnectVPP disconnects from VPP in case it is connected. func (c *Connection) disconnectVPP() { - if atomic.CompareAndSwapUint32(&c.connected, 1, 0) { - c.vpp.Disconnect() + if atomic.CompareAndSwapUint32(&c.vppConnected, 1, 0) { + c.vppClient.Disconnect() } } @@ -325,7 +326,7 @@ func (c *Connection) disconnectVPP() { func (c *Connection) connectLoop(connChan chan ConnectionEvent) { // loop until connected for { - if err := c.vpp.WaitReady(); err != nil { + if err := c.vppClient.WaitReady(); err != nil { log.Warnf("wait ready failed: %v", err) } if err := c.connectVPP(); err == nil { @@ -362,7 +363,7 @@ func (c *Connection) healthCheckLoop(connChan chan ConnectionEvent) { // sleep until next health check probe period time.Sleep(HealthCheckProbeInterval) - if atomic.LoadUint32(&c.connected) == 0 { + if atomic.LoadUint32(&c.vppConnected) == 0 { // Disconnect has been called in the meantime, return the healthcheck - reconnect loop log.Debug("Disconnected on request, exiting health check loop.") return diff --git a/core/request_handler.go b/core/request_handler.go index e52e262..545f235 100644 --- a/core/request_handler.go +++ b/core/request_handler.go @@ -49,7 +49,7 @@ func (c *Connection) watchRequests(ch *Channel) { // processRequest processes a single request received on the request channel. func (c *Connection) processRequest(ch *Channel, req *vppRequest) error { // check whether we are connected to VPP - if atomic.LoadUint32(&c.connected) == 0 { + if atomic.LoadUint32(&c.vppConnected) == 0 { err := ErrNotConnected log.Errorf("processing request failed: %v", err) return err @@ -95,7 +95,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error { } // send the request to VPP - err = c.vpp.SendMsg(context, data) + err = c.vppClient.SendMsg(context, data) if err != nil { err = fmt.Errorf("unable to send the message: %v", err) log.WithFields(logger.Fields{ @@ -118,7 +118,7 @@ func (c *Connection) processRequest(ch *Channel, req *vppRequest) error { "seq_num": req.seqNum, }).Debug(" -> Sending a control ping to VPP.") - if err := c.vpp.SendMsg(context, pingData); err != nil { + if err := c.vppClient.SendMsg(context, pingData); err != nil { log.WithFields(logger.Fields{ "context": context, "msg_id": msgID, diff --git a/examples/cmd/stats-api/stats_api.go b/examples/cmd/stats-api/stats_api.go new file mode 100644 index 0000000..74454ab --- /dev/null +++ b/examples/cmd/stats-api/stats_api.go @@ -0,0 +1,66 @@ +// Copyright (c) 2018 Cisco and/or its affiliates. +// +// 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 main + +import ( + "fmt" + "log" + + "git.fd.io/govpp.git/adapter" + "git.fd.io/govpp.git/adapter/vppapiclient" +) + +// This example shows how to work with VPP's new stats API. + +func main() { + fmt.Println("Starting VPP stats API example..") + + client := vppapiclient.NewStatClient(vppapiclient.DefaultStatSocket) + + // connect to stats API + if err := client.Connect(); err != nil { + log.Fatalln("connecting client failed:", err) + } + defer client.Disconnect() + + // list stats by patterns + // you can omit parameters to list all stats + list, err := client.ListStats("/if", "/sys") + if err != nil { + log.Fatalln("listing stats failed:", err) + } + + for _, stat := range list { + fmt.Printf(" - %v\n", stat) + } + fmt.Printf("listed %d stats\n", len(list)) + + // dump stats by patterns to retrieve stats with the stats data + stats, err := client.DumpStats() + if err != nil { + log.Fatalln("dumping stats failed:", err) + } + + for _, stat := range stats { + switch data := stat.Data.(type) { + case adapter.ErrorStat: + if data == 0 { + // skip printing errors with 0 value + continue + } + } + fmt.Printf(" - %-25s %25v %+v\n", stat.Name, stat.Type, stat.Data) + } +} diff --git a/examples/cmd/stats-client/stats_client.go b/examples/cmd/stats-client/stats_client.go index f61f975..7a2c313 100644 --- a/examples/cmd/stats-client/stats_client.go +++ b/examples/cmd/stats-client/stats_client.go @@ -28,6 +28,15 @@ import ( "git.fd.io/govpp.git/examples/bin_api/stats" ) +/* + + IMPORTANT NOTICE! + + The binary API module stats used in this example will be deprecated in VPP 19.01. + VPP's new stats API should be used, you can find basic usage of new stats API in example stats-api. + +*/ + func main() { fmt.Println("Starting stats VPP client..") diff --git a/govpp.go b/govpp.go index ff45b78..f679242 100644 --- a/govpp.go +++ b/govpp.go @@ -20,16 +20,28 @@ import ( "git.fd.io/govpp.git/core" ) -var vppAdapter adapter.VppAdapter // VPP Adapter that will be used in the subsequent Connect calls +var ( + // VPP binary API adapter that will be used in the subsequent Connect calls + vppAdapter adapter.VppAPI +) + +func getVppAdapter(shm string) adapter.VppAPI { + if vppAdapter == nil { + vppAdapter = vppapiclient.NewVppClient(shm) + } + return vppAdapter +} + +// SetVppAdapter sets the adapter that will be used for connections to VPP in the subsequent `Connect` calls. +func SetVppAdapter(a adapter.VppAPI) { + vppAdapter = a +} // Connect connects the govpp core to VPP either using the default VPP Adapter, or using the adapter previously // set by SetAdapter (useful mostly just for unit/integration tests with mocked VPP adapter). // This call blocks until VPP is connected, or an error occurs. Only one connection attempt will be performed. func Connect(shm string) (*core.Connection, error) { - if vppAdapter == nil { - vppAdapter = vppapiclient.NewVppAdapter(shm) - } - return core.Connect(vppAdapter) + return core.Connect(getVppAdapter(shm)) } // AsyncConnect asynchronously connects the govpp core to VPP either using the default VPP Adapter, @@ -38,13 +50,5 @@ func Connect(shm string) (*core.Connection, error) { // supposed to watch the returned ConnectionState channel for Connected/Disconnected events. // In case of disconnect, the library will asynchronously try to reconnect. func AsyncConnect(shm string) (*core.Connection, chan core.ConnectionEvent, error) { - if vppAdapter == nil { - vppAdapter = vppapiclient.NewVppAdapter(shm) - } - return core.AsyncConnect(vppAdapter) + return core.AsyncConnect(getVppAdapter(shm)) } - -// SetAdapter sets the adapter that will be used for connections to VPP in the subsequent `Connect` calls. -func SetAdapter(ad adapter.VppAdapter) { - vppAdapter = ad -} \ No newline at end of file -- cgit 1.2.3-korg