/
service_installer.go
464 lines (390 loc) · 17.2 KB
/
service_installer.go
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
// Copyright 2021 Red Hat, Inc. and/or its affiliates
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
package installers
import (
"fmt"
"sigs.k8s.io/controller-runtime/pkg/client"
"sync"
"time"
"github.com/kiegroup/kogito-operator/test/pkg/framework"
)
var (
waitForAllCrsRemovalTimeout = 1 * time.Minute
// Map of installed services for namespaces, contains slices of service installers installing services in those namespaces
installedNamespacedServices sync.Map
// Map of cluster wide installed services
installedClusterWideServices sync.Map
)
// ServiceInstaller is the API to install services
type ServiceInstaller interface {
// Install the service into cloud to serve the namespace
Install(namespace string) error
// Return all CRs of this service which exists in this namespace
getAllCrsInNamespace(namespace string) ([]client.Object, error)
// Returns service name for logging purposes
getServiceName() string
// Cleanup the namespace from service CRs. This functionality is needed because of Kogito service KogitoInfra object.
// KogitoInfra can exists with or without owner and when owner is deleted then KogitoInfra will remain.
// Other services just remove CRs without owner and that will remove all other CRs.
// Returns true in case of success, false in case of some error, logging is handled within the function.
cleanupCrsInNamespace(namespace string) bool
}
// ClusterWideServiceInstaller is the API of cluster wide services
type ClusterWideServiceInstaller interface {
ServiceInstaller
// Uninstall service from whole cluster
uninstallFromCluster() error
}
// NamespacedServiceInstaller is the API of namespaced services
type NamespacedServiceInstaller interface {
ServiceInstaller
// Uninstall service from specific namespace
uninstallFromNamespace(namespace string) error
}
// Generic API for all services
// UninstallServicesFromNamespace uninstalls all services from specific namespace. Returns false in case of any error while uninstalling.
func UninstallServicesFromNamespace(namespace string) (success bool) {
success = true
// Delete all CRs without owner
if ok := executeOnClusterWideServices(func(si ClusterWideServiceInstaller) bool {
return si.cleanupCrsInNamespace(namespace)
}); !ok {
success = false
}
if ok := executeOnNamespacedServices(namespace, func(si NamespacedServiceInstaller) bool {
return si.cleanupCrsInNamespace(namespace)
}); !ok {
success = false
}
// Wait until all CRs are removed
if ok := executeOnClusterWideServices(func(si ClusterWideServiceInstaller) bool {
return waitForAllCrsRemoval(si, namespace, waitForAllCrsRemovalTimeout)
}); !ok {
success = false
}
if ok := executeOnNamespacedServices(namespace, func(si NamespacedServiceInstaller) bool {
return waitForAllCrsRemoval(si, namespace, waitForAllCrsRemovalTimeout)
}); !ok {
success = false
}
// Remove namespaced services
servicesNotDeleted := []NamespacedServiceInstaller{}
if ok := executeOnNamespacedServices(namespace, func(si NamespacedServiceInstaller) bool {
if err := si.uninstallFromNamespace(namespace); err != nil {
framework.GetLogger(namespace).Error(err, "Error uninstalling service from namespace", "service name", si.getServiceName())
servicesNotDeleted = append(servicesNotDeleted, si)
return false
}
return true
}); !ok {
success = false
}
// Add not removed services back to the list
installedNamespacedServices.Store(namespace, servicesNotDeleted)
return success
}
// UninstallServicesFromCluster uninstalls the cluster wide service from whole cluster. Returns false in case of any error while uninstalling.
func UninstallServicesFromCluster() (success bool) {
success = true
servicesDeleted := []ClusterWideServiceInstaller{}
if ok := executeOnClusterWideServices(func(si ClusterWideServiceInstaller) bool {
if err := si.uninstallFromCluster(); err != nil {
framework.GetMainLogger().Error(err, "Error uninstalling service from cluster", "service name", si.getServiceName())
return false
}
servicesDeleted = append(servicesDeleted, si)
return true
}); !ok {
success = false
}
// Remove deleted services from list
for si := range servicesDeleted {
installedClusterWideServices.Delete(si)
}
return success
}
// YAML namespaced service specification
// Make sure that YamlNamespacedServiceInstaller can be typed to NamespacedServiceInstaller
var _ NamespacedServiceInstaller = &YamlNamespacedServiceInstaller{}
// YamlNamespacedServiceInstaller installs service using YAML files applied to specific namespace
type YamlNamespacedServiceInstaller struct {
// Install service for specific namespaces
InstallNamespacedYaml func(namespace string) error
// Wait until the service is up and running
WaitForNamespacedServiceRunning func(namespace string) error
// Return all CRs of this service which exists in this namespace
GetAllNamespaceYamlCrs func(string) ([]client.Object, error)
// Uninstall service from namespace
UninstallNamespaceYaml func(namespace string) error
// Service name
NamespacedYamlServiceName string
// Cleanup functionality, will delete CRs without owner if not defined
CleanupNamespaceYamlCrs func(namespace string) bool
}
// Install the namespaced service using YAML files into cloud
func (installer *YamlNamespacedServiceInstaller) Install(namespace string) error {
// Store service installer for namespace to use for uninstalling purposes
if sis, loaded := installedNamespacedServices.LoadOrStore(namespace, []NamespacedServiceInstaller{installer}); loaded {
installedNamespacedServices.Store(namespace, append(sis.([]NamespacedServiceInstaller), installer))
}
if err := installer.InstallNamespacedYaml(namespace); err != nil {
return err
}
return installer.WaitForNamespacedServiceRunning(namespace)
}
func (installer *YamlNamespacedServiceInstaller) getAllCrsInNamespace(namespace string) ([]client.Object, error) {
return installer.GetAllNamespaceYamlCrs(namespace)
}
func (installer *YamlNamespacedServiceInstaller) uninstallFromNamespace(namespace string) error {
return installer.UninstallNamespaceYaml(namespace)
}
func (installer *YamlNamespacedServiceInstaller) getServiceName() string {
return installer.NamespacedYamlServiceName
}
func (installer *YamlNamespacedServiceInstaller) cleanupCrsInNamespace(namespace string) bool {
if installer.CleanupNamespaceYamlCrs == nil {
return deleteCrsWithoutOwner(installer, namespace)
}
return installer.CleanupNamespaceYamlCrs(namespace)
}
// YAML cluster wide service specification
// Make sure that YamlClusterWideServiceInstaller can be typed to ClusterWideServiceInstaller
var _ ClusterWideServiceInstaller = &YamlClusterWideServiceInstaller{}
// YamlClusterWideServiceInstaller installs service using YAML files applied to specific namespace
type YamlClusterWideServiceInstaller struct {
// Install service for all namespaces
InstallClusterYaml func() error
// Namespace used for cluster wide service installation.
InstallationNamespace string
// Wait until the service is up and running
WaitForClusterYamlServiceRunning func() error
// Return all CRs of this service which exists in this namespace
GetAllClusterYamlCrsInNamespace func(string) ([]client.Object, error)
// Uninstall service from whole cluster
UninstallClusterYaml func() error
// Service name
ClusterYamlServiceName string
// Cleanup functionality, will delete CRs without owner if not defined
CleanupClusterYamlCrsInNamespace func(namespace string) bool
}
// Install the cluster wide service using YAML files into cloud
func (installer *YamlClusterWideServiceInstaller) Install(namespace string) error {
// Store cluster wide service installer to use for uninstalling purposes
if _, loaded := installedClusterWideServices.LoadOrStore(installer, true); loaded {
// Should be running already, wait until it is up
return installer.WaitForClusterYamlServiceRunning()
}
monitorNamespace(installer.InstallationNamespace)
if err := installer.InstallClusterYaml(); err != nil {
return err
}
framework.OnNamespacePostCreated(installer.InstallationNamespace)
return installer.WaitForClusterYamlServiceRunning()
}
func (installer *YamlClusterWideServiceInstaller) getAllCrsInNamespace(namespace string) ([]client.Object, error) {
return installer.GetAllClusterYamlCrsInNamespace(namespace)
}
func (installer *YamlClusterWideServiceInstaller) uninstallFromCluster() error {
stopNamespaceMonitoring(installer.InstallationNamespace)
if err := installer.UninstallClusterYaml(); err != nil {
return err
}
framework.OnNamespacePostDeleted(installer.InstallationNamespace)
return nil
}
func (installer *YamlClusterWideServiceInstaller) getServiceName() string {
return installer.ClusterYamlServiceName
}
func (installer *YamlClusterWideServiceInstaller) cleanupCrsInNamespace(namespace string) bool {
if installer.CleanupClusterYamlCrsInNamespace == nil {
return deleteCrsWithoutOwner(installer, namespace)
}
return installer.CleanupClusterYamlCrsInNamespace(namespace)
}
// OLM namespaced service specification
// Make sure that OlmNamespacedServiceInstaller can be typed to NamespacedServiceInstaller
var _ NamespacedServiceInstaller = &OlmNamespacedServiceInstaller{}
// OlmNamespacedServiceInstaller installs service using OLM, installed to specific namespace
type OlmNamespacedServiceInstaller struct {
SubscriptionName string
Channel string
StartingCSV string
Catalog func() framework.OperatorCatalog
InstallationTimeoutInMinutes int
// Return all CRs of this service which exists in this namespace
GetAllNamespacedOlmCrsInNamespace func(string) ([]client.Object, error)
// Cleanup functionality, will delete CRs without owner if not defined
CleanupNamespacedOlmCrsInNamespace func(namespace string) bool
}
// Install the namespaced service using OLM into cloud
func (installer *OlmNamespacedServiceInstaller) Install(namespace string) error {
// Store service installer for namespace to use for uninstalling purposes
if sis, loaded := installedNamespacedServices.LoadOrStore(namespace, []NamespacedServiceInstaller{installer}); loaded {
installedNamespacedServices.Store(namespace, append(sis.([]NamespacedServiceInstaller), installer))
}
if err := framework.InstallOperator(namespace, installer.SubscriptionName, installer.Channel, installer.StartingCSV, installer.Catalog()); err != nil {
return err
}
return framework.WaitForOperatorRunning(namespace, installer.SubscriptionName, installer.Catalog(), installer.InstallationTimeoutInMinutes)
}
func (installer *OlmNamespacedServiceInstaller) getAllCrsInNamespace(namespace string) ([]client.Object, error) {
return installer.GetAllNamespacedOlmCrsInNamespace(namespace)
}
func (installer *OlmNamespacedServiceInstaller) uninstallFromNamespace(namespace string) error {
subscription, err := framework.GetSubscription(namespace, installer.SubscriptionName, installer.Catalog())
if err != nil {
return err
}
return framework.DeleteSubscription(subscription)
}
func (installer *OlmNamespacedServiceInstaller) getServiceName() string {
return installer.SubscriptionName
}
func (installer *OlmNamespacedServiceInstaller) cleanupCrsInNamespace(namespace string) bool {
if installer.CleanupNamespacedOlmCrsInNamespace == nil {
return deleteCrsWithoutOwner(installer, namespace)
}
return installer.CleanupNamespacedOlmCrsInNamespace(namespace)
}
// OLM cluster wide service specification
// Make sure that OlmClusterWideServiceInstaller can be typed to ClusterWideServiceInstaller
var _ ClusterWideServiceInstaller = &OlmClusterWideServiceInstaller{}
// OlmClusterWideServiceInstaller installs service using OLM, installed cluster wide
type OlmClusterWideServiceInstaller struct {
SubscriptionName string
Channel string
StartingCSV string
Catalog func() framework.OperatorCatalog
InstallationTimeoutInMinutes int
// Return all CRs of this service which exists in this namespace
GetAllClusterWideOlmCrsInNamespace func(string) ([]client.Object, error)
// Cleanup functionality, will delete CRs without owner if not defined
CleanupClusterWideOlmCrsInNamespace func(namespace string) bool
}
// Install the cluster wide service using OLM into cloud
func (installer *OlmClusterWideServiceInstaller) Install(namespace string) error {
// Store cluster wide service installer to use for uninstalling purposes
if _, loaded := installedClusterWideServices.LoadOrStore(installer, true); loaded {
// Should be running already, wait until it is up
return framework.WaitForClusterWideOperatorRunning(installer.SubscriptionName, installer.Catalog(), installer.InstallationTimeoutInMinutes)
}
if err := framework.InstallClusterWideOperator(installer.SubscriptionName, installer.Channel, installer.StartingCSV, installer.Catalog()); err != nil {
return err
}
return framework.WaitForClusterWideOperatorRunning(installer.SubscriptionName, installer.Catalog(), installer.InstallationTimeoutInMinutes)
}
func (installer *OlmClusterWideServiceInstaller) getAllCrsInNamespace(namespace string) ([]client.Object, error) {
return installer.GetAllClusterWideOlmCrsInNamespace(namespace)
}
func (installer *OlmClusterWideServiceInstaller) uninstallFromCluster() error {
subscription, err := framework.GetClusterWideSubscription(installer.SubscriptionName, installer.Catalog())
if err != nil {
return err
}
return framework.DeleteSubscription(subscription)
}
func (installer *OlmClusterWideServiceInstaller) getServiceName() string {
return installer.SubscriptionName
}
func (installer *OlmClusterWideServiceInstaller) cleanupCrsInNamespace(namespace string) bool {
if installer.CleanupClusterWideOlmCrsInNamespace == nil {
return deleteCrsWithoutOwner(installer, namespace)
}
return installer.CleanupClusterWideOlmCrsInNamespace(namespace)
}
// Helper methods
// Execute a function on all deployed cluster wide services
func executeOnClusterWideServices(execute func(si ClusterWideServiceInstaller) bool) bool {
success := true
installedClusterWideServices.Range(func(si, _ interface{}) bool {
if ok := execute(si.(ClusterWideServiceInstaller)); !ok {
success = false
}
return true
})
return success
}
// Execute a function on all deployed namespaced services
func executeOnNamespacedServices(namespace string, execute func(si NamespacedServiceInstaller) bool) bool {
success := true
if sis, ok := installedNamespacedServices.Load(namespace); ok {
for _, si := range sis.([]NamespacedServiceInstaller) {
if ok := execute(si); !ok {
success = false
}
}
}
return success
}
// Delete all CRs of the service which don't have owner
func deleteCrsWithoutOwner(installer ServiceInstaller, namespace string) bool {
crs, err := getCrsWithoutOwner(installer, namespace)
if err != nil {
framework.GetLogger(namespace).Error(err, "Error getting CRs without owner.", "service name", installer.getServiceName())
return false
}
for _, cr := range crs {
if err := framework.DeleteObject(cr); err != nil {
framework.GetLogger(namespace).Error(err, "Error deleting CR.", "service name", installer.getServiceName(), "CR name", cr.GetName())
return false
}
}
return true
}
// Get all CRs of the service which don't have owner
func getCrsWithoutOwner(installer ServiceInstaller, namespace string) (crsWithoutOwner []client.Object, err error) {
crs, err := installer.getAllCrsInNamespace(namespace)
if err != nil {
return nil, err
}
crsWithoutOwner = []client.Object{}
for _, cr := range crs {
if len(cr.GetOwnerReferences()) == 0 {
crsWithoutOwner = append(crsWithoutOwner, cr)
}
}
return crsWithoutOwner, nil
}
// Wait until all CRs of the service are removed from namespace
func waitForAllCrsRemoval(installer ServiceInstaller, namespace string, timeout time.Duration) bool {
success := true
err := framework.WaitFor(namespace, fmt.Sprintf("Removal of all related CRs for service %s", installer.getServiceName()), timeout, func() (bool, error) {
crs, err := installer.getAllCrsInNamespace(namespace)
if err != nil {
return false, err
}
return len(crs) == 0, nil
})
if err != nil {
framework.GetLogger(namespace).Error(err, "Error waiting on removal of all related CRs", "service name", installer.getServiceName())
success = false
}
return success
}
func monitorNamespace(namespace string) {
go func() {
err := framework.StartPodLogCollector(namespace)
if err != nil {
framework.GetLogger(namespace).Error(err, "Error starting log collector", "namespace", namespace)
}
}()
}
func stopNamespaceMonitoring(namespace string) {
if err := framework.StopPodLogCollector(namespace); err != nil {
framework.GetMainLogger().Error(err, "Error stopping log collector", "namespace", namespace)
}
if err := framework.BumpEvents(namespace); err != nil {
framework.GetMainLogger().Error(err, "Error bumping events", "namespace", namespace)
}
}