/
helper_hostpath.go
223 lines (190 loc) · 6.83 KB
/
helper_hostpath.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
/*
Copyright 2019 The OpenEBS Authors.
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.
This code was taken from https://github.com/rancher/local-path-provisioner
and modified to work with the configuration options used by OpenEBS
*/
package app
import (
"path/filepath"
"time"
errors "github.com/pkg/errors"
"k8s.io/klog"
hostpath "github.com/openebs/maya/pkg/hostpath/v1alpha1"
container "github.com/openebs/maya/pkg/kubernetes/container/v1alpha1"
pod "github.com/openebs/maya/pkg/kubernetes/pod/v1alpha1"
volume "github.com/openebs/maya/pkg/kubernetes/volume/v1alpha1"
corev1 "k8s.io/api/core/v1"
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
)
type podConfig struct {
pOpts *HelperPodOptions
parentDir, volumeDir, podName string
taints []corev1.Taint
}
var (
//CmdTimeoutCounts specifies the duration to wait for cleanup pod
//to be launched.
CmdTimeoutCounts = 120
)
// HelperPodOptions contains the options that
// will launch a Pod on a specific node (nodeHostname)
// to execute a command (cmdsForPath) on a given
// volume path (path)
type HelperPodOptions struct {
//nodeHostname represents the hostname of the node where pod should be launched.
nodeHostname string
//name is the name of the PV for which the pod is being launched
name string
//cmdsForPath represent either create (mkdir) or delete(rm)
//commands that need to be executed on the volume path.
cmdsForPath []string
//path is the volume hostpath directory
path string
// serviceAccountName is the service account with which the pod should be launched
serviceAccountName string
selectedNodeTaints []corev1.Taint
}
// validate checks that the required fields to launch
// helper pods are valid. helper pods are used to either
// create or delete a directory (path) on a given node hostname (nodeHostname).
// name refers to the volume being created or deleted.
func (pOpts *HelperPodOptions) validate() error {
if pOpts.name == "" ||
pOpts.path == "" ||
pOpts.nodeHostname == "" ||
pOpts.serviceAccountName == "" {
return errors.Errorf("invalid empty name or hostpath or hostname or service account name")
}
return nil
}
// createInitPod launches a helper(busybox) pod, to create the host path.
// The local pv expect the hostpath to be already present before mounting
// into pod. Validate that the local pv host path is not created under root.
func (p *Provisioner) createInitPod(pOpts *HelperPodOptions) error {
var config podConfig
config.pOpts, config.podName = pOpts, "init"
//err := pOpts.validate()
if err := pOpts.validate(); err != nil {
return err
}
// Initialize HostPath builder and validate that
// volume directory is not directly under root.
// Extract the base path and the volume unique path.
var vErr error
config.parentDir, config.volumeDir, vErr = hostpath.NewBuilder().WithPath(pOpts.path).
WithCheckf(hostpath.IsNonRoot(), "volume directory {%v} should not be under root directory", pOpts.path).
ExtractSubPath()
if vErr != nil {
return vErr
}
//Pass on the taints, to create tolerations.
config.taints = pOpts.selectedNodeTaints
iPod, err := p.launchPod(config)
if err != nil {
return err
}
if err := p.exitPod(iPod); err != nil {
return err
}
return nil
}
// createCleanupPod launches a helper(busybox) pod, to delete the host path.
// This provisioner expects that the host paths are created using
// an unique PV path - under a given BasePath. From the absolute path,
// it extracts the base path and the PV path. The helper pod is then launched
// by mounting the base path - and performing a delete on the unique PV path.
func (p *Provisioner) createCleanupPod(pOpts *HelperPodOptions) error {
var config podConfig
config.pOpts, config.podName = pOpts, "cleanup"
//err := pOpts.validate()
if err := pOpts.validate(); err != nil {
return err
}
config.taints = pOpts.selectedNodeTaints
// Initialize HostPath builder and validate that
// volume directory is not directly under root.
// Extract the base path and the volume unique path.
var vErr error
config.parentDir, config.volumeDir, vErr = hostpath.NewBuilder().WithPath(pOpts.path).
WithCheckf(hostpath.IsNonRoot(), "volume directory {%v} should not be under root directory", pOpts.path).
ExtractSubPath()
if vErr != nil {
return vErr
}
cPod, err := p.launchPod(config)
if err != nil {
return err
}
if err := p.exitPod(cPod); err != nil {
return err
}
return nil
}
func (p *Provisioner) launchPod(config podConfig) (*corev1.Pod, error) {
// the helper pod need to be launched in privileged mode. This is because in CoreOS
// nodes, pods without privileged access cannot write to the host directory.
// Helper pods need to create and delete directories on the host.
privileged := true
helperPod, err := pod.NewBuilder().
WithName(config.podName + "-" + config.pOpts.name).
WithRestartPolicy(corev1.RestartPolicyNever).
WithNodeSelectorHostnameNew(config.pOpts.nodeHostname).
WithServiceAccountName(config.pOpts.serviceAccountName).
WithTolerationsForTaints(config.taints...).
WithContainerBuilder(
container.NewBuilder().
WithName("local-path-" + config.podName).
WithImage(p.helperImage).
WithCommandNew(append(config.pOpts.cmdsForPath, filepath.Join("/data/", config.volumeDir))).
WithVolumeMountsNew([]corev1.VolumeMount{
{
Name: "data",
ReadOnly: false,
MountPath: "/data/",
},
}).
WithPrivilegedSecurityContext(&privileged),
).
WithVolumeBuilder(
volume.NewBuilder().
WithName("data").
WithHostDirectory(config.parentDir),
).
Build()
//Launch the helper pod.
hPod, err := p.kubeClient.CoreV1().Pods(p.namespace).Create(helperPod)
return hPod, err
}
func (p *Provisioner) exitPod(hPod *corev1.Pod) error {
defer func() {
e := p.kubeClient.CoreV1().Pods(p.namespace).Delete(hPod.Name, &metav1.DeleteOptions{})
if e != nil {
klog.Errorf("unable to delete the helper pod: %v", e)
}
}()
//Wait for the helper pod to complete it job and exit
completed := false
for i := 0; i < CmdTimeoutCounts; i++ {
checkPod, err := p.kubeClient.CoreV1().Pods(p.namespace).Get(hPod.Name, metav1.GetOptions{})
if err != nil {
return err
} else if checkPod.Status.Phase == corev1.PodSucceeded {
completed = true
break
}
time.Sleep(1 * time.Second)
}
if !completed {
return errors.Errorf("create process timeout after %v seconds", CmdTimeoutCounts)
}
return nil
}