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 integrationTest = true 84 ts := testutil.TokenSource(ctx, logging.AdminScope) 85 if ts == nil { 86 log.Fatal("The project key must be set. See CONTRIBUTING.md for details") 87 } 88 log.Printf("running integration tests with project %s", testProjectID) 89 newClient = func(ctx context.Context, projectID string) *Client { 90 c, err := NewClient(ctx, projectID, option.WithTokenSource(ts), 91 option.WithGRPCDialOption(grpc.WithBlock())) 92 if err != nil { 93 log.Fatalf("creating prod client: %v", err) 94 } 95 return c 96 } 97 } 98 client = newClient(ctx, testProjectID) 99 initMetrics(ctx) 100 cleanup := initSinks(ctx) 101 exit := m.Run() 102 cleanup() 103 client.Close() 104 os.Exit(exit) 105} 106 107// EntryIterator and DeleteLog are tested in the logging package. 108 109func TestClientClose(t *testing.T) { 110 c := newClient(context.Background(), testProjectID) 111 if err := c.Close(); err != nil { 112 t.Errorf("want got %v, want nil", err) 113 } 114} 115 116func TestFromLogEntry(t *testing.T) { 117 now := time.Now() 118 res := &mrpb.MonitoredResource{Type: "global"} 119 ts, err := ptypes.TimestampProto(now) 120 if err != nil { 121 t.Fatal(err) 122 } 123 logEntry := logpb.LogEntry{ 124 LogName: "projects/PROJECT_ID/logs/LOG_ID", 125 Resource: res, 126 Payload: &logpb.LogEntry_TextPayload{TextPayload: "hello"}, 127 Timestamp: ts, 128 Severity: logtypepb.LogSeverity_INFO, 129 InsertId: "123", 130 HttpRequest: &logtypepb.HttpRequest{ 131 RequestMethod: "GET", 132 RequestUrl: "http:://example.com/path?q=1", 133 RequestSize: 100, 134 Status: 200, 135 ResponseSize: 25, 136 Latency: &durpb.Duration{Seconds: 100}, 137 UserAgent: "user-agent", 138 RemoteIp: "127.0.0.1", 139 Referer: "referer", 140 CacheHit: true, 141 CacheValidatedWithOriginServer: true, 142 }, 143 Labels: map[string]string{ 144 "a": "1", 145 "b": "two", 146 "c": "true", 147 }, 148 SourceLocation: &logpb.LogEntrySourceLocation{ 149 File: "some_file.go", 150 Line: 1, 151 Function: "someFunction", 152 }, 153 } 154 u, err := url.Parse("http:://example.com/path?q=1") 155 if err != nil { 156 t.Fatal(err) 157 } 158 want := &logging.Entry{ 159 LogName: "projects/PROJECT_ID/logs/LOG_ID", 160 Resource: res, 161 Timestamp: now.In(time.UTC), 162 Severity: logging.Info, 163 Payload: "hello", 164 Labels: map[string]string{ 165 "a": "1", 166 "b": "two", 167 "c": "true", 168 }, 169 InsertID: "123", 170 HTTPRequest: &logging.HTTPRequest{ 171 Request: &http.Request{ 172 Method: "GET", 173 URL: u, 174 Header: map[string][]string{ 175 "User-Agent": {"user-agent"}, 176 "Referer": {"referer"}, 177 }, 178 }, 179 RequestSize: 100, 180 Status: 200, 181 ResponseSize: 25, 182 Latency: 100 * time.Second, 183 RemoteIP: "127.0.0.1", 184 CacheHit: true, 185 CacheValidatedWithOriginServer: true, 186 }, 187 SourceLocation: &logpb.LogEntrySourceLocation{ 188 File: "some_file.go", 189 Line: 1, 190 Function: "someFunction", 191 }, 192 } 193 got, err := fromLogEntry(&logEntry) 194 if err != nil { 195 t.Fatal(err) 196 } 197 if diff := testutil.Diff(got, want, cmpopts.IgnoreUnexported(http.Request{})); diff != "" { 198 t.Errorf("FullEntry:\n%s", diff) 199 } 200 201 // Proto payload. 202 alog := &audit.AuditLog{ 203 ServiceName: "svc", 204 MethodName: "method", 205 ResourceName: "shelves/S/books/B", 206 } 207 any, err := ptypes.MarshalAny(alog) 208 if err != nil { 209 t.Fatal(err) 210 } 211 logEntry = logpb.LogEntry{ 212 LogName: "projects/PROJECT_ID/logs/LOG_ID", 213 Resource: res, 214 Timestamp: ts, 215 Payload: &logpb.LogEntry_ProtoPayload{ProtoPayload: any}, 216 } 217 got, err = fromLogEntry(&logEntry) 218 if err != nil { 219 t.Fatal(err) 220 } 221 if !ltesting.PayloadEqual(got.Payload, alog) { 222 t.Errorf("got %+v, want %+v", got.Payload, alog) 223 } 224 225 // JSON payload. 226 jstruct := &structpb.Struct{Fields: map[string]*structpb.Value{ 227 "f": {Kind: &structpb.Value_NumberValue{NumberValue: 3.1}}, 228 }} 229 logEntry = logpb.LogEntry{ 230 LogName: "projects/PROJECT_ID/logs/LOG_ID", 231 Resource: res, 232 Timestamp: ts, 233 Payload: &logpb.LogEntry_JsonPayload{JsonPayload: jstruct}, 234 } 235 got, err = fromLogEntry(&logEntry) 236 if err != nil { 237 t.Fatal(err) 238 } 239 if !ltesting.PayloadEqual(got.Payload, jstruct) { 240 t.Errorf("got %+v, want %+v", got.Payload, jstruct) 241 } 242} 243 244func TestListLogEntriesRequest(t *testing.T) { 245 for _, test := range []struct { 246 opts []EntriesOption 247 resourceNames []string 248 filter string 249 orderBy string 250 }{ 251 // Default is client's project ID, empty filter and orderBy. 252 {nil, []string{"projects/PROJECT_ID"}, "", ""}, 253 {[]EntriesOption{NewestFirst(), Filter("f")}, 254 []string{"projects/PROJECT_ID"}, "f", "timestamp desc"}, 255 {[]EntriesOption{ProjectIDs([]string{"foo"})}, 256 []string{"projects/foo"}, "", ""}, 257 {[]EntriesOption{ResourceNames([]string{"folders/F", "organizations/O"})}, 258 []string{"folders/F", "organizations/O"}, "", ""}, 259 {[]EntriesOption{NewestFirst(), Filter("f"), ProjectIDs([]string{"foo"})}, 260 []string{"projects/foo"}, "f", "timestamp desc"}, 261 {[]EntriesOption{NewestFirst(), Filter("f"), ProjectIDs([]string{"foo"})}, 262 []string{"projects/foo"}, "f", "timestamp desc"}, 263 // If there are repeats, last one wins. 264 {[]EntriesOption{NewestFirst(), Filter("no"), ProjectIDs([]string{"foo"}), Filter("f")}, 265 []string{"projects/foo"}, "f", "timestamp desc"}, 266 } { 267 got := listLogEntriesRequest("projects/PROJECT_ID", test.opts) 268 want := &logpb.ListLogEntriesRequest{ 269 ResourceNames: test.resourceNames, 270 Filter: test.filter, 271 OrderBy: test.orderBy, 272 } 273 if !proto.Equal(got, want) { 274 t.Errorf("%v:\ngot %v\nwant %v", test.opts, got, want) 275 } 276 } 277} 278