Skip to content
This repository was archived by the owner on Oct 26, 2022. It is now read-only.

Commit 7216ea7

Browse files
Add web page summary (#45)
Add web page summary
1 parent dbd9913 commit 7216ea7

File tree

7 files changed

+209
-66
lines changed

7 files changed

+209
-66
lines changed

cmd/mock_server.go

Lines changed: 31 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -16,6 +16,7 @@ package main
1616

1717
import (
1818
"flag"
19+
"html/template"
1920
"log"
2021
"net"
2122
"os"
@@ -26,7 +27,6 @@ import (
2627

2728
"github.com/googleinterns/cloud-operations-api-mock/server/metric"
2829
"github.com/googleinterns/cloud-operations-api-mock/server/trace"
29-
3030
"google.golang.org/genproto/googleapis/devtools/cloudtrace/v2"
3131
"google.golang.org/genproto/googleapis/monitoring/v3"
3232
"google.golang.org/grpc"
@@ -38,14 +38,21 @@ const (
3838

3939
var (
4040
address = flag.String("address", defaultAddress,
41-
"The address to run the server on, of the form <host>:<port>.")
41+
"The address to run the server on, of the form <host>:<port>")
42+
summary = flag.Bool("summary", false,
43+
"If flag is set, a summary page HTML file will be generated")
4244
)
4345

4446
func main() {
4547
flag.Parse()
4648
startStandaloneServer()
4749
}
4850

51+
// startStandaloneServer spins up the mock server, and listens for requests.
52+
// Upon detecting a kill signal, it will shutdown and optionally print out a table of results.
53+
// Flags:
54+
// -address=<host:port> will start the server at the given address
55+
// -summary will tell the server to generate an HTML file containing the data received.
4956
func startStandaloneServer() {
5057
lis, err := net.Listen("tcp", *address)
5158
if err != nil {
@@ -62,12 +69,34 @@ func startStandaloneServer() {
6269

6370
sig := make(chan os.Signal)
6471
signal.Notify(sig, os.Interrupt, syscall.SIGTERM)
72+
73+
// Allow the summary to be fully written before exiting.
74+
finish := make(chan bool)
75+
6576
go func() {
6677
<-sig
6778
grpcServer.GracefulStop()
79+
if *summary {
80+
writeSummaryPage(mockTraceServer.ResultTable())
81+
}
82+
finish <- true
6883
}()
6984

7085
if err := grpcServer.Serve(lis); err != nil {
7186
log.Fatalf("mock server failed to serve: %v", err)
7287
}
88+
<-finish
89+
}
90+
91+
// writeSummaryPage creates summary.html from the results and the template HTML.
92+
func writeSummaryPage(results []*cloudtrace.Span) {
93+
outputFile, err := os.Create("summary.html")
94+
if err != nil {
95+
panic(err)
96+
}
97+
t := template.Must(template.ParseFiles("../static/summary_template.html"))
98+
err = t.Execute(outputFile, results)
99+
if err != nil {
100+
panic(err)
101+
}
73102
}

go.mod

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -10,4 +10,4 @@ require (
1010
google.golang.org/genproto v0.0.0-20200605102947-12044bf5ea91
1111
google.golang.org/grpc v1.29.1
1212
google.golang.org/protobuf v1.24.0
13-
)
13+
)

internal/validation/mock_trace_validation.go

Lines changed: 81 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -16,15 +16,16 @@ package validation
1616

1717
import (
1818
"context"
19-
"fmt"
2019
"reflect"
2120
"regexp"
21+
"sync"
2222
"time"
2323

2424
"github.com/golang/protobuf/ptypes"
25-
2625
"google.golang.org/genproto/googleapis/devtools/cloudtrace/v2"
27-
"google.golang.org/genproto/googleapis/rpc/errdetails"
26+
genprotoStatus "google.golang.org/genproto/googleapis/rpc/status"
27+
"google.golang.org/grpc/codes"
28+
"google.golang.org/grpc/status"
2829
)
2930

3031
const (
@@ -56,57 +57,98 @@ var (
5657
agentRegex = regexp.MustCompile(`^opentelemetry-[a-zA-Z]+ [0-9]+\.[0-9]+\.[0-9]+; google-cloud-trace-exporter [0-9]+\.[0-9]+\.[0-9]+$`)
5758
)
5859

60+
// SpanData wraps all the span data on the server into a struct.
61+
type SpanData struct {
62+
// If a batch has a bad span, we don't write batch to memory, but still want
63+
// info on them for summary, so need SpansSummary
64+
SpansSummary []*cloudtrace.Span
65+
UploadedSpanNames map[string]struct{}
66+
UploadedSpans []*cloudtrace.Span
67+
Mutex sync.RWMutex
68+
}
69+
5970
// ValidateSpans checks that the spans conform to the API requirements.
6071
// That is, required fields are present, and optional fields are of the correct form.
61-
func ValidateSpans(requestName string, spans ...*cloudtrace.Span) error {
72+
// If any violations are detected, the errors will be added to the result table.
73+
func ValidateSpans(requestName string, spanData *SpanData, spans ...*cloudtrace.Span) error {
74+
var overallError error
75+
currentRequestSpanNames := make(map[string]struct{})
76+
6277
for _, span := range spans {
78+
var currentError error
79+
6380
// Validate required fields are present and semantically make sense.
6481
if err := CheckForRequiredFields(requiredFields, reflect.ValueOf(span), requestName); err != nil {
65-
return err
82+
addSpanToSummary(&spanData.SpansSummary, span, err)
83+
currentError = err
6684
}
67-
if err := validateName(span.Name); err != nil {
68-
return err
85+
if err := validateName(span.Name, spanData.UploadedSpanNames, currentRequestSpanNames); err != nil {
86+
addSpanToSummary(&spanData.SpansSummary, span, err)
87+
currentError = err
6988
}
7089
if err := validateTimeStamps(span); err != nil {
71-
return err
90+
addSpanToSummary(&spanData.SpansSummary, span, err)
91+
currentError = err
7292
}
7393
if err := validateDisplayName(span.DisplayName); err != nil {
74-
return err
94+
addSpanToSummary(&spanData.SpansSummary, span, err)
95+
currentError = err
7596
}
7697

7798
// Validate that if optional fields are present, they conform to the API.
7899
if err := validateAttributes(span.Attributes, maxAttributes); err != nil {
79-
return err
100+
addSpanToSummary(&spanData.SpansSummary, span, err)
101+
currentError = err
80102
}
81103
if err := validateTimeEvents(span.TimeEvents); err != nil {
82-
return err
104+
addSpanToSummary(&spanData.SpansSummary, span, err)
105+
currentError = err
83106
}
84107
if err := validateLinks(span.Links); err != nil {
85-
return err
108+
addSpanToSummary(&spanData.SpansSummary, span, err)
109+
currentError = err
86110
}
111+
112+
if currentError == nil {
113+
addSpanToSummary(&spanData.SpansSummary, span, nil)
114+
} else {
115+
overallError = currentError
116+
}
117+
}
118+
119+
if overallError != nil {
120+
return overallError
87121
}
122+
88123
return nil
89124
}
90125

91-
// AddSpans adds the given spans to the list of uploaded spans as long as there are no duplicate names.
92-
// If a duplicate span name is detected, it will not be written, and an error is returned.
93-
func AddSpans(uploadedSpans *[]*cloudtrace.Span, uploadedSpanNames map[string]struct{}, spans ...*cloudtrace.Span) error {
94-
br := &errdetails.ErrorInfo{}
126+
// addSpanToSummary sets the span's status and adds it to the summary slice.
127+
func addSpanToSummary(spanSummary *[]*cloudtrace.Span, span *cloudtrace.Span, err error) {
128+
setSpanStatus(span, err)
129+
*spanSummary = append(*spanSummary, span)
130+
}
95131

96-
for _, span := range spans {
97-
if _, ok := uploadedSpanNames[span.Name]; ok {
98-
br.Reason = span.Name
99-
st, err := statusDuplicateSpanName.WithDetails(br)
100-
if err != nil {
101-
panic(fmt.Sprintf("unexpected error attaching metadata: %v", err))
102-
}
103-
return st.Err()
132+
func setSpanStatus(span *cloudtrace.Span, err error) {
133+
if err == nil {
134+
span.Status = &genprotoStatus.Status{
135+
Code: int32(codes.OK),
136+
Message: "OK",
137+
}
138+
} else {
139+
span.Status = &genprotoStatus.Status{
140+
Code: int32(status.Convert(err).Code()),
141+
Message: status.Convert(err).Message(),
104142
}
105-
*uploadedSpans = append(*uploadedSpans, span)
106-
uploadedSpanNames[span.Name] = struct{}{}
107143
}
144+
}
108145

109-
return nil
146+
// AddSpans adds the given spans to the list of uploaded spans.
147+
func AddSpans(spanData *SpanData, spans ...*cloudtrace.Span) {
148+
for _, span := range spans {
149+
spanData.UploadedSpans = append(spanData.UploadedSpans, span)
150+
spanData.UploadedSpanNames[span.Name] = struct{}{}
151+
}
110152
}
111153

112154
// Delay will block for the specified amount of time.
@@ -137,13 +179,23 @@ func validateDisplayName(displayName *cloudtrace.TruncatableString) error {
137179
return nil
138180
}
139181

140-
// validateName verifies that the span is of the form:
182+
// validateName verifies that the span name is not a duplicate, and is of the form:
141183
// projects/{project_id}/traces/{trace_id}/spans/{span_id}
142184
// where trace_id is a 32-char hex encoding, and span_id is a 16-char hex encoding.
143-
func validateName(name string) error {
185+
func validateName(name string, spanNames map[string]struct{}, currentRequestSpanNames map[string]struct{}) error {
186+
if _, ok := spanNames[name]; ok {
187+
return statusDuplicateSpanName
188+
}
189+
190+
if _, ok := currentRequestSpanNames[name]; ok {
191+
return statusDuplicateSpanName
192+
}
193+
144194
if !spanNameRegex.MatchString(name) {
145195
return statusInvalidSpanName
146196
}
197+
198+
currentRequestSpanNames[name] = struct{}{}
147199
return nil
148200
}
149201

internal/validation/statuses.go

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -56,7 +56,7 @@ var (
5656
fmt.Sprintf("annotation descriptions have a max length of %v bytes", maxAnnotationBytes))
5757
statusTooManyLinks = status.Error(codes.InvalidArgument,
5858
fmt.Sprintf("a span can have at most %v links", maxLinks))
59-
statusDuplicateSpanName = status.New(codes.AlreadyExists, "duplicate span name")
59+
statusDuplicateSpanName = status.Error(codes.AlreadyExists, "duplicate span name")
6060

6161
// Metric statuses.
6262
statusDuplicateMetricDescriptorType = status.New(codes.AlreadyExists, "metric descriptor of same type already exists")

server/trace/mock_trace.go

Lines changed: 38 additions & 26 deletions
Original file line numberDiff line numberDiff line change
@@ -30,46 +30,54 @@ import (
3030
type MockTraceServer struct {
3131
cloudtrace.UnimplementedTraceServiceServer
3232
mocktrace.UnimplementedMockTraceServiceServer
33-
uploadedSpanNames map[string]struct{}
34-
uploadedSpans []*cloudtrace.Span
35-
uploadedSpansLock sync.Mutex
36-
delay time.Duration
37-
delayLock sync.Mutex
38-
onUpload func(ctx context.Context, spans []*cloudtrace.Span)
39-
onUploadLock sync.Mutex
33+
spanData *validation.SpanData
34+
delay time.Duration
35+
delayLock sync.Mutex
36+
onUpload func(ctx context.Context, spans []*cloudtrace.Span)
37+
onUploadLock sync.Mutex
4038
}
4139

4240
// NewMockTraceServer creates a new MockTraceServer and returns a pointer to it.
4341
func NewMockTraceServer() *MockTraceServer {
4442
uploadedSpanNames := make(map[string]struct{})
45-
return &MockTraceServer{uploadedSpanNames: uploadedSpanNames}
43+
spanData := &validation.SpanData{
44+
UploadedSpanNames: uploadedSpanNames,
45+
}
46+
return &MockTraceServer{spanData: spanData}
4647
}
4748

4849
// BatchWriteSpans creates and stores a list of spans on the server.
50+
// If ANY of the spans in the request are invalid, ALL spans will be dropped.
4951
func (s *MockTraceServer) BatchWriteSpans(ctx context.Context, req *cloudtrace.BatchWriteSpansRequest) (*empty.Empty, error) {
5052
if err := validation.ValidateProjectName(req.Name); err != nil {
5153
return nil, err
5254
}
53-
if err := validation.ValidateSpans("BatchWriteSpans", req.Spans...); err != nil {
55+
56+
if err := validation.Delay(ctx, s.delay); err != nil {
5457
return nil, err
5558
}
59+
5660
if s.onUpload != nil {
5761
s.onUpload(ctx, req.Spans)
5862
}
59-
if err := validation.Delay(ctx, s.delay); err != nil {
60-
return nil, err
61-
}
62-
s.uploadedSpansLock.Lock()
63-
defer s.uploadedSpansLock.Unlock()
64-
if err := validation.AddSpans(&s.uploadedSpans, s.uploadedSpanNames, req.Spans...); err != nil {
63+
64+
s.spanData.Mutex.Lock()
65+
defer s.spanData.Mutex.Unlock()
66+
if err := validation.ValidateSpans("BatchWriteSpans", s.spanData, req.Spans...); err != nil {
6567
return nil, err
6668
}
69+
70+
validation.AddSpans(s.spanData, req.Spans...)
71+
6772
return &empty.Empty{}, nil
6873
}
6974

70-
// CreateSpan creates and stores a single span on the server.
75+
// CreateSpan validates a single span, and returns the Span proto.
7176
func (s *MockTraceServer) CreateSpan(ctx context.Context, span *cloudtrace.Span) (*cloudtrace.Span, error) {
72-
if err := validation.ValidateSpans("CreateSpan", span); err != nil {
77+
// Validate the span and add to result table.
78+
s.spanData.Mutex.Lock()
79+
defer s.spanData.Mutex.Unlock()
80+
if err := validation.ValidateSpans("CreateSpan", s.spanData, span); err != nil {
7381
return nil, err
7482
}
7583
return span, nil
@@ -78,26 +86,26 @@ func (s *MockTraceServer) CreateSpan(ctx context.Context, span *cloudtrace.Span)
7886
// GetNumSpans returns the number of spans currently stored on the server.
7987
// Used by the library implementation to avoid having to make a network call.
8088
func (s *MockTraceServer) GetNumSpans() int {
81-
s.uploadedSpansLock.Lock()
82-
defer s.uploadedSpansLock.Unlock()
83-
return len(s.uploadedSpans)
89+
s.spanData.Mutex.RLock()
90+
defer s.spanData.Mutex.RUnlock()
91+
return len(s.spanData.UploadedSpans)
8492
}
8593

8694
// ListSpans returns a list of all the spans currently stored on the server.
8795
func (s *MockTraceServer) ListSpans(ctx context.Context, req *empty.Empty) (*mocktrace.ListSpansResponse, error) {
88-
s.uploadedSpansLock.Lock()
89-
defer s.uploadedSpansLock.Unlock()
96+
s.spanData.Mutex.RLock()
97+
defer s.spanData.Mutex.RUnlock()
9098
return &mocktrace.ListSpansResponse{
91-
Spans: s.uploadedSpans,
99+
Spans: s.spanData.UploadedSpans,
92100
}, nil
93101
}
94102

95103
// GetSpan returns the span that was stored in memory at the given index.
96104
// If the index is out of bounds, nil is returned.
97105
func (s *MockTraceServer) GetSpan(index int) *cloudtrace.Span {
98-
s.uploadedSpansLock.Lock()
99-
defer s.uploadedSpansLock.Unlock()
100-
span := validation.AccessSpan(index, s.uploadedSpans)
106+
s.spanData.Mutex.RLock()
107+
defer s.spanData.Mutex.RUnlock()
108+
span := validation.AccessSpan(index, s.spanData.UploadedSpans)
101109
return span
102110
}
103111

@@ -114,3 +122,7 @@ func (s *MockTraceServer) SetOnUpload(onUpload func(ctx context.Context, spans [
114122
defer s.onUploadLock.Unlock()
115123
s.onUpload = onUpload
116124
}
125+
126+
func (s *MockTraceServer) ResultTable() []*cloudtrace.Span {
127+
return s.spanData.SpansSummary
128+
}

0 commit comments

Comments
 (0)