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 15// TODO(jba): test that OnError is getting called appropriately. 16 17package logadmin 18 19import ( 20 "context" 21 "flag" 22 "log" 23 "net/http" 24 "net/url" 25 "os" 26 "testing" 27 "time" 28 29 "cloud.google.com/go/internal/testutil" 30 "cloud.google.com/go/logging" 31 ltesting "cloud.google.com/go/logging/internal/testing" 32 "github.com/golang/protobuf/proto" 33 "github.com/golang/protobuf/ptypes" 34 durpb "github.com/golang/protobuf/ptypes/duration" 35 structpb "github.com/golang/protobuf/ptypes/struct" 36 "github.com/google/go-cmp/cmp/cmpopts" 37 "google.golang.org/api/option" 38 mrpb "google.golang.org/genproto/googleapis/api/monitoredres" 39 audit "google.golang.org/genproto/googleapis/cloud/audit" 40 logtypepb "google.golang.org/genproto/googleapis/logging/type" 41 logpb "google.golang.org/genproto/googleapis/logging/v2" 42 "google.golang.org/grpc" 43) 44 45var ( 46 client *Client 47 testProjectID string 48) 49 50var ( 51 // If true, this test is using the production service, not a fake. 52 integrationTest bool 53 54 newClient func(ctx context.Context, projectID string) *Client 55) 56 57func TestMain(m *testing.M) { 58 flag.Parse() // needed for testing.Short() 59 ctx := context.Background() 60 testProjectID = testutil.ProjID() 61 if testProjectID == "" || testing.Short() { 62 integrationTest = false 63 if testProjectID != "" { 64 log.Print("Integration tests skipped in short mode (using fake instead)") 65 } 66 testProjectID = "PROJECT_ID" 67 addr, err := ltesting.NewServer() 68 if err != nil { 69 log.Fatalf("creating fake server: %v", err) 70 } 71 newClient = func(ctx context.Context, projectID string) *Client { 72 conn, err := grpc.Dial(addr, grpc.WithInsecure(), grpc.WithBlock()) 73 if err != nil { 74 log.Fatalf("dialing %q: %v", addr, err) 75 } 76 c, err := NewClient(ctx, projectID, option.WithGRPCConn(conn)) 77 if err != nil { 78 log.Fatalf("creating client for fake at %q: %v", addr, err) 79 } 80 return c 81 } 82 } else { 83 // TODO(enocom): Delete this once we can get these tests to reliably pass. 84 return 85 86 integrationTest = true 87 ts := testutil.TokenSource(ctx, logging.AdminScope) 88 if ts == nil { 89 log.Fatal("The project key must be set. See CONTRIBUTING.md for details") 90 } 91 log.Printf("running integration tests with project %s", testProjectID) 92 newClient = func(ctx context.Context, projectID string) *Client { 93 c, err := NewClient(ctx, projectID, option.WithTokenSource(ts), 94 option.WithGRPCDialOption(grpc.WithBlock())) 95 if err != nil { 96 log.Fatalf("creating prod client: %v", err) 97 } 98 return c 99 } 100 } 101 client = newClient(ctx, testProjectID) 102 initMetrics(ctx) 103 cleanup := initSinks(ctx) 104 exit := m.Run() 105 cleanup() 106 client.Close() 107 os.Exit(exit) 108} 109 110// EntryIterator and DeleteLog are tested in the logging package. 111 112func TestClientClose(t *testing.T) { 113 c := newClient(context.Background(), testProjectID) 114 if err := c.Close(); err != nil { 115 t.Errorf("want got %v, want nil", err) 116 } 117} 118 119func TestFromLogEntry(t *testing.T) { 120 now := time.Now() 121 res := &mrpb.MonitoredResource{Type: "global"} 122 ts, err := ptypes.TimestampProto(now) 123 if err != nil { 124 t.Fatal(err) 125 } 126 logEntry := logpb.LogEntry{ 127 LogName: "projects/PROJECT_ID/logs/LOG_ID", 128 Resource: res, 129 Payload: &logpb.LogEntry_TextPayload{TextPayload: "hello"}, 130 Timestamp: ts, 131 Severity: logtypepb.LogSeverity_INFO, 132 InsertId: "123", 133 HttpRequest: &logtypepb.HttpRequest{ 134 RequestMethod: "GET", 135 RequestUrl: "http:://example.com/path?q=1", 136 RequestSize: 100, 137 Status: 200, 138 ResponseSize: 25, 139 Latency: &durpb.Duration{Seconds: 100}, 140 UserAgent: "user-agent", 141 RemoteIp: "127.0.0.1", 142 Referer: "referer", 143 CacheHit: true, 144 CacheValidatedWithOriginServer: true, 145 }, 146 Labels: map[string]string{ 147 "a": "1", 148 "b": "two", 149 "c": "true", 150 }, 151 SourceLocation: &logpb.LogEntrySourceLocation{ 152 File: "some_file.go", 153 Line: 1, 154 Function: "someFunction", 155 }, 156 } 157 u, err := url.Parse("http:://example.com/path?q=1") 158 if err != nil { 159 t.Fatal(err) 160 } 161 want := &logging.Entry{ 162 LogName: "projects/PROJECT_ID/logs/LOG_ID", 163 Resource: res, 164 Timestamp: now.In(time.UTC), 165 Severity: logging.Info, 166 Payload: "hello", 167 Labels: map[string]string{ 168 "a": "1", 169 "b": "two", 170 "c": "true", 171 }, 172 InsertID: "123", 173 HTTPRequest: &logging.HTTPRequest{ 174 Request: &http.Request{ 175 Method: "GET", 176 URL: u, 177 Header: map[string][]string{ 178 "User-Agent": {"user-agent"}, 179 "Referer": {"referer"}, 180 }, 181 }, 182 RequestSize: 100, 183 Status: 200, 184 ResponseSize: 25, 185 Latency: 100 * time.Second, 186 RemoteIP: "127.0.0.1", 187 CacheHit: true, 188 CacheValidatedWithOriginServer: true, 189 }, 190 SourceLocation: &logpb.LogEntrySourceLocation{ 191 File: "some_file.go", 192 Line: 1, 193 Function: "someFunction", 194 }, 195 } 196 got, err := fromLogEntry(&logEntry) 197 if err != nil { 198 t.Fatal(err) 199 } 200 if diff := testutil.Diff(got, want, cmpopts.IgnoreUnexported(http.Request{})); diff != "" { 201 t.Errorf("FullEntry:\n%s", diff) 202 } 203 204 // Proto payload. 205 alog := &audit.AuditLog{ 206 ServiceName: "svc", 207 MethodName: "method", 208 ResourceName: "shelves/S/books/B", 209 } 210 any, err := ptypes.MarshalAny(alog) 211 if err != nil { 212 t.Fatal(err) 213 } 214 logEntry = logpb.LogEntry{ 215 LogName: "projects/PROJECT_ID/logs/LOG_ID", 216 Resource: res, 217 Timestamp: ts, 218 Payload: &logpb.LogEntry_ProtoPayload{ProtoPayload: any}, 219 } 220 got, err = fromLogEntry(&logEntry) 221 if err != nil { 222 t.Fatal(err) 223 } 224 if !ltesting.PayloadEqual(got.Payload, alog) { 225 t.Errorf("got %+v, want %+v", got.Payload, alog) 226 } 227 228 // JSON payload. 229 jstruct := &structpb.Struct{Fields: map[string]*structpb.Value{ 230 "f": {Kind: &structpb.Value_NumberValue{NumberValue: 3.1}}, 231 }} 232 logEntry = logpb.LogEntry{ 233 LogName: "projects/PROJECT_ID/logs/LOG_ID", 234 Resource: res, 235 Timestamp: ts, 236 Payload: &logpb.LogEntry_JsonPayload{JsonPayload: jstruct}, 237 } 238 got, err = fromLogEntry(&logEntry) 239 if err != nil { 240 t.Fatal(err) 241 } 242 if !ltesting.PayloadEqual(got.Payload, jstruct) { 243 t.Errorf("got %+v, want %+v", got.Payload, jstruct) 244 } 245 246 // No payload. 247 logEntry = logpb.LogEntry{ 248 LogName: "projects/PROJECT_ID/logs/LOG_ID", 249 Resource: res, 250 Timestamp: ts, 251 } 252 got, err = fromLogEntry(&logEntry) 253 if err != nil { 254 t.Fatal(err) 255 } 256 if !ltesting.PayloadEqual(got.Payload, nil) { 257 t.Errorf("got %+v, want %+v", got.Payload, nil) 258 } 259} 260 261func TestListLogEntriesRequest(t *testing.T) { 262 for _, test := range []struct { 263 opts []EntriesOption 264 resourceNames []string 265 filter string 266 orderBy string 267 }{ 268 // Default is client's project ID, empty filter and orderBy. 269 {nil, []string{"projects/PROJECT_ID"}, "", ""}, 270 {[]EntriesOption{NewestFirst(), Filter("f")}, 271 []string{"projects/PROJECT_ID"}, "f", "timestamp desc"}, 272 {[]EntriesOption{ProjectIDs([]string{"foo"})}, 273 []string{"projects/foo"}, "", ""}, 274 {[]EntriesOption{ResourceNames([]string{"folders/F", "organizations/O"})}, 275 []string{"folders/F", "organizations/O"}, "", ""}, 276 {[]EntriesOption{NewestFirst(), Filter("f"), ProjectIDs([]string{"foo"})}, 277 []string{"projects/foo"}, "f", "timestamp desc"}, 278 {[]EntriesOption{NewestFirst(), Filter("f"), ProjectIDs([]string{"foo"})}, 279 []string{"projects/foo"}, "f", "timestamp desc"}, 280 // If there are repeats, last one wins. 281 {[]EntriesOption{NewestFirst(), Filter("no"), ProjectIDs([]string{"foo"}), Filter("f")}, 282 []string{"projects/foo"}, "f", "timestamp desc"}, 283 } { 284 got := listLogEntriesRequest("projects/PROJECT_ID", test.opts) 285 want := &logpb.ListLogEntriesRequest{ 286 ResourceNames: test.resourceNames, 287 Filter: test.filter, 288 OrderBy: test.orderBy, 289 } 290 if !proto.Equal(got, want) { 291 t.Errorf("%v:\ngot %v\nwant %v", test.opts, got, want) 292 } 293 } 294} 295