Skip to content

Commit b9e4436

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 b9e4436

File tree

8 files changed

+672
-6
lines changed

8 files changed

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

translib/tlerr/tlerr.go

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

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

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)