Nevar pievienot vairāk kā 25 tēmas Tēmai ir jāsākas ar burtu vai ciparu, tā var saturēt domu zīmes ('-') un var būt līdz 35 simboliem gara.
 
 
 

292 rindas
9.6 KiB

  1. // Copyright 2016 Google LLC
  2. //
  3. // Licensed under the Apache License, Version 2.0 (the "License");
  4. // you may not use this file except in compliance with the License.
  5. // You may obtain a copy of the License at
  6. //
  7. // http://www.apache.org/licenses/LICENSE-2.0
  8. //
  9. // Unless required by applicable law or agreed to in writing, software
  10. // distributed under the License is distributed on an "AS IS" BASIS,
  11. // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  12. // See the License for the specific language governing permissions and
  13. // limitations under the License.
  14. // Package controller is a library for interacting with the Google Cloud Debugger's Debuglet Controller service.
  15. package controller
  16. import (
  17. "context"
  18. "crypto/sha256"
  19. "encoding/json"
  20. "errors"
  21. "fmt"
  22. "log"
  23. "sync"
  24. "golang.org/x/oauth2"
  25. cd "google.golang.org/api/clouddebugger/v2"
  26. "google.golang.org/api/googleapi"
  27. "google.golang.org/api/option"
  28. htransport "google.golang.org/api/transport/http"
  29. )
  30. const (
  31. // agentVersionString identifies the agent to the service.
  32. agentVersionString = "google.com/go-gcp/v0.2"
  33. // initWaitToken is the wait token sent in the first Update request to a server.
  34. initWaitToken = "init"
  35. )
  36. var (
  37. // ErrListUnchanged is returned by List if the server time limit is reached
  38. // before the list of breakpoints changes.
  39. ErrListUnchanged = errors.New("breakpoint list unchanged")
  40. // ErrDebuggeeDisabled is returned by List or Update if the server has disabled
  41. // this Debuggee. The caller can retry later.
  42. ErrDebuggeeDisabled = errors.New("debuglet disabled by server")
  43. )
  44. // Controller manages a connection to the Debuglet Controller service.
  45. type Controller struct {
  46. s serviceInterface
  47. // waitToken is sent with List requests so the server knows which set of
  48. // breakpoints this client has already seen. Each successful List request
  49. // returns a new waitToken to send in the next request.
  50. waitToken string
  51. // verbose determines whether to do some logging
  52. verbose bool
  53. // options, uniquifier and description are used in register.
  54. options Options
  55. uniquifier string
  56. description string
  57. // labels are included when registering the debuggee. They should contain
  58. // the module name, version and minorversion, and are used by the debug UI
  59. // to label the correct version active for debugging.
  60. labels map[string]string
  61. // mu protects debuggeeID
  62. mu sync.Mutex
  63. // debuggeeID is returned from the server on registration, and is passed back
  64. // to the server in List and Update requests.
  65. debuggeeID string
  66. }
  67. // Options controls how the Debuglet Controller client identifies itself to the server.
  68. // See https://cloud.google.com/storage/docs/projects and
  69. // https://cloud.google.com/tools/cloud-debugger/setting-up-on-compute-engine
  70. // for further documentation of these parameters.
  71. type Options struct {
  72. ProjectNumber string // GCP Project Number.
  73. ProjectID string // GCP Project ID.
  74. AppModule string // Module name for the debugged program.
  75. AppVersion string // Version number for this module.
  76. SourceContexts []*cd.SourceContext // Description of source.
  77. Verbose bool
  78. TokenSource oauth2.TokenSource // Source of Credentials used for Stackdriver Debugger.
  79. }
  80. type serviceInterface interface {
  81. Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error)
  82. Update(ctx context.Context, debuggeeID, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error)
  83. List(ctx context.Context, debuggeeID, waitToken string) (*cd.ListActiveBreakpointsResponse, error)
  84. }
  85. var newService = func(ctx context.Context, tokenSource oauth2.TokenSource) (serviceInterface, error) {
  86. httpClient, endpoint, err := htransport.NewClient(ctx, option.WithTokenSource(tokenSource))
  87. if err != nil {
  88. return nil, err
  89. }
  90. s, err := cd.New(httpClient)
  91. if err != nil {
  92. return nil, err
  93. }
  94. if endpoint != "" {
  95. s.BasePath = endpoint
  96. }
  97. return &service{s: s}, nil
  98. }
  99. type service struct {
  100. s *cd.Service
  101. }
  102. func (s service) Register(ctx context.Context, req *cd.RegisterDebuggeeRequest) (*cd.RegisterDebuggeeResponse, error) {
  103. call := cd.NewControllerDebuggeesService(s.s).Register(req)
  104. return call.Context(ctx).Do()
  105. }
  106. func (s service) Update(ctx context.Context, debuggeeID, breakpointID string, req *cd.UpdateActiveBreakpointRequest) (*cd.UpdateActiveBreakpointResponse, error) {
  107. call := cd.NewControllerDebuggeesBreakpointsService(s.s).Update(debuggeeID, breakpointID, req)
  108. return call.Context(ctx).Do()
  109. }
  110. func (s service) List(ctx context.Context, debuggeeID, waitToken string) (*cd.ListActiveBreakpointsResponse, error) {
  111. call := cd.NewControllerDebuggeesBreakpointsService(s.s).List(debuggeeID)
  112. call.WaitToken(waitToken)
  113. return call.Context(ctx).Do()
  114. }
  115. // NewController connects to the Debuglet Controller server using the given options,
  116. // and returns a Controller for that connection.
  117. // Google Application Default Credentials are used to connect to the Debuglet Controller;
  118. // see https://developers.google.com/identity/protocols/application-default-credentials
  119. func NewController(ctx context.Context, o Options) (*Controller, error) {
  120. // We build a JSON encoding of o.SourceContexts so we can hash it.
  121. scJSON, err := json.Marshal(o.SourceContexts)
  122. if err != nil {
  123. scJSON = nil
  124. o.SourceContexts = nil
  125. }
  126. const minorversion = "107157" // any arbitrary numeric string
  127. // Compute a uniquifier string by hashing the project number, app module name,
  128. // app module version, debuglet version, and source context.
  129. // The choice of hash function is arbitrary.
  130. h := sha256.Sum256([]byte(fmt.Sprintf("%d %s %d %s %d %s %d %s %d %s %d %s",
  131. len(o.ProjectNumber), o.ProjectNumber,
  132. len(o.AppModule), o.AppModule,
  133. len(o.AppVersion), o.AppVersion,
  134. len(agentVersionString), agentVersionString,
  135. len(scJSON), scJSON,
  136. len(minorversion), minorversion)))
  137. uniquifier := fmt.Sprintf("%X", h[0:16]) // 32 hex characters
  138. description := o.ProjectID
  139. if o.AppModule != "" {
  140. description += "-" + o.AppModule
  141. }
  142. if o.AppVersion != "" {
  143. description += "-" + o.AppVersion
  144. }
  145. s, err := newService(ctx, o.TokenSource)
  146. if err != nil {
  147. return nil, err
  148. }
  149. // Construct client.
  150. c := &Controller{
  151. s: s,
  152. waitToken: initWaitToken,
  153. verbose: o.Verbose,
  154. options: o,
  155. uniquifier: uniquifier,
  156. description: description,
  157. labels: map[string]string{
  158. "module": o.AppModule,
  159. "version": o.AppVersion,
  160. "minorversion": minorversion,
  161. },
  162. }
  163. return c, nil
  164. }
  165. func (c *Controller) getDebuggeeID(ctx context.Context) (string, error) {
  166. c.mu.Lock()
  167. defer c.mu.Unlock()
  168. if c.debuggeeID != "" {
  169. return c.debuggeeID, nil
  170. }
  171. // The debuglet hasn't been registered yet, or it is disabled and we should try registering again.
  172. if err := c.register(ctx); err != nil {
  173. return "", err
  174. }
  175. return c.debuggeeID, nil
  176. }
  177. // List retrieves the current list of breakpoints from the server.
  178. // If the set of breakpoints on the server is the same as the one returned in
  179. // the previous call to List, the server can delay responding until it changes,
  180. // and return an error instead if no change occurs before a time limit the
  181. // server sets. List can't be called concurrently with itself.
  182. func (c *Controller) List(ctx context.Context) (*cd.ListActiveBreakpointsResponse, error) {
  183. id, err := c.getDebuggeeID(ctx)
  184. if err != nil {
  185. return nil, err
  186. }
  187. resp, err := c.s.List(ctx, id, c.waitToken)
  188. if err != nil {
  189. if isAbortedError(err) {
  190. return nil, ErrListUnchanged
  191. }
  192. // For other errors, the protocol requires that we attempt to re-register.
  193. c.mu.Lock()
  194. defer c.mu.Unlock()
  195. if regError := c.register(ctx); regError != nil {
  196. return nil, regError
  197. }
  198. return nil, err
  199. }
  200. if resp == nil {
  201. return nil, errors.New("no response")
  202. }
  203. if c.verbose {
  204. log.Printf("List response: %v", resp)
  205. }
  206. c.waitToken = resp.NextWaitToken
  207. return resp, nil
  208. }
  209. // isAbortedError tests if err is a *googleapi.Error, that it contains one error
  210. // in Errors, and that that error's Reason is "aborted".
  211. func isAbortedError(err error) bool {
  212. e, _ := err.(*googleapi.Error)
  213. if e == nil {
  214. return false
  215. }
  216. if len(e.Errors) != 1 {
  217. return false
  218. }
  219. return e.Errors[0].Reason == "aborted"
  220. }
  221. // Update reports information to the server about a breakpoint that was hit.
  222. // Update can be called concurrently with List and Update.
  223. func (c *Controller) Update(ctx context.Context, breakpointID string, bp *cd.Breakpoint) error {
  224. req := &cd.UpdateActiveBreakpointRequest{Breakpoint: bp}
  225. if c.verbose {
  226. log.Printf("sending update for %s: %v", breakpointID, req)
  227. }
  228. id, err := c.getDebuggeeID(ctx)
  229. if err != nil {
  230. return err
  231. }
  232. _, err = c.s.Update(ctx, id, breakpointID, req)
  233. return err
  234. }
  235. // register calls the Debuglet Controller Register method, and sets c.debuggeeID.
  236. // c.mu should be locked while calling this function. List and Update can't
  237. // make progress until it returns.
  238. func (c *Controller) register(ctx context.Context) error {
  239. req := cd.RegisterDebuggeeRequest{
  240. Debuggee: &cd.Debuggee{
  241. AgentVersion: agentVersionString,
  242. Description: c.description,
  243. Project: c.options.ProjectNumber,
  244. SourceContexts: c.options.SourceContexts,
  245. Uniquifier: c.uniquifier,
  246. Labels: c.labels,
  247. },
  248. }
  249. resp, err := c.s.Register(ctx, &req)
  250. if err != nil {
  251. return err
  252. }
  253. if resp == nil {
  254. return errors.New("register: no response")
  255. }
  256. if resp.Debuggee.IsDisabled {
  257. // Setting c.debuggeeID to empty makes sure future List and Update calls
  258. // will call register first.
  259. c.debuggeeID = ""
  260. } else {
  261. c.debuggeeID = resp.Debuggee.Id
  262. }
  263. if c.debuggeeID == "" {
  264. return ErrDebuggeeDisabled
  265. }
  266. return nil
  267. }