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 }