Skip to content

Commit a9002d6

Browse files
[gNOI] Initial implementation of gNOI-OS service
1 parent db9afb6 commit a9002d6

File tree

6 files changed

+409
-1
lines changed

6 files changed

+409
-1
lines changed

common_utils/context.go

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -43,6 +43,7 @@ const (
4343
GNMI_SET
4444
GNMI_SET_FAIL
4545
GNOI_REBOOT
46+
GNOI_OS_INSTALL
4647
DBUS
4748
DBUS_FAIL
4849
DBUS_APPLY_PATCH_DB
@@ -77,6 +78,8 @@ func (c CounterType) String() string {
7778
return "GNMI set fail"
7879
case GNOI_REBOOT:
7980
return "GNOI reboot"
81+
case GNOI_OS_INSTALL:
82+
return "GNOI OS Install"
8083
case DBUS:
8184
return "DBUS"
8285
case DBUS_FAIL:

gnmi_server/gnoi_os.go

Lines changed: 312 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,312 @@
1+
package gnmi
2+
3+
import (
4+
"fmt"
5+
log "github.com/golang/glog"
6+
ospb "github.com/openconfig/gnoi/os"
7+
ssc "github.com/sonic-net/sonic-gnmi/sonic_service_client"
8+
"google.golang.org/grpc/codes"
9+
"google.golang.org/grpc/status"
10+
json "google.golang.org/protobuf/encoding/protojson"
11+
"io"
12+
"os"
13+
"sync"
14+
)
15+
16+
var (
17+
sem sync.Mutex
18+
)
19+
20+
// ProcessInstallFromBackEnd makes call via the sonic-host-service.
21+
func ProcessInstallFromBackEnd(req string) (string, error) {
22+
sc, err := ssc.NewDbusClient()
23+
if err != nil {
24+
return "", err
25+
}
26+
return sc.InstallOS(req)
27+
}
28+
func handleErrorResponse(f string, a ...any) *ospb.InstallResponse {
29+
errStr := fmt.Sprintf(f, a...)
30+
log.V(1).Infoln(errStr)
31+
return &ospb.InstallResponse{
32+
Response: &ospb.InstallResponse_InstallError{
33+
InstallError: &ospb.InstallError{
34+
Detail: errStr,
35+
},
36+
},
37+
}
38+
}
39+
func (srv *OSServer) processTransferReq(req *ospb.InstallRequest) *ospb.InstallResponse {
40+
trfReq := req.GetTransferRequest()
41+
if trfReq.GetVersion() == "" {
42+
log.V(1).Infoln("TransferRequest must contain a valid OS version.")
43+
return &ospb.InstallResponse{
44+
Response: &ospb.InstallResponse_InstallError{
45+
InstallError: &ospb.InstallError{
46+
Type: ospb.InstallError_PARSE_FAIL,
47+
Detail: "TransferRequest must contain a valid OS version.",
48+
},
49+
},
50+
}
51+
}
52+
// Front end marshals the request, and sends to the sonic-host-service.
53+
// Back end is expected to return the response in JSON format.
54+
reqStr, err := json.Marshal(req)
55+
if err != nil {
56+
return handleErrorResponse("Failed to marshal TransferReady JSON: err: %v, req: %v, reqStr: %v", err, req, reqStr)
57+
}
58+
respStr, err := ProcessInstallFromBackEnd(string(reqStr))
59+
if err != nil {
60+
return handleErrorResponse("Received error from OSServer.TransferReady: err: %v, reqStr: %v, respStr: %v", err, reqStr, respStr)
61+
}
62+
resp := &ospb.InstallResponse{}
63+
if err := json.Unmarshal([]byte(respStr), resp); err != nil {
64+
return handleErrorResponse("Failed to unmarshal TransferReady JSON: err: %v, respStr: %v", err, respStr)
65+
}
66+
return resp
67+
}
68+
func (srv *OSServer) processTransferEnd(req *ospb.InstallRequest) *ospb.InstallResponse {
69+
// Front end marshals the request, and sends to the sonic-host-service.
70+
// Back end is expected to return the response in JSON format.
71+
reqStr, err := json.Marshal(req)
72+
if err != nil {
73+
return handleErrorResponse("Failed to marshal TransferEnd JSON: err: %v, req: %v, reqStr: %v", err, req, reqStr)
74+
}
75+
respStr, err := ProcessInstallFromBackEnd(string(reqStr))
76+
if err != nil {
77+
return handleErrorResponse("Received error from OSServer.TransferEnd: err: %v, reqStr: %v, respStr: %v", err, reqStr, respStr)
78+
}
79+
resp := &ospb.InstallResponse{}
80+
if err := json.Unmarshal([]byte(respStr), resp); err != nil {
81+
return handleErrorResponse("Failed to unmarshal TransferEnd JSON: err: %v, respStr: %v", err, respStr)
82+
}
83+
return resp
84+
}
85+
func (srv *OSServer) processTransferContent(trfCnt []byte, imgPath string) *ospb.InstallResponse {
86+
errResp := &ospb.InstallResponse{
87+
Response: &ospb.InstallResponse_InstallError{
88+
InstallError: &ospb.InstallError{
89+
Detail: fmt.Sprintf("Failed to open file [%s].", imgPath),
90+
},
91+
},
92+
}
93+
// If the file doesn't exist, create it, or append to the file
94+
f, err := os.OpenFile(imgPath, os.O_APPEND|os.O_CREATE|os.O_WRONLY, 0644)
95+
if err != nil {
96+
log.V(1).Infoln(err)
97+
return errResp
98+
}
99+
if _, err := f.Write(trfCnt); err != nil {
100+
f.Close()
101+
log.V(1).Infoln(err)
102+
return errResp
103+
}
104+
if err := f.Close(); err != nil {
105+
log.V(1).Infoln(err)
106+
return errResp
107+
}
108+
return &ospb.InstallResponse{
109+
Response: &ospb.InstallResponse_TransferProgress{
110+
TransferProgress: &ospb.TransferProgress{
111+
BytesReceived: uint64(len(trfCnt)),
112+
},
113+
},
114+
}
115+
}
116+
func (srv *OSServer) getVersionPath(version string) string {
117+
return "/tmp" + "/" + version
118+
}
119+
func (srv *OSServer) imageExists(path string) bool {
120+
if _, err := os.Lstat(path); err == nil {
121+
return true
122+
}
123+
return false
124+
}
125+
func (srv *OSServer) removeIncompleteTrf(imgPath string) {
126+
if !srv.imageExists(imgPath) {
127+
return
128+
}
129+
log.V(1).Infoln("Remove incomplete image: ", imgPath)
130+
// Now remove the file.
131+
if err := os.Remove(imgPath); err != nil {
132+
log.V(1).Infoln("Failed to remove incomplete image: ", err)
133+
}
134+
}
135+
136+
// TODO(b/328077908) Alarms to be implemented later
137+
//
138+
// func raiseAlarm(err error) {
139+
// csh, cshErr := common_utils.NewComponentStateHelper(common_utils.Telemetry)
140+
// if cshErr != nil {
141+
// log.V(lvl.ERROR).Infof("gNOI OS: failed to create new ComponentStateHelper - %v", cshErr)
142+
// return
143+
// }
144+
// defer csh.Close()
145+
// if rcsErr := csh.ReportComponentState(common_utils.ComponentMinor, err.Error()); rcsErr != nil {
146+
// log.V(lvl.ERROR).Infof("Failed to raise ComponentMinor Alarm: %v", rcsErr)
147+
// }
148+
// }
149+
//
150+
// Install implements correspondig RPC
151+
func (srv *OSServer) Install(stream ospb.OS_InstallServer) error {
152+
ctx := stream.Context()
153+
ctx, err := authenticate(srv.config, ctx, "gnoi", false)
154+
if err != nil {
155+
return err
156+
}
157+
log.V(1).Info("gNOI: os.Install")
158+
// Concurrent Install RPCs are not allowed.
159+
if !sem.TryLock() {
160+
log.V(1).Infoln("Concurrent Install RPCs are not allowed.")
161+
// Send InstallError response.
162+
err = stream.Send(&ospb.InstallResponse{
163+
Response: &ospb.InstallResponse_InstallError{
164+
InstallError: &ospb.InstallError{
165+
Type: ospb.InstallError_INSTALL_IN_PROGRESS,
166+
Detail: "Concurrent Install RPCs are not allowed.",
167+
},
168+
},
169+
})
170+
if err != nil {
171+
log.V(1).Infoln("Error while sending InstallError response: ", err)
172+
return status.Errorf(codes.Aborted, err.Error())
173+
}
174+
return status.Errorf(codes.Aborted, "Concurrent Install RPCs are not allowed.")
175+
}
176+
defer sem.Unlock()
177+
// Receive TransferReq message.
178+
req, err := stream.Recv()
179+
if err == io.EOF {
180+
log.V(1).Infoln("Received EOF instead of TransferRequest!")
181+
return nil
182+
}
183+
if err != nil {
184+
log.V(1).Infoln("Received error: ", err)
185+
// TODO(b/328077908) Alarms to be implemented later
186+
// raiseAlarm(err)
187+
return status.Errorf(codes.Aborted, err.Error())
188+
}
189+
trfReq := req.GetTransferRequest()
190+
if trfReq == nil {
191+
log.V(1).Infoln("Did not receive a TransferRequest.")
192+
err = status.Errorf(codes.InvalidArgument, "Expected TransferRequest.")
193+
// TODO(b/328077908) Alarms to be implemented later
194+
// raiseAlarm(err)
195+
return err
196+
}
197+
resp := srv.processTransferReq(req)
198+
if resp != nil {
199+
if err := stream.Send(resp); err != nil {
200+
log.V(1).Infoln("Error while sending response: ", err)
201+
// TODO(b/328077908) Alarms to be implemented later
202+
// raiseAlarm(err)
203+
return status.Errorf(codes.Aborted, err.Error())
204+
}
205+
}
206+
if resp == nil || resp.GetInstallError() != nil {
207+
err = status.Errorf(codes.Aborted, "Failed to process TransferRequest.")
208+
// TODO(b/328077908) Alarms to be implemented later
209+
// raiseAlarm(err)
210+
return err
211+
}
212+
imgPath := srv.getVersionPath(trfReq.GetVersion())
213+
imgTrfInitiated := false
214+
for {
215+
req, err = stream.Recv()
216+
if err == io.EOF {
217+
log.V(1).Infoln("Received EOF instead of TransferContent request!")
218+
if imgTrfInitiated {
219+
srv.removeIncompleteTrf(imgPath)
220+
}
221+
return nil
222+
}
223+
if err != nil {
224+
log.V(1).Infoln("Received error: ", err)
225+
if imgTrfInitiated {
226+
srv.removeIncompleteTrf(imgPath)
227+
}
228+
// TODO(b/328077908) Alarms to be implemented later
229+
// raiseAlarm(err)
230+
return status.Errorf(codes.Aborted, err.Error())
231+
}
232+
if trfReq := req.GetTransferRequest(); trfReq != nil {
233+
log.V(1).Infoln("Received a TransferReq out-of-sequence.")
234+
if imgTrfInitiated {
235+
srv.removeIncompleteTrf(imgPath)
236+
}
237+
err = status.Errorf(codes.InvalidArgument, "Expected TransferContent, or TransferEnd.")
238+
// TODO(b/328077908) Alarms to be implemented later
239+
// raiseAlarm(err)
240+
return err
241+
}
242+
// Transferring content is complete.
243+
if trfEnd := req.GetTransferEnd(); trfEnd != nil {
244+
break
245+
}
246+
// Process content transfer.
247+
// If image exists, target should have sent Validated | InstallError on TransferRequest.
248+
if !imgTrfInitiated && srv.imageExists(imgPath) {
249+
resp := &ospb.InstallResponse{
250+
Response: &ospb.InstallResponse_InstallError{
251+
InstallError: &ospb.InstallError{
252+
Detail: fmt.Sprintf("File exists [%s]!", imgPath),
253+
},
254+
},
255+
}
256+
if err := stream.Send(resp); err != nil {
257+
log.V(1).Infoln("Error while sending response: ", err)
258+
}
259+
err = status.Errorf(codes.Aborted, "Failed as image exists!")
260+
// TODO(b/328077908) Alarms to be implemented later
261+
// raiseAlarm(err)
262+
return err
263+
}
264+
imgTrfInitiated = true
265+
resp := srv.processTransferContent(req.GetTransferContent(), imgPath)
266+
if resp != nil {
267+
if err := stream.Send(resp); err != nil {
268+
log.V(1).Infoln("Error while sending response: ", err)
269+
srv.removeIncompleteTrf(imgPath)
270+
// TODO(b/328077908) Alarms to be implemented later
271+
// raiseAlarm(err)
272+
return status.Errorf(codes.Aborted, err.Error())
273+
}
274+
}
275+
if resp == nil || resp.GetInstallError() != nil {
276+
srv.removeIncompleteTrf(imgPath)
277+
err = status.Errorf(codes.Aborted, "Failed to process TransferContent.")
278+
// TODO(b/328077908) Alarms to be implemented later
279+
// raiseAlarm(err)
280+
return err
281+
}
282+
}
283+
// Receive TransferEnd message.
284+
trfEnd := req.GetTransferEnd()
285+
if trfEnd == nil {
286+
log.V(1).Infoln("Did not receive a TransferEnd")
287+
srv.removeIncompleteTrf(imgPath)
288+
err = status.Errorf(codes.InvalidArgument, "Expected TransferEnd")
289+
// TODO(b/328077908) Alarms to be implemented later
290+
// raiseAlarm(err)
291+
return err
292+
}
293+
resp = srv.processTransferEnd(req)
294+
if resp != nil {
295+
if err := stream.Send(resp); err != nil {
296+
log.V(1).Infoln("Error while sending response: ", err)
297+
srv.removeIncompleteTrf(imgPath)
298+
// TODO(b/328077908) Alarms to be implemented later
299+
// raiseAlarm(err)
300+
return status.Errorf(codes.Aborted, err.Error())
301+
}
302+
}
303+
if resp == nil || resp.GetInstallError() != nil {
304+
srv.removeIncompleteTrf(imgPath)
305+
err = status.Errorf(codes.Aborted, "Failed to process TransferEnd.")
306+
// TODO(b/328077908) Alarms to be implemented later
307+
// raiseAlarm(err)
308+
return err
309+
}
310+
log.V(1).Info("OS.Install is complete.")
311+
return nil
312+
}

gnoi_client/gnoi_client.go

Lines changed: 2 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -62,6 +62,8 @@ func main() {
6262
gnoi_os.Verify(conn, ctx)
6363
case "Activate":
6464
gnoi_os.Activate(conn, ctx)
65+
case "Install":
66+
gnoi_os.Install(conn, ctx)
6567
default:
6668
panic("Invalid RPC Name")
6769
}

0 commit comments

Comments
 (0)