Skip to content

Commit 1173807

Browse files
Implement translib backend logic for the gNSI Authz
This commit adds the necessary transformer code to read authorization policy counters and related state from the Redis databases.
1 parent 71b3577 commit 1173807

File tree

9 files changed

+685
-6
lines changed

9 files changed

+685
-6
lines changed

config/transformer/models_list

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -15,3 +15,4 @@ gnsi-authz.yang
1515
gnsi-pathz.yang
1616
gnsi-certz.yang
1717
gnsi-credentialz.yang
18+
openconfig-system-annot.yang
Lines changed: 25 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,25 @@
1+
module openconfig-system-annot {
2+
3+
   yang-version "1";
4+
5+
   namespace "http://openconfig.net/yang/openconfig-system-annot";
6+
   prefix "oc-sys-annot";
7+
8+
   import openconfig-system { prefix oc-sys; }
9+
   import sonic-extensions {prefix sonic-ext; }
10+
import openconfig-system-grpc { prefix oc-sys-grpc; }
11+
12+
   deviation /oc-sys:system/oc-sys:aaa/oc-sys:authorization/oc-sys:state {
13+
     deviate add {
14+
       sonic-ext:db-name "STATE_DB";
15+
       sonic-ext:subtree-transformer "authz_policy_xfmr";
16+
     }
17+
   }
18+
19+
deviation /oc-sys:system/oc-sys-grpc:grpc-servers {
20+
deviate add {
21+
sonic-ext:key-transformer "grpc_server_key_xfmr";
22+
sonic-ext:subtree-transformer "grpc_server_xfmr";
23+
}
24+
}
25+
}

translib/db/db_redis_opts.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -144,7 +144,7 @@ func adjustRedisOpts(dbOpt *Options) *redis.Options {
144144
return &redisOpts
145145
}
146146

147-
func init() {
147+
func initializeRedisOpts() {
148148
flag.StringVar(&goRedisOpts, "go_redis_opts", "", "Options for go-redis")
149149
}
150150

translib/db/rcm.go

Lines changed: 197 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,197 @@
1+
package db
2+
3+
import (
4+
"flag"
5+
"fmt"
6+
"sync"
7+
"sync/atomic"
8+
9+
lvl "github.com/Azure/sonic-mgmt-common/translib/log"
10+
"github.com/go-redis/redis/v7"
11+
log "github.com/golang/glog"
12+
)
13+
14+
var usePools = flag.Bool("use_connection_pools", true, "use connection pools for Redis Clients")
15+
16+
const (
17+
POOL_SIZE = 25
18+
)
19+
20+
var rcm *redisClientManager
21+
var initMu = &sync.Mutex{}
22+
23+
type redisClientManager struct {
24+
// clients holds one Redis Client for each DBNum
25+
clients [MaxDB + 1]*redis.Client
26+
mu *sync.RWMutex
27+
curTransactionalClients atomic.Int32
28+
totalPoolClientsRequested atomic.Uint64
29+
totalTransactionalClientsRequested atomic.Uint64
30+
}
31+
32+
type RedisCounters struct {
33+
CurTransactionalClients uint32 // The number of transactional clients currently opened.
34+
TotalPoolClientsRequested uint64 // The total number of Redis Clients using a connection pool requested.
35+
TotalTransactionalClientsRequested uint64 // The total number of Transactional Redis Clients requested.
36+
PoolStatsPerDB map[string]*redis.PoolStats // The pool counters for each Redis Client in the cache.
37+
}
38+
39+
func init() {
40+
initializeRedisOpts()
41+
initializeRedisClientManager()
42+
}
43+
44+
func initializeRedisClientManager() {
45+
initMu.Lock()
46+
defer initMu.Unlock()
47+
if rcm != nil {
48+
return
49+
}
50+
rcm = &redisClientManager{
51+
clients: [MaxDB + 1]*redis.Client{},
52+
mu: &sync.RWMutex{},
53+
curTransactionalClients: atomic.Int32{},
54+
totalPoolClientsRequested: atomic.Uint64{},
55+
totalTransactionalClientsRequested: atomic.Uint64{},
56+
}
57+
rcm.mu.Lock()
58+
defer rcm.mu.Unlock()
59+
for dbNum := DBNum(0); dbNum < MaxDB; dbNum++ {
60+
if len(getDBInstName(dbNum)) == 0 {
61+
continue
62+
}
63+
// Create a Redis Client for each database.
64+
rcm.clients[int(dbNum)] = createRedisClient(dbNum, POOL_SIZE)
65+
}
66+
}
67+
68+
func createRedisClient(db DBNum, poolSize int) *redis.Client {
69+
opts := adjustRedisOpts(&Options{DBNo: db})
70+
opts.PoolSize = poolSize
71+
client := redis.NewClient(opts)
72+
if _, err := client.Ping().Result(); err != nil {
73+
log.V(lvl.ERROR).Infof("RCM error during Redis Client creation for DBNum=%v: %v", db, err)
74+
}
75+
return client
76+
}
77+
78+
func createRedisClientWithOpts(opts *redis.Options) *redis.Client {
79+
client := redis.NewClient(opts)
80+
if _, err := client.Ping().Result(); err != nil {
81+
log.V(lvl.ERROR).Infof("RCM error during Redis Client creation for DBNum=%v: %v", opts.DB, err)
82+
}
83+
return client
84+
}
85+
86+
func getClient(db DBNum) *redis.Client {
87+
rcm.mu.RLock()
88+
defer rcm.mu.RUnlock()
89+
return rcm.clients[int(db)]
90+
}
91+
92+
// RedisClient will return a Redis Client that can be used for non-transactional Redis operations.
93+
// The client returned by this function is shared among many DB readers/writers and uses
94+
// a connection pool. For transactional Redis operations, please use GetRedisClientForTransaction().
95+
func RedisClient(db DBNum) *redis.Client {
96+
if rcm == nil {
97+
initializeRedisClientManager()
98+
}
99+
if !(*usePools) { // Connection Pooling is disabled.
100+
return TransactionalRedisClient(db)
101+
}
102+
if len(getDBInstName(db)) == 0 {
103+
log.V(lvl.ERROR).Infof("Invalid DBNum requested: %v", db)
104+
return nil
105+
}
106+
rcm.totalPoolClientsRequested.Add(1)
107+
rc := getClient(db)
108+
if rc == nil {
109+
log.V(lvl.ERROR).Infof("RCM Redis client for DBNum=%v is nil!", db)
110+
rcm.mu.Lock()
111+
defer rcm.mu.Unlock()
112+
if rc = rcm.clients[int(db)]; rc != nil {
113+
return rc
114+
}
115+
rc = createRedisClient(db, POOL_SIZE)
116+
rcm.clients[int(db)] = rc
117+
}
118+
return rc
119+
}
120+
121+
// TransactionalRedisClient will create and return a unique Redis client. This client can be used
122+
// for transactional operations. These operations include MULTI, PSUBSCRIBE (PubSub), and SCAN. This
123+
// client must be closed using CloseRedisClient when it is no longer needed.
124+
func TransactionalRedisClient(db DBNum) *redis.Client {
125+
if rcm == nil {
126+
initializeRedisClientManager()
127+
}
128+
if len(getDBInstName(db)) == 0 {
129+
log.V(lvl.ERROR).Infof("Invalid DBNum requested: %v", db)
130+
return nil
131+
}
132+
rcm.totalTransactionalClientsRequested.Add(1)
133+
client := createRedisClient(db, 1)
134+
rcm.curTransactionalClients.Add(1)
135+
return client
136+
}
137+
138+
func TransactionalRedisClientWithOpts(opts *redis.Options) *redis.Client {
139+
if rcm == nil {
140+
initializeRedisClientManager()
141+
}
142+
rcm.totalTransactionalClientsRequested.Add(1)
143+
opts.PoolSize = 1
144+
client := createRedisClientWithOpts(opts)
145+
rcm.curTransactionalClients.Add(1)
146+
return client
147+
}
148+
149+
// CloseUniqueRedisClient will close the Redis client that is passed in.
150+
func CloseRedisClient(rc *redis.Client) error {
151+
if rcm == nil {
152+
return fmt.Errorf("RCM is nil when trying to close Redis Client: %v", rc)
153+
}
154+
if rc == nil {
155+
return nil
156+
}
157+
// Closing a Redis Client with a connection pool is a no-op because these clients need to stay open.
158+
if !IsTransactionalClient(rc) {
159+
return nil
160+
}
161+
if err := rc.Close(); err != nil {
162+
return err
163+
}
164+
rcm.curTransactionalClients.Add(-1)
165+
return nil
166+
}
167+
168+
// IsTransactionalClient returns true if rc is a transactional client and false otherwise.
169+
func IsTransactionalClient(rc *redis.Client) bool {
170+
if rc == nil {
171+
return false
172+
}
173+
return rc.Options().PoolSize == 1
174+
}
175+
176+
// RedisClientManagerCounters returns the counters stored in the RCM.
177+
func RedisClientManagerCounters() *RedisCounters {
178+
if rcm == nil {
179+
initializeRedisClientManager()
180+
}
181+
counters := &RedisCounters{
182+
CurTransactionalClients: uint32(rcm.curTransactionalClients.Load()),
183+
TotalPoolClientsRequested: rcm.totalPoolClientsRequested.Load(),
184+
TotalTransactionalClientsRequested: rcm.totalTransactionalClientsRequested.Load(),
185+
PoolStatsPerDB: map[string]*redis.PoolStats{},
186+
}
187+
rcm.mu.RLock()
188+
defer rcm.mu.RUnlock()
189+
for db, client := range rcm.clients {
190+
dbName := getDBInstName(DBNum(db))
191+
if dbName == "" || client == nil {
192+
continue
193+
}
194+
counters.PoolStatsPerDB[dbName] = client.PoolStats()
195+
}
196+
return counters
197+
}

translib/log/level.go

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,10 @@
1+
package log
2+
3+
import "github.com/golang/glog"
4+
5+
const (
6+
ERROR glog.Level = iota
7+
WARNING
8+
INFO
9+
DEBUG
10+
)

translib/tlerr/tlerr.go

Lines changed: 32 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -31,7 +31,10 @@ package tlerr
3131

3232
import (
3333
// "fmt"
34+
"errors"
3435
"github.com/Azure/sonic-mgmt-common/cvl"
36+
lvl "github.com/Azure/sonic-mgmt-common/translib/log"
37+
"github.com/golang/glog"
3538
"golang.org/x/text/language"
3639
"golang.org/x/text/message"
3740
// "errors"
@@ -190,3 +193,32 @@ type TranslibBusy struct {
190193
func (e TranslibBusy) Error() string {
191194
return p.Sprintf("Translib Busy")
192195
}
196+
197+
func IsTranslibRedisClientEntryNotExist(err error) bool {
198+
switch err.(type) {
199+
case *TranslibRedisClientEntryNotExist, TranslibRedisClientEntryNotExist:
200+
return true
201+
}
202+
return false
203+
}
204+
205+
// isDBEntryNotExistError returns `true` if `err` is (or wraps around) an error
206+
// of type `TranslibRedisClientEntryNotExist`.
207+
func isDBEntryNotExistError(err error) bool {
208+
if IsTranslibRedisClientEntryNotExist(err) {
209+
return true
210+
}
211+
pdberr := &TranslibRedisClientEntryNotExist{}
212+
return errors.As(err, &TranslibRedisClientEntryNotExist{}) || errors.As(err, &pdberr)
213+
}
214+
215+
// ErrorSeverity based on `err` calculates the VLOG level.
216+
func ErrorSeverity(err error) glog.Level {
217+
if err == nil {
218+
return lvl.DEBUG
219+
}
220+
if isDBEntryNotExistError(err) {
221+
return lvl.DEBUG
222+
}
223+
return lvl.ERROR
224+
}

translib/transformer/subscribe_req_xlate.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -478,7 +478,7 @@ func (pathXltr *subscribePathXlator) handleSubtreeNodeXlate() error {
478478
log.Info(pathXltr.subReq.reqLogId, "handleSubtreeNodeXlate: handleSubtreeNodeXlate: uriSubtree: ", uriSubtree)
479479
}
480480

481-
subInParam := XfmrSubscInParams{uriSubtree, pathXltr.subReq.dbs, make(RedisDbMap), TRANSLATE_SUBSCRIBE}
481+
subInParam := XfmrSubscInParams{uri: uriSubtree, requestURI: pathXltr.subReq.reqUri, dbs: pathXltr.subReq.dbs, dbDataMap: make(RedisDbMap), subscProc: TRANSLATE_SUBSCRIBE}
482482
subOutPram, subErr := xfmrSubscSubtreeHandler(subInParam, ygXpathInfo.xfmrFunc)
483483

484484
if log.V(dbLgLvl) {

translib/transformer/xfmr_interface.go

Lines changed: 5 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -76,10 +76,11 @@ type notificationOpts struct {
7676

7777
// XfmrSubscInParams represents input to subscribe subtree callbacks - request uri, DBs info access-pointers, DB info for request uri and subscription process type from translib.
7878
type XfmrSubscInParams struct {
79-
uri string
80-
dbs [db.MaxDB]*db.DB
81-
dbDataMap RedisDbMap
82-
subscProc SubscProcType
79+
uri string
80+
requestURI string
81+
dbs [db.MaxDB]*db.DB
82+
dbDataMap RedisDbMap
83+
subscProc SubscProcType
8384
}
8485

8586
// XfmrSubscOutParams represents output from subscribe subtree callback - DB data for request uri, Need cache, OnChange, subscription preference and interval.

0 commit comments

Comments
 (0)