github.com/google/cloudprober@v0.11.3/prober/serviceimpl_test.go (about)

     1  // Copyright 2019 The Cloudprober Authors.
     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  package prober
    16  
    17  import (
    18  	"context"
    19  	"reflect"
    20  	"sort"
    21  	"testing"
    22  	"time"
    23  
    24  	"github.com/google/cloudprober/metrics"
    25  	pb "github.com/google/cloudprober/prober/proto"
    26  	"github.com/google/cloudprober/probes"
    27  	"github.com/google/cloudprober/probes/options"
    28  	probes_configpb "github.com/google/cloudprober/probes/proto"
    29  	testdatapb "github.com/google/cloudprober/probes/testdata"
    30  	targetspb "github.com/google/cloudprober/targets/proto"
    31  	"google.golang.org/protobuf/proto"
    32  )
    33  
    34  func testProber() *Prober {
    35  	pr := &Prober{
    36  		Probes:           make(map[string]*probes.ProbeInfo),
    37  		probeCancelFunc:  make(map[string]context.CancelFunc),
    38  		grpcStartProbeCh: make(chan string),
    39  	}
    40  
    41  	// Start a never-ending loop to clear pr.grpcStartProbeCh channel.
    42  	go func() {
    43  		for {
    44  			select {
    45  			case name := <-pr.grpcStartProbeCh:
    46  				pr.startProbe(context.Background(), name)
    47  			}
    48  		}
    49  	}()
    50  
    51  	return pr
    52  }
    53  
    54  // testProbe implements the probes.Probe interface, while providing
    55  // facilities to examine the probe status for the purpose of testing.
    56  // Since cloudprober has to be aware of the probe type, we add testProbe to
    57  // cloudprober as an EXTENSION probe type (done through the init() function
    58  // below).
    59  type testProbe struct {
    60  	intialized      bool
    61  	runningStatusCh chan bool
    62  }
    63  
    64  func (p *testProbe) Init(name string, opts *options.Options) error {
    65  	p.intialized = true
    66  	p.runningStatusCh = make(chan bool)
    67  	return nil
    68  }
    69  
    70  func (p *testProbe) Start(ctx context.Context, dataChan chan *metrics.EventMetrics) {
    71  	p.runningStatusCh <- true
    72  
    73  	// If context is done (used to stop a running probe before removing it),
    74  	// change probe state to not-running.
    75  	<-ctx.Done()
    76  	p.runningStatusCh <- false
    77  	close(p.runningStatusCh)
    78  }
    79  
    80  // We use an EXTENSION probe for testing. Following has the same effect as:
    81  // This has the same effect as using the following in your config:
    82  // probe {
    83  //    name: "<name>"
    84  //    targets {
    85  //     dummy_targets{}
    86  //    }
    87  //    [cloudprober.probes.testdata.fancy_probe] {
    88  //      name: "fancy"
    89  //    }
    90  // }
    91  func testProbeDef(name string) *probes_configpb.ProbeDef {
    92  	probeDef := &probes_configpb.ProbeDef{
    93  		Name: proto.String(name),
    94  		Type: probes_configpb.ProbeDef_EXTENSION.Enum(),
    95  		Targets: &targetspb.TargetsDef{
    96  			Type: &targetspb.TargetsDef_DummyTargets{},
    97  		},
    98  	}
    99  	proto.SetExtension(probeDef, testdatapb.E_FancyProbe, &testdatapb.FancyProbe{Name: proto.String("fancy-" + name)})
   100  	return probeDef
   101  }
   102  
   103  // verifyProbeRunningStatus is a helper function to verify probe's running
   104  // status.
   105  func verifyProbeRunningStatus(t *testing.T, p *testProbe, expectedRunning bool) {
   106  	t.Helper()
   107  
   108  	ctx, cancel := context.WithTimeout(context.Background(), 5*time.Second)
   109  	defer cancel()
   110  
   111  	var running, timedout bool
   112  
   113  	select {
   114  	case running = <-p.runningStatusCh:
   115  	case <-ctx.Done():
   116  		timedout = true
   117  	}
   118  
   119  	if timedout {
   120  		t.Errorf("timed out while waiting for test probe's running status")
   121  		return
   122  	}
   123  
   124  	if running != expectedRunning {
   125  		t.Errorf("Running status: expected=%v, got=%v", expectedRunning, running)
   126  	}
   127  }
   128  
   129  func TestAddProbe(t *testing.T) {
   130  	pr := testProber()
   131  
   132  	// Test AddProbe()
   133  	// Empty probe config should return an error
   134  	_, err := pr.AddProbe(context.Background(), &pb.AddProbeRequest{})
   135  	if err == nil {
   136  		t.Error("empty probe config didn't result in error")
   137  	}
   138  
   139  	// Add a valid test probe
   140  	testProbeName := "test-probe"
   141  	_, err = pr.AddProbe(context.Background(), &pb.AddProbeRequest{ProbeConfig: testProbeDef(testProbeName)})
   142  	if err != nil {
   143  		t.Errorf("error while adding the probe: %v", err)
   144  	}
   145  	if pr.Probes[testProbeName] == nil {
   146  		t.Errorf("test-probe not added to the probes database")
   147  	}
   148  
   149  	p := pr.Probes[testProbeName].Probe.(*testProbe)
   150  	if !p.intialized {
   151  		t.Errorf("test probe not initialized. p.initialized=%v", p.intialized)
   152  	}
   153  	verifyProbeRunningStatus(t, p, true)
   154  
   155  	// Add test probe again, should result in error
   156  	_, err = pr.AddProbe(context.Background(), &pb.AddProbeRequest{ProbeConfig: testProbeDef(testProbeName)})
   157  	if err == nil {
   158  		t.Error("empty probe config didn't result in error")
   159  	}
   160  }
   161  
   162  func TestListProbes(t *testing.T) {
   163  	pr := testProber()
   164  
   165  	// Add couple of probes for testing
   166  	testProbes := []string{"test-probe-1", "test-probe-2"}
   167  	for _, name := range testProbes {
   168  		pr.AddProbe(context.Background(), &pb.AddProbeRequest{ProbeConfig: testProbeDef(name)})
   169  	}
   170  
   171  	// Test ListProbes()
   172  	resp, err := pr.ListProbes(context.Background(), &pb.ListProbesRequest{})
   173  	if err != nil {
   174  		t.Errorf("error while list probes: %v", err)
   175  	}
   176  	respProbes := resp.GetProbe()
   177  	if len(respProbes) != len(testProbes) {
   178  		t.Errorf("didn't get correct number of probe in ListProbes response. Got %d probes, expected %d probes", len(respProbes), len(testProbes))
   179  	}
   180  	var respProbeNames []string
   181  	for _, p := range respProbes {
   182  		respProbeNames = append(respProbeNames, p.GetName())
   183  		t.Logf("Probe Config: %+v", p.GetConfig())
   184  	}
   185  	sort.Strings(respProbeNames)
   186  	if !reflect.DeepEqual(respProbeNames, testProbes) {
   187  		t.Errorf("Probes in ListProbes() response: %v, expected: %s", respProbeNames, testProbes)
   188  	}
   189  }
   190  
   191  func TestRemoveProbes(t *testing.T) {
   192  	pr := testProber()
   193  
   194  	testProbeName := "test-probe"
   195  
   196  	// Remove a non-existent probe, should result in error.
   197  	_, err := pr.RemoveProbe(context.Background(), &pb.RemoveProbeRequest{ProbeName: &testProbeName})
   198  	if err == nil {
   199  		t.Error("removing non-existent probe didn't result in error")
   200  	}
   201  
   202  	// Add a probe for testing
   203  	_, err = pr.AddProbe(context.Background(), &pb.AddProbeRequest{ProbeConfig: testProbeDef(testProbeName)})
   204  	if err != nil {
   205  		t.Errorf("error while adding test probe: %v", err)
   206  	}
   207  	p := pr.Probes[testProbeName].Probe.(*testProbe)
   208  	// Clear probe's running status channel before removing the probe and thus
   209  	// causing another running status update
   210  	<-p.runningStatusCh
   211  
   212  	_, err = pr.RemoveProbe(context.Background(), &pb.RemoveProbeRequest{ProbeName: &testProbeName})
   213  	if err != nil {
   214  		t.Errorf("error while removing probe: %v", err)
   215  	}
   216  
   217  	if pr.Probes[testProbeName] != nil {
   218  		t.Errorf("test probe still in the probes database: %v", pr.Probes[testProbeName])
   219  	}
   220  
   221  	// Verify that probe is not running anymore
   222  	verifyProbeRunningStatus(t, p, false)
   223  }
   224  
   225  func init() {
   226  	// Register extension probe.
   227  	probes.RegisterProbeType(200, func() probes.Probe {
   228  		return &testProbe{}
   229  	})
   230  }