Skip to content

Commit 0606803

Browse files
Implements the frontend logic for gNSI Pathz
Signed-off-by: Niranjani Vivek <niranjaniv@google.com>
1 parent 153f13f commit 0606803

File tree

12 files changed

+2684
-20
lines changed

12 files changed

+2684
-20
lines changed

Makefile

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -192,6 +192,8 @@ $(BUILD_GNOI_YANG_PROTO_DIR)/.proto_sonic_done: $(SONIC_YANGS)
192192

193193
$(GNOI_YANG): $(BUILD_GNOI_YANG_PROTO_DIR)/.proto_api_done $(BUILD_GNOI_YANG_PROTO_DIR)/.proto_sonic_done
194194
@echo "+++++ Compiling PROTOBUF files; +++++"
195+
# Remove the toolchain directive added by newer Go versions
196+
sed -i '/^toolchain/d' go.mod
195197
$(GO) install github.com/gogo/protobuf/protoc-gen-gofast
196198
@mkdir -p $(@D)
197199
$(foreach file, $(wildcard $(BUILD_GNOI_YANG_PROTO_DIR)/*/*.proto), PATH=$(PROTOC_PATH) protoc -I$(@D) $(PROTOC_OPTS_WITHOUT_VENDOR) --gofast_out=plugins=grpc,Mgoogle/protobuf/struct.proto=github.com/gogo/protobuf/types:$(BUILD_GNOI_YANG_PROTO_DIR) $(file);)
@@ -223,6 +225,7 @@ check_gotest: $(DBCONFG) $(ENVFILE)
223225
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-telemetry.txt -covermode=atomic -mod=vendor -v github.com/sonic-net/sonic-gnmi/telemetry
224226
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(GO) test -race -coverprofile=coverage-config.txt -covermode=atomic -v github.com/sonic-net/sonic-gnmi/sonic_db_config
225227
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -timeout 20m -coverprofile=coverage-gnmi.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -v github.com/sonic-net/sonic-gnmi/gnmi_server -coverpkg ../...
228+
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -race -timeout 20m -coverprofile=coverage-pathz_authorizer.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -v github.com/sonic-net/sonic-gnmi/pathz_authorizer -coverpkg ../...
226229
ifneq ($(ENABLE_DIALOUT_VALUE),0)
227230
sudo CGO_LDFLAGS="$(CGO_LDFLAGS)" CGO_CXXFLAGS="$(CGO_CXXFLAGS)" $(TESTENV) $(GO) test -coverprofile=coverage-dialout.txt -covermode=atomic -mod=vendor $(BLD_FLAGS) -v github.com/sonic-net/sonic-gnmi/dialout/dialout_client
228231
endif

gnmi_server/gnsi_pathz.go

Lines changed: 276 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,276 @@
1+
package gnmi
2+
3+
import (
4+
"bytes"
5+
"context"
6+
"encoding/json"
7+
"fmt"
8+
"io"
9+
"os"
10+
"strconv"
11+
"sync"
12+
13+
"github.com/sonic-net/sonic-gnmi/pathz_authorizer"
14+
15+
log "github.com/golang/glog"
16+
"github.com/golang/protobuf/proto"
17+
"github.com/openconfig/gnsi/pathz"
18+
"google.golang.org/grpc/codes"
19+
"google.golang.org/grpc/status"
20+
)
21+
22+
var (
23+
pathzMu sync.Mutex
24+
)
25+
var (
26+
//Point the variable to the real function by default
27+
authenticateFunc = authenticate
28+
)
29+
30+
const (
31+
pathzTbl string = "PATHZ_POLICY|"
32+
pathzVersionFld string = "pathz_version"
33+
pathzCreatedOnFld string = "pathz_created_on"
34+
pathzPolicyActive pathzInstance = "ACTIVE"
35+
// support for sandbox not yet implemented
36+
pathzPolicySandbox pathzInstance = "SANDBOX"
37+
)
38+
39+
type pathzInstance string
40+
type PathzMetadata struct {
41+
PathzVersion string `json:"pathz_version"`
42+
PathzCreatedOn string `json:"pathz_created_on"`
43+
}
44+
45+
type GNSIPathzServer struct {
46+
*Server
47+
pathzProcessor pathz_authorizer.GnmiAuthzProcessorInterface
48+
pathzMetadata *PathzMetadata
49+
pathzMetadataCopy *PathzMetadata
50+
policyCopy *pathz.AuthorizationPolicy
51+
policyUpdated bool
52+
pathzV1Policy string
53+
pathzV1PolicyBackup string
54+
pathz.UnimplementedPathzServer
55+
}
56+
57+
func NewPathzMetadata() *PathzMetadata {
58+
return &PathzMetadata{
59+
PathzVersion: "unknown",
60+
PathzCreatedOn: "0",
61+
}
62+
}
63+
64+
func (srv *GNSIPathzServer) Probe(context.Context, *pathz.ProbeRequest) (*pathz.ProbeResponse, error) {
65+
return nil, status.Errorf(codes.Unimplemented, "method Probe not implemented")
66+
}
67+
68+
func (srv *GNSIPathzServer) Get(context.Context, *pathz.GetRequest) (*pathz.GetResponse, error) {
69+
return nil, status.Errorf(codes.Unimplemented, "method Get not implemented")
70+
}
71+
func NewGNSIPathzServer(srv *Server) *GNSIPathzServer {
72+
ret := &GNSIPathzServer{
73+
Server: srv,
74+
pathzProcessor: &pathz_authorizer.GnmiAuthzProcessor{},
75+
pathzMetadata: NewPathzMetadata(),
76+
pathzV1Policy: srv.config.PathzPolicyFile,
77+
pathzV1PolicyBackup: srv.config.PathzPolicyFile + ".backup",
78+
}
79+
if err := ret.loadPathzFreshness(srv.config.PathzMetaFile); err != nil {
80+
log.V(0).Info(err)
81+
}
82+
ret.writePathzMetadataToDB(pathzPolicyActive)
83+
if srv.config.PathzPolicy {
84+
if err := ret.pathzProcessor.UpdatePolicyFromFile(ret.pathzV1Policy); err != nil {
85+
log.V(0).Infof("Failed to load gNMI pathz file %s: %v", ret.pathzV1Policy, err)
86+
}
87+
}
88+
return ret
89+
}
90+
91+
func (srv *GNSIPathzServer) savePathzFileFreshess(path string) error {
92+
log.V(2).Infof("Saving pathz metadata to file: %s", path)
93+
buf := new(bytes.Buffer)
94+
enc := json.NewEncoder(buf)
95+
if err := enc.Encode(*srv.pathzMetadata); err != nil {
96+
log.V(0).Info(err)
97+
return err
98+
}
99+
return attemptWrite(path, buf.Bytes(), 0o644)
100+
}
101+
102+
func (srv *GNSIPathzServer) loadPathzFreshness(path string) error {
103+
bytes, err := os.ReadFile(path)
104+
if err != nil {
105+
return err
106+
}
107+
return json.Unmarshal(bytes, srv.pathzMetadata)
108+
}
109+
110+
func (srv *GNSIPathzServer) savePathzPolicyToFile(p *pathz.AuthorizationPolicy) (string, error) {
111+
content := proto.MarshalTextString(p)
112+
log.V(3).Infof("Saving pathz policy to file: %s", srv.pathzV1Policy)
113+
return content, attemptWrite(srv.pathzV1Policy, []byte(content), 0o644)
114+
}
115+
116+
func (srv *GNSIPathzServer) verifyPathzFile(c string) error {
117+
content, err := os.ReadFile(srv.pathzV1Policy)
118+
if err != nil {
119+
return err
120+
}
121+
if c != string(content) {
122+
return fmt.Errorf("Pathz file %s contains error.", srv.pathzV1Policy)
123+
}
124+
return nil
125+
}
126+
127+
func (srv *GNSIPathzServer) writePathzMetadataToDB(instance pathzInstance) error {
128+
id := string(instance)
129+
log.V(2).Infof("Writing pathz metadata to DB: %s Version: %s CreatedOn: %s", id, srv.pathzMetadata.PathzVersion, srv.pathzMetadata.PathzCreatedOn)
130+
if err := writeCredentialsMetadataToDB(pathzTbl+id, "", pathzVersionFld, srv.pathzMetadata.PathzVersion); err != nil {
131+
return err
132+
}
133+
return writeCredentialsMetadataToDB(pathzTbl+id, "", pathzCreatedOnFld, srv.pathzMetadata.PathzCreatedOn)
134+
}
135+
136+
func (srv *GNSIPathzServer) updatePolicy(p *pathz.AuthorizationPolicy) error {
137+
log.V(2).Info("Updating gNMI pathz policy")
138+
log.V(3).Infof("Policy: %v", p.String())
139+
c, err := srv.savePathzPolicyToFile(p)
140+
if err != nil {
141+
return err
142+
}
143+
if err := srv.verifyPathzFile(c); err != nil {
144+
log.V(0).Infof("Failed to verify gNMI pathz policy: %v", err)
145+
return err
146+
}
147+
err = srv.pathzProcessor.UpdatePolicyFromProto(p)
148+
if err != nil {
149+
log.V(0).Infof("Failed to update gNMI pathz policy: %v", err)
150+
}
151+
return err
152+
}
153+
154+
func (srv *GNSIPathzServer) createCheckpoint() error {
155+
log.V(2).Info("Creating gNMI pathz policy checkpoint")
156+
srv.policyCopy = srv.pathzProcessor.GetPolicy()
157+
srv.policyUpdated = false
158+
srv.pathzMetadataCopy = srv.pathzMetadata
159+
return copyFile(srv.pathzV1Policy, srv.pathzV1PolicyBackup)
160+
}
161+
162+
func (srv *GNSIPathzServer) revertPolicy() error {
163+
log.V(2).Info("Reverting gNMI pathz policy")
164+
if srv.policyUpdated {
165+
srv.policyUpdated = false
166+
if err := srv.pathzProcessor.UpdatePolicyFromProto(srv.policyCopy); err != nil {
167+
log.V(0).Infof("Failed to revert gNMI pathz policy: %v", err)
168+
os.Remove(srv.pathzV1PolicyBackup)
169+
return err
170+
}
171+
}
172+
srv.pathzMetadata = srv.pathzMetadataCopy
173+
return os.Rename(srv.pathzV1PolicyBackup, srv.pathzV1Policy)
174+
}
175+
176+
func (srv *GNSIPathzServer) commitChanges() error {
177+
log.V(2).Info("Committing gNMI pathz policy changes")
178+
if err := srv.writePathzMetadataToDB(pathzPolicyActive); err != nil {
179+
return err
180+
}
181+
return srv.savePathzFileFreshess(srv.config.PathzMetaFile)
182+
}
183+
184+
// Rotate implements the gNSI.pathz.Rotate RPC.
185+
func (srv *GNSIPathzServer) Rotate(stream pathz.Pathz_RotateServer) error {
186+
log.V(2).Info("gNSI pathz Rotate RPC")
187+
ctx := stream.Context()
188+
ctx, err := authenticateFunc(srv.config, ctx, "gnoi", false)
189+
if err != nil {
190+
return err
191+
}
192+
// Concurrent Pathz RPCs are not allowed.
193+
if !pathzMu.TryLock() {
194+
log.V(0).Infoln("Concurrent Pathz RPCs are not allowed")
195+
return status.Errorf(codes.Aborted, "Concurrent Pathz RPCs are not allowed")
196+
}
197+
defer pathzMu.Unlock()
198+
if err := fileCheck(srv.pathzV1Policy); err != nil {
199+
log.V(0).Infof("Error in reading file %s: %v", srv.pathzV1Policy, err)
200+
return status.Errorf(codes.NotFound, "Error in reading file %s: %v", srv.pathzV1Policy, err)
201+
}
202+
if err := srv.createCheckpoint(); err != nil {
203+
log.V(0).Infof("Error in creating checkpoint: %v", err)
204+
return status.Errorf(codes.Aborted, "Error in creating checkpoint: %v", err)
205+
}
206+
for {
207+
req, err := stream.Recv()
208+
log.V(3).Infof("Received a Rotate request message: %v", req.String())
209+
if err == io.EOF {
210+
log.V(0).Infoln("Received EOF instead of a UploadRequest/Finalize request! Reverting to last good state")
211+
// Connection closed without Finalize message. Revert all changes made until now.
212+
if err := srv.revertPolicy(); err != nil {
213+
return status.Errorf(codes.Aborted, "Error in reverting policy: %v", err)
214+
}
215+
return status.Errorf(codes.Aborted, "No Finalize message")
216+
}
217+
if err != nil {
218+
log.V(0).Infof("Reverting to last good state Received error: %v", err)
219+
// Connection closed without Finalize message. Revert all changes made until now.
220+
srv.revertPolicy()
221+
return status.Errorf(codes.Aborted, err.Error())
222+
}
223+
if endReq := req.GetFinalizeRotation(); endReq != nil {
224+
// This is the last message. All changes are final.
225+
log.V(2).Infof("Received a Finalize request message: %v", endReq)
226+
if !srv.policyUpdated {
227+
log.V(0).Infoln("Received finalize message without successful rotation")
228+
srv.revertPolicy()
229+
return status.Errorf(codes.Aborted, "Received finalize message without successful rotation")
230+
}
231+
if err := srv.commitChanges(); err != nil {
232+
// Revert won't be called if the final commit fails.
233+
return status.Errorf(codes.Aborted, "Final policy commit fails: %v", err)
234+
}
235+
os.Remove(srv.pathzV1PolicyBackup)
236+
return nil
237+
}
238+
resp, err := srv.processRotateRequest(req)
239+
if err != nil {
240+
log.V(0).Infof("Reverting to last good state; While processing a rotate request got error: %v", err)
241+
// Connection closed without Finalize message. Revert all changes made until now.
242+
srv.revertPolicy()
243+
return err
244+
}
245+
if err := stream.Send(resp); err != nil {
246+
log.V(0).Infof("Reverting to last good state; While sending a confirmation got error: %v", err)
247+
// Connection closed without Finalize message. Revert all changes made until now.
248+
srv.revertPolicy()
249+
return status.Errorf(codes.Aborted, err.Error())
250+
}
251+
}
252+
}
253+
254+
func (srv *GNSIPathzServer) processRotateRequest(req *pathz.RotateRequest) (*pathz.RotateResponse, error) {
255+
policyReq := req.GetUploadRequest()
256+
if policyReq == nil {
257+
return nil, status.Errorf(codes.Aborted, "Unknown request: %v", req)
258+
}
259+
log.V(2).Infof("Received a gNSI.Pathz UploadRequest request message")
260+
if len(policyReq.GetVersion()) == 0 {
261+
return nil, status.Errorf(codes.Aborted, "Pathz policy version cannot be empty")
262+
}
263+
if srv.pathzMetadata.PathzVersion == policyReq.GetVersion() && !req.GetForceOverwrite() {
264+
return nil, status.Errorf(codes.AlreadyExists, "Pathz with version `%v` already exists", policyReq.GetVersion())
265+
}
266+
srv.pathzMetadata.PathzVersion = policyReq.GetVersion()
267+
srv.pathzMetadata.PathzCreatedOn = strconv.FormatUint(policyReq.GetCreatedOn(), 10)
268+
if err := srv.updatePolicy(policyReq.GetPolicy()); err != nil {
269+
return nil, status.Errorf(codes.Aborted, err.Error())
270+
}
271+
srv.policyUpdated = true
272+
resp := &pathz.RotateResponse{
273+
Response: &pathz.RotateResponse_Upload{},
274+
}
275+
return resp, nil
276+
}

0 commit comments

Comments
 (0)