@@ -22,6 +22,7 @@ const (
2222 topologyKey = "kubernetes.io/hostname"
2323 deleteJobNS = "kube-system"
2424 deleteJobTimeout = 2 * time .Minute
25+ createJobTimeout = 5 * time .Minute
2526)
2627
2728var errNodeNotFound = errors .New ("node not found" )
@@ -30,28 +31,44 @@ var errNoNodeAffinity = errors.New("no nodeAffinity on PV")
3031type ControllerServer struct {
3132 csi.UnimplementedControllerServer
3233 kubeClient kubernetes.Interface
34+ jobImage string
3335}
3436
35- func (s * ControllerServer ) CreateVolume (_ context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
37+ func (s * ControllerServer ) CreateVolume (ctx context.Context , req * csi.CreateVolumeRequest ) (* csi.CreateVolumeResponse , error ) {
3638 if req .GetName () == "" {
3739 return nil , status .Error (codes .InvalidArgument , "volume name is required" )
3840 }
3941
42+ sizeBytes := req .GetCapacityRange ().GetRequiredBytes ()
43+ if sizeBytes == 0 {
44+ return nil , status .Error (codes .InvalidArgument , "required bytes must be greater than 0" )
45+ }
46+
4047 // Preferred → Requisite の順でトポロジーを選ぶ。
4148 // external-provisioner が WaitForFirstConsumer で決定したノードに PV を固定
4249 var topology []* csi.Topology
50+ var nodeName string
4351 if topo := req .GetAccessibilityRequirements (); topo != nil {
4452 if len (topo .GetPreferred ()) > 0 {
4553 topology = []* csi.Topology {topo .GetPreferred ()[0 ]}
54+ nodeName = topo .GetPreferred ()[0 ].GetSegments ()[topologyKey ]
4655 } else if len (topo .GetRequisite ()) > 0 {
4756 topology = []* csi.Topology {topo .GetRequisite ()[0 ]}
57+ nodeName = topo .GetRequisite ()[0 ].GetSegments ()[topologyKey ]
4858 }
4959 }
60+ if nodeName == "" {
61+ return nil , status .Error (codes .InvalidArgument , "cannot determine target node from topology" )
62+ }
63+
64+ if err := s .runCreateJob (ctx , req .GetName (), nodeName , sizeBytes ); err != nil {
65+ return nil , status .Errorf (codes .Internal , "create job failed: %v" , err )
66+ }
5067
5168 return & csi.CreateVolumeResponse {
5269 Volume : & csi.Volume {
5370 VolumeId : req .GetName (),
54- CapacityBytes : req . GetCapacityRange (). GetRequiredBytes () ,
71+ CapacityBytes : sizeBytes ,
5572 AccessibleTopology : topology ,
5673 },
5774 }, nil
@@ -107,7 +124,104 @@ func (s *ControllerServer) nodeFromPV(ctx context.Context, volumeID string) (str
107124 return "" , errNodeNotFound
108125}
109126
110- // は対象ノードで `rm -rf <volumeBasePath>/<volumeID>` を実行する Job を作成し、完了を待つ。
127+ // 対象ノードでイメージファイルの作成・ループデバイス割り当て・フォーマットを行う Job を実行する。
128+ func (s * ControllerServer ) runCreateJob (ctx context.Context , volumeID , nodeName string , sizeBytes int64 ) error {
129+ jobName := createJobName (volumeID )
130+ imgPath := fmt .Sprintf ("%s/%s.img" , volumeBasePath , volumeID )
131+
132+ script := fmt .Sprintf (`set -e
133+ IMG='%s'
134+ if [ ! -f "$IMG" ]; then
135+ fallocate -l %d "$IMG"
136+ fi
137+ DEV=$(losetup -j "$IMG" | cut -d: -f1)
138+ if [ -z "$DEV" ]; then
139+ DEV=$(losetup -f --show "$IMG")
140+ fi
141+ if ! blkid "$DEV" >/dev/null 2>&1; then
142+ mkfs.ext4 "$DEV"
143+ fi` , imgPath , sizeBytes )
144+
145+ privileged := true
146+ job := & batchv1.Job {
147+ ObjectMeta : metav1.ObjectMeta {
148+ Name : jobName ,
149+ Namespace : deleteJobNS ,
150+ },
151+ Spec : batchv1.JobSpec {
152+ Template : corev1.PodTemplateSpec {
153+ Spec : corev1.PodSpec {
154+ NodeName : nodeName ,
155+ RestartPolicy : corev1 .RestartPolicyNever ,
156+ Containers : []corev1.Container {
157+ {
158+ Name : "create" ,
159+ Image : s .jobImage ,
160+ Command : []string {"/bin/sh" , "-c" , script },
161+ SecurityContext : & corev1.SecurityContext {
162+ Privileged : & privileged ,
163+ },
164+ VolumeMounts : []corev1.VolumeMount {
165+ {Name : "data" , MountPath : volumeBasePath },
166+ {Name : "dev" , MountPath : "/dev" },
167+ },
168+ },
169+ },
170+ Volumes : []corev1.Volume {
171+ {
172+ Name : "data" ,
173+ VolumeSource : corev1.VolumeSource {
174+ HostPath : & corev1.HostPathVolumeSource {Path : volumeBasePath },
175+ },
176+ },
177+ {
178+ Name : "dev" ,
179+ VolumeSource : corev1.VolumeSource {
180+ HostPath : & corev1.HostPathVolumeSource {Path : "/dev" },
181+ },
182+ },
183+ },
184+ },
185+ },
186+ },
187+ }
188+
189+ _ , err := s .kubeClient .BatchV1 ().Jobs (deleteJobNS ).Create (ctx , job , metav1.CreateOptions {})
190+ if err != nil && ! k8serrors .IsAlreadyExists (err ) {
191+ return err
192+ }
193+
194+ deadline := time .Now ().Add (createJobTimeout )
195+ for time .Now ().Before (deadline ) {
196+ j , err := s .kubeClient .BatchV1 ().Jobs (deleteJobNS ).Get (ctx , jobName , metav1.GetOptions {})
197+ if err != nil {
198+ return err
199+ }
200+ if j .Status .Succeeded > 0 {
201+ _ = s .kubeClient .BatchV1 ().Jobs (deleteJobNS ).Delete (ctx , jobName , metav1.DeleteOptions {})
202+ return nil
203+ }
204+ if j .Status .Failed > 3 {
205+ _ = s .kubeClient .BatchV1 ().Jobs (deleteJobNS ).Delete (ctx , jobName , metav1.DeleteOptions {})
206+ return fmt .Errorf ("create job failed after retries" )
207+ }
208+ time .Sleep (3 * time .Second )
209+ }
210+
211+ return fmt .Errorf ("create job timed out after %v" , createJobTimeout )
212+ }
213+
214+ func createJobName (volumeID string ) string {
215+ const prefix = "csi-create-"
216+ name := prefix + volumeID
217+ if len (name ) <= 63 {
218+ return name
219+ }
220+ h := sha256 .Sum256 ([]byte (volumeID ))
221+ return fmt .Sprintf ("%s%x" , prefix , h [:])[:63 ]
222+ }
223+
224+ // 対象ノードで `rm -rf <volumeBasePath>/<volumeID>` を実行する Job を作成し、完了を待つ。
111225func (s * ControllerServer ) runDeleteJob (ctx context.Context , volumeID , nodeName string ) error {
112226 jobName := deleteJobName (volumeID )
113227
0 commit comments