github.com/GoogleContainerTools/skaffold/v2@v2.13.2/integration/rpc_test.go (about) 1 /* 2 Copyright 2019 The Skaffold Authors 3 4 Licensed under the Apache License, Version 2.0 (the "License"); 5 you may not use this file except in compliance with the License. 6 You may obtain a copy of the License at 7 8 http://www.apache.org/licenses/LICENSE-2.0 9 10 Unless required by applicable law or agreed to in writing, software 11 distributed under the License is distributed on an "AS IS" BASIS, 12 WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 See the License for the specific language governing permissions and 14 limitations under the License. 15 */ 16 17 package integration 18 19 import ( 20 "context" 21 "fmt" 22 "io" 23 "math/rand" 24 "net" 25 "net/http" 26 "strconv" 27 "strings" 28 "testing" 29 "time" 30 31 //nolint:golint,staticcheck 32 "github.com/golang/protobuf/jsonpb" 33 "github.com/golang/protobuf/ptypes/empty" 34 "google.golang.org/grpc" 35 "google.golang.org/protobuf/types/known/emptypb" 36 37 "github.com/GoogleContainerTools/skaffold/v2/integration/skaffold" 38 event "github.com/GoogleContainerTools/skaffold/v2/pkg/skaffold/event/v2" 39 "github.com/GoogleContainerTools/skaffold/v2/proto/v1" 40 protoV2 "github.com/GoogleContainerTools/skaffold/v2/proto/v2" 41 "github.com/GoogleContainerTools/skaffold/v2/testutil" 42 ) 43 44 var ( 45 connectionRetries = 5 46 readRetries = 20 47 numLogEntries = 7 48 waitTime = 1 * time.Second 49 ) 50 51 func TestEnableRPCFlagDeprecation(t *testing.T) { 52 MarkIntegrationTest(t, CanRunWithoutGcp) 53 rpcPort := randomPort() 54 out, err := skaffold.Build("--enable-rpc", "--rpc-port", rpcPort).InDir("testdata/build").RunWithCombinedOutput(t) 55 testutil.CheckError(t, false, err) 56 testutil.CheckContains(t, "Flag --enable-rpc has been deprecated", string(out)) 57 58 rpcPort = randomPort() 59 out, err = skaffold.Build("--rpc-port", rpcPort).InDir("testdata/build").RunWithCombinedOutput(t) 60 testutil.CheckError(t, false, err) 61 testutil.CheckNotContains(t, "Flag --enable-rpc has been deprecated", string(out)) 62 } 63 64 func TestEventsRPC(t *testing.T) { 65 MarkIntegrationTest(t, CanRunWithoutGcp) 66 67 rpcAddr := randomPort() 68 setupSkaffoldWithArgs(t, "--rpc-port", rpcAddr, "--status-check=false") 69 70 // start a grpc client and make sure we can connect properly 71 var ( 72 conn *grpc.ClientConn 73 err error 74 client proto.SkaffoldServiceClient 75 ) 76 77 // connect to the skaffold grpc server 78 for i := 0; i < connectionRetries; i++ { 79 conn, err = grpc.Dial(fmt.Sprintf(":%s", rpcAddr), grpc.WithInsecure()) 80 if err != nil { 81 t.Logf("unable to establish skaffold grpc connection: retrying...") 82 time.Sleep(waitTime) 83 continue 84 } 85 defer conn.Close() 86 87 client = proto.NewSkaffoldServiceClient(conn) 88 break 89 } 90 91 if client == nil { 92 t.Fatalf("error establishing skaffold grpc connection") 93 } 94 95 ctx, ctxCancel := context.WithCancel(context.Background()) 96 defer ctxCancel() 97 98 // read the event log stream from the skaffold grpc server 99 var stream proto.SkaffoldService_EventsClient 100 for i := 0; i < readRetries; i++ { 101 stream, err = client.Events(ctx, &empty.Empty{}) 102 if err == nil { 103 break 104 } 105 t.Logf("waiting for connection...") 106 time.Sleep(waitTime) 107 } 108 if stream == nil { 109 t.Fatalf("error retrieving event log: %v\n", err) 110 } 111 112 // read a preset number of entries from the event log 113 var logEntries []*proto.LogEntry 114 entriesReceived := 0 115 for { 116 entry, err := stream.Recv() 117 if err != nil { 118 t.Errorf("error receiving entry from stream: %s", err) 119 } 120 121 if entry != nil { 122 logEntries = append(logEntries, entry) 123 entriesReceived++ 124 } 125 if entriesReceived == numLogEntries { 126 break 127 } 128 } 129 metaEntries, buildEntries, deployEntries, devLoopEntries := 0, 0, 0, 0 130 for _, entry := range logEntries { 131 switch entry.Event.GetEventType().(type) { 132 case *proto.Event_MetaEvent: 133 metaEntries++ 134 t.Logf("meta event %d: %v", metaEntries, entry.Event) 135 case *proto.Event_BuildEvent: 136 buildEntries++ 137 t.Logf("build event %d: %v", buildEntries, entry.Event) 138 case *proto.Event_DeployEvent: 139 deployEntries++ 140 t.Logf("deploy event %d: %v", deployEntries, entry.Event) 141 case *proto.Event_DevLoopEvent: 142 devLoopEntries++ 143 t.Logf("devloop event event %d: %v", devLoopEntries, entry.Event) 144 default: 145 t.Logf("unknown event: %v", entry.Event) 146 } 147 } 148 // make sure we have exactly 1 meta entry, 2 deploy entries and 2 build entries and 2 devLoopEntries 149 testutil.CheckDeepEqual(t, 1, metaEntries) 150 testutil.CheckDeepEqual(t, 2, deployEntries) 151 testutil.CheckDeepEqual(t, 2, buildEntries) 152 testutil.CheckDeepEqual(t, 2, devLoopEntries) 153 } 154 155 func TestEventLogHTTP(t *testing.T) { 156 tests := []struct { 157 description string 158 endpoint string 159 }{ 160 { 161 // TODO deprecate (https://github.com/GoogleContainerTools/skaffold/issues/3168) 162 description: "/v1/event_log", 163 endpoint: "/v1/event_log", 164 }, 165 { 166 description: "/v1/events", 167 endpoint: "/v1/events", 168 }, 169 } 170 for _, test := range tests { 171 t.Run(test.description, func(t *testing.T) { 172 MarkIntegrationTest(t, CanRunWithoutGcp) 173 httpAddr := randomPort() 174 setupSkaffoldWithArgs(t, "--rpc-http-port", httpAddr, "--status-check=false") 175 time.Sleep(500 * time.Millisecond) // give skaffold time to process all events 176 177 httpResponse, err := http.Get(fmt.Sprintf("http://localhost:%s%s", httpAddr, test.endpoint)) 178 if err != nil { 179 t.Fatalf("error connecting to gRPC REST API: %s", err.Error()) 180 } 181 defer httpResponse.Body.Close() 182 183 numEntries := 0 184 var logEntries []*proto.LogEntry 185 for { 186 e := make([]byte, 1024) 187 l, err := httpResponse.Body.Read(e) 188 if err != nil { 189 t.Errorf("error reading body from http response: %s", err.Error()) 190 } 191 e = e[0:l] // remove empty bytes from slice 192 193 // sometimes reads can encompass multiple log entries, since Read() doesn't count newlines as EOF. 194 readEntries := strings.Split(string(e), "\n") 195 for _, entryStr := range readEntries { 196 if entryStr == "" { 197 continue 198 } 199 entry := new(proto.LogEntry) 200 // the HTTP wrapper sticks the proto messages into a map of "result" -> message. 201 // attempting to JSON unmarshal drops necessary proto information, so we just manually 202 // strip the string off the response and unmarshal directly to the proto message 203 entryStr = strings.Replace(entryStr, "{\"result\":", "", 1) 204 entryStr = entryStr[:len(entryStr)-1] 205 if err := jsonpb.UnmarshalString(entryStr, entry); err != nil { 206 t.Errorf("error converting http response %s to proto: %s", entryStr, err.Error()) 207 } 208 numEntries++ 209 logEntries = append(logEntries, entry) 210 } 211 if numEntries >= numLogEntries { 212 break 213 } 214 } 215 216 metaEntries, buildEntries, deployEntries, devLoopEntries := 0, 0, 0, 0 217 for _, entry := range logEntries { 218 switch entry.Event.GetEventType().(type) { 219 case *proto.Event_MetaEvent: 220 metaEntries++ 221 t.Logf("meta event %d: %v", metaEntries, entry.Event) 222 case *proto.Event_BuildEvent: 223 buildEntries++ 224 t.Logf("build event %d: %v", buildEntries, entry.Event) 225 case *proto.Event_DeployEvent: 226 deployEntries++ 227 t.Logf("deploy event %d: %v", deployEntries, entry.Event) 228 case *proto.Event_DevLoopEvent: 229 devLoopEntries++ 230 t.Logf("devloop event event %d: %v", devLoopEntries, entry.Event) 231 default: 232 t.Logf("unknown event: %v", entry.Event) 233 } 234 } 235 // make sure we have exactly 1 meta entry, 2 deploy entries, 2 build entries and 2 devLoopEntries 236 testutil.CheckDeepEqual(t, 1, metaEntries) 237 testutil.CheckDeepEqual(t, 2, deployEntries) 238 testutil.CheckDeepEqual(t, 2, buildEntries) 239 testutil.CheckDeepEqual(t, 2, devLoopEntries) 240 }) 241 } 242 } 243 244 func TestGetStateRPC(t *testing.T) { 245 MarkIntegrationTest(t, CanRunWithoutGcp) 246 247 rpcAddr := randomPort() 248 // start a skaffold dev loop on an example 249 setupSkaffoldWithArgs(t, "--rpc-port", rpcAddr) 250 251 // start a grpc client and make sure we can connect properly 252 var ( 253 conn *grpc.ClientConn 254 err error 255 client proto.SkaffoldServiceClient 256 ) 257 258 for i := 0; i < connectionRetries; i++ { 259 conn, err = grpc.Dial(fmt.Sprintf(":%s", rpcAddr), grpc.WithInsecure()) 260 if err != nil { 261 t.Logf("unable to establish skaffold grpc connection: retrying...") 262 time.Sleep(waitTime) 263 continue 264 } 265 defer conn.Close() 266 267 client = proto.NewSkaffoldServiceClient(conn) 268 break 269 } 270 271 if client == nil { 272 t.Fatalf("error establishing skaffold grpc connection") 273 } 274 ctx, ctxCancel := context.WithCancel(context.Background()) 275 defer ctxCancel() 276 277 // try a few times and wait around until we see the build is complete, or fail. 278 success := false 279 var grpcState *proto.State 280 for i := 0; i < readRetries; i++ { 281 grpcState = retrieveRPCState(ctx, t, client) 282 if grpcState != nil && checkBuildAndDeployComplete(grpcState) { 283 success = true 284 break 285 } 286 time.Sleep(waitTime) 287 } 288 if !success { 289 t.Errorf("skaffold build or deploy not complete. state: %+v\n", grpcState) 290 } 291 } 292 293 func TestGetStateHTTP(t *testing.T) { 294 MarkIntegrationTest(t, CanRunWithoutGcp) 295 296 httpAddr := randomPort() 297 setupSkaffoldWithArgs(t, "--rpc-http-port", httpAddr) 298 time.Sleep(3 * time.Second) // give skaffold time to process all events 299 300 success := false 301 var httpState *proto.State 302 for i := 0; i < readRetries; i++ { 303 httpState = retrieveHTTPState(t, httpAddr) 304 if checkBuildAndDeployComplete(httpState) { 305 success = true 306 break 307 } 308 time.Sleep(waitTime) 309 } 310 if !success { 311 t.Errorf("skaffold build or deploy not complete. state: %+v\n", httpState) 312 } 313 } 314 315 func retrieveRPCState(ctx context.Context, t *testing.T, client proto.SkaffoldServiceClient) (state *proto.State) { 316 var err error 317 for attempts := 0; attempts < connectionRetries; attempts++ { 318 state, err = client.GetState(ctx, &empty.Empty{}) 319 if err == nil { 320 return 321 } 322 t.Logf("waiting for connection...") 323 time.Sleep(waitTime) 324 } 325 t.Fatalf("error retrieving state: %v\n", err) 326 return 327 } 328 329 func retrieveHTTPState(t *testing.T, httpAddr string) *proto.State { 330 httpState := new(proto.State) 331 332 // retrieve the state via HTTP as well, and verify the result is the same 333 httpResponse, err := http.Get(fmt.Sprintf("http://localhost:%s/v1/state", httpAddr)) 334 if err != nil { 335 t.Fatalf("error connecting to gRPC REST API: %s", err.Error()) 336 } 337 defer httpResponse.Body.Close() 338 339 b, err := io.ReadAll(httpResponse.Body) 340 if err != nil { 341 t.Errorf("error reading body from http response: %s", err.Error()) 342 } 343 if err := jsonpb.UnmarshalString(string(b), httpState); err != nil { 344 t.Errorf("error converting http response to proto: %s", err.Error()) 345 } 346 return httpState 347 } 348 349 func setupSkaffoldWithArgs(t *testing.T, args ...string) { 350 Run(t, "testdata/dev", "sh", "-c", "echo foo > foo") 351 352 // Run skaffold build first to fail quickly on a build failure 353 skaffold.Build().InDir("testdata/dev").RunOrFail(t) 354 355 // start a skaffold dev loop on an example 356 ns, _ := SetupNamespace(t) 357 358 skaffold.Dev(append([]string{"--cache-artifacts=false"}, args...)...).InDir("testdata/dev").InNs(ns.Name).RunBackground(t) 359 360 t.Cleanup(func() { 361 Run(t, "testdata/dev", "rm", "foo") 362 }) 363 } 364 365 // randomPort chooses a random port 366 func randomPort() string { 367 l, err := net.Listen("tcp", "localhost:0") 368 if err != nil { 369 // listening for port 0 should never error but just in case 370 return strconv.Itoa(1024 + rand.Intn(65536-1024)) 371 } 372 373 p := l.Addr().(*net.TCPAddr).Port 374 l.Close() 375 return strconv.Itoa(p) 376 } 377 378 func checkBuildAndDeployComplete(state *proto.State) bool { 379 if state.BuildState == nil || state.DeployState == nil { 380 return false 381 } 382 383 for _, a := range state.BuildState.Artifacts { 384 if a != event.Complete { 385 return false 386 } 387 } 388 389 return state.DeployState.Status == event.Complete 390 } 391 392 func apiEvents(t *testing.T, rpcAddr string) (proto.SkaffoldServiceClient, chan *proto.LogEntry) { // nolint 393 client := setupRPCClient(t, rpcAddr) 394 395 stream, err := readEventAPIStream(client, t, readRetries) 396 if stream == nil { 397 t.Fatalf("error retrieving event log: %v\n", err) 398 } 399 400 // read entries from the log 401 entries := make(chan *proto.LogEntry) 402 go func() { 403 for { 404 entry, _ := stream.Recv() 405 if entry != nil { 406 entries <- entry 407 } 408 } 409 }() 410 411 return client, entries 412 } 413 414 func readEventAPIStream(client proto.SkaffoldServiceClient, t *testing.T, retries int) (proto.SkaffoldService_EventLogClient, error) { 415 t.Helper() 416 // read the event log stream from the skaffold grpc server 417 var stream proto.SkaffoldService_EventLogClient 418 var err error 419 for i := 0; i < retries; i++ { 420 stream, err = client.EventLog(context.Background(), grpc.WaitForReady(true)) 421 if err == nil { 422 break 423 } 424 t.Logf("waiting for connection...") 425 time.Sleep(waitTime) 426 } 427 return stream, err 428 } 429 430 func setupRPCClient(t *testing.T, port string) proto.SkaffoldServiceClient { 431 // start a grpc client 432 var ( 433 conn *grpc.ClientConn 434 err error 435 client proto.SkaffoldServiceClient 436 ) 437 438 // connect to the skaffold grpc server 439 for i := 0; i < connectionRetries; i++ { 440 conn, err = grpc.Dial(fmt.Sprintf(":%s", port), grpc.WithInsecure(), grpc.WithBackoffMaxDelay(10*time.Second)) 441 if err != nil { 442 t.Logf("unable to establish skaffold grpc connection: retrying...") 443 time.Sleep(waitTime) 444 continue 445 } 446 447 client = proto.NewSkaffoldServiceClient(conn) 448 break 449 } 450 451 if client == nil { 452 t.Fatalf("error establishing skaffold grpc connection") 453 } 454 455 t.Cleanup(func() { conn.Close() }) 456 457 return client 458 } 459 460 func setupV2RPCClient(t *testing.T, port string) protoV2.SkaffoldV2ServiceClient { 461 // start a grpc client 462 var ( 463 conn *grpc.ClientConn 464 err error 465 client protoV2.SkaffoldV2ServiceClient 466 ) 467 468 // connect to the skaffold grpc server 469 for i := 0; i < connectionRetries; i++ { 470 conn, err = grpc.Dial(fmt.Sprintf(":%s", port), grpc.WithInsecure(), grpc.WithBackoffMaxDelay(10*time.Second)) 471 if err != nil { 472 t.Logf("unable to establish skaffold grpc connection: retrying...") 473 time.Sleep(waitTime) 474 continue 475 } 476 477 client = protoV2.NewSkaffoldV2ServiceClient(conn) 478 break 479 } 480 481 if client == nil { 482 t.Fatalf("error establishing skaffold grpc connection") 483 } 484 485 t.Cleanup(func() { conn.Close() }) 486 487 return client 488 } 489 490 func readV2EventAPIStream(client protoV2.SkaffoldV2ServiceClient, t *testing.T, retries int) (protoV2.SkaffoldV2Service_EventsClient, error) { 491 t.Helper() 492 // read the event log stream from the skaffold grpc server 493 var stream protoV2.SkaffoldV2Service_EventsClient 494 var err error 495 var protoReq emptypb.Empty 496 for i := 0; i < retries; i++ { 497 stream, err = client.Events(context.Background(), &protoReq, grpc.WaitForReady(true)) 498 if err == nil { 499 break 500 } 501 t.Logf("waiting for connection...") 502 time.Sleep(waitTime) 503 } 504 return stream, err 505 } 506 507 func v2apiEvents(t *testing.T, rpcAddr string) (protoV2.SkaffoldV2ServiceClient, chan *protoV2.Event) { // nolint 508 client := setupV2RPCClient(t, rpcAddr) 509 510 stream, err := readV2EventAPIStream(client, t, readRetries) 511 if stream == nil { 512 t.Fatalf("error retrieving event log: %v\n", err) 513 } 514 515 // read entries from the log 516 entries := make(chan *protoV2.Event) 517 go func() { 518 for { 519 entry, _ := stream.Recv() 520 if entry != nil { 521 entries <- entry 522 } 523 } 524 }() 525 526 return client, entries 527 } 528 529 func waitForV2Event(timeout time.Duration, entries chan *protoV2.Event, condition func(event2 *protoV2.Event) bool) error { 530 ctx, cancelTimeout := context.WithTimeout(context.Background(), timeout) 531 defer cancelTimeout() 532 for { 533 select { 534 case <-ctx.Done(): 535 return fmt.Errorf("timed out waiting for condition on log entry") 536 case ev := <-entries: 537 if condition(ev) { 538 return nil 539 } 540 } 541 } 542 }