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  }