Skip to content

Commit b8f90a0

Browse files
authored
Merge pull request #2422 from kiashok/gcs-sidecar-framework
Gcs sidecar framework
2 parents 49e98ce + c3dcf03 commit b8f90a0

36 files changed

+2523
-659
lines changed

cmd/gcs-sidecar/main.go

Lines changed: 244 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,244 @@
1+
//go:build windows
2+
// +build windows
3+
4+
package main
5+
6+
import (
7+
"context"
8+
"flag"
9+
"fmt"
10+
"net"
11+
"os"
12+
"time"
13+
14+
"github.com/Microsoft/go-winio"
15+
"github.com/Microsoft/hcsshim/internal/gcs/prot"
16+
shimlog "github.com/Microsoft/hcsshim/internal/log"
17+
"github.com/Microsoft/hcsshim/internal/oc"
18+
"github.com/Microsoft/hcsshim/pkg/securitypolicy"
19+
"github.com/sirupsen/logrus"
20+
"go.opencensus.io/trace"
21+
"golang.org/x/sys/windows"
22+
"golang.org/x/sys/windows/svc"
23+
"golang.org/x/sys/windows/svc/debug"
24+
25+
sidecar "github.com/Microsoft/hcsshim/internal/gcs-sidecar"
26+
)
27+
28+
var (
29+
defaultLogFile = "C:\\gcs-sidecar-logs.log"
30+
defaultLogLevel = "trace"
31+
)
32+
33+
type handler struct {
34+
fromsvc chan error
35+
}
36+
37+
// Accepts new connection and closes listener.
38+
func acceptAndClose(ctx context.Context, l net.Listener) (net.Conn, error) {
39+
var conn net.Conn
40+
ch := make(chan error)
41+
go func() {
42+
var err error
43+
conn, err = l.Accept()
44+
ch <- err
45+
}()
46+
select {
47+
case err := <-ch:
48+
l.Close()
49+
return conn, err
50+
case <-ctx.Done():
51+
}
52+
l.Close()
53+
err := <-ch
54+
if err == nil {
55+
return conn, err
56+
}
57+
58+
if ctx.Err() != nil {
59+
return nil, ctx.Err()
60+
}
61+
return nil, err
62+
}
63+
64+
func (h *handler) Execute(args []string, r <-chan svc.ChangeRequest, status chan<- svc.Status) (bool, uint32) {
65+
const cmdsAccepted = svc.AcceptStop | svc.AcceptShutdown | svc.Accepted(windows.SERVICE_ACCEPT_PARAMCHANGE)
66+
67+
status <- svc.Status{State: svc.StartPending, Accepts: 0}
68+
// unblock runService()
69+
h.fromsvc <- nil
70+
71+
status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
72+
73+
loop:
74+
for c := range r {
75+
switch c.Cmd {
76+
case svc.Interrogate:
77+
status <- c.CurrentStatus
78+
case svc.Stop, svc.Shutdown:
79+
logrus.Println("Shutting service...!")
80+
break loop
81+
case svc.Pause:
82+
status <- svc.Status{State: svc.Paused, Accepts: cmdsAccepted}
83+
case svc.Continue:
84+
status <- svc.Status{State: svc.Running, Accepts: cmdsAccepted}
85+
default:
86+
logrus.Printf("Unexpected service control request #%d", c)
87+
}
88+
}
89+
90+
status <- svc.Status{State: svc.StopPending}
91+
return false, 1
92+
}
93+
94+
func runService(name string, isDebug bool) error {
95+
h := &handler{
96+
fromsvc: make(chan error),
97+
}
98+
99+
var err error
100+
go func() {
101+
if isDebug {
102+
err = debug.Run(name, h)
103+
if err != nil {
104+
logrus.Errorf("Error running service in debug mode.Err: %v", err)
105+
}
106+
} else {
107+
err = svc.Run(name, h)
108+
if err != nil {
109+
logrus.Errorf("Error running service in Service Control mode.Err %v", err)
110+
}
111+
}
112+
h.fromsvc <- err
113+
}()
114+
115+
// Wait for the first signal from the service handler.
116+
logrus.Tracef("waiting for first signal from service handler\n")
117+
return <-h.fromsvc
118+
}
119+
120+
func main() {
121+
logLevel := flag.String("loglevel",
122+
defaultLogLevel,
123+
"Logging Level: trace, debug, info, warning, error, fatal, panic.")
124+
logFile := flag.String("logfile",
125+
defaultLogFile,
126+
"Logging Target. Default is at C:\\gcs-sidecar-logs.log inside UVM")
127+
128+
flag.Usage = func() {
129+
fmt.Fprintf(os.Stderr, "\nUsage of %s:\n", os.Args[0])
130+
flag.PrintDefaults()
131+
fmt.Fprintf(os.Stderr, "Examples:\n")
132+
fmt.Fprintf(os.Stderr, " %s -loglevel=trace -logfile=C:\\sidecarLogs.log \n", os.Args[0])
133+
}
134+
135+
flag.Parse()
136+
137+
ctx, cancel := context.WithTimeout(context.Background(), 2*time.Minute)
138+
defer cancel()
139+
140+
logFileHandle, err := os.OpenFile(*logFile, os.O_RDWR|os.O_CREATE|os.O_SYNC|os.O_TRUNC, 0666)
141+
if err != nil {
142+
fmt.Printf("error opening file: %v", err)
143+
}
144+
defer logFileHandle.Close()
145+
146+
logrus.AddHook(shimlog.NewHook())
147+
148+
level, err := logrus.ParseLevel(*logLevel)
149+
if err != nil {
150+
logrus.Fatal(err)
151+
}
152+
logrus.SetLevel(level)
153+
logrus.SetOutput(logFileHandle)
154+
trace.ApplyConfig(trace.Config{DefaultSampler: trace.AlwaysSample()})
155+
trace.RegisterExporter(&oc.LogrusExporter{})
156+
157+
if err := windows.SetStdHandle(windows.STD_ERROR_HANDLE, windows.Handle(logFileHandle.Fd())); err != nil {
158+
logrus.WithError(err).Error("error redirecting handle")
159+
return
160+
}
161+
os.Stderr = logFileHandle
162+
163+
chsrv := make(chan error)
164+
go func() {
165+
defer close(chsrv)
166+
167+
if err := runService("gcs-sidecar", false); err != nil {
168+
logrus.Errorf("error starting gcs-sidecar service: %v", err)
169+
}
170+
171+
chsrv <- err
172+
}()
173+
174+
select {
175+
case <-ctx.Done():
176+
logrus.Error("context deadline exceeded")
177+
return
178+
case r := <-chsrv:
179+
if r != nil {
180+
logrus.Error(r)
181+
return
182+
}
183+
}
184+
185+
// 1. Start external server to connect with inbox GCS
186+
listener, err := winio.ListenHvsock(&winio.HvsockAddr{
187+
VMID: prot.HvGUIDLoopback,
188+
ServiceID: prot.WindowsGcsHvsockServiceID,
189+
})
190+
if err != nil {
191+
logrus.WithError(err).Error("error starting listener for sidecar <-> inbox gcs communication")
192+
return
193+
}
194+
195+
var gcsListener net.Listener = listener
196+
gcsCon, err := acceptAndClose(ctx, gcsListener)
197+
if err != nil {
198+
logrus.WithError(err).Error("error accepting inbox GCS connection")
199+
return
200+
}
201+
202+
// 2. Setup connection with external gcs connection started from hcsshim
203+
hvsockAddr := &winio.HvsockAddr{
204+
VMID: prot.HvGUIDParent,
205+
ServiceID: prot.WindowsSidecarGcsHvsockServiceID,
206+
}
207+
208+
logrus.WithFields(logrus.Fields{
209+
"hvsockAddr": hvsockAddr,
210+
}).Tracef("Dialing to hcsshim external bridge at address %v", hvsockAddr)
211+
shimCon, err := winio.Dial(ctx, hvsockAddr)
212+
if err != nil {
213+
logrus.WithError(err).Error("error dialing hcsshim external bridge")
214+
return
215+
}
216+
217+
// gcs-sidecar can be used for non-confidentail hyperv wcow
218+
// as well. So we do not always want to check for initialPolicyStance
219+
var initialEnforcer securitypolicy.SecurityPolicyEnforcer
220+
// TODO (kiashok/Mahati): The initialPolicyStance is set to allow
221+
// only for dev. This will eventually be set to allow/deny depending on
222+
// on whether SNP is supported or not.
223+
initialPolicyStance := "allow"
224+
switch initialPolicyStance {
225+
case "allow":
226+
initialEnforcer = &securitypolicy.OpenDoorSecurityPolicyEnforcer{}
227+
logrus.Tracef("initial-policy-stance: allow")
228+
case "deny":
229+
initialEnforcer = &securitypolicy.ClosedDoorSecurityPolicyEnforcer{}
230+
logrus.Tracef("initial-policy-stance: deny")
231+
default:
232+
logrus.Error("unknown initial-policy-stance")
233+
}
234+
235+
// 3. Create bridge and initializa
236+
brdg := sidecar.NewBridge(shimCon, gcsCon, initialEnforcer)
237+
brdg.AssignHandlers()
238+
239+
// 3. Listen and serve for hcsshim requests.
240+
err = brdg.ListenAndServeShimRequests()
241+
if err != nil {
242+
logrus.WithError(err).Error("failed to serve request")
243+
}
244+
}
Lines changed: 83 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,83 @@
1+
package commonutils
2+
3+
import (
4+
"encoding/json"
5+
"fmt"
6+
"io"
7+
"math"
8+
"strconv"
9+
10+
"github.com/Microsoft/hcsshim/internal/bridgeutils/gcserr"
11+
"github.com/sirupsen/logrus"
12+
)
13+
14+
type ErrorRecord struct {
15+
Result int32 // HResult
16+
Message string
17+
StackTrace string `json:",omitempty"`
18+
ModuleName string
19+
FileName string
20+
Line uint32
21+
FunctionName string `json:",omitempty"`
22+
}
23+
24+
// UnmarshalJSONWithHresult unmarshals the given data into the given interface, and
25+
// wraps any error returned in an HRESULT error.
26+
func UnmarshalJSONWithHresult(data []byte, v interface{}) error {
27+
if err := json.Unmarshal(data, v); err != nil {
28+
return gcserr.WrapHresult(err, gcserr.HrVmcomputeInvalidJSON)
29+
}
30+
return nil
31+
}
32+
33+
// DecodeJSONWithHresult decodes the JSON from the given reader into the given
34+
// interface, and wraps any error returned in an HRESULT error.
35+
func DecodeJSONWithHresult(r io.Reader, v interface{}) error {
36+
if err := json.NewDecoder(r).Decode(v); err != nil {
37+
return gcserr.WrapHresult(err, gcserr.HrVmcomputeInvalidJSON)
38+
}
39+
return nil
40+
}
41+
42+
func SetErrorForResponseBaseUtil(errForResponse error, moduleName string) (hresult gcserr.Hresult, errorMessage string, newRecord ErrorRecord) {
43+
errorMessage = errForResponse.Error()
44+
stackString := ""
45+
fileName := ""
46+
// We use -1 as a sentinel if no line number found (or it cannot be parsed),
47+
// but that will ultimately end up as [math.MaxUint32], so set it to that explicitly.
48+
// (Still keep using -1 for backwards compatibility ...)
49+
lineNumber := uint32(math.MaxUint32)
50+
functionName := ""
51+
if stack := gcserr.BaseStackTrace(errForResponse); stack != nil {
52+
bottomFrame := stack[0]
53+
stackString = fmt.Sprintf("%+v", stack)
54+
fileName = fmt.Sprintf("%s", bottomFrame)
55+
lineNumberStr := fmt.Sprintf("%d", bottomFrame)
56+
if n, err := strconv.ParseUint(lineNumberStr, 10, 32); err == nil {
57+
lineNumber = uint32(n)
58+
} else {
59+
logrus.WithFields(logrus.Fields{
60+
"line-number": lineNumberStr,
61+
logrus.ErrorKey: err,
62+
}).Error("opengcs::bridge::setErrorForResponseBase - failed to parse line number, using -1 instead")
63+
}
64+
functionName = fmt.Sprintf("%n", bottomFrame)
65+
}
66+
hresult, err := gcserr.GetHresult(errForResponse)
67+
if err != nil {
68+
// Default to using the generic failure HRESULT.
69+
hresult = gcserr.HrFail
70+
}
71+
72+
newRecord = ErrorRecord{
73+
Result: int32(hresult),
74+
Message: errorMessage,
75+
StackTrace: stackString,
76+
ModuleName: moduleName,
77+
FileName: fileName,
78+
Line: lineNumber,
79+
FunctionName: functionName,
80+
}
81+
82+
return hresult, errorMessage, newRecord
83+
}
File renamed without changes.

0 commit comments

Comments
 (0)