golang.org/x/build@v0.0.0-20240506185731-218518f32b70/internal/coordinator/pool/ec2_test.go (about)

     1  // Copyright 2020 The Go Authors. All rights reserved.
     2  // Use of this source code is governed by a BSD-style
     3  // license that can be found in the LICENSE file.
     4  
     5  //go:build linux || darwin
     6  
     7  package pool
     8  
     9  import (
    10  	"context"
    11  	"errors"
    12  	"fmt"
    13  	"sort"
    14  	"testing"
    15  	"time"
    16  
    17  	"github.com/google/go-cmp/cmp"
    18  	"golang.org/x/build/buildenv"
    19  	"golang.org/x/build/buildlet"
    20  	"golang.org/x/build/dashboard"
    21  	"golang.org/x/build/internal/cloud"
    22  	"golang.org/x/build/internal/coordinator/pool/queue"
    23  	"golang.org/x/build/internal/spanlog"
    24  )
    25  
    26  func TestEC2BuildletGetBuildlet(t *testing.T) {
    27  	host := "host-type-x"
    28  
    29  	l := newLedger()
    30  	l.UpdateInstanceTypes([]*cloud.InstanceType{
    31  		// set to default gce type because there is no way to set the machine
    32  		// type from outside of the buildenv package.
    33  		{
    34  			Type: "e2-standard-16",
    35  			CPU:  16,
    36  		},
    37  	})
    38  	l.SetCPULimit(20)
    39  
    40  	bp := &EC2Buildlet{
    41  		buildletClient: &fakeEC2BuildletClient{
    42  			createVMRequestSuccess: true,
    43  			VMCreated:              true,
    44  			buildletCreated:        true,
    45  		},
    46  		buildEnv: &buildenv.Environment{},
    47  		ledger:   l,
    48  		hosts: map[string]*dashboard.HostConfig{
    49  			host: {
    50  				VMImage:        "ami-15",
    51  				ContainerImage: "bar-arm64:latest",
    52  				SSHUsername:    "foo",
    53  			},
    54  		},
    55  	}
    56  	_, err := bp.GetBuildlet(context.Background(), host, noopEventTimeLogger{}, new(queue.SchedItem))
    57  	if err != nil {
    58  		t.Errorf("EC2Buildlet.GetBuildlet(ctx, %q, %+v) = _, %s; want no error", host, noopEventTimeLogger{}, err)
    59  	}
    60  }
    61  
    62  func TestEC2BuildletGetBuildletError(t *testing.T) {
    63  	host := "host-type-x"
    64  	testCases := []struct {
    65  		desc           string
    66  		hostType       string
    67  		logger         Logger
    68  		ledger         *ledger
    69  		types          []*cloud.InstanceType
    70  		buildletClient ec2BuildletClient
    71  		hosts          map[string]*dashboard.HostConfig
    72  	}{
    73  		{
    74  			desc:     "invalid-host-type",
    75  			hostType: host,
    76  			ledger:   newLedger(),
    77  			types: []*cloud.InstanceType{
    78  				{
    79  					Type: "e2-highcpu-2",
    80  					CPU:  4,
    81  				},
    82  			},
    83  			hosts: map[string]*dashboard.HostConfig{
    84  				"wrong-host-type": {},
    85  			},
    86  			logger: noopEventTimeLogger{},
    87  			buildletClient: &fakeEC2BuildletClient{
    88  				createVMRequestSuccess: true,
    89  				VMCreated:              true,
    90  			},
    91  		},
    92  		{
    93  			desc:     "buildlet-client-failed-instance-created",
    94  			hostType: host,
    95  			ledger:   newLedger(),
    96  			types: []*cloud.InstanceType{
    97  				{
    98  					Type: "e2-highcpu-2",
    99  					CPU:  4,
   100  				},
   101  			},
   102  			hosts: map[string]*dashboard.HostConfig{
   103  				host: {},
   104  			},
   105  			logger: noopEventTimeLogger{},
   106  			buildletClient: &fakeEC2BuildletClient{
   107  				createVMRequestSuccess: false,
   108  				VMCreated:              false,
   109  			},
   110  		},
   111  		{
   112  			desc:     "buildlet-client-failed-instance-not-created",
   113  			hostType: host,
   114  			ledger:   newLedger(),
   115  			types: []*cloud.InstanceType{
   116  				{
   117  					Type: "e2-highcpu-2",
   118  					CPU:  4,
   119  				},
   120  			},
   121  			hosts: map[string]*dashboard.HostConfig{
   122  				host: {},
   123  			},
   124  			logger: noopEventTimeLogger{},
   125  			buildletClient: &fakeEC2BuildletClient{
   126  				createVMRequestSuccess: true,
   127  				VMCreated:              false,
   128  			},
   129  		},
   130  	}
   131  	for _, tt := range testCases {
   132  		t.Run(tt.desc, func(t *testing.T) {
   133  			bp := &EC2Buildlet{
   134  				buildletClient: tt.buildletClient,
   135  				buildEnv:       &buildenv.Environment{},
   136  				ledger:         tt.ledger,
   137  				hosts:          tt.hosts,
   138  			}
   139  			tt.ledger.UpdateInstanceTypes(tt.types)
   140  			_, gotErr := bp.GetBuildlet(context.Background(), tt.hostType, tt.logger, new(queue.SchedItem))
   141  			if gotErr == nil {
   142  				t.Errorf("EC2Buildlet.GetBuildlet(ctx, %q, %+v) = _, %s", tt.hostType, tt.logger, gotErr)
   143  			}
   144  		})
   145  	}
   146  }
   147  
   148  func TestEC2BuildletGetBuildletLogger(t *testing.T) {
   149  	host := "host-type-x"
   150  	testCases := []struct {
   151  		desc           string
   152  		buildletClient ec2BuildletClient
   153  		hostType       string
   154  		hosts          map[string]*dashboard.HostConfig
   155  		ledger         *ledger
   156  		types          []*cloud.InstanceType
   157  		wantLogs       []string
   158  		wantSpans      []string
   159  		wantSpansErr   []string
   160  	}{
   161  		{
   162  			desc:     "buildlet-client-failed-instance-create-request-failed",
   163  			hostType: host,
   164  			ledger:   newLedger(),
   165  			types: []*cloud.InstanceType{
   166  				{
   167  					Type: "e2-standard-8",
   168  					CPU:  8,
   169  				},
   170  			},
   171  			hosts: map[string]*dashboard.HostConfig{
   172  				host: {},
   173  			},
   174  			buildletClient: &fakeEC2BuildletClient{
   175  				createVMRequestSuccess: false,
   176  				VMCreated:              false,
   177  				buildletCreated:        false,
   178  			},
   179  			wantSpans:    []string{"create_ec2_instance", "awaiting_ec2_quota", "create_ec2_buildlet"},
   180  			wantSpansErr: []string{"create_ec2_buildlet", "create_ec2_instance"},
   181  		},
   182  		{
   183  			desc:     "buildlet-client-failed-instance-not-created",
   184  			hostType: host,
   185  			ledger:   newLedger(),
   186  			types: []*cloud.InstanceType{
   187  				{
   188  					Type: "e2-standard-8",
   189  					CPU:  8,
   190  				},
   191  			},
   192  			hosts: map[string]*dashboard.HostConfig{
   193  				host: {},
   194  			},
   195  			buildletClient: &fakeEC2BuildletClient{
   196  				createVMRequestSuccess: true,
   197  				VMCreated:              false,
   198  				buildletCreated:        false,
   199  			},
   200  			wantSpans:    []string{"create_ec2_instance", "awaiting_ec2_quota", "create_ec2_buildlet"},
   201  			wantSpansErr: []string{"create_ec2_buildlet", "create_ec2_instance"},
   202  		},
   203  		{
   204  			desc:     "buildlet-client-failed-instance-created",
   205  			hostType: host,
   206  			ledger:   newLedger(),
   207  			types: []*cloud.InstanceType{
   208  				{
   209  					Type: "e2-standard-8",
   210  					CPU:  8,
   211  				},
   212  			},
   213  			hosts: map[string]*dashboard.HostConfig{
   214  				host: {},
   215  			},
   216  			buildletClient: &fakeEC2BuildletClient{
   217  				createVMRequestSuccess: true,
   218  				VMCreated:              true,
   219  				buildletCreated:        false,
   220  			},
   221  			wantSpans:    []string{"create_ec2_instance", "awaiting_ec2_quota", "create_ec2_buildlet", "wait_buildlet_start"},
   222  			wantSpansErr: []string{"create_ec2_buildlet", "wait_buildlet_start"},
   223  		},
   224  		{
   225  			desc:     "success",
   226  			hostType: host,
   227  			ledger:   newLedger(),
   228  			types: []*cloud.InstanceType{
   229  				{
   230  					Type: "e2-standard-8",
   231  					CPU:  8,
   232  				},
   233  			},
   234  			hosts: map[string]*dashboard.HostConfig{
   235  				host: {},
   236  			},
   237  			buildletClient: &fakeEC2BuildletClient{
   238  				createVMRequestSuccess: true,
   239  				VMCreated:              true,
   240  				buildletCreated:        true,
   241  			},
   242  			wantSpans:    []string{"create_ec2_instance", "create_ec2_buildlet", "awaiting_ec2_quota", "wait_buildlet_start"},
   243  			wantSpansErr: []string{},
   244  		},
   245  	}
   246  	for _, tc := range testCases {
   247  		t.Run(tc.desc, func(t *testing.T) {
   248  			bp := &EC2Buildlet{
   249  				buildletClient: tc.buildletClient,
   250  				buildEnv:       &buildenv.Environment{},
   251  				ledger:         tc.ledger,
   252  				hosts:          tc.hosts,
   253  			}
   254  			bp.ledger.SetCPULimit(20)
   255  			bp.ledger.UpdateInstanceTypes(tc.types)
   256  			l := newTestLogger()
   257  			_, _ = bp.GetBuildlet(context.Background(), tc.hostType, l, new(queue.SchedItem))
   258  			if !cmp.Equal(l.spanEvents(), tc.wantSpans, cmp.Transformer("sort", func(in []string) []string {
   259  				out := append([]string(nil), in...)
   260  				sort.Strings(out)
   261  				return out
   262  			})) {
   263  				t.Errorf("span events = %+v; want %+v", l.spanEvents(), tc.wantSpans)
   264  			}
   265  			for _, spanErr := range tc.wantSpansErr {
   266  				s, ok := l.spans[spanErr]
   267  				if !ok {
   268  					t.Fatalf("log span %q does not exist", spanErr)
   269  				}
   270  				if s.err == nil {
   271  					t.Fatalf("testLogger.span[%q].err is nil", spanErr)
   272  				}
   273  			}
   274  		})
   275  	}
   276  }
   277  
   278  func TestEC2BuildletString(t *testing.T) {
   279  	testCases := []struct {
   280  		desc      string
   281  		instCount int64
   282  		cpuCount  int64
   283  		cpuLimit  int64
   284  	}{
   285  		{"default", 0, 0, 0},
   286  		{"non-default", 2, 2, 3},
   287  	}
   288  	for _, tc := range testCases {
   289  		t.Run(tc.desc, func(t *testing.T) {
   290  			es := make([]*entry, tc.instCount)
   291  			entries := make(map[string]*entry)
   292  			for i, e := range es {
   293  				entries[fmt.Sprintf("%d", i)] = e
   294  			}
   295  			l := newLedger()
   296  			eb := &EC2Buildlet{ledger: l}
   297  			l.entries = entries
   298  			eb.ledger.cpuQueue.UpdateQuotas(int(tc.cpuCount), int(tc.cpuLimit))
   299  			want := fmt.Sprintf("EC2 pool capacity: %d instances; %d/%d CPUs", tc.instCount, tc.cpuCount, tc.cpuLimit)
   300  			got := eb.String()
   301  			if got != want {
   302  				t.Errorf("EC2Buildlet.String() = %s; want %s", got, want)
   303  			}
   304  		})
   305  	}
   306  }
   307  
   308  func TestEC2BuildletCapacityString(t *testing.T) {
   309  	testCases := []struct {
   310  		desc      string
   311  		instCount int64
   312  		cpuCount  int64
   313  		cpuLimit  int64
   314  	}{
   315  		{"defaults", 0, 0, 0},
   316  		{"non-default", 2, 2, 3},
   317  	}
   318  	for _, tc := range testCases {
   319  		t.Run(tc.desc, func(t *testing.T) {
   320  			es := make([]*entry, tc.instCount)
   321  			entries := make(map[string]*entry)
   322  			for i, e := range es {
   323  				entries[fmt.Sprintf("%d", i)] = e
   324  			}
   325  			l := newLedger()
   326  			l.entries = entries
   327  			eb := &EC2Buildlet{ledger: l}
   328  			eb.ledger.cpuQueue.UpdateQuotas(int(tc.cpuCount), int(tc.cpuLimit))
   329  			want := fmt.Sprintf("%d instances; %d/%d CPUs", tc.instCount, tc.cpuCount, tc.cpuLimit)
   330  			got := eb.capacityString()
   331  			if got != want {
   332  				t.Errorf("EC2Buildlet.capacityString() = %s; want %s", got, want)
   333  			}
   334  		})
   335  	}
   336  }
   337  
   338  func TestEC2BuildletbuildletDone(t *testing.T) {
   339  	t.Run("done-successful", func(t *testing.T) {
   340  		instName := "instance-name-x"
   341  
   342  		awsC := cloud.NewFakeAWSClient()
   343  		inst, err := awsC.CreateInstance(context.Background(), &cloud.EC2VMConfiguration{
   344  			Description: "test instance",
   345  			ImageID:     "image-x",
   346  			Name:        instName,
   347  			SSHKeyID:    "key-14",
   348  			Tags:        map[string]string{},
   349  			Type:        "type-x",
   350  			Zone:        "zone-1",
   351  		})
   352  		if err != nil {
   353  			t.Errorf("unable to create instance: %s", err)
   354  		}
   355  
   356  		l := newLedger()
   357  		pool := &EC2Buildlet{
   358  			awsClient: awsC,
   359  			ledger:    l,
   360  		}
   361  		l.entries = map[string]*entry{
   362  			instName: {
   363  				createdAt:    time.Now(),
   364  				instanceID:   inst.ID,
   365  				instanceName: instName,
   366  				vCPUCount:    5,
   367  				quota:        new(queue.Item),
   368  			},
   369  		}
   370  		pool.buildletDone(instName)
   371  		if gotID := pool.ledger.InstanceID(instName); gotID != "" {
   372  			t.Errorf("ledger.instanceID = %q; want %q", gotID, "")
   373  		}
   374  		gotInsts, err := awsC.RunningInstances(context.Background())
   375  		if err != nil || len(gotInsts) != 0 {
   376  			t.Errorf("awsClient.RunningInstances(ctx) = %+v, %s; want [], nil", gotInsts, err)
   377  		}
   378  	})
   379  	t.Run("instance-not-in-ledger", func(t *testing.T) {
   380  		instName := "instance-name-x"
   381  
   382  		awsC := cloud.NewFakeAWSClient()
   383  		inst, err := awsC.CreateInstance(context.Background(), &cloud.EC2VMConfiguration{
   384  			Description: "test instance",
   385  			ImageID:     "image-x",
   386  			Name:        instName,
   387  			SSHKeyID:    "key-14",
   388  			Tags:        map[string]string{},
   389  			Type:        "type-x",
   390  			Zone:        "zone-1",
   391  		})
   392  		if err != nil {
   393  			t.Errorf("unable to create instance: %s", err)
   394  		}
   395  
   396  		pool := &EC2Buildlet{
   397  			awsClient: awsC,
   398  			ledger:    newLedger(),
   399  		}
   400  		pool.buildletDone(inst.Name)
   401  		gotInsts, err := awsC.RunningInstances(context.Background())
   402  		if err != nil || len(gotInsts) != 1 {
   403  			t.Errorf("awsClient.RunningInstances(ctx) = %+v, %s; want 1 instance, nil", gotInsts, err)
   404  		}
   405  	})
   406  	t.Run("instance-not-in-ec2", func(t *testing.T) {
   407  		instName := "instance-name-x"
   408  		l := newLedger()
   409  		pool := &EC2Buildlet{
   410  			awsClient: cloud.NewFakeAWSClient(),
   411  			ledger:    l,
   412  		}
   413  		l.entries = map[string]*entry{
   414  			instName: {
   415  				createdAt:    time.Now(),
   416  				instanceID:   "instance-id-14",
   417  				instanceName: instName,
   418  				vCPUCount:    5,
   419  				quota:        new(queue.Item),
   420  			},
   421  		}
   422  		pool.buildletDone(instName)
   423  		if gotID := pool.ledger.InstanceID(instName); gotID != "" {
   424  			t.Errorf("ledger.instanceID = %q; want %q", gotID, "")
   425  		}
   426  	})
   427  }
   428  
   429  func TestEC2BuildletClose(t *testing.T) {
   430  	cancelled := false
   431  	pool := &EC2Buildlet{
   432  		cancelPoll: func() { cancelled = true },
   433  	}
   434  	pool.Close()
   435  	if !cancelled {
   436  		t.Error("EC2Buildlet.pollCancel not called")
   437  	}
   438  }
   439  
   440  func TestEC2BuildletRetrieveAndSetQuota(t *testing.T) {
   441  	pool := &EC2Buildlet{
   442  		awsClient: cloud.NewFakeAWSClient(),
   443  		ledger:    newLedger(),
   444  	}
   445  	err := pool.retrieveAndSetQuota(context.Background())
   446  	if err != nil {
   447  		t.Errorf("EC2Buildlet.retrieveAndSetQuota(ctx) = %s; want nil", err)
   448  	}
   449  	usage := pool.ledger.cpuQueue.Quotas()
   450  	if usage.Limit == 0 {
   451  		t.Errorf("ledger.cpuLimit = %d; want non-zero", usage.Limit)
   452  	}
   453  }
   454  
   455  func TestEC2BuildletRetrieveAndSetInstanceTypes(t *testing.T) {
   456  	pool := &EC2Buildlet{
   457  		awsClient: cloud.NewFakeAWSClient(),
   458  		ledger:    newLedger(),
   459  	}
   460  	err := pool.retrieveAndSetInstanceTypes()
   461  	if err != nil {
   462  		t.Errorf("EC2Buildlet.retrieveAndSetInstanceTypes() = %s; want nil", err)
   463  	}
   464  	if len(pool.ledger.types) == 0 {
   465  		t.Errorf("len(pool.ledger.types) = %d; want non-zero", len(pool.ledger.types))
   466  	}
   467  }
   468  
   469  func TestEC2BuildeletDestroyUntrackedInstances(t *testing.T) {
   470  	awsC := cloud.NewFakeAWSClient()
   471  	create := func(name string) *cloud.Instance {
   472  		inst, err := awsC.CreateInstance(context.Background(), &cloud.EC2VMConfiguration{
   473  			Description: "test instance",
   474  			ImageID:     "image-x",
   475  			Name:        name,
   476  			SSHKeyID:    "key-14",
   477  			Tags:        map[string]string{},
   478  			Type:        "type-x",
   479  			Zone:        "zone-1",
   480  		})
   481  		if err != nil {
   482  			t.Errorf("unable to create instance: %s", err)
   483  		}
   484  		return inst
   485  	}
   486  	// create untracked instances
   487  	for it := 0; it < 10; it++ {
   488  		_ = create(instanceName("host-test-type", 10))
   489  	}
   490  	wantTrackedInst := create(instanceName("host-test-type", 10))
   491  	wantRemoteInst := create(instanceName("host-test-type", 10))
   492  	_ = create("debug-tiger-host-14") // non buildlet instance
   493  
   494  	l := newLedger()
   495  	pool := &EC2Buildlet{
   496  		awsClient: awsC,
   497  		isRemoteBuildlet: func(name string) bool {
   498  			if name == wantRemoteInst.Name {
   499  				return true
   500  			}
   501  			return false
   502  		},
   503  		ledger: l,
   504  	}
   505  	l.entries = map[string]*entry{
   506  		wantTrackedInst.Name: {
   507  			createdAt:    time.Now(),
   508  			instanceID:   wantTrackedInst.ID,
   509  			instanceName: wantTrackedInst.Name,
   510  			vCPUCount:    4,
   511  		},
   512  	}
   513  	pool.destroyUntrackedInstances(context.Background())
   514  	wantInstCount := 3
   515  	gotInsts, err := awsC.RunningInstances(context.Background())
   516  	if err != nil || len(gotInsts) != wantInstCount {
   517  		t.Errorf("awsClient.RunningInstances(ctx) = %+v, %s; want %d instances and no error", gotInsts, err, wantInstCount)
   518  	}
   519  }
   520  
   521  // fakeEC2BuildletClient is the client used to create buildlets on EC2.
   522  type fakeEC2BuildletClient struct {
   523  	createVMRequestSuccess bool
   524  	VMCreated              bool
   525  	buildletCreated        bool
   526  }
   527  
   528  // StartNewVM boots a new VM on EC2, waits until the client is accepting connections
   529  // on the configured port and returns a buildlet client configured communicate with it.
   530  func (f *fakeEC2BuildletClient) StartNewVM(ctx context.Context, buildEnv *buildenv.Environment, hconf *dashboard.HostConfig, vmName, hostType string, opts *buildlet.VMOpts) (buildlet.Client, error) {
   531  	// check required params
   532  	if opts == nil || opts.TLS.IsZero() {
   533  		return nil, errors.New("TLS keypair is not set")
   534  	}
   535  	if buildEnv == nil {
   536  		return nil, errors.New("invalid build environment")
   537  	}
   538  	if hconf == nil {
   539  		return nil, errors.New("invalid host configuration")
   540  	}
   541  	if vmName == "" || hostType == "" {
   542  		return nil, fmt.Errorf("invalid vmName: %q and hostType: %q", vmName, hostType)
   543  	}
   544  	if opts.DeleteIn == 0 {
   545  		// Note: This implements a short default in the rare case the caller doesn't care.
   546  		opts.DeleteIn = 30 * time.Minute
   547  	}
   548  	if !f.createVMRequestSuccess {
   549  		return nil, fmt.Errorf("unable to create instance %s: creation disabled", vmName)
   550  	}
   551  	condRun := func(fn func()) {
   552  		if fn != nil {
   553  			fn()
   554  		}
   555  	}
   556  	condRun(opts.OnInstanceRequested)
   557  	if !f.VMCreated {
   558  		return nil, errors.New("error waiting for instance to exist: vm existence disabled")
   559  	}
   560  
   561  	condRun(opts.OnInstanceCreated)
   562  
   563  	if !f.buildletCreated {
   564  		return nil, errors.New("error waiting for buildlet: buildlet creation disabled")
   565  	}
   566  
   567  	if opts.OnGotEC2InstanceInfo != nil {
   568  		opts.OnGotEC2InstanceInfo(&cloud.Instance{
   569  			CPUCount:          4,
   570  			CreatedAt:         time.Time{},
   571  			Description:       "sample vm",
   572  			ID:                "id-" + instanceName("random", 4),
   573  			IPAddressExternal: "127.0.0.1",
   574  			IPAddressInternal: "127.0.0.1",
   575  			ImageID:           "image-x",
   576  			Name:              vmName,
   577  			SSHKeyID:          "key-15",
   578  			SecurityGroups:    nil,
   579  			State:             "running",
   580  			Tags: map[string]string{
   581  				"foo": "bar",
   582  			},
   583  			Type: "yy.large",
   584  			Zone: "zone-a",
   585  		})
   586  	}
   587  	return &buildlet.FakeClient{}, nil
   588  }
   589  
   590  type testLogger struct {
   591  	eventTimes []eventTime
   592  	spans      map[string]*span
   593  }
   594  
   595  type eventTime struct {
   596  	event string
   597  	opt   []string
   598  }
   599  
   600  type span struct {
   601  	event      string
   602  	opt        []string
   603  	err        error
   604  	calledDone bool
   605  }
   606  
   607  func (s *span) Done(err error) error {
   608  	s.err = err
   609  	s.calledDone = true
   610  	return nil
   611  }
   612  
   613  func newTestLogger() *testLogger {
   614  	return &testLogger{
   615  		eventTimes: make([]eventTime, 0, 5),
   616  		spans:      make(map[string]*span),
   617  	}
   618  }
   619  
   620  func (l *testLogger) LogEventTime(event string, optText ...string) {
   621  	l.eventTimes = append(l.eventTimes, eventTime{
   622  		event: event,
   623  		opt:   optText,
   624  	})
   625  }
   626  
   627  func (l *testLogger) CreateSpan(event string, optText ...string) spanlog.Span {
   628  	s := &span{
   629  		event: event,
   630  		opt:   optText,
   631  	}
   632  	l.spans[event] = s
   633  	return s
   634  }
   635  
   636  func (l *testLogger) spanEvents() []string {
   637  	se := make([]string, 0, len(l.spans))
   638  	for k, s := range l.spans {
   639  		if !s.calledDone {
   640  			continue
   641  		}
   642  		se = append(se, k)
   643  	}
   644  	return se
   645  }
   646  
   647  type noopEventTimeLogger struct{}
   648  
   649  func (l noopEventTimeLogger) LogEventTime(event string, optText ...string) {}
   650  func (l noopEventTimeLogger) CreateSpan(event string, optText ...string) spanlog.Span {
   651  	return noopSpan{}
   652  }
   653  
   654  type noopSpan struct{}
   655  
   656  func (s noopSpan) Done(err error) error { return nil }