1
16
17
18 package testing
19
20 import (
21 "context"
22 "errors"
23 "fmt"
24 "regexp"
25 "sort"
26 "strings"
27 "sync"
28 "time"
29
30 "cloud.google.com/go/internal/testutil"
31 logpb "cloud.google.com/go/logging/apiv2/loggingpb"
32 lpb "google.golang.org/genproto/googleapis/api/label"
33 mrpb "google.golang.org/genproto/googleapis/api/monitoredres"
34 emptypb "google.golang.org/protobuf/types/known/emptypb"
35 tspb "google.golang.org/protobuf/types/known/timestamppb"
36 )
37
38 type loggingHandler struct {
39 logpb.LoggingServiceV2Server
40
41 mu sync.Mutex
42 logs map[string][]*logpb.LogEntry
43 }
44
45 type configHandler struct {
46 logpb.ConfigServiceV2Server
47
48 mu sync.Mutex
49 sinks map[string]*logpb.LogSink
50 }
51
52 type metricHandler struct {
53 logpb.MetricsServiceV2Server
54
55 mu sync.Mutex
56 metrics map[string]*logpb.LogMetric
57 }
58
59
60
61 func NewServer() (string, error) {
62 srv, err := testutil.NewServer()
63 if err != nil {
64 return "", err
65 }
66 logpb.RegisterLoggingServiceV2Server(srv.Gsrv, &loggingHandler{
67 logs: make(map[string][]*logpb.LogEntry),
68 })
69 logpb.RegisterConfigServiceV2Server(srv.Gsrv, &configHandler{
70 sinks: make(map[string]*logpb.LogSink),
71 })
72 logpb.RegisterMetricsServiceV2Server(srv.Gsrv, &metricHandler{
73 metrics: make(map[string]*logpb.LogMetric),
74 })
75 srv.Start()
76 return srv.Addr, nil
77 }
78
79
80
81 func (h *loggingHandler) DeleteLog(_ context.Context, req *logpb.DeleteLogRequest) (*emptypb.Empty, error) {
82
83 h.mu.Lock()
84 defer h.mu.Unlock()
85 delete(h.logs, req.LogName)
86 return &emptypb.Empty{}, nil
87 }
88
89
90
91 const (
92 ValidProjectID = "PROJECT_ID"
93 ValidOrgID = "433637338589"
94
95 SharedServiceAccount = "serviceAccount:cloud-logs@system.gserviceaccount.com"
96 )
97
98
99
100 func (h *loggingHandler) WriteLogEntries(_ context.Context, req *logpb.WriteLogEntriesRequest) (*logpb.WriteLogEntriesResponse, error) {
101 if !strings.HasPrefix(req.LogName, "projects/"+ValidProjectID+"/") && !strings.HasPrefix(req.LogName, "organizations/"+ValidOrgID+"/") {
102 return nil, fmt.Errorf("bad LogName: %q", req.LogName)
103 }
104
105 h.mu.Lock()
106 defer h.mu.Unlock()
107 for _, e := range req.Entries {
108
109 if e.Timestamp == nil {
110 e.Timestamp = &tspb.Timestamp{Seconds: time.Now().Unix(), Nanos: 0}
111 }
112
113 if e.LogName == "" {
114 e.LogName = req.LogName
115 }
116 if e.Resource == nil {
117
118 e.Resource = req.Resource
119 }
120 for k, v := range req.Labels {
121 if _, ok := e.Labels[k]; !ok {
122 e.Labels[k] = v
123 }
124 }
125
126
127 h.logs[e.LogName] = append(h.logs[e.LogName], e)
128 }
129 return &logpb.WriteLogEntriesResponse{}, nil
130 }
131
132
133
134
135
136
137 func (h *loggingHandler) ListLogEntries(_ context.Context, req *logpb.ListLogEntriesRequest) (*logpb.ListLogEntriesResponse, error) {
138 h.mu.Lock()
139 defer h.mu.Unlock()
140 entries, err := h.filterEntries(req.Filter)
141 if err != nil {
142 return nil, err
143 }
144 if err = sortEntries(entries, req.OrderBy); err != nil {
145 return nil, err
146 }
147
148 from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(entries))
149 if err != nil {
150 return nil, err
151 }
152 return &logpb.ListLogEntriesResponse{
153 Entries: entries[from:to],
154 NextPageToken: nextPageToken,
155 }, nil
156 }
157
158 func (h *loggingHandler) filterEntries(filter string) ([]*logpb.LogEntry, error) {
159 logName, err := parseFilter(filter)
160 if err != nil {
161 return nil, err
162 }
163 if logName != "" {
164 return h.logs[logName], nil
165 }
166 var entries []*logpb.LogEntry
167 for _, es := range h.logs {
168 entries = append(entries, es...)
169 }
170 return entries, nil
171 }
172
173 var filterRegexp = regexp.MustCompile(`^logName\s*=\s*"?([-_/.%\w]+)"?`)
174
175
176 func parseFilter(filter string) (string, error) {
177 if filter == "" {
178 return "", nil
179 }
180 subs := filterRegexp.FindStringSubmatch(filter)
181 if subs == nil {
182 return "", invalidArgument(fmt.Sprintf("fake.go: failed to parse filter %s", filter))
183 }
184 return subs[1], nil
185 }
186
187 func sortEntries(entries []*logpb.LogEntry, orderBy string) error {
188 switch orderBy {
189 case "", "timestamp asc":
190 sort.Sort(byTimestamp(entries))
191 return nil
192
193 case "timestamp desc":
194 sort.Sort(sort.Reverse(byTimestamp(entries)))
195 return nil
196
197 default:
198 return invalidArgument("bad order_by")
199 }
200 }
201
202 type byTimestamp []*logpb.LogEntry
203
204 func (s byTimestamp) Len() int { return len(s) }
205 func (s byTimestamp) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
206 func (s byTimestamp) Less(i, j int) bool {
207 c := compareTimestamps(s[i].Timestamp, s[j].Timestamp)
208 switch {
209 case c < 0:
210 return true
211 case c > 0:
212 return false
213 default:
214 return s[i].InsertId < s[j].InsertId
215 }
216 }
217
218 func compareTimestamps(ts1, ts2 *tspb.Timestamp) int64 {
219 if ts1.Seconds != ts2.Seconds {
220 return ts1.Seconds - ts2.Seconds
221 }
222 return int64(ts1.Nanos - ts2.Nanos)
223 }
224
225
226 func (h *loggingHandler) ListMonitoredResourceDescriptors(context.Context, *logpb.ListMonitoredResourceDescriptorsRequest) (*logpb.ListMonitoredResourceDescriptorsResponse, error) {
227 return &logpb.ListMonitoredResourceDescriptorsResponse{
228 ResourceDescriptors: []*mrpb.MonitoredResourceDescriptor{
229 {
230 Type: "global",
231 DisplayName: "Global",
232 Description: "... a log is not associated with any specific resource.",
233 Labels: []*lpb.LabelDescriptor{
234 {Key: "project_id", Description: "The identifier of the GCP project..."},
235 },
236 },
237 },
238 }, nil
239 }
240
241
242 func (h *loggingHandler) ListLogs(_ context.Context, req *logpb.ListLogsRequest) (*logpb.ListLogsResponse, error) {
243
244 logNames := []string{"a", "b", "c"}
245 from, to, npt, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(logNames))
246 if err != nil {
247 return nil, err
248 }
249 var lns []string
250 for _, ln := range logNames[from:to] {
251 lns = append(lns, req.Parent+"/logs/"+ln)
252 }
253 return &logpb.ListLogsResponse{
254 LogNames: lns,
255 NextPageToken: npt,
256 }, nil
257 }
258
259
260 func (h *configHandler) GetSink(_ context.Context, req *logpb.GetSinkRequest) (*logpb.LogSink, error) {
261 h.mu.Lock()
262 defer h.mu.Unlock()
263 if s, ok := h.sinks[req.SinkName]; ok {
264 return s, nil
265 }
266
267 return nil, fmt.Errorf("sink %q not found", req.SinkName)
268 }
269
270
271 func (h *configHandler) CreateSink(_ context.Context, req *logpb.CreateSinkRequest) (*logpb.LogSink, error) {
272 h.mu.Lock()
273 defer h.mu.Unlock()
274 fullName := fmt.Sprintf("%s/sinks/%s", req.Parent, req.Sink.Name)
275 if _, ok := h.sinks[fullName]; ok {
276 return nil, fmt.Errorf("sink with name %q already exists", fullName)
277 }
278 h.setSink(fullName, req.Sink, req.UniqueWriterIdentity)
279 return req.Sink, nil
280 }
281
282 func (h *configHandler) setSink(name string, s *logpb.LogSink, uniqueWriterIdentity bool) {
283 if uniqueWriterIdentity {
284 s.WriterIdentity = "serviceAccount:" + name + "@gmail.com"
285 } else {
286 s.WriterIdentity = SharedServiceAccount
287 }
288 h.sinks[name] = s
289 }
290
291
292 func (h *configHandler) UpdateSink(_ context.Context, req *logpb.UpdateSinkRequest) (*logpb.LogSink, error) {
293 h.mu.Lock()
294 defer h.mu.Unlock()
295 sink := h.sinks[req.SinkName]
296
297 if sink == nil {
298 h.setSink(req.SinkName, req.Sink, req.UniqueWriterIdentity)
299 sink = req.Sink
300 } else {
301
302
303 paths := req.UpdateMask.GetPaths()
304 if len(paths) == 0 {
305
306 paths = []string{"destination", "filter", "include_children"}
307 }
308 for _, p := range paths {
309 switch p {
310 case "destination":
311 sink.Destination = req.Sink.Destination
312 case "filter":
313 sink.Filter = req.Sink.Filter
314 case "include_children":
315 sink.IncludeChildren = req.Sink.IncludeChildren
316 case "output_version_format":
317
318 default:
319 return nil, fmt.Errorf("unknown path in mask: %q", p)
320 }
321 }
322 if req.UniqueWriterIdentity {
323 if sink.WriterIdentity != SharedServiceAccount {
324 return nil, invalidArgument("cannot change unique writer identity")
325 }
326 sink.WriterIdentity = "serviceAccount:" + req.SinkName + "@gmail.com"
327 }
328 }
329 return sink, nil
330
331 }
332
333
334 func (h *configHandler) DeleteSink(_ context.Context, req *logpb.DeleteSinkRequest) (*emptypb.Empty, error) {
335 h.mu.Lock()
336 defer h.mu.Unlock()
337 delete(h.sinks, req.SinkName)
338 return &emptypb.Empty{}, nil
339 }
340
341
342
343 func (h *configHandler) ListSinks(_ context.Context, req *logpb.ListSinksRequest) (*logpb.ListSinksResponse, error) {
344 h.mu.Lock()
345 var sinks []*logpb.LogSink
346 for _, s := range h.sinks {
347 sinks = append(sinks, s)
348 }
349 h.mu.Unlock()
350
351 sort.Sort(sinksByName(sinks))
352 from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(sinks))
353 if err != nil {
354 return nil, err
355 }
356 return &logpb.ListSinksResponse{
357 Sinks: sinks[from:to],
358 NextPageToken: nextPageToken,
359 }, nil
360 }
361
362 type sinksByName []*logpb.LogSink
363
364 func (s sinksByName) Len() int { return len(s) }
365 func (s sinksByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
366 func (s sinksByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
367
368
369 func (h *metricHandler) GetLogMetric(_ context.Context, req *logpb.GetLogMetricRequest) (*logpb.LogMetric, error) {
370 h.mu.Lock()
371 defer h.mu.Unlock()
372 if s, ok := h.metrics[req.MetricName]; ok {
373 return s, nil
374 }
375
376 return nil, fmt.Errorf("metric %q not found", req.MetricName)
377 }
378
379
380 func (h *metricHandler) CreateLogMetric(_ context.Context, req *logpb.CreateLogMetricRequest) (*logpb.LogMetric, error) {
381 h.mu.Lock()
382 defer h.mu.Unlock()
383 fullName := fmt.Sprintf("%s/metrics/%s", req.Parent, req.Metric.Name)
384 if _, ok := h.metrics[fullName]; ok {
385 return nil, fmt.Errorf("metric with name %q already exists", fullName)
386 }
387 h.metrics[fullName] = req.Metric
388 return req.Metric, nil
389 }
390
391
392 func (h *metricHandler) UpdateLogMetric(_ context.Context, req *logpb.UpdateLogMetricRequest) (*logpb.LogMetric, error) {
393 h.mu.Lock()
394 defer h.mu.Unlock()
395
396 h.metrics[req.MetricName] = req.Metric
397 return req.Metric, nil
398 }
399
400
401 func (h *metricHandler) DeleteLogMetric(_ context.Context, req *logpb.DeleteLogMetricRequest) (*emptypb.Empty, error) {
402 h.mu.Lock()
403 defer h.mu.Unlock()
404 delete(h.metrics, req.MetricName)
405 return &emptypb.Empty{}, nil
406 }
407
408
409
410 func (h *metricHandler) ListLogMetrics(_ context.Context, req *logpb.ListLogMetricsRequest) (*logpb.ListLogMetricsResponse, error) {
411 h.mu.Lock()
412 var metrics []*logpb.LogMetric
413 for _, s := range h.metrics {
414 metrics = append(metrics, s)
415 }
416 h.mu.Unlock()
417
418 sort.Sort(metricsByName(metrics))
419 from, to, nextPageToken, err := testutil.PageBounds(int(req.PageSize), req.PageToken, len(metrics))
420 if err != nil {
421 return nil, err
422 }
423 return &logpb.ListLogMetricsResponse{
424 Metrics: metrics[from:to],
425 NextPageToken: nextPageToken,
426 }, nil
427 }
428
429 type metricsByName []*logpb.LogMetric
430
431 func (s metricsByName) Len() int { return len(s) }
432 func (s metricsByName) Swap(i, j int) { s[i], s[j] = s[j], s[i] }
433 func (s metricsByName) Less(i, j int) bool { return s[i].Name < s[j].Name }
434
435 func invalidArgument(msg string) error {
436
437 return errors.New(msg)
438 }
439
View as plain text