Du kan inte välja fler än 25 ämnen Ämnen måste starta med en bokstav eller siffra, kan innehålla bindestreck ('-') och vara max 35 tecken långa.
 
 
 

396 rader
12 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. "errors"
  17. "fmt"
  18. "regexp"
  19. "sort"
  20. "strings"
  21. "sync"
  22. "time"
  23. emptypb "github.com/golang/protobuf/ptypes/empty"
  24. tspb "github.com/golang/protobuf/ptypes/timestamp"
  25. "cloud.google.com/go/internal/testutil"
  26. context "golang.org/x/net/context"
  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. )
  80. // WriteLogEntries writes log entries to Stackdriver Logging. All log entries in
  81. // Stackdriver Logging are written by this method.
  82. func (h *loggingHandler) WriteLogEntries(_ context.Context, req *logpb.WriteLogEntriesRequest) (*logpb.WriteLogEntriesResponse, error) {
  83. if !strings.HasPrefix(req.LogName, "projects/"+validProjectID+"/") && !strings.HasPrefix(req.LogName, "organizations/"+validOrgID+"/") {
  84. return nil, fmt.Errorf("bad LogName: %q", req.LogName)
  85. }
  86. // TODO(jba): support insertId?
  87. h.mu.Lock()
  88. defer h.mu.Unlock()
  89. for _, e := range req.Entries {
  90. // Assign timestamp if missing.
  91. if e.Timestamp == nil {
  92. e.Timestamp = &tspb.Timestamp{Seconds: time.Now().Unix(), Nanos: 0}
  93. }
  94. // Fill from common fields in request.
  95. if e.LogName == "" {
  96. e.LogName = req.LogName
  97. }
  98. if e.Resource == nil {
  99. // TODO(jba): use a global one if nil?
  100. e.Resource = req.Resource
  101. }
  102. for k, v := range req.Labels {
  103. if _, ok := e.Labels[k]; !ok {
  104. e.Labels[k] = v
  105. }
  106. }
  107. // Store by log name.
  108. h.logs[e.LogName] = append(h.logs[e.LogName], e)
  109. }
  110. return &logpb.WriteLogEntriesResponse{}, nil
  111. }
  112. // ListLogEntries lists log entries. Use this method to retrieve log entries
  113. // from Stackdriver Logging.
  114. //
  115. // This fake implementation ignores project IDs. It does not support full filtering, only
  116. // expressions of the form "logName = NAME".
  117. func (h *loggingHandler) ListLogEntries(_ context.Context, req *logpb.ListLogEntriesRequest) (*logpb.ListLogEntriesResponse, error) {
  118. h.mu.Lock()
  119. defer h.mu.Unlock()
  120. entries, err := h.filterEntries(req.Filter)
  121. if err != nil {
  122. return nil, err
  123. }
  124. if err = sortEntries(entries, req.OrderBy); err != nil {
  125. return nil, err
  126. }
  127. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(entries))
  128. if err != nil {
  129. return nil, err
  130. }
  131. return &logpb.ListLogEntriesResponse{
  132. Entries: entries[from:to],
  133. NextPageToken: nextPageToken,
  134. }, nil
  135. }
  136. func (h *loggingHandler) filterEntries(filter string) ([]*logpb.LogEntry, error) {
  137. logName, err := parseFilter(filter)
  138. if err != nil {
  139. return nil, err
  140. }
  141. if logName != "" {
  142. return h.logs[logName], nil
  143. }
  144. var entries []*logpb.LogEntry
  145. for _, es := range h.logs {
  146. entries = append(entries, es...)
  147. }
  148. return entries, nil
  149. }
  150. var filterRegexp = regexp.MustCompile(`^logName\s*=\s*"?([-_/.%\w]+)"?$`)
  151. // returns the log name, or "" for the empty filter
  152. func parseFilter(filter string) (string, error) {
  153. if filter == "" {
  154. return "", nil
  155. }
  156. subs := filterRegexp.FindStringSubmatch(filter)
  157. if subs == nil {
  158. return "", invalidArgument("bad filter")
  159. }
  160. return subs[1], nil // cannot panic by construction of regexp
  161. }
  162. func sortEntries(entries []*logpb.LogEntry, orderBy string) error {
  163. switch orderBy {
  164. case "", "timestamp asc":
  165. sort.Sort(byTimestamp(entries))
  166. return nil
  167. case "timestamp desc":
  168. sort.Sort(sort.Reverse(byTimestamp(entries)))
  169. return nil
  170. default:
  171. return invalidArgument("bad order_by")
  172. }
  173. }
  174. type byTimestamp []*logpb.LogEntry
  175. func (s byTimestamp) Len() int { return len(s) }
  176. func (s byTimestamp) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  177. func (s byTimestamp) Less(i, j int) bool {
  178. c := compareTimestamps(s[i].Timestamp, s[j].Timestamp)
  179. switch {
  180. case c < 0:
  181. return true
  182. case c > 0:
  183. return false
  184. default:
  185. return s[i].InsertId < s[j].InsertId
  186. }
  187. }
  188. func compareTimestamps(ts1, ts2 *tspb.Timestamp) int64 {
  189. if ts1.Seconds != ts2.Seconds {
  190. return ts1.Seconds - ts2.Seconds
  191. }
  192. return int64(ts1.Nanos - ts2.Nanos)
  193. }
  194. // Lists monitored resource descriptors that are used by Stackdriver Logging.
  195. func (h *loggingHandler) ListMonitoredResourceDescriptors(context.Context, *logpb.ListMonitoredResourceDescriptorsRequest) (*logpb.ListMonitoredResourceDescriptorsResponse, error) {
  196. return &logpb.ListMonitoredResourceDescriptorsResponse{
  197. ResourceDescriptors: []*mrpb.MonitoredResourceDescriptor{
  198. {
  199. Type: "global",
  200. DisplayName: "Global",
  201. Description: "... a log is not associated with any specific resource.",
  202. Labels: []*lpb.LabelDescriptor{
  203. {Key: "project_id", Description: "The identifier of the GCP project..."},
  204. },
  205. },
  206. },
  207. }, nil
  208. }
  209. // Lists logs.
  210. func (h *loggingHandler) ListLogs(_ context.Context, req *logpb.ListLogsRequest) (*logpb.ListLogsResponse, error) {
  211. // Return fixed, fake response.
  212. logNames := []string{"a", "b", "c"}
  213. from, to, npt, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(logNames))
  214. if err != nil {
  215. return nil, err
  216. }
  217. var lns []string
  218. for _, ln := range logNames[from:to] {
  219. lns = append(lns, req.Parent+"/logs/"+ln)
  220. }
  221. return &logpb.ListLogsResponse{
  222. LogNames: lns,
  223. NextPageToken: npt,
  224. }, nil
  225. }
  226. // Gets a sink.
  227. func (h *configHandler) GetSink(_ context.Context, req *logpb.GetSinkRequest) (*logpb.LogSink, error) {
  228. h.mu.Lock()
  229. defer h.mu.Unlock()
  230. if s, ok := h.sinks[req.SinkName]; ok {
  231. return s, nil
  232. }
  233. // TODO(jba): use error codes
  234. return nil, fmt.Errorf("sink %q not found", req.SinkName)
  235. }
  236. // Creates a sink.
  237. func (h *configHandler) CreateSink(_ context.Context, req *logpb.CreateSinkRequest) (*logpb.LogSink, error) {
  238. h.mu.Lock()
  239. defer h.mu.Unlock()
  240. fullName := fmt.Sprintf("%s/sinks/%s", req.Parent, req.Sink.Name)
  241. if _, ok := h.sinks[fullName]; ok {
  242. return nil, fmt.Errorf("sink with name %q already exists", fullName)
  243. }
  244. h.sinks[fullName] = req.Sink
  245. return req.Sink, nil
  246. }
  247. // Creates or updates a sink.
  248. func (h *configHandler) UpdateSink(_ context.Context, req *logpb.UpdateSinkRequest) (*logpb.LogSink, error) {
  249. h.mu.Lock()
  250. defer h.mu.Unlock()
  251. // Update of a non-existent sink will create it.
  252. h.sinks[req.SinkName] = req.Sink
  253. return req.Sink, nil
  254. }
  255. // Deletes a sink.
  256. func (h *configHandler) DeleteSink(_ context.Context, req *logpb.DeleteSinkRequest) (*emptypb.Empty, error) {
  257. h.mu.Lock()
  258. defer h.mu.Unlock()
  259. delete(h.sinks, req.SinkName)
  260. return &emptypb.Empty{}, nil
  261. }
  262. // Lists sinks. This fake implementation ignores the Parent field of
  263. // ListSinksRequest. All sinks are listed, regardless of their project.
  264. func (h *configHandler) ListSinks(_ context.Context, req *logpb.ListSinksRequest) (*logpb.ListSinksResponse, error) {
  265. h.mu.Lock()
  266. var sinks []*logpb.LogSink
  267. for _, s := range h.sinks {
  268. sinks = append(sinks, s)
  269. }
  270. h.mu.Unlock() // safe because no *logpb.LogSink is ever modified
  271. // Since map iteration varies, sort the sinks.
  272. sort.Sort(sinksByName(sinks))
  273. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(sinks))
  274. if err != nil {
  275. return nil, err
  276. }
  277. return &logpb.ListSinksResponse{
  278. Sinks: sinks[from:to],
  279. NextPageToken: nextPageToken,
  280. }, nil
  281. }
  282. type sinksByName []*logpb.LogSink
  283. func (s sinksByName) Len() int { return len(s) }
  284. func (s sinksByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  285. func (s sinksByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
  286. // Gets a metric.
  287. func (h *metricHandler) GetLogMetric(_ context.Context, req *logpb.GetLogMetricRequest) (*logpb.LogMetric, error) {
  288. h.mu.Lock()
  289. defer h.mu.Unlock()
  290. if s, ok := h.metrics[req.MetricName]; ok {
  291. return s, nil
  292. }
  293. // TODO(jba): use error codes
  294. return nil, fmt.Errorf("metric %q not found", req.MetricName)
  295. }
  296. // Creates a metric.
  297. func (h *metricHandler) CreateLogMetric(_ context.Context, req *logpb.CreateLogMetricRequest) (*logpb.LogMetric, error) {
  298. h.mu.Lock()
  299. defer h.mu.Unlock()
  300. fullName := fmt.Sprintf("%s/metrics/%s", req.Parent, req.Metric.Name)
  301. if _, ok := h.metrics[fullName]; ok {
  302. return nil, fmt.Errorf("metric with name %q already exists", fullName)
  303. }
  304. h.metrics[fullName] = req.Metric
  305. return req.Metric, nil
  306. }
  307. // Creates or updates a metric.
  308. func (h *metricHandler) UpdateLogMetric(_ context.Context, req *logpb.UpdateLogMetricRequest) (*logpb.LogMetric, error) {
  309. h.mu.Lock()
  310. defer h.mu.Unlock()
  311. // Update of a non-existent metric will create it.
  312. h.metrics[req.MetricName] = req.Metric
  313. return req.Metric, nil
  314. }
  315. // Deletes a metric.
  316. func (h *metricHandler) DeleteLogMetric(_ context.Context, req *logpb.DeleteLogMetricRequest) (*emptypb.Empty, error) {
  317. h.mu.Lock()
  318. defer h.mu.Unlock()
  319. delete(h.metrics, req.MetricName)
  320. return &emptypb.Empty{}, nil
  321. }
  322. // Lists metrics. This fake implementation ignores the Parent field of
  323. // ListMetricsRequest. All metrics are listed, regardless of their project.
  324. func (h *metricHandler) ListLogMetrics(_ context.Context, req *logpb.ListLogMetricsRequest) (*logpb.ListLogMetricsResponse, error) {
  325. h.mu.Lock()
  326. var metrics []*logpb.LogMetric
  327. for _, s := range h.metrics {
  328. metrics = append(metrics, s)
  329. }
  330. h.mu.Unlock() // safe because no *logpb.LogMetric is ever modified
  331. // Since map iteration varies, sort the metrics.
  332. sort.Sort(metricsByName(metrics))
  333. from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(metrics))
  334. if err != nil {
  335. return nil, err
  336. }
  337. return &logpb.ListLogMetricsResponse{
  338. Metrics: metrics[from:to],
  339. NextPageToken: nextPageToken,
  340. }, nil
  341. }
  342. type metricsByName []*logpb.LogMetric
  343. func (s metricsByName) Len() int { return len(s) }
  344. func (s metricsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
  345. func (s metricsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
  346. func invalidArgument(msg string) error {
  347. // TODO(jba): status codes
  348. return errors.New(msg)
  349. }