Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
27 commits
Select commit Hold shift + click to select a range
d6f8e05
Disable TLS session tickets to fix FIPS AES-128-CTR panic (#635)
hdwhdw Apr 1, 2026
181841b
Add inline diff coverage check to Azure Pipeline (#597)
hdwhdw Apr 1, 2026
1bd6083
Fix too_many_pings during DPU SetPackage and add ARM64 CI build (#634)
hdwhdw Apr 2, 2026
d9de588
gnmi_server: do not fail server when one listener fails (#636)
hdwhdw Apr 6, 2026
c4f9f16
Fix poll mode for all DBs to match gNMI specification (#639)
zbud-msft Apr 7, 2026
fe8b6e3
Fix concurrent read and writes for global virtual db maps (#641)
zbud-msft Apr 8, 2026
7b3e549
Fix:gNSI Certz Flaky test due to timing sensitivity in concurrent gRP…
jayaragini-hcl Apr 8, 2026
c305521
Support stream multiplexing (#644)
zbud-msft Apr 10, 2026
a553233
Skip flaky TestGnsiCertzServer/Rotate_ConcurrentRPC_ReturnsAborted (#…
hdwhdw Apr 10, 2026
e48f52d
Migrate CI pipeline from Bookworm to Trixie and upgrade Go to 1.24.4 …
hdwhdw Apr 13, 2026
2ad9446
Add ability to bind GNMI and Telemetry to mgmt (and default) VRFs (#503)
spandan-nexthop Apr 16, 2026
5494eaa
Skip flaky TestGnsiPathzRotation subtests (#650)
niranjanivivek Apr 18, 2026
4cc592b
telemetry: add --bind_address flag to restrict TCP listener (#652)
xq9mend Apr 20, 2026
14aa47e
Validate paths for Get Requests for mixed_db_client.go (#653)
zbud-msft Apr 21, 2026
73adaa2
Decouple diff-cover enforcement from integration test job (#648)
hdwhdw Apr 21, 2026
4cdd42e
Add ConfigDB Journal (#569)
niranjanivivek Apr 22, 2026
fa26041
Parallelize GET and SUBSCRIBE processing (#582)
niranjanivivek Apr 22, 2026
59fcbc1
[SmartSwitch] Support maximum gnmi message size of 125k messages (#643)
croos12 Apr 27, 2026
1c39543
Deduplicate Subscriptions (#586)
niranjanivivek Apr 30, 2026
98df0bc
Add OS.Verify and OS.Activate to DPU proxy forwardable methods (#660)
rookie-who May 1, 2026
27c621e
Add OS.Verify and OS.Activate DPU proxy forwarding (#663)
rookie-who May 4, 2026
e39c4b8
gNSI:Add sonic service client support for Credentialz (#573)
jayaragini-hcl May 6, 2026
9f3eda3
Revert "telemetry: add --bind_address flag to restrict TCP listener" …
hdwhdw May 7, 2026
ac99af7
ci: surface gofmt failures instead of masking them as missing coverag…
hdwhdw May 8, 2026
eb635b7
Add completeness test for DPU proxy ForwardToDPU handlers (#664)
rookie-who May 8, 2026
31feba3
Merge remote-tracking branch 'upstream-gnmi/master' into merge_latest…
zbud-msft May 15, 2026
047d119
Fix duplicate sync.Once declarations from merge
zbud-msft May 15, 2026
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
23 changes: 23 additions & 0 deletions .azure/templates/install-go.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,23 @@
# Azure DevOps YAML Template: Install Go toolchain
#
# Downloads and installs the requested Go release into /usr/local/go and
# exports it on PATH for subsequent steps in the same job.
#
# Usage:
# - template: .azure/templates/install-go.yml
# parameters:
# version: '1.24.4'

parameters:
- name: version
type: string
default: '1.24.4'

steps:
- script: |
set -euo pipefail
wget -q https://go.dev/dl/go${{ parameters.version }}.linux-amd64.tar.gz
sudo tar -C /usr/local -xzf go${{ parameters.version }}.linux-amd64.tar.gz
export PATH=$PATH:/usr/local/go/bin
go version
displayName: 'Install Go ${{ parameters.version }}'
50 changes: 50 additions & 0 deletions .azure/templates/setup-test-env.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
# Azure DevOps YAML Template: Prepare a SONiC test environment
#
# Consolidates the preamble shared by every job that runs sonic-gnmi Go tests
# inside the sonic-slave container: checkout sonic-gnmi + sonic-mgmt-common +
# sonic-swss-common, install SONiC dependencies, and build sonic-mgmt-common
# (which generates Go code that sonic-gnmi imports via $(MGMT_COMMON_DIR)/build/yang).
#
# Usage:
# - template: .azure/templates/setup-test-env.yml
# parameters:
# buildBranch: $(BUILD_BRANCH)
# fetchDepth: 0 # optional; 0 means full history

parameters:
- name: buildBranch
type: string
default: $(BUILD_BRANCH)
- name: fetchDepth
type: number
default: 1

steps:
- checkout: self
clean: true
submodules: recursive
fetchDepth: ${{ parameters.fetchDepth }}
displayName: 'Checkout code'

- checkout: sonic-mgmt-common
clean: true
submodules: recursive
displayName: 'Checkout sonic-mgmt-common'

- checkout: sonic-swss-common
clean: true
submodules: recursive
displayName: 'Checkout sonic-swss-common'

- template: install-dependencies.yml
parameters:
buildBranch: ${{ parameters.buildBranch }}
arch: amd64
installTestDeps: true

- script: |
set -ex
pushd sonic-mgmt-common
NO_TEST_BINS=1 dpkg-buildpackage -rfakeroot -b -us -uc
popd
displayName: 'Build sonic-mgmt-common'
6 changes: 6 additions & 0 deletions common_utils/context.go
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ const (
GNOI_HEALTHZ_ACK
GNOI_HEALTHZ_CHECK
GNOI_HEALTHZ_COLLECT
GNSI_CREDZ_SET
GNSI_CREDZ_CHECKPOINT
DBUS
DBUS_FAIL
DBUS_APPLY_PATCH_DB
Expand Down Expand Up @@ -95,6 +97,10 @@ func (c CounterType) String() string {
return "GNOI Healthz Check"
case GNOI_HEALTHZ_COLLECT:
return "GNOI Healthz Collect"
case GNSI_CREDZ_SET:
return "GNSI Credz Set"
case GNSI_CREDZ_CHECKPOINT:
return "GNSI Credz Checkpoint"
case DBUS:
return "DBUS"
case DBUS_FAIL:
Expand Down
293 changes: 293 additions & 0 deletions gnmi_server/db_journal.go
Original file line number Diff line number Diff line change
@@ -0,0 +1,293 @@
package gnmi

import (
"compress/gzip"
"context"
"errors"
"fmt"
"io"
"os"
"path/filepath"
"strings"
"time"

log "github.com/golang/glog"
"github.com/redis/go-redis/v9"

"github.com/Azure/sonic-mgmt-common/translib/db"
sdcfg "github.com/sonic-net/sonic-gnmi/sonic_db_config"
)

const (
maxFileSize = 2000000 // Bytes
maxBackups = 1
)

type DbJournal struct {
database string
rc *redis.Client
ps *redis.PubSub
notifications <-chan *redis.Message
cache map[string]map[string]string
file *os.File
fileName string
done chan bool
}

var dbNums = map[string]db.DBNum{
"CONFIG_DB": db.ConfigDB,
"STATE_DB": db.StateDB,
}

// NewDbJournal returns a new DbJournal for the specified database.
func NewDbJournal(database string) (*DbJournal, error) {
var err error
journal := &DbJournal{}
journal.database = database
dbNum, ok := dbNums[journal.database]
if !ok {
return nil, errors.New("Invalid database passed into NewDbJournal")
}

ns, _ := sdcfg.GetDbDefaultNamespace()
addr, _ := sdcfg.GetDbTcpAddr(journal.database, ns)
dbId, _ := sdcfg.GetDbId(journal.database, ns)
journal.rc = db.TransactionalRedisClientWithOpts(&redis.Options{
Network: "tcp",
Addr: addr,
Password: "",
DB: dbId,
DialTimeout: 0,
})

if err = journal.init(); err != nil {
return nil, err
}

keyspace := fmt.Sprintf("__keyspace@%d__:*", dbNum)
keyevent := fmt.Sprintf("__keyevent@%d__:*", dbNum)
journal.ps = journal.rc.PSubscribe(context.Background(), keyspace, keyevent)
if _, err = journal.ps.Receive(context.Background()); err != nil {
return nil, err
}

journal.notifications = journal.ps.Channel()

journal.fileName = filepath.Join(HostVarLogPath, strings.ToLower(journal.database)+".txt")
if journal.file, err = os.OpenFile(journal.fileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644); err != nil {
return nil, err
}

journal.done = make(chan bool, 1)

go journal.journal()
log.V(2).Infof("Successfully started the DbJournal for %v", journal.database)
return journal, nil
}

// Close closes the redis objects and the journal file.
func (dbj *DbJournal) Close() {
if dbj == nil {
return
}
dbj.done <- true
}

func (dbj *DbJournal) cleanup() {
if dbj == nil {
return
}
if dbj.ps != nil {
dbj.ps.Close()
}
if dbj.rc != nil {
db.CloseRedisClient(dbj.rc)
dbj.rc = nil
}
if dbj.file != nil {
dbj.file.Close()
}
if dbj.cache != nil {
dbj.cache = map[string]map[string]string{}
}
log.V(2).Infof("DbJournal closed successfully!")
}

// init initializes the journal's cache.
func (dbj *DbJournal) init() error {
if dbj == nil || dbj.rc == nil {
return errors.New("DbJournal: redis client is nil")
}
dbj.cache = map[string]map[string]string{}
keys, kErr := dbj.rc.Keys(context.Background(), "*").Result()
if kErr != nil {
return kErr
}
for _, key := range keys {
entry, eErr := dbj.rc.HGetAll(context.Background(), key).Result()
if eErr != nil {
entry = map[string]string{}
}
dbj.cache[key] = entry
}
return nil
}

// journal monitors the database notifications and logs events to the file.
func (dbj *DbJournal) journal() {
if dbj == nil {
return
}
defer dbj.cleanup()
var event []string
for {
select {
case msg := <-dbj.notifications:
event = append(event, msg.Payload)
if len(event) != 2 {
continue
}
op := event[0]
table := event[1]
entry := fmt.Sprintf("%v: %v %v", time.Now().Format("2006-01-02.15:04:05.000000"), op, table)
diff, dErr := dbj.updateCache(event)
if dErr != nil {
log.V(0).Infof("Shutting down %v Journal: %v", dbj.database, dErr)
return
}
event = []string{}

if diff != "" {
entry += " " + diff
}
// If no fields were changed or the operation is a set on a table that contains the DB name, don't log the event.
if (diff == "" && (op == "hset" || op == "hdel")) || (op == "set" && strings.Contains(table, dbj.database)) {
continue
}

if err := dbj.rotateFile(); err != nil {
log.V(0).Infof("Shutting down DbJournal, failed to manage file rotation: %v", err)
return
}
_, writeErr := dbj.file.Write([]byte(entry + "\n"))
if writeErr != nil {
log.V(0).Infof("Failed to write to DbJournal file: %v", writeErr)
}
case <-dbj.done:
return
}
}
}

// updateCache updates the cache with the latest database entry and returns the diff.
func (dbj *DbJournal) updateCache(event []string) (string, error) {
op := event[0]
table := event[1]
if dbj == nil || dbj.cache == nil || dbj.rc == nil {
return "", errors.New("nil members present in DbJournal")
}
oldEntry, ok := dbj.cache[table]
if !ok {
oldEntry = map[string]string{}
}
newEntry, err := dbj.rc.HGetAll(context.Background(), table).Result()
if err != nil {
newEntry = map[string]string{}
}
// Update the cache
dbj.cache[table] = newEntry

if op == "del" {
return "", nil
}

diff := ""
// Find deleted and changed fields
for k, v := range oldEntry {
newVal, ok := newEntry[k]
if !ok {
diff += "-" + k + " "
continue
}
if newVal != v {
diff += k + "=" + newVal + " "
}
}

// Find added fields
for k, v := range newEntry {
if _, ok := oldEntry[k]; !ok {
diff += "+" + k + ":" + v + " "
}
}

return diff, nil
}

// rotateFile makes sure the journal file is opened correctly and rotates it
// if it exceeds the maximum size.
func (dbj *DbJournal) rotateFile() error {
if dbj == nil {
return errors.New("Couldn't rotate file, DbJournal is nil")
}
fileStat, err := os.Stat(dbj.fileName)
if err != nil || dbj.file == nil {
// File does not exist or it is closed, create/open it
if dbj.file, err = os.OpenFile(dbj.fileName, os.O_APPEND|os.O_CREATE|os.O_RDWR, 0644); err != nil {
return err
}
return nil
}

if fileStat.Size() >= maxFileSize {
// Close the journal file and open it as read-only to copy it
dbj.file.Close()
if dbj.file, err = os.OpenFile(dbj.fileName, os.O_RDONLY, 0644); err != nil {
return err
}

// Remove a rotated, zipped file if the maxBackups limit is reached
files, err := os.ReadDir(HostVarLogPath)
if err != nil {
return err
}
var count uint
var oldest string
for _, file := range files {
if strings.HasPrefix(file.Name(), strings.ToLower(dbj.database)) && strings.HasSuffix(file.Name(), ".gz") {
count++
if strings.Compare(file.Name(), oldest) == -1 || oldest == "" {
oldest = file.Name()
}
}
}
if count >= maxBackups {
if err := os.Remove(filepath.Join(HostVarLogPath, oldest)); err != nil {
return err
}
}

// Compress the file
zipName := filepath.Join(HostVarLogPath, strings.ToLower(dbj.database)+"_"+time.Now().Format("20060102150405")+".gz")
zipFile, err := os.Create(zipName)
if err != nil {
return err
}
defer zipFile.Close()
zipWriter := gzip.NewWriter(zipFile)
defer zipWriter.Close()

if _, err = io.Copy(zipWriter, dbj.file); err != nil {
return err
}
if err = zipWriter.Flush(); err != nil {
return err
}

// Recreate the journal file
if dbj.file, err = os.Create(dbj.fileName); err != nil {
return err
}
}
return nil
}
Loading
Loading