Non puoi selezionare più di 25 argomenti Gli argomenti devono iniziare con una lettera o un numero, possono includere trattini ('-') e possono essere lunghi fino a 35 caratteri.
 
 
 

439 righe
13 KiB

  1. /*
  2. Copyright 2016 Google LLC
  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. http://www.apache.org/licenses/LICENSE-2.0
  7. Unless required by applicable law or agreed to in writing, software
  8. distributed under the License is distributed on an "AS IS" BASIS,
  9. WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  10. See the License for the specific language governing permissions and
  11. limitations under the License.
  12. */
  13. // Package testing provides support for testing the logging client.
  14. package testing
  15. import (
  16. "context"
  17. "errors"
  18. "fmt"
  19. "regexp"
  20. "sort"
  21. "strings"
  22. "sync"
  23. "time"
  24. "cloud.google.com/go/internal/testutil"
  25. emptypb "github.com/golang/protobuf/ptypes/empty"
  26. tspb "github.com/golang/protobuf/ptypes/timestamp"
  27. lpb "google.golang.org/genproto/googleapis/api/label"
  28. mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
  29. logpb "google.golang.org/genproto/googleapis/logging/v2"
  30. )
  31. type loggingHandler struct {
  32. logpb.LoggingServiceV2Server
  33. mu sync.Mutex
  34. logs map[string][]*logpb.LogEntry // indexed by log name
  35. }
  36. type configHandler struct {
  37. logpb.ConfigServiceV2Server
  38. mu sync.Mutex
  39. sinks map[string]*logpb.LogSink // indexed by (full) sink name
  40. }
  41. type metricHandler struct {
  42. logpb.MetricsServiceV2Server
  43. mu sync.Mutex
  44. metrics map[string]*logpb.LogMetric // indexed by (full) metric name
  45. }
  46. // NewServer creates a new in-memory fake server implementing the logging service.
  47. // It returns the address of the server.
  48. func NewServer() (string, error) {
  49. srv, err := testutil.NewServer()
  50. if err != nil {
  51. return "", err
  52. }
  53. logpb.RegisterLoggingServiceV2Server(srv.Gsrv, &loggingHandler{
  54. logs: make(map[string][]*logpb.LogEntry),
  55. })
  56. logpb.RegisterConfigServiceV2Server(srv.Gsrv, &configHandler{
  57. sinks: make(map[string]*logpb.LogSink),
  58. })
  59. logpb.RegisterMetricsServiceV2Server(srv.Gsrv, &metricHandler{
  60. metrics: make(map[string]*logpb.LogMetric),
  61. })
  62. srv.Start()
  63. return srv.Addr, nil
  64. }
  65. // DeleteLog deletes a log and all its log entries. The log will reappear if it
  66. // receives new entries.
  67. func (h *loggingHandler) DeleteLog(_ context.Context, req *logpb.DeleteLogRequest) (*emptypb.Empty, error) {
  68. // TODO(jba): return NotFound if log isn't there?
  69. h.mu.Lock()
  70. defer h.mu.Unlock()
  71. delete(h.logs, req.LogName)
  72. return &emptypb.Empty{}, nil
  73. }
  74. // The only IDs that WriteLogEntries will accept.
  75. // Important for testing Ping.
  76. const (
  77. ValidProjectID = "PROJECT_ID"
  78. ValidOrgID = "433637338589"
  79. SharedServiceAccount = "serviceAccount:cloud-logs@system.gserviceaccount.com"
  80. )
  81. // WriteLogEntries writes log entries to Stackdriver Logging. All log entries in
  82. // Stackdriver Logging are written by this method.
  83. func (h *loggingHandler) WriteLogEntries(_ context.Context, req *logpb.WriteLogEntriesRequest) (*logpb.WriteLogEntriesResponse, error) {
  84. if !strings.HasPrefix(req.LogName, "projects/"+ValidProjectID+"/") && !strings.HasPrefix(req.LogName, "organizations/"+ValidOrgID+"/") {
  85. return nil, fmt.Errorf("bad LogName: %q", req.LogName)
  86. }
  87. // TODO(jba): support insertId?
  88. h.mu.Lock()
  89. defer h.mu.Unlock()
  90. for _, e := range req.Entries {
  91. // Assign timestamp if missing.
  92. if e.Timestamp == nil {
  93. e.Timestamp = &tspb.Timestamp{Seconds: time.Now().Unix(), Nanos: 0}
  94. }
  95. // Fill from common fields in request.
  96. if e.LogName == "" {
  97. e.LogName = req.LogName
  98. }
  99. if e.Resource == nil {
  100. // TODO(jba): use a global one if nil?
  101. e.Resource = req.Resource
  102. }
  103. for k, v := range req.Labels {
  104. if _, ok := e.Labels[k]; !ok {
  105. e.Labels[k] = v
  106. }
  107. }
  108. // Store by log name.
  109. h.logs[e.LogName] = append(h.logs[e.LogName], e)
  110. }
  111. return &logpb.WriteLogEntriesResponse{}, nil
  112. }
  113. // ListLogEntries lists log entries. Use this method to retrieve log entries
  114. // from Stackdriver Logging.
  115. //
  116. // This fake implementation ignores project IDs. It does not support full filtering, only
  117. // expressions of the form "logName = NAME".
  118. func (h *loggingHandler) ListLogEntries(_ context.Context, req *logpb.ListLogEntriesRequest) (*logpb.ListLogEntriesResponse, error) {
  119. h.mu.Lock()
  120. defer h.mu.Unlock()
  121. entries, err := h.filterEntries(req.Filter)
  122. if err != nil {
  123. return nil, err
  124. }
  125. if err = sortEntries(entries, req.OrderBy); err != nil {
  126. return nil, err
  127. }
  128. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(entries))
  129. if err != nil {
  130. return nil, err
  131. }
  132. return &logpb.ListLogEntriesResponse{
  133. Entries: entries[from:to],
  134. NextPageToken: nextPageToken,
  135. }, nil
  136. }
  137. func (h *loggingHandler) filterEntries(filter string) ([]*logpb.LogEntry, error) {
  138. logName, err := parseFilter(filter)
  139. if err != nil {
  140. return nil, err
  141. }
  142. if logName != "" {
  143. return h.logs[logName], nil
  144. }
  145. var entries []*logpb.LogEntry
  146. for _, es := range h.logs {
  147. entries = append(entries, es...)
  148. }
  149. return entries, nil
  150. }
  151. var filterRegexp = regexp.MustCompile(`^logName\s*=\s*"?([-_/.%\w]+)"?`)
  152. // returns the log name, or "" for the empty filter
  153. func parseFilter(filter string) (string, error) {
  154. if filter == "" {
  155. return "", nil
  156. }
  157. subs := filterRegexp.FindStringSubmatch(filter)
  158. if subs == nil {
  159. return "", invalidArgument(fmt.Sprintf("fake.go: failed to parse filter %s", filter))
  160. }
  161. return subs[1], nil // cannot panic by construction of regexp
  162. }
  163. func sortEntries(entries []*logpb.LogEntry, orderBy string) error {
  164. switch orderBy {
  165. case "", "timestamp asc":
  166. sort.Sort(byTimestamp(entries))
  167. return nil
  168. case "timestamp desc":
  169. sort.Sort(sort.Reverse(byTimestamp(entries)))
  170. return nil
  171. default:
  172. return invalidArgument("bad order_by")
  173. }
  174. }
  175. type byTimestamp []*logpb.LogEntry
  176. func (s byTimestamp) Len() int { return len(s) }
  177. func (s byTimestamp) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  178. func (s byTimestamp) Less(i, j int) bool {
  179. c := compareTimestamps(s[i].Timestamp, s[j].Timestamp)
  180. switch {
  181. case c < 0:
  182. return true
  183. case c > 0:
  184. return false
  185. default:
  186. return s[i].InsertId < s[j].InsertId
  187. }
  188. }
  189. func compareTimestamps(ts1, ts2 *tspb.Timestamp) int64 {
  190. if ts1.Seconds != ts2.Seconds {
  191. return ts1.Seconds - ts2.Seconds
  192. }
  193. return int64(ts1.Nanos - ts2.Nanos)
  194. }
  195. // Lists monitored resource descriptors that are used by Stackdriver Logging.
  196. func (h *loggingHandler) ListMonitoredResourceDescriptors(context.Context, *logpb.ListMonitoredResourceDescriptorsRequest) (*logpb.ListMonitoredResourceDescriptorsResponse, error) {
  197. return &logpb.ListMonitoredResourceDescriptorsResponse{
  198. ResourceDescriptors: []*mrpb.MonitoredResourceDescriptor{
  199. {
  200. Type: "global",
  201. DisplayName: "Global",
  202. Description: "... a log is not associated with any specific resource.",
  203. Labels: []*lpb.LabelDescriptor{
  204. {Key: "project_id", Description: "The identifier of the GCP project..."},
  205. },
  206. },
  207. },
  208. }, nil
  209. }
  210. // Lists logs.
  211. func (h *loggingHandler) ListLogs(_ context.Context, req *logpb.ListLogsRequest) (*logpb.ListLogsResponse, error) {
  212. // Return fixed, fake response.
  213. logNames := []string{"a", "b", "c"}
  214. from, to, npt, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(logNames))
  215. if err != nil {
  216. return nil, err
  217. }
  218. var lns []string
  219. for _, ln := range logNames[from:to] {
  220. lns = append(lns, req.Parent+"/logs/"+ln)
  221. }
  222. return &logpb.ListLogsResponse{
  223. LogNames: lns,
  224. NextPageToken: npt,
  225. }, nil
  226. }
  227. // Gets a sink.
  228. func (h *configHandler) GetSink(_ context.Context, req *logpb.GetSinkRequest) (*logpb.LogSink, error) {
  229. h.mu.Lock()
  230. defer h.mu.Unlock()
  231. if s, ok := h.sinks[req.SinkName]; ok {
  232. return s, nil
  233. }
  234. // TODO(jba): use error codes
  235. return nil, fmt.Errorf("sink %q not found", req.SinkName)
  236. }
  237. // Creates a sink.
  238. func (h *configHandler) CreateSink(_ context.Context, req *logpb.CreateSinkRequest) (*logpb.LogSink, error) {
  239. h.mu.Lock()
  240. defer h.mu.Unlock()
  241. fullName := fmt.Sprintf("%s/sinks/%s", req.Parent, req.Sink.Name)
  242. if _, ok := h.sinks[fullName]; ok {
  243. return nil, fmt.Errorf("sink with name %q already exists", fullName)
  244. }
  245. h.setSink(fullName, req.Sink, req.UniqueWriterIdentity)
  246. return req.Sink, nil
  247. }
  248. func (h *configHandler) setSink(name string, s *logpb.LogSink, uniqueWriterIdentity bool) {
  249. if uniqueWriterIdentity {
  250. s.WriterIdentity = "serviceAccount:" + name + "@gmail.com"
  251. } else {
  252. s.WriterIdentity = SharedServiceAccount
  253. }
  254. h.sinks[name] = s
  255. }
  256. // Creates or updates a sink.
  257. func (h *configHandler) UpdateSink(_ context.Context, req *logpb.UpdateSinkRequest) (*logpb.LogSink, error) {
  258. h.mu.Lock()
  259. defer h.mu.Unlock()
  260. sink := h.sinks[req.SinkName]
  261. // Update of a non-existent sink will create it.
  262. if sink == nil {
  263. h.setSink(req.SinkName, req.Sink, req.UniqueWriterIdentity)
  264. sink = req.Sink
  265. } else {
  266. // sink is the existing sink named req.SinkName.
  267. // Update all and only the fields of sink that are specified in the update mask.
  268. paths := req.UpdateMask.GetPaths()
  269. if len(paths) == 0 {
  270. // An empty update mask is considered to have these fields by default.
  271. paths = []string{"destination", "filter", "include_children"}
  272. }
  273. for _, p := range paths {
  274. switch p {
  275. case "destination":
  276. sink.Destination = req.Sink.Destination
  277. case "filter":
  278. sink.Filter = req.Sink.Filter
  279. case "include_children":
  280. sink.IncludeChildren = req.Sink.IncludeChildren
  281. case "output_version_format":
  282. // noop
  283. default:
  284. return nil, fmt.Errorf("unknown path in mask: %q", p)
  285. }
  286. }
  287. if req.UniqueWriterIdentity {
  288. if sink.WriterIdentity != SharedServiceAccount {
  289. return nil, invalidArgument("cannot change unique writer identity")
  290. }
  291. sink.WriterIdentity = "serviceAccount:" + req.SinkName + "@gmail.com"
  292. }
  293. }
  294. return sink, nil
  295. }
  296. // Deletes a sink.
  297. func (h *configHandler) DeleteSink(_ context.Context, req *logpb.DeleteSinkRequest) (*emptypb.Empty, error) {
  298. h.mu.Lock()
  299. defer h.mu.Unlock()
  300. delete(h.sinks, req.SinkName)
  301. return &emptypb.Empty{}, nil
  302. }
  303. // Lists sinks. This fake implementation ignores the Parent field of
  304. // ListSinksRequest. All sinks are listed, regardless of their project.
  305. func (h *configHandler) ListSinks(_ context.Context, req *logpb.ListSinksRequest) (*logpb.ListSinksResponse, error) {
  306. h.mu.Lock()
  307. var sinks []*logpb.LogSink
  308. for _, s := range h.sinks {
  309. sinks = append(sinks, s)
  310. }
  311. h.mu.Unlock() // safe because no *logpb.LogSink is ever modified
  312. // Since map iteration varies, sort the sinks.
  313. sort.Sort(sinksByName(sinks))
  314. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(sinks))
  315. if err != nil {
  316. return nil, err
  317. }
  318. return &logpb.ListSinksResponse{
  319. Sinks: sinks[from:to],
  320. NextPageToken: nextPageToken,
  321. }, nil
  322. }
  323. type sinksByName []*logpb.LogSink
  324. func (s sinksByName) Len() int { return len(s) }
  325. func (s sinksByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  326. func (s sinksByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
  327. // Gets a metric.
  328. func (h *metricHandler) GetLogMetric(_ context.Context, req *logpb.GetLogMetricRequest) (*logpb.LogMetric, error) {
  329. h.mu.Lock()
  330. defer h.mu.Unlock()
  331. if s, ok := h.metrics[req.MetricName]; ok {
  332. return s, nil
  333. }
  334. // TODO(jba): use error codes
  335. return nil, fmt.Errorf("metric %q not found", req.MetricName)
  336. }
  337. // Creates a metric.
  338. func (h *metricHandler) CreateLogMetric(_ context.Context, req *logpb.CreateLogMetricRequest) (*logpb.LogMetric, error) {
  339. h.mu.Lock()
  340. defer h.mu.Unlock()
  341. fullName := fmt.Sprintf("%s/metrics/%s", req.Parent, req.Metric.Name)
  342. if _, ok := h.metrics[fullName]; ok {
  343. return nil, fmt.Errorf("metric with name %q already exists", fullName)
  344. }
  345. h.metrics[fullName] = req.Metric
  346. return req.Metric, nil
  347. }
  348. // Creates or updates a metric.
  349. func (h *metricHandler) UpdateLogMetric(_ context.Context, req *logpb.UpdateLogMetricRequest) (*logpb.LogMetric, error) {
  350. h.mu.Lock()
  351. defer h.mu.Unlock()
  352. // Update of a non-existent metric will create it.
  353. h.metrics[req.MetricName] = req.Metric
  354. return req.Metric, nil
  355. }
  356. // Deletes a metric.
  357. func (h *metricHandler) DeleteLogMetric(_ context.Context, req *logpb.DeleteLogMetricRequest) (*emptypb.Empty, error) {
  358. h.mu.Lock()
  359. defer h.mu.Unlock()
  360. delete(h.metrics, req.MetricName)
  361. return &emptypb.Empty{}, nil
  362. }
  363. // Lists metrics. This fake implementation ignores the Parent field of
  364. // ListMetricsRequest. All metrics are listed, regardless of their project.
  365. func (h *metricHandler) ListLogMetrics(_ context.Context, req *logpb.ListLogMetricsRequest) (*logpb.ListLogMetricsResponse, error) {
  366. h.mu.Lock()
  367. var metrics []*logpb.LogMetric
  368. for _, s := range h.metrics {
  369. metrics = append(metrics, s)
  370. }
  371. h.mu.Unlock() // safe because no *logpb.LogMetric is ever modified
  372. // Since map iteration varies, sort the metrics.
  373. sort.Sort(metricsByName(metrics))
  374. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(metrics))
  375. if err != nil {
  376. return nil, err
  377. }
  378. return &logpb.ListLogMetricsResponse{
  379. Metrics: metrics[from:to],
  380. NextPageToken: nextPageToken,
  381. }, nil
  382. }
  383. type metricsByName []*logpb.LogMetric
  384. func (s metricsByName) Len() int { return len(s) }
  385. func (s metricsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  386. func (s metricsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
  387. func invalidArgument(msg string) error {
  388. // TODO(jba): status codes
  389. return errors.New(msg)
  390. }