diff --git a/images/sds-lvm-csi/api/v1alpha1/lvm_logical_volume.go b/images/sds-lvm-csi/api/v1alpha1/lvm_logical_volume.go new file mode 100644 index 00000000..36acd845 --- /dev/null +++ b/images/sds-lvm-csi/api/v1alpha1/lvm_logical_volume.go @@ -0,0 +1,54 @@ +/* +Copyright 2023 Flant JSC + +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 v1alpha1 + +import ( + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" +) + +type LvmLogicalVolumeList struct { + metav1.TypeMeta `json:",inline"` + metav1.ListMeta `json:"metadata"` + + Items []LvmLogicalVolume `json:"items"` +} + +type LvmLogicalVolume struct { + metav1.TypeMeta `json:",inline"` + metav1.ObjectMeta `json:"metadata,omitempty"` + + Spec LvmLogicalVolumeSpec `json:"spec"` + Status *LvmLogicalVolumeStatus `json:"status,omitempty"` +} + +type LvmLogicalVolumeSpec struct { + Type string `json:"type"` + Size resource.Quantity `json:"size"` + LvmVolumeGroup string `json:"lvmVolumeGroup"` + Thin *ThinLogicalVolumeSpec `json:"thin,omitempty"` +} + +type ThinLogicalVolumeSpec struct { + PoolName string `json:"poolName"` +} + +type LvmLogicalVolumeStatus struct { + Phase string `json:"phase"` + Reason string `json:"reason"` + ActualSize resource.Quantity `json:"actualSize"` +} diff --git a/images/sds-lvm-csi/api/v1alpha1/register.go b/images/sds-lvm-csi/api/v1alpha1/register.go index d0d060a7..1a5043bb 100644 --- a/images/sds-lvm-csi/api/v1alpha1/register.go +++ b/images/sds-lvm-csi/api/v1alpha1/register.go @@ -23,10 +23,11 @@ import ( ) const ( - LVMVolumeGroupKind = "LvmVolumeGroup" - APIGroup = "storage.deckhouse.io" - APIVersion = "v1alpha1" - TypeMediaAPIVersion = APIGroup + "/" + APIVersion + LVMVolumeGroupKind = "LvmVolumeGroup" + LVMLogicalVolumeKind = "LvmLogicalVolume" + APIGroup = "storage.deckhouse.io" + APIVersion = "v1alpha1" + TypeMediaAPIVersion = APIGroup + "/" + APIVersion ) // SchemeGroupVersion is group version used to register these objects @@ -44,6 +45,8 @@ func addKnownTypes(scheme *runtime.Scheme) error { scheme.AddKnownTypes(SchemeGroupVersion, &LvmVolumeGroup{}, &LvmVolumeGroupList{}, + &LvmLogicalVolume{}, + &LvmLogicalVolumeList{}, ) metav1.AddToGroupVersion(scheme, SchemeGroupVersion) return nil diff --git a/images/sds-lvm-csi/api/v1alpha1/zz_generated.deepcopy.go b/images/sds-lvm-csi/api/v1alpha1/zz_generated.deepcopy.go index ab6654af..a6456841 100644 --- a/images/sds-lvm-csi/api/v1alpha1/zz_generated.deepcopy.go +++ b/images/sds-lvm-csi/api/v1alpha1/zz_generated.deepcopy.go @@ -75,3 +75,63 @@ func (in *LvmVolumeGroupList) DeepCopyObject() runtime.Object { } return nil } + +// -------------- LvmLogicalVolume ---------- + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LvmLogicalVolume) DeepCopyInto(out *LvmLogicalVolume) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ObjectMeta.DeepCopyInto(&out.ObjectMeta) + +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new EmptyBlockDevice. +func (in *LvmLogicalVolume) DeepCopy() *LvmLogicalVolume { + if in == nil { + return nil + } + out := new(LvmLogicalVolume) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LvmLogicalVolume) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} + +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *LvmLogicalVolumeList) DeepCopyInto(out *LvmLogicalVolumeList) { + *out = *in + out.TypeMeta = in.TypeMeta + in.ListMeta.DeepCopyInto(&out.ListMeta) + if in.Items != nil { + in, out := &in.Items, &out.Items + *out = make([]LvmLogicalVolume, len(*in)) + for i := range *in { + (*in)[i].DeepCopyInto(&(*out)[i]) + } + } +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new GuestbookList. +func (in *LvmLogicalVolumeList) DeepCopy() *LvmLogicalVolumeList { + if in == nil { + return nil + } + out := new(LvmLogicalVolumeList) + in.DeepCopyInto(out) + return out +} + +// DeepCopyObject is an autogenerated deepcopy function, copying the receiver, creating a new runtime.Object. +func (in *LvmLogicalVolumeList) DeepCopyObject() runtime.Object { + if c := in.DeepCopy(); c != nil { + return c + } + return nil +} diff --git a/images/sds-lvm-csi/driver/const.go b/images/sds-lvm-csi/driver/const.go new file mode 100644 index 00000000..89102e36 --- /dev/null +++ b/images/sds-lvm-csi/driver/const.go @@ -0,0 +1,31 @@ +/* +Copyright 2023 Flant JSC + +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 driver + +const ( + lvmType = "lvm.csi.storage.deckhouse.io/lvm-type" + lvmBindingMode = "lvm.csi.storage.deckhouse.io/volume-binding-mode" + lvmVolumeGroup = "lvm.csi.storage.deckhouse.io/lvm-volume-groups" + topologyKey = "topology.sds-lvm-csi/node" + subPath = "subPath" + VGNameKey = "vgname" + LLVTypeThin = "Thin" + LLVTypeThick = "Thick" + LLVStatusCreated = "Created" + BindingModeWFFC = "WaitForFirstConsumer" + BindingModeI = "Immediate" +) diff --git a/images/sds-lvm-csi/driver/controller.go b/images/sds-lvm-csi/driver/controller.go index 2e1fe8a1..9b06def6 100644 --- a/images/sds-lvm-csi/driver/controller.go +++ b/images/sds-lvm-csi/driver/controller.go @@ -27,16 +27,6 @@ import ( "google.golang.org/grpc/status" "gopkg.in/yaml.v3" "k8s.io/apimachinery/pkg/api/resource" - metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" - "k8s.io/apimachinery/pkg/labels" - "sigs.k8s.io/controller-runtime/pkg/client" -) - -const ( - lvmSelector = "lvm.csi.storage.deckhouse.io/lvm-vg-selector" - topologyKey = "topology.sds-lvm-csi/node" - subPath = "subPath" - VGNameKey = "vgname" ) func (d *Driver) CreateVolume(ctx context.Context, request *csi.CreateVolumeRequest) (*csi.CreateVolumeResponse, error) { @@ -45,79 +35,112 @@ func (d *Driver) CreateVolume(ctx context.Context, request *csi.CreateVolumeRequ d.log.Info("========== CreateVolume ============") d.log.Info(request.String()) d.log.Info("========== CreateVolume ============") - fmt.Println("request.GetVolumeCapabilities():", request.GetVolumeCapabilities()) - d.log.Info("------------------------------------") - l := make(map[string]string) - err := yaml.Unmarshal([]byte(request.GetParameters()[lvmSelector]), &l) - if err != nil { - d.log.Error(err, "unmarshal labels") - return nil, status.Error(codes.Internal, "Unmarshal labels") + if len(request.Name) == 0 { + return nil, status.Error(codes.InvalidArgument, "Volume Name cannot be empty") } - - selector := labels.SelectorFromSet(l) - if err != nil { - d.log.Error(err, "build selector") - return nil, status.Error(codes.Internal, "Build selector") + if request.VolumeCapabilities == nil { + return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot de empty") } - listLvgs := &v1alpha1.LvmVolumeGroupList{ - TypeMeta: metav1.TypeMeta{ - Kind: v1alpha1.LVMVolumeGroupKind, - APIVersion: v1alpha1.TypeMediaAPIVersion, - }, - ListMeta: metav1.ListMeta{}, - Items: []v1alpha1.LvmVolumeGroup{}, + var LvmBindingMode string + switch request.GetParameters()[lvmBindingMode] { + case BindingModeWFFC: + LvmBindingMode = BindingModeWFFC + case BindingModeI: + LvmBindingMode = BindingModeI } - err = d.cl.List(ctx, listLvgs, &client.ListOptions{ - LabelSelector: selector, - }) - if err != nil { - d.log.Error(err, "get list lvg") - return nil, status.Error(codes.Internal, "Get list lvg") - } - - nodesVGSize := make(map[string]int64) - var vgName string - for _, lvg := range listLvgs.Items { - obj := &v1alpha1.LvmVolumeGroup{} - err = d.cl.Get(ctx, client.ObjectKey{ - Name: lvg.Name, - Namespace: lvg.Namespace, - }, obj) + d.log.Info(fmt.Sprintf("storage class LvmBindingMode: %s", LvmBindingMode)) + + var LvmType string + switch request.GetParameters()[lvmType] { + case LLVTypeThin: + LvmType = LLVTypeThin + case LLVTypeThick: + LvmType = LLVTypeThick + } + d.log.Info(fmt.Sprintf("storage class LvmType: %s", LvmType)) + + lvmVG := make(map[string]string) + if len(request.GetParameters()[lvmVolumeGroup]) != 0 { + var lvmVolumeGroups LVMVolumeGroups + err := yaml.Unmarshal([]byte(request.GetParameters()[lvmVolumeGroup]), &lvmVolumeGroups) if err != nil { - d.log.Error(err, fmt.Sprintf("get lvg name = %s", lvg.Name)) - return nil, status.Error(codes.Internal, "Get lvg name") + d.log.Error(err, "unmarshal yaml lvmVolumeGroup") } + for _, v := range lvmVolumeGroups { + lvmVG[v.Name] = v.Thin.PoolName + } + } + d.log.Info(fmt.Sprintf("lvm-volume-groups: %+v", lvmVG)) - vgSize, err := resource.ParseQuantity(lvg.Status.VGSize) + llvName := request.Name + d.log.Info(fmt.Sprintf("llv name: %s ", llvName)) + + llvSize := resource.NewQuantity(request.CapacityRange.GetRequiredBytes(), resource.BinarySI) + d.log.Info(fmt.Sprintf("llv size: %s ", llvSize.String())) + + var preferredNode string + if LvmBindingMode == BindingModeI { + prefNode, freeSpace, err := utils.GetNodeMaxVGSize(ctx, d.cl) if err != nil { - d.log.Error(err, "parse size vgSize") - return nil, status.Error(codes.Internal, "Parse size vgSize") + d.log.Error(err, "error GetNodeMaxVGSize") } + preferredNode = prefNode + d.log.Info(fmt.Sprintf("prefered node: %s, free space %s ", prefNode, freeSpace)) + } - d.log.Info("------------------------------") - d.log.Info(lvg.Name) - d.log.Info(lvg.Spec.ActualVGNameOnTheNode) - d.log.Info(vgSize.String()) - d.log.Info("------------------------------") + if LvmBindingMode == BindingModeWFFC { + if len(request.AccessibilityRequirements.Preferred) != 0 { + t := request.AccessibilityRequirements.Preferred[0].Segments + preferredNode = t[topologyKey] + } + } - nodesVGSize[lvg.Name] = vgSize.Value() - vgName = lvg.Spec.ActualVGNameOnTheNode + lvmVolumeGroupName, vgName, err := utils.GetVGName(ctx, d.cl, lvmVG, preferredNode, LvmType) + if err != nil { + d.log.Error(err, "error GetVGName") } - nodeName, _ := utils.NodeWithMaxSize(nodesVGSize) - d.log.Info("nodeName maxSize = " + nodeName) + d.log.Info(fmt.Sprintf("LvmVolumeGroup: %s", lvmVolumeGroupName)) + d.log.Info(fmt.Sprintf("VGName: %s", vgName)) + d.log.Info(fmt.Sprintf("prefered node: %s", preferredNode)) - if len(request.Name) == 0 { - return nil, status.Error(codes.InvalidArgument, "Volume Name cannot be empty") + d.log.Info("------------ CreateLVMLogicalVolume ------------") + llvThin := &v1alpha1.ThinLogicalVolumeSpec{} + if LvmType == LLVTypeThick { + llvThin = nil } - if request.VolumeCapabilities == nil { - return nil, status.Error(codes.InvalidArgument, "Volume Capability cannot de empty") + if LvmType == LLVTypeThin { + llvThin.PoolName = lvmVG[lvmVolumeGroupName] } - requiredCap := request.CapacityRange.GetRequiredBytes() + spec := v1alpha1.LvmLogicalVolumeSpec{ + Type: LvmType, + Size: *llvSize, + LvmVolumeGroup: lvmVolumeGroupName, + Thin: llvThin, + } + + d.log.Info(fmt.Sprintf("LvmLogicalVolumeSpec : %+v", spec)) + + err, llv := utils.CreateLVMLogicalVolume(ctx, d.cl, llvName, spec) + if err != nil { + d.log.Error(err, "error CreateLVMLogicalVolume") + // todo if llv exist? + //return nil, err + } + d.log.Info("------------ CreateLVMLogicalVolume ------------") + d.log.Info("start wait CreateLVMLogicalVolume ") + attemptCounter, err := utils.WaitForStatusUpdate(ctx, d.cl, request.Name, llv.Namespace) + if err != nil { + d.log.Error(err, "error WaitForStatusUpdate") + return nil, err + } + d.log.Info(fmt.Sprintf("stop wait CreateLVMLogicalVolume, attempt сounter = %d ", attemptCounter)) + + //Create context volCtx := make(map[string]string) for k, v := range request.Parameters { volCtx[k] = v @@ -126,20 +149,15 @@ func (d *Driver) CreateVolume(ctx context.Context, request *csi.CreateVolumeRequ volCtx[subPath] = request.Name volCtx[VGNameKey] = vgName - d.log.Info("========== CreateVolume ============") - fmt.Println(".>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") - fmt.Println("nodeName = ", nodeName) - fmt.Println(".>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>>") - return &csi.CreateVolumeResponse{ Volume: &csi.Volume{ - CapacityBytes: requiredCap, + CapacityBytes: request.CapacityRange.GetRequiredBytes(), VolumeId: request.Name, VolumeContext: volCtx, ContentSource: request.VolumeContentSource, AccessibleTopology: []*csi.Topology{ {Segments: map[string]string{ - topologyKey: "a-ohrimenko-worker-1", + topologyKey: preferredNode, }}, }, }, @@ -148,15 +166,16 @@ func (d *Driver) CreateVolume(ctx context.Context, request *csi.CreateVolumeRequ func (d *Driver) DeleteVolume(ctx context.Context, request *csi.DeleteVolumeRequest) (*csi.DeleteVolumeResponse, error) { d.log.Info("method DeleteVolume") + err := utils.DeleteLVMLogicalVolume(ctx, d.cl, request.VolumeId) + if err != nil { + d.log.Error(err, "error DeleteLVMLogicalVolume") + } + d.log.Info(fmt.Sprintf("delete volume %s", request.VolumeId)) return &csi.DeleteVolumeResponse{}, nil } func (d *Driver) ControllerPublishVolume(ctx context.Context, request *csi.ControllerPublishVolumeRequest) (*csi.ControllerPublishVolumeResponse, error) { d.log.Info("method ControllerPublishVolume") - fmt.Println("///////////// ControllerPublishVolume ///////////////////////") - fmt.Println("request.String() = ", request.String()) - fmt.Println("///////////// ControllerPublishVolume ///////////////////////") - return &csi.ControllerPublishVolumeResponse{ PublishContext: map[string]string{ d.publishInfoVolumeName: request.VolumeId, @@ -166,9 +185,7 @@ func (d *Driver) ControllerPublishVolume(ctx context.Context, request *csi.Contr func (d *Driver) ControllerUnpublishVolume(ctx context.Context, request *csi.ControllerUnpublishVolumeRequest) (*csi.ControllerUnpublishVolumeResponse, error) { d.log.Info("method ControllerUnpublishVolume") - fmt.Println("00000000000 ControllerUnpublishVolume 00000000000000000000000000000000") - fmt.Println(request.String()) - fmt.Println("00000000000 ControllerUnpublishVolume 00000000000000000000000000000000") + // todo called Immediate return &csi.ControllerUnpublishVolumeResponse{}, nil } @@ -183,12 +200,13 @@ func (d *Driver) ListVolumes(ctx context.Context, request *csi.ListVolumesReques } func (d *Driver) GetCapacity(ctx context.Context, request *csi.GetCapacityRequest) (*csi.GetCapacityResponse, error) { - d.log.Info("method GetCapacity - 1") + d.log.Info("method GetCapacity") + //todo MaxSize one PV //todo call volumeBindingMode: WaitForFirstConsumer return &csi.GetCapacityResponse{ - AvailableCapacity: 1000000000000, + AvailableCapacity: 1000000, MaximumVolumeSize: nil, MinimumVolumeSize: nil, }, nil diff --git a/images/sds-lvm-csi/driver/node.go b/images/sds-lvm-csi/driver/node.go index fd7b0e52..386d26a2 100644 --- a/images/sds-lvm-csi/driver/node.go +++ b/images/sds-lvm-csi/driver/node.go @@ -19,12 +19,8 @@ package driver import ( "context" "fmt" + "github.com/container-storage-interface/spec/lib/go/csi" - "google.golang.org/grpc/codes" - "google.golang.org/grpc/status" - "gopkg.in/yaml.v3" - "k8s.io/apimachinery/pkg/api/resource" - "sds-lvm-csi/pkg/utils" ) func (d *Driver) NodeStageVolume(ctx context.Context, request *csi.NodeStageVolumeRequest) (*csi.NodeStageVolumeResponse, error) { @@ -42,43 +38,6 @@ func (d *Driver) NodePublishVolume(ctx context.Context, request *csi.NodePublish d.log.Info("------------- NodePublishVolume --------------") d.log.Info(request.String()) d.log.Info("------------- NodePublishVolume --------------") - d.log.Info("------------- Extend params --------------") - d.log.Info("request.GetVolumeCapability().GetBlock():", request.GetVolumeCapability().GetBlock().String()) - d.log.Info("request.GetVolumeCapability().GetMount():", request.GetVolumeCapability().GetMount().String()) - d.log.Info("------------- Extend params --------------") - - // Extract VGName - vgName := make(map[string]string) - err := yaml.Unmarshal([]byte(request.GetVolumeContext()[lvmSelector]), &vgName) - if err != nil { - d.log.Error(err, "unmarshal labels") - return nil, status.Error(codes.Internal, "Unmarshal volume context") - } - - d.log.Info("---------------- LVCreate External code ----------------") - command, _, err := utils.LVExist(request.GetVolumeContext()[VGNameKey], request.VolumeId) - d.log.Info(command) - if err != nil { - d.log.Error(err, " error utils.LVExist") - - d.log.Info("LV Create START") - deviceSize, err := resource.ParseQuantity("1000000000") - if err != nil { - fmt.Println(err) - } - - lv, err := utils.CreateLV(deviceSize.String(), request.VolumeId, request.GetVolumeContext()[VGNameKey]) - if err != nil { - d.log.Error(err, "") - } - d.log.Info(fmt.Sprintf("[lv create] size=%s pvc=%s vg=%s", deviceSize.String(), request.VolumeId, request.GetVolumeContext()[VGNameKey])) - fmt.Println("lv create command = ", lv) - if err != nil { - fmt.Println(err) - } - d.log.Info("LV Create STOP") - } - d.log.Info("---------------- LVCreate External code ----------------") dev := fmt.Sprintf("/dev/%s/%s", request.GetVolumeContext()[VGNameKey], request.VolumeId) @@ -99,7 +58,7 @@ func (d *Driver) NodePublishVolume(ctx context.Context, request *csi.NodePublish mountOptions = append(mountOptions, mnt.GetMountFlags()...) } - err = d.mounter.Mount(dev, request.GetTargetPath(), IsBlock, fsType, false, mountOptions) + err := d.mounter.Mount(dev, request.GetTargetPath(), IsBlock, fsType, false, mountOptions) if err != nil { d.log.Error(err, "d.mounter.Mount :") return nil, err @@ -111,7 +70,7 @@ func (d *Driver) NodePublishVolume(ctx context.Context, request *csi.NodePublish func (d *Driver) NodeUnpublishVolume(ctx context.Context, request *csi.NodeUnpublishVolumeRequest) (*csi.NodeUnpublishVolumeResponse, error) { d.log.Info("method NodeUnpublishVolume") fmt.Println("------------- NodeUnpublishVolume --------------") - fmt.Println(request) + fmt.Println(request.String()) fmt.Println("------------- NodeUnpublishVolume --------------") err := d.mounter.Unmount(request.GetTargetPath()) @@ -137,7 +96,7 @@ func (d *Driver) NodeGetCapabilities(ctx context.Context, request *csi.NodeGetCa } func (d *Driver) NodeGetInfo(ctx context.Context, request *csi.NodeGetInfoRequest) (*csi.NodeGetInfoResponse, error) { - d.log.Info("method NodeGetInfo 0 2") + d.log.Info("method NodeGetInfo") d.log.Info("hostID = ", d.hostID) return &csi.NodeGetInfoResponse{ diff --git a/images/sds-lvm-csi/pkg/utils/utilfunc.go b/images/sds-lvm-csi/driver/type.go similarity index 72% rename from images/sds-lvm-csi/pkg/utils/utilfunc.go rename to images/sds-lvm-csi/driver/type.go index 3835b9ae..138e822b 100644 --- a/images/sds-lvm-csi/pkg/utils/utilfunc.go +++ b/images/sds-lvm-csi/driver/type.go @@ -14,15 +14,13 @@ See the License for the specific language governing permissions and limitations under the License. */ -package utils +package driver -func NodeWithMaxSize(vgNameAndSize map[string]int64) (vgName string, maxSize int64) { - - for k, n := range vgNameAndSize { - if n > maxSize { - maxSize = n - vgName = k - } - } - return vgName, maxSize +type VolumeGroup struct { + Name string `yaml:"name"` + Thin struct { + PoolName string `yaml:"poolName"` + } `yaml:"thin"` } + +type LVMVolumeGroups []VolumeGroup diff --git a/images/sds-lvm-csi/pkg/utils/command.go b/images/sds-lvm-csi/pkg/utils/command.go deleted file mode 100644 index fff570dd..00000000 --- a/images/sds-lvm-csi/pkg/utils/command.go +++ /dev/null @@ -1,35 +0,0 @@ -package utils - -import ( - "bytes" - "fmt" - "os/exec" -) - -func CreateLV(size, pvName, VGName string) (string, error) { - - cmd := exec.Command( - "lvcreate", "-L", size, "-n", pvName, VGName) - - var stderr bytes.Buffer - cmd.Stderr = &stderr - - if err := cmd.Run(); err != nil { - return cmd.String(), fmt.Errorf("unable to CreateLV, err: %w tderr = %s", err, stderr.String()) - } - return cmd.String(), nil -} - -func LVExist(vgName, lvName string) (command string, stdErr bytes.Buffer, err error) { - var outs bytes.Buffer - lv := fmt.Sprintf("/dev/%s/%s", vgName, lvName) - cmd := exec.Command("lvdisplay", lv) - cmd.Stdout = &outs - cmd.Stderr = &stdErr - - if err := cmd.Run(); err != nil { - return cmd.String(), stdErr, fmt.Errorf("lv %s in not exist, err: %w", lv, err) - } - - return cmd.String(), stdErr, nil -} diff --git a/images/sds-lvm-csi/pkg/utils/func.go b/images/sds-lvm-csi/pkg/utils/func.go new file mode 100644 index 00000000..f81afa92 --- /dev/null +++ b/images/sds-lvm-csi/pkg/utils/func.go @@ -0,0 +1,181 @@ +/* +Copyright 2023 Flant JSC + +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 utils + +import ( + "context" + "errors" + "fmt" + "sds-lvm-csi/api/v1alpha1" + "time" + + "k8s.io/apimachinery/pkg/api/resource" + metav1 "k8s.io/apimachinery/pkg/apis/meta/v1" + "sigs.k8s.io/controller-runtime/pkg/client" +) + +const ( + LLVStatusCreated = "Created" + LLVTypeThin = "Thin" +) + +func NodeWithMaxSize(vgNameAndSize map[string]int64) (vgName string, maxSize int64) { + + for k, n := range vgNameAndSize { + if n > maxSize { + maxSize = n + vgName = k + } + } + return vgName, maxSize +} + +func CreateLVMLogicalVolume(ctx context.Context, kc client.Client, name string, LvmLogicalVolumeSpec v1alpha1.LvmLogicalVolumeSpec) (error, *v1alpha1.LvmLogicalVolume) { + llv := &v1alpha1.LvmLogicalVolume{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.LVMLogicalVolumeKind, + APIVersion: v1alpha1.TypeMediaAPIVersion, + }, + ObjectMeta: metav1.ObjectMeta{ + Name: name, + OwnerReferences: []metav1.OwnerReference{}, + }, + Spec: LvmLogicalVolumeSpec, + } + + err := kc.Create(ctx, llv) + if err != nil { + return err, nil + } + return nil, llv +} + +func DeleteLVMLogicalVolume(ctx context.Context, kc client.Client, LvmLogicalVolumeName string) error { + llv := &v1alpha1.LvmLogicalVolume{ + ObjectMeta: metav1.ObjectMeta{ + Name: LvmLogicalVolumeName, + }, + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.LVMLogicalVolumeKind, + APIVersion: v1alpha1.TypeMediaAPIVersion, + }, + } + err := kc.Delete(ctx, llv) + if err != nil { + return err + } + return nil +} + +func WaitForStatusUpdate(ctx context.Context, kc client.Client, LvmLogicalVolumeName, namespace string) (int, error) { + var newLV v1alpha1.LvmLogicalVolume + var attemptCounter int + for { + attemptCounter++ + select { + case <-ctx.Done(): + return attemptCounter, ctx.Err() + case <-time.After(500 * time.Millisecond): + } + + err := kc.Get(ctx, client.ObjectKey{ + Name: LvmLogicalVolumeName, + Namespace: namespace, + }, &newLV) + if err != nil { + return attemptCounter, err + } + + if newLV.Status != nil && newLV.Status.Phase == LLVStatusCreated { + return attemptCounter, nil + } + } +} + +func GetNodeMaxVGSize(ctx context.Context, kc client.Client) (nodeName string, freeSpace string, err error) { + listLvgs := &v1alpha1.LvmVolumeGroupList{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.LVMVolumeGroupKind, + APIVersion: v1alpha1.TypeMediaAPIVersion, + }, + ListMeta: metav1.ListMeta{}, + Items: []v1alpha1.LvmVolumeGroup{}, + } + err = kc.List(ctx, listLvgs) + if err != nil { + return "", "", err + } + + nodesVGSize := make(map[string]int64) + vgNameNodeName := make(map[string]string) + for _, lvg := range listLvgs.Items { + obj := &v1alpha1.LvmVolumeGroup{} + err = kc.Get(ctx, client.ObjectKey{ + Name: lvg.Name, + Namespace: lvg.Namespace, + }, obj) + if err != nil { + return "", "", errors.New(fmt.Sprintf("get lvg name: %s", lvg.Name)) + } + + vgSize, err := resource.ParseQuantity(lvg.Status.VGSize) + if err != nil { + return "", "", errors.New("parse size vgSize") + } + nodesVGSize[lvg.Name] = vgSize.Value() + vgNameNodeName[lvg.Name] = lvg.Status.Nodes[0].Name + } + + VGNameWihMaxFreeSpace, _ := NodeWithMaxSize(nodesVGSize) + fs := resource.NewQuantity(nodesVGSize[VGNameWihMaxFreeSpace], resource.BinarySI) + freeSpace = fs.String() + nodeName = vgNameNodeName[VGNameWihMaxFreeSpace] + + return nodeName, freeSpace, nil +} + +func GetVGName(ctx context.Context, kc client.Client, lvmVG map[string]string, nodeName, LvmType string) (lvgName, vgName string, err error) { + listLvgs := &v1alpha1.LvmVolumeGroupList{ + TypeMeta: metav1.TypeMeta{ + Kind: v1alpha1.LVMVolumeGroupKind, + APIVersion: v1alpha1.TypeMediaAPIVersion, + }, + ListMeta: metav1.ListMeta{}, + Items: []v1alpha1.LvmVolumeGroup{}, + } + err = kc.List(ctx, listLvgs) + if err != nil { + return "", "", err + } + + for _, lvg := range listLvgs.Items { + _, ok := lvmVG[lvg.Spec.ActualVGNameOnTheNode] + if ok && lvg.Status.Nodes[0].Name == nodeName { + if LvmType == LLVTypeThin { + for _, thinPool := range lvg.Status.ThinPools { + for _, tp := range lvmVG { + if thinPool.Name == tp { + return lvg.Name, lvg.Spec.ActualVGNameOnTheNode, nil + } + } + } + } + return lvg.Name, lvg.Spec.ActualVGNameOnTheNode, nil + } + } + return "", "", errors.New("there are no matches") +} diff --git a/images/sds-lvm-csi/pkg/utils/volume.go b/images/sds-lvm-csi/pkg/utils/volume.go index a407280b..c01c7156 100644 --- a/images/sds-lvm-csi/pkg/utils/volume.go +++ b/images/sds-lvm-csi/pkg/utils/volume.go @@ -1,3 +1,19 @@ +/* +Copyright 2023 Flant JSC + +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 utils import (