aboutsummaryrefslogtreecommitdiffstats
path: root/src/k8splugin/internal/app/hook.go
blob: ebf5f8e3db37f2b124823ec9a16cba60b110bcec (plain)
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
/*
Copyright © 2021 Nokia Bell Labs
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 app

import (
	"fmt"
	"github.com/onap/multicloud-k8s/src/k8splugin/internal/db"
	"github.com/onap/multicloud-k8s/src/k8splugin/internal/helm"
	"helm.sh/helm/v3/pkg/release"
	"log"
	"strings"
	"time"
)

// Timeout used when deleting resources with a hook-delete-policy.
const defaultHookDeleteTimeoutInSeconds = int64(60)

// HookClient implements the Helm Hook interface
type HookClient struct {
	kubeNameSpace 	string
	id     			string
	dbStoreName		string
	dbTagInst		string
}

type MultiCloudHook struct{
	release.Hook
	Group   string
	Version string
}

// NewHookClient returns a new instance of HookClient
func NewHookClient(namespace, id, dbStoreName, dbTagInst string) *HookClient {
	return &HookClient{
		kubeNameSpace: namespace,
		id: id,
		dbStoreName: dbStoreName,
		dbTagInst: dbTagInst,
	}
}

func (hc *HookClient) getHookByEvent(hs []*helm.Hook, hook release.HookEvent) []*helm.Hook {
	hooks := []*helm.Hook{}
	for _, h := range hs {
		for _, e := range h.Hook.Events {
			if e == hook {
				hooks = append(hooks, h)
			}
		}
	}
	return hooks
}

// Mimic function ExecHook in helm/pkg/tiller/release_server.go
func (hc *HookClient) ExecHook(
	k8sClient KubernetesClient,
	hs []*helm.Hook,
	hook release.HookEvent,
	timeout int64,
	startIndex int,
	dbData *InstanceDbData) (error){
	executingHooks := hc.getHookByEvent(hs, hook)
	key := InstanceKey{
		ID: hc.id,
	}
	log.Printf("Executing %d %s hook(s) for instance %s", len(executingHooks), hook, hc.id)
	executingHooks = sortByHookWeight(executingHooks)

	for index, h := range executingHooks {
		if index < startIndex {
			continue
		}
		// Set default delete policy to before-hook-creation
		if h.Hook.DeletePolicies == nil || len(h.Hook.DeletePolicies) == 0 {
			h.Hook.DeletePolicies = []release.HookDeletePolicy{release.HookBeforeHookCreation}
		}
		if err := hc.deleteHookByPolicy(h, release.HookBeforeHookCreation, k8sClient); err != nil {
			return err
		}
		//update DB here before the creation of the hook, if the plugin quits
		//-> when it comes back, it will continue from next hook and consider that this one is done
		if dbData != nil {
			dbData.HookProgress = fmt.Sprintf("%d/%d", index + 1, len(executingHooks))
			err := db.DBconn.Update(hc.dbStoreName, key, hc.dbTagInst, dbData)
			if err != nil {
				return err
			}
		}
		log.Printf("  Instance: %s, Creating %s hook %s, index %d", hc.id, hook, h.Hook.Name, index)
		resTempl := helm.KubernetesResourceTemplate{
			GVK:      h.KRT.GVK,
			FilePath: h.KRT.FilePath,
		}
		createdHook, err := k8sClient.CreateKind(resTempl, hc.kubeNameSpace)
		if  err != nil {
			log.Printf("  Instance: %s, Warning: %s hook %s, filePath: %s, error: %s", hc.id, hook, h.Hook.Name, h.KRT.FilePath, err)
			hc.deleteHookByPolicy(h, release.HookFailed, k8sClient)
			return err
		}
		if hook != "crd-install" {
			//timeout <= 0 -> do not wait
			if timeout > 0 {
				// Watch hook resources until they are completed
				err = k8sClient.WatchHookUntilReady(time.Duration(timeout)*time.Second, hc.kubeNameSpace, createdHook)
				if err != nil {
					// If a hook is failed, check the annotation of the hook to determine whether the hook should be deleted
					// under failed condition. If so, then clear the corresponding resource object in the hook
					if err := hc.deleteHookByPolicy(h, release.HookFailed, k8sClient); err != nil {
						return err
					}
					return err
				}
			}
		} else {
			//Do not handle CRD Hooks
		}
	}

	for _, h := range executingHooks {
		if err := hc.deleteHookByPolicy(h, release.HookSucceeded, k8sClient); err != nil {
			log.Printf("  Instance: %s, Warning: Error deleting %s hook %s based on delete policy, continue", hc.id, hook, h.Hook.Name)
			return err
		}
	}
	log.Printf("%d %s hook(s) complete for release %s", len(executingHooks), hook, hc.id)
	return nil
}

func (hc *HookClient) deleteHookByPolicy(h *helm.Hook, policy release.HookDeletePolicy, k8sClient KubernetesClient) error {
	rss := helm.KubernetesResource{
		GVK:  h.KRT.GVK,
		Name: h.Hook.Name,
	}
	if hookHasDeletePolicy(h, policy) {
		log.Printf("  Instance: %s, Deleting hook %s due to %q policy", hc.id, h.Hook.Name, policy)
		if errHookDelete := k8sClient.deleteResources(append([]helm.KubernetesResource{}, rss), hc.kubeNameSpace); errHookDelete != nil {
			if strings.Contains(errHookDelete.Error(), "not found") {
				return nil
			} else {
				log.Printf("  Instance: %s, Warning: hook %s, filePath %s could not be deleted: %s", hc.id, h.Hook.Name, h.KRT.FilePath ,errHookDelete)
				return errHookDelete
			}
		} else {
			//Verify that the rss is deleted
			isDeleted := false
			for !isDeleted {
				log.Printf("  Instance: %s, Waiting on deleting hook %s for release %s due to %q policy", hc.id, h.Hook.Name, hc.id, policy)
				if _, err := k8sClient.GetResourceStatus(rss, hc.kubeNameSpace); err != nil {
					if strings.Contains(err.Error(), "not found") {
						log.Printf("  Instance: %s, Deleted hook %s for release %s due to %q policy", hc.id, h.Hook.Name, hc.id, policy)
						return nil
					} else {
						isDeleted = true
					}
				}
				time.Sleep(5 * time.Second)
			}
		}
	}
	return nil
}

// hookHasDeletePolicy determines whether the defined hook deletion policy matches the hook deletion polices
// supported by helm. If so, mark the hook as one should be deleted.
func hookHasDeletePolicy(h *helm.Hook, policy release.HookDeletePolicy) bool {
	for _, v := range h.Hook.DeletePolicies {
		if policy == v {
			return true
		}
	}
	return false
}