github.com/kubeshop/testkube@v1.17.23/pkg/triggers/service.go (about) 1 package triggers 2 3 import ( 4 "context" 5 "fmt" 6 "os" 7 "strings" 8 "time" 9 10 "go.uber.org/zap" 11 "k8s.io/client-go/kubernetes" 12 13 testsv3 "github.com/kubeshop/testkube-operator/api/tests/v3" 14 testsuitev3 "github.com/kubeshop/testkube-operator/api/testsuite/v3" 15 testtriggersv1 "github.com/kubeshop/testkube-operator/api/testtriggers/v1" 16 executorsclientv1 "github.com/kubeshop/testkube-operator/pkg/client/executors/v1" 17 testsclientv3 "github.com/kubeshop/testkube-operator/pkg/client/tests/v3" 18 testsuitesclientv3 "github.com/kubeshop/testkube-operator/pkg/client/testsuites/v3" 19 testkubeclientsetv1 "github.com/kubeshop/testkube-operator/pkg/clientset/versioned" 20 "github.com/kubeshop/testkube/internal/app/api/metrics" 21 "github.com/kubeshop/testkube/pkg/api/v1/testkube" 22 "github.com/kubeshop/testkube/pkg/event/bus" 23 "github.com/kubeshop/testkube/pkg/executor/client" 24 "github.com/kubeshop/testkube/pkg/http" 25 "github.com/kubeshop/testkube/pkg/repository/config" 26 "github.com/kubeshop/testkube/pkg/repository/result" 27 "github.com/kubeshop/testkube/pkg/repository/testresult" 28 "github.com/kubeshop/testkube/pkg/scheduler" 29 "github.com/kubeshop/testkube/pkg/telemetry" 30 "github.com/kubeshop/testkube/pkg/utils" 31 "github.com/kubeshop/testkube/pkg/version" 32 ) 33 34 const ( 35 defaultScraperInterval = 5 * time.Second 36 defaultLeaseCheckInterval = 5 * time.Second 37 defaultMaxLeaseDuration = 1 * time.Minute 38 defaultConditionsCheckBackoff = 1 * time.Second 39 defaultConditionsCheckTimeout = 60 * time.Second 40 defaultProbesCheckBackoff = 1 * time.Second 41 defaultProbesCheckTimeout = 60 * time.Second 42 defaultClusterID = "testkube-api" 43 defaultIdentifierFormat = "testkube-api-%s" 44 ) 45 46 type Service struct { 47 informers *k8sInformers 48 leaseBackend LeaseBackend 49 identifier string 50 clusterID string 51 triggerExecutor ExecutorF 52 scraperInterval time.Duration 53 leaseCheckInterval time.Duration 54 maxLeaseDuration time.Duration 55 defaultConditionsCheckTimeout time.Duration 56 defaultConditionsCheckBackoff time.Duration 57 defaultProbesCheckTimeout time.Duration 58 defaultProbesCheckBackoff time.Duration 59 watchFromDate time.Time 60 triggerStatus map[statusKey]*triggerStatus 61 scheduler *scheduler.Scheduler 62 clientset kubernetes.Interface 63 testKubeClientset testkubeclientsetv1.Interface 64 testSuitesClient testsuitesclientv3.Interface 65 testsClient testsclientv3.Interface 66 resultRepository result.Repository 67 testResultRepository testresult.Repository 68 logger *zap.SugaredLogger 69 configMap config.Repository 70 executorsClient executorsclientv1.Interface 71 httpClient http.HttpClient 72 testExecutor client.Executor 73 eventsBus bus.Bus 74 metrics metrics.Metrics 75 testkubeNamespace string 76 watcherNamespaces []string 77 disableSecretCreation bool 78 } 79 80 type Option func(*Service) 81 82 func NewService( 83 scheduler *scheduler.Scheduler, 84 clientset kubernetes.Interface, 85 testKubeClientset testkubeclientsetv1.Interface, 86 testSuitesClient testsuitesclientv3.Interface, 87 testsClient testsclientv3.Interface, 88 resultRepository result.Repository, 89 testResultRepository testresult.Repository, 90 leaseBackend LeaseBackend, 91 logger *zap.SugaredLogger, 92 configMap config.Repository, 93 executorsClient executorsclientv1.Interface, 94 testExecutor client.Executor, 95 eventsBus bus.Bus, 96 metrics metrics.Metrics, 97 opts ...Option, 98 ) *Service { 99 identifier := fmt.Sprintf(defaultIdentifierFormat, utils.RandAlphanum(10)) 100 s := &Service{ 101 identifier: identifier, 102 clusterID: defaultClusterID, 103 scraperInterval: defaultScraperInterval, 104 leaseCheckInterval: defaultLeaseCheckInterval, 105 maxLeaseDuration: defaultMaxLeaseDuration, 106 defaultConditionsCheckTimeout: defaultConditionsCheckTimeout, 107 defaultConditionsCheckBackoff: defaultConditionsCheckBackoff, 108 defaultProbesCheckTimeout: defaultProbesCheckTimeout, 109 defaultProbesCheckBackoff: defaultProbesCheckBackoff, 110 scheduler: scheduler, 111 clientset: clientset, 112 testKubeClientset: testKubeClientset, 113 testSuitesClient: testSuitesClient, 114 testsClient: testsClient, 115 resultRepository: resultRepository, 116 testResultRepository: testResultRepository, 117 leaseBackend: leaseBackend, 118 logger: logger, 119 configMap: configMap, 120 executorsClient: executorsClient, 121 testExecutor: testExecutor, 122 eventsBus: eventsBus, 123 metrics: metrics, 124 httpClient: http.NewClient(), 125 watchFromDate: time.Now(), 126 triggerStatus: make(map[statusKey]*triggerStatus), 127 } 128 if s.triggerExecutor == nil { 129 s.triggerExecutor = s.execute 130 } 131 132 for _, opt := range opts { 133 opt(s) 134 } 135 136 s.informers = newK8sInformers(clientset, testKubeClientset, s.testkubeNamespace, s.watcherNamespaces) 137 138 return s 139 } 140 141 func WithIdentifier(id string) Option { 142 return func(s *Service) { 143 s.identifier = id 144 } 145 } 146 147 func WithHostnameIdentifier() Option { 148 return func(s *Service) { 149 identifier, err := os.Hostname() 150 if err == nil { 151 s.identifier = identifier 152 } 153 } 154 } 155 156 func WithClusterID(id string) Option { 157 return func(s *Service) { 158 s.clusterID = id 159 } 160 } 161 162 func WithWatchFromDate(from time.Time) Option { 163 return func(s *Service) { 164 s.watchFromDate = from 165 } 166 } 167 168 func WithLeaseCheckerInterval(interval time.Duration) Option { 169 return func(s *Service) { 170 s.leaseCheckInterval = interval 171 } 172 } 173 174 func WithScraperInterval(interval time.Duration) Option { 175 return func(s *Service) { 176 s.scraperInterval = interval 177 } 178 } 179 180 func WithExecutor(triggerExecutor ExecutorF) Option { 181 return func(s *Service) { 182 s.triggerExecutor = triggerExecutor 183 } 184 } 185 186 func WithTestkubeNamespace(namespace string) Option { 187 return func(s *Service) { 188 s.testkubeNamespace = namespace 189 } 190 } 191 192 func WithWatcherNamespaces(namespaces string) Option { 193 return func(s *Service) { 194 for _, namespace := range strings.Split(namespaces, ",") { 195 value := strings.TrimSpace(namespace) 196 if value != "" { 197 s.watcherNamespaces = append(s.watcherNamespaces, value) 198 } 199 } 200 } 201 } 202 203 func WithDisableSecretCreation(disableSecretCreation bool) Option { 204 return func(s *Service) { 205 s.disableSecretCreation = disableSecretCreation 206 } 207 } 208 209 func (s *Service) Run(ctx context.Context) { 210 leaseChan := make(chan bool) 211 212 go s.runLeaseChecker(ctx, leaseChan) 213 214 go s.runWatcher(ctx, leaseChan) 215 216 go s.runExecutionScraper(ctx) 217 } 218 219 func (s *Service) addTrigger(t *testtriggersv1.TestTrigger) { 220 key := newStatusKey(t.Namespace, t.Name) 221 s.triggerStatus[key] = newTriggerStatus(t) 222 } 223 224 func (s *Service) updateTrigger(target *testtriggersv1.TestTrigger) { 225 key := newStatusKey(target.Namespace, target.Name) 226 if s.triggerStatus[key] != nil { 227 s.triggerStatus[key].testTrigger = target 228 } else { 229 s.triggerStatus[key] = newTriggerStatus(target) 230 } 231 } 232 233 func (s *Service) removeTrigger(target *testtriggersv1.TestTrigger) { 234 key := newStatusKey(target.Namespace, target.Name) 235 delete(s.triggerStatus, key) 236 } 237 238 func (s *Service) addTest(test *testsv3.Test) { 239 ctx := context.Background() 240 telemetryEnabled, err := s.configMap.GetTelemetryEnabled(ctx) 241 if err != nil { 242 s.logger.Debugw("getting telemetry enabled error", "error", err) 243 } 244 245 if !telemetryEnabled { 246 return 247 } 248 249 clusterID, err := s.configMap.GetUniqueClusterId(ctx) 250 if err != nil { 251 s.logger.Debugw("getting cluster id error", "error", err) 252 } 253 254 host, err := os.Hostname() 255 if err != nil { 256 s.logger.Debugw("getting hostname error", "hostname", host, "error", err) 257 } 258 259 var dataSource string 260 if test.Spec.Content != nil { 261 dataSource = string(test.Spec.Content.Type_) 262 } 263 264 out, err := telemetry.SendCreateEvent("testkube_api_create_test", telemetry.CreateParams{ 265 AppVersion: version.Version, 266 DataSource: dataSource, 267 Host: host, 268 ClusterID: clusterID, 269 TestType: test.Spec.Type_, 270 TestSource: test.Spec.Source, 271 }) 272 if err != nil { 273 s.logger.Debugw("sending create test telemetry event error", "error", err) 274 } else { 275 s.logger.Debugw("sending create test telemetry event", "output", out) 276 } 277 278 if test.Labels == nil { 279 test.Labels = make(map[string]string) 280 } 281 282 test.Labels[testkube.TestLabelTestType] = utils.SanitizeName(test.Spec.Type_) 283 executorCR, err := s.executorsClient.GetByType(test.Spec.Type_) 284 if err == nil { 285 test.Labels[testkube.TestLabelExecutor] = executorCR.Name 286 } else { 287 s.logger.Debugw("can't get executor spec", "error", err) 288 } 289 290 if _, err = s.testsClient.Update(test, s.disableSecretCreation); err != nil { 291 s.logger.Debugw("can't update test spec", "error", err) 292 } 293 } 294 295 func (s *Service) updateTest(test *testsv3.Test) { 296 changed := false 297 if test.Labels == nil { 298 test.Labels = make(map[string]string) 299 } 300 301 testType := utils.SanitizeName(test.Spec.Type_) 302 if test.Labels[testkube.TestLabelTestType] != testType { 303 test.Labels[testkube.TestLabelTestType] = testType 304 changed = true 305 } 306 307 executorCR, err := s.executorsClient.GetByType(test.Spec.Type_) 308 if err == nil { 309 if test.Labels[testkube.TestLabelExecutor] != executorCR.Name { 310 test.Labels[testkube.TestLabelExecutor] = executorCR.Name 311 changed = true 312 } 313 } else { 314 s.logger.Debugw("can't get executor spec", "error", err) 315 } 316 317 if changed { 318 if _, err = s.testsClient.Update(test, s.disableSecretCreation); err != nil { 319 s.logger.Debugw("can't update test spec", "error", err) 320 } 321 } 322 } 323 324 func (s *Service) addTestSuite(testSuite *testsuitev3.TestSuite) { 325 ctx := context.Background() 326 telemetryEnabled, err := s.configMap.GetTelemetryEnabled(ctx) 327 if err != nil { 328 s.logger.Debugw("getting telemetry enabled error", "error", err) 329 } 330 331 if !telemetryEnabled { 332 return 333 } 334 335 clusterID, err := s.configMap.GetUniqueClusterId(ctx) 336 if err != nil { 337 s.logger.Debugw("getting cluster id error", "error", err) 338 } 339 340 host, err := os.Hostname() 341 if err != nil { 342 s.logger.Debugw("getting hostname error", "hostname", host, "error", err) 343 } 344 345 out, err := telemetry.SendCreateEvent("testkube_api_create_test_suite", telemetry.CreateParams{ 346 AppVersion: version.Version, 347 Host: host, 348 ClusterID: clusterID, 349 TestSuiteSteps: int32(len(testSuite.Spec.Before) + len(testSuite.Spec.Steps) + len(testSuite.Spec.After)), 350 }) 351 if err != nil { 352 s.logger.Debugw("sending create test suite telemetry event error", "error", err) 353 } else { 354 s.logger.Debugw("sending create test suite telemetry event", "output", out) 355 } 356 }