diff --git a/api/backingimage.go b/api/backingimage.go index 2a5c024708..b6eaed6261 100644 --- a/api/backingimage.go +++ b/api/backingimage.go @@ -52,7 +52,7 @@ func (s *Server) BackingImageCreate(rw http.ResponseWriter, req *http.Request) e return err } - bi, err := s.m.CreateBackingImage(input.Name, input.ExpectedChecksum, input.SourceType, input.Parameters, input.MinNumberOfCopies, input.NodeSelector, input.DiskSelector, input.Secret, input.SecretNamespace) + bi, err := s.m.CreateBackingImage(input.Name, input.ExpectedChecksum, input.SourceType, input.Parameters, input.MinNumberOfCopies, input.NodeSelector, input.DiskSelector, input.Secret, input.SecretNamespace, input.DataEngine) if err != nil { return errors.Wrapf(err, "failed to create backing image %v from source type %v with parameters %+v", input.Name, input.SourceType, input.Parameters) } diff --git a/api/model.go b/api/model.go index 60df599c22..2d43ce4f89 100644 --- a/api/model.go +++ b/api/model.go @@ -266,6 +266,7 @@ type BackingImage struct { NodeSelector []string `json:"nodeSelector"` MinNumberOfCopies int `json:"minNumberOfCopies"` ExpectedChecksum string `json:"expectedChecksum"` + DataEngine string `json:"dataEngine"` DiskFileStatusMap map[string]longhorn.BackingImageDiskFileStatus `json:"diskFileStatusMap"` Size int64 `json:"size"` @@ -2011,6 +2012,7 @@ func toBackingImageResource(bi *longhorn.BackingImage, apiContext *api.ApiContex MinNumberOfCopies: bi.Spec.MinNumberOfCopies, NodeSelector: bi.Spec.NodeSelector, DiskSelector: bi.Spec.DiskSelector, + DataEngine: string(bi.Spec.DataEngine), DiskFileStatusMap: diskFileStatusMap, Size: bi.Status.Size, diff --git a/client/generated_backing_image.go b/client/generated_backing_image.go index c6ec25e4bc..a86783df54 100644 --- a/client/generated_backing_image.go +++ b/client/generated_backing_image.go @@ -9,6 +9,8 @@ type BackingImage struct { CurrentChecksum string `json:"currentChecksum,omitempty" yaml:"current_checksum,omitempty"` + DataEngine string `json:"dataEngine,omitempty" yaml:"data_engine,omitempty"` + DeletionTimestamp string `json:"deletionTimestamp,omitempty" yaml:"deletion_timestamp,omitempty"` DiskFileStatusMap map[string]BackingImageDiskFileStatus `json:"diskFileStatusMap,omitempty" yaml:"disk_file_status_map,omitempty"` @@ -27,7 +29,7 @@ type BackingImage struct { Secret string `json:"secret,omitempty" yaml:"secret,omitempty"` - SecretNamespace string `json:"secretNamespace,omitempty" yaml:"secretNamespace,omitempty"` + SecretNamespace string `json:"secretNamespace,omitempty" yaml:"secret_namespace,omitempty"` Size int64 `json:"size,omitempty" yaml:"size,omitempty"` diff --git a/client/generated_backing_image_disk_file_status.go b/client/generated_backing_image_disk_file_status.go index fc3f7baf65..1e7b066bad 100644 --- a/client/generated_backing_image_disk_file_status.go +++ b/client/generated_backing_image_disk_file_status.go @@ -7,6 +7,8 @@ const ( type BackingImageDiskFileStatus struct { Resource `yaml:"-"` + DataEngine string `json:"dataEngine,omitempty" yaml:"data_engine,omitempty"` + LastStateTransitionTime string `json:"lastStateTransitionTime,omitempty" yaml:"last_state_transition_time,omitempty"` Message string `json:"message,omitempty" yaml:"message,omitempty"` diff --git a/controller/backing_image_controller.go b/controller/backing_image_controller.go index 42a9c499a8..c2f4ee4cef 100644 --- a/controller/backing_image_controller.go +++ b/controller/backing_image_controller.go @@ -2,7 +2,9 @@ package controller import ( "fmt" + "net/url" "reflect" + "regexp" "time" "github.com/pkg/errors" @@ -12,6 +14,7 @@ import ( "k8s.io/apimachinery/pkg/util/wait" "k8s.io/client-go/tools/cache" "k8s.io/client-go/tools/record" + "k8s.io/client-go/util/flowcontrol" "k8s.io/kubernetes/pkg/controller" corev1 "k8s.io/api/core/v1" @@ -23,6 +26,7 @@ import ( "github.com/longhorn/longhorn-manager/constant" "github.com/longhorn/longhorn-manager/datastore" + "github.com/longhorn/longhorn-manager/engineapi" "github.com/longhorn/longhorn-manager/types" "github.com/longhorn/longhorn-manager/util" @@ -45,6 +49,10 @@ type BackingImageController struct { ds *datastore.DataStore cacheSyncs []cache.InformerSynced + + v2CopyBackoff *flowcontrol.Backoff + + proxyConnCounter util.Counter } func NewBackingImageController( @@ -52,7 +60,8 @@ func NewBackingImageController( ds *datastore.DataStore, scheme *runtime.Scheme, kubeClient clientset.Interface, - namespace string, controllerID, serviceAccount, backingImageManagerImage string) (*BackingImageController, error) { + namespace string, controllerID, serviceAccount, backingImageManagerImage string, + proxyConnCounter util.Counter) (*BackingImageController, error) { eventBroadcaster := record.NewBroadcaster() eventBroadcaster.StartLogging(logrus.Infof) @@ -71,6 +80,10 @@ func NewBackingImageController( eventRecorder: eventBroadcaster.NewRecorder(scheme, corev1.EventSource{Component: "longhorn-backing-image-controller"}), ds: ds, + + v2CopyBackoff: flowcontrol.NewBackOff(time.Minute, time.Minute*5), + + proxyConnCounter: proxyConnCounter, } var err error @@ -117,6 +130,15 @@ func NewBackingImageController( } bic.cacheSyncs = append(bic.cacheSyncs, ds.NodeInformer.HasSynced) + if _, err = ds.InstanceManagerInformer.AddEventHandlerWithResyncPeriod(cache.ResourceEventHandlerFuncs{ + AddFunc: bic.enqueueBackingImageForInstanceManagerUpdate, + UpdateFunc: func(old, cur interface{}) { bic.enqueueBackingImageForInstanceManagerUpdate(cur) }, + DeleteFunc: bic.enqueueBackingImageForInstanceManagerUpdate, + }, 0); err != nil { + return nil, err + } + bic.cacheSyncs = append(bic.cacheSyncs, ds.InstanceManagerInformer.HasSynced) + return bic, nil } @@ -221,26 +243,6 @@ func (bic *BackingImageController) syncBackingImage(key string) (err error) { log.Infof("Backing image got new owner %v", bic.controllerID) } - if backingImage.DeletionTimestamp != nil { - replicas, err := bic.ds.ListReplicasByBackingImage(backingImage.Name) - if err != nil { - return err - } - if len(replicas) != 0 { - log.Warn("Waiting for all replicas stopping using this backing image before removing the finalizer") - return nil - } - if _, err := bic.IsBackingImageDataSourceCleaned(backingImage); err != nil { - log.WithError(err).Warn("Waiting until backing image data source is cleaned before removing the finalizer") - return nil - } - log.Info("Cleaning up the record for backing image managers and remove the finalizer") - if err := bic.cleanupBackingImageManagers(backingImage); err != nil { - return err - } - return bic.ds.RemoveFinalizerForBackingImage(backingImage) - } - // UUID is immutable once it's set. // Should make sure UUID is not empty before syncing with other resources. if backingImage.Status.UUID == "" { @@ -276,6 +278,39 @@ func (bic *BackingImageController) syncBackingImage(key string) (err error) { } }() + if backingImage.DeletionTimestamp != nil { + replicas, err := bic.ds.ListReplicasByBackingImage(backingImage.Name) + if err != nil { + return err + } + if len(replicas) != 0 { + log.Warn("Waiting for all replicas stopping using this backing image before removing the finalizer") + return nil + } + if _, err := bic.IsBackingImageDataSourceCleaned(backingImage); err != nil { + log.WithError(err).Warn("Waiting until backing image data source is cleaned before removing the finalizer") + return nil + } + log.Info("Cleaning up the record for backing image managers and remove the finalizer") + if err := bic.cleanupBackingImageManagers(backingImage); err != nil { + return err + } + + // delete all the v2 backing image in the instance-manager + if backingImage.Spec.DataEngine == longhorn.DataEngineTypeV2 { + cleaned, err := bic.cleanupAllV2BackingImageCopies(backingImage) + if err != nil { + return err + } + if !cleaned { + log.Warn("Waiting until all v2 backing image copies are cleaned up before removing the finalizer") + return nil + } + } + + return bic.ds.RemoveFinalizerForBackingImage(backingImage) + } + if backingImage.Status.DiskFileStatusMap == nil { backingImage.Status.DiskFileStatusMap = map[string]*longhorn.BackingImageDiskFileStatus{} } @@ -310,10 +345,585 @@ func (bic *BackingImageController) syncBackingImage(key string) (err error) { bic.cleanupEvictionRequestedBackingImageCopies(backingImage) + if backingImage.Spec.DataEngine == longhorn.DataEngineTypeV2 { + if err := bic.handleV2BackingImage(backingImage); err != nil { + return err + } + } + + return nil +} + +func (bic *BackingImageController) handleV2BackingImage(bi *longhorn.BackingImage) (err error) { + if err := bic.prepareFirstV2Copy(bi); err != nil { + return errors.Wrapf(err, "failed to prepare the first v2 backing image") + } + + if bic.syncV2StatusWithInstanceManager(bi); err != nil { + return errors.Wrapf(err, "failed to sync v2 backing image status from instance manager") + } + + if err := bic.deleteInvalidV2Copy(bi); err != nil { + return errors.Wrapf(err, "failed to delete invalid v2 backing image") + } + + if err := bic.prepareV2Copy(bi); err != nil { + return errors.Wrapf(err, "failed to prepare v2 backing image") + } + + return nil +} + +func (bic *BackingImageController) isFirstV2CopyInState(bi *longhorn.BackingImage, state longhorn.BackingImageState) bool { + if bi.Status.V2FirstCopyDisk != "" && bi.Status.V2FirstCopyStatus == state { + return true + } + + if bi.Status.V2FirstCopyDisk != "" { + if status, exists := bi.Status.DiskFileStatusMap[bi.Status.V2FirstCopyDisk]; exists && status.State == state { + return true + } + } + + return false +} + +func (bic *BackingImageController) cleanupFirstFailedV2Copy(bi *longhorn.BackingImage) (err error) { + if bi.Status.V2FirstCopyDisk == "" { + return nil + } + + log := getLoggerForBackingImage(bic.logger, bi) + + node, _, err := bic.ds.GetReadyDiskNodeRO(bi.Status.V2FirstCopyDisk) + if err != nil { + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", bi.Status.V2FirstCopyDisk) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + defer engineClientProxy.Close() + + // clean up the failed first v2 copy + err = engineClientProxy.SPDKBackingImageDelete(bi.Name, bi.Status.V2FirstCopyDisk) + if err != nil { + return errors.Wrapf(err, "failed to delete the failed v2 copy on the disk %v", bi.Status.V2FirstCopyDisk) + } + delete(bi.Spec.DiskFileSpecMap, bi.Status.V2FirstCopyDisk) + delete(bi.Status.DiskFileStatusMap, bi.Status.V2FirstCopyDisk) + bi.Status.V2FirstCopyDisk = "" + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStatePending + return nil +} + +func (bic *BackingImageController) deleteAllV1FileCopies(bi *longhorn.BackingImage) { + for diskUUID, fileSpec := range bi.Spec.DiskFileSpecMap { + if fileSpec.DataEngine == longhorn.DataEngineTypeV1 { + delete(bi.Spec.DiskFileSpecMap, diskUUID) + } + } +} + +func (bic *BackingImageController) prepareFirstV2Copy(bi *longhorn.BackingImage) (err error) { + log := getLoggerForBackingImage(bic.logger, bi) + + // The preparation state transition: Pending -> InProgress -> Ready/Failed + // we retry when failed by deleting the failed copy and cleanup the state. + + // If the first v2 copy is ready, we can delete all the v1 file copies and return. + isPrepared := bic.isFirstV2CopyInState(bi, longhorn.BackingImageStateReady) + if isPrepared { + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStateReady + bic.deleteAllV1FileCopies(bi) + bic.v2CopyBackoff.DeleteEntry(bi.Status.V2FirstCopyDisk) + return nil + } + + // If the first v2 copy is failed, we cleanup the failed copy and retry the preparation. + // If the first v2 copy is in unknown, that means the instance manager may crahsh when creating the first v2 copy. + // We cleanup the unknown copy and retry the preparation. + isFailedToPrepare := bic.isFirstV2CopyInState(bi, longhorn.BackingImageStateFailed) + isUnknown := bic.isFirstV2CopyInState(bi, longhorn.BackingImageStateUnknown) + if isFailedToPrepare || isUnknown { + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStateFailed + return bic.cleanupFirstFailedV2Copy(bi) + } + + // If the first v2 copy is in progress, we wait for the next reconciliation + isInProgress := bic.isFirstV2CopyInState(bi, longhorn.BackingImageStateInProgress) + if isInProgress { + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStateInProgress + bi.Spec.DiskFileSpecMap[bi.Status.V2FirstCopyDisk] = &longhorn.BackingImageDiskFileSpec{DataEngine: longhorn.DataEngineTypeV2} + return nil + } + + // Wait for the first v1 file to be ready. + bids, err := bic.ds.GetBackingImageDataSource(bi.Name) + if err != nil { + if apierrors.IsNotFound(err) { + log.Warn("Backing image data source not found when preparing first v2 copy, reconcile later") + return nil + } + return errors.Wrap(err, "failed to get the backing image data source when preparing first v2 copy") + } + if !bids.Spec.FileTransferred { + log.Info("Backing image data source has not prepared the first v1 file, reconcile later") + return nil + } + firstV1FileDiskUUID := bids.Spec.DiskUUID + if status, exists := bi.Status.DiskFileStatusMap[firstV1FileDiskUUID]; exists && status.State != longhorn.BackingImageStateReady { + log.Infof("The first v1 file copy is not ready, reconcile later") + return nil + } + + firstV2CopyNode, firstV2CopyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrap(err, "failed to find a ready disk for the first v2 backing image copy") + } + firstV2CopyNodeName := firstV2CopyNode.Name + firstV2CopyDiskUUID := firstV2CopyNode.Status.DiskStatus[firstV2CopyDiskName].DiskUUID + log.Infof("Founded the ready node %v disk %v for the first v2 backing image copy", firstV2CopyNodeName, firstV2CopyDiskUUID) + + if bic.v2CopyBackoff.IsInBackOffSinceUpdate(firstV2CopyDiskUUID, time.Now()) { + log.Debugf("Skip preparing first v2 backing image copy to disk %v since it is still in the backoff window", firstV2CopyDiskUUID) + return nil + } + + // Get proxy client for creating v2 backing image copy + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(firstV2CopyNodeName, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", firstV2CopyNodeName) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + defer engineClientProxy.Close() + + // Create v2 backing image copy by downloading the data from first file copy + fileDownloadAddress, err := bic.getFileDownloadAddress(bi, firstV1FileDiskUUID) + if err != nil { + return errors.Wrap(err, "failed to get the v1 file download address when preparing first v2 copy") + } + _, err = engineClientProxy.SPDKBackingImageCreate(bi.Name, bi.Status.UUID, firstV2CopyDiskUUID, bi.Status.Checksum, fileDownloadAddress, "", uint64(bi.Status.Size)) + if err != nil { + if types.ErrorAlreadyExists(err) { + log.Infof("backing image already exists when preparing first v2 copy on disk %v", firstV2CopyDiskUUID) + } + bic.v2CopyBackoff.Next(firstV2CopyDiskUUID, time.Now()) + return errors.Wrapf(err, "failed to create backing image on disk %v when preparing first v2 copy for backing image %v", firstV2CopyDiskUUID, bi.Name) + } + + bi.Status.V2FirstCopyDisk = firstV2CopyDiskUUID + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStateInProgress + bic.v2CopyBackoff.Next(firstV2CopyDiskUUID, time.Now()) + return nil +} + +func (bic *BackingImageController) getFileDownloadAddress(bi *longhorn.BackingImage, diskUUID string) (string, error) { + bimName := types.GetBackingImageManagerName(bic.bimImageName, diskUUID) + bim, err := bic.ds.GetBackingImageManager(bimName) + if err != nil { + return "", err + } + bimCli, err := engineapi.NewBackingImageManagerClient(bim) + if err != nil { + return "", err + } + // get the file download address of sync server in the backing image manager + filePath, addr, err := bimCli.PrepareDownload(bi.Name, bi.Status.UUID) + if err != nil { + return "", err + } + + fileDownloadAddress := fmt.Sprintf("http://%s/v1/files/%s/download", addr, url.PathEscape(filePath)) + return fileDownloadAddress, nil +} + +func (bic *BackingImageController) deleteInvalidV2Copy(bi *longhorn.BackingImage) (err error) { + log := getLoggerForBackingImage(bic.logger, bi) + + if bi.Status.V2FirstCopyStatus != longhorn.BackingImageStateReady { + return nil + } + + v2CopiesUnknown := []string{} + v2CopiesFailed := []string{} + missingSpecV2Copies := []string{} + hasReadyV2BackingImage := false + + for v2DiskUUID, copyStatus := range bi.Status.DiskFileStatusMap { + // only handle v2 copy in this function + if copyStatus.DataEngine == longhorn.DataEngineTypeV1 { + continue + } + + // Delete the v2 copy if the spec record is removed + if _, exists := bi.Spec.DiskFileSpecMap[v2DiskUUID]; !exists { + missingSpecV2Copies = append(missingSpecV2Copies, v2DiskUUID) + continue + } + + if copyStatus.State == longhorn.BackingImageStateReady { + hasReadyV2BackingImage = true + continue + } + + if copyStatus.State == longhorn.BackingImageStateFailed { + v2CopiesFailed = append(v2CopiesFailed, v2DiskUUID) + continue + } + if copyStatus.State == longhorn.BackingImageStateUnknown { + v2CopiesUnknown = append(v2CopiesUnknown, v2DiskUUID) + continue + } + } + + for _, v2DiskUUID := range missingSpecV2Copies { + node, _, err := bic.ds.GetReadyDiskNodeRO(v2DiskUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", v2DiskUUID) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + + log.Infof("Deleting the missing spec v2 copy on disk %v", v2DiskUUID) + err = engineClientProxy.SPDKBackingImageDelete(bi.Name, v2DiskUUID) + engineClientProxy.Close() + if err != nil { + return errors.Wrapf(err, "failed to delete the v2 backing image on diskUUID %v", v2DiskUUID) + } + + delete(bi.Status.DiskFileStatusMap, v2DiskUUID) + bic.v2CopyBackoff.DeleteEntry(v2DiskUUID) + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonDelete, "Deleted failed v2 backing image %v on disk %v on node %v", bi.Name, v2DiskUUID, node.Name) + } + + // When the backing image is created on that disk, it is either ready, failed or unknown. + // We directly delete the failed v2 copy if there is ready copy + // For unknown v2 copy, it can happen in the following case, + // 1. If the instance-manager pod is not running or the instance-manager CR is not ready. + // 2. Backing image copy has status (ready or failed) before but can't get the ststus now. + // 3. Backing image copy is failed to be created on the disk and there is no lvol snapshot and at the same time the node is rebooted. + // It will keeps in unknown state forever because spdk server has no snapshot lvol to pick up to reconstruct the record. + // For case 1 and case 2, the status will come back automatically when server pick the snapshot lvol up and reconstruct the record. + // We delete the unknown copy only for the case 3 if there is a ready copy so it can be recreated. + if !hasReadyV2BackingImage { + return nil + } + + for _, v2DiskUUID := range v2CopiesUnknown { + node, _, err := bic.ds.GetReadyDiskNodeRO(v2DiskUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", v2DiskUUID) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + + // Get will return diffrent error if lvol is found on the disk but the record is not found. + // We don't delete the v2 copy in this case + _, err = engineClientProxy.SPDKBackingImageGet(bi.Name, v2DiskUUID) + if err != nil { + if types.ErrorRecordNotFoundButLvolFound(err) { + log.Infof("Unknown v2 Copy on disk %v has lvol but no record yet, wait for the server to reconstruct the record.", v2DiskUUID) + continue + } + } + + log.Infof("Deleting the unknown v2 copy on disk %v", v2DiskUUID) + err = engineClientProxy.SPDKBackingImageDelete(bi.Name, v2DiskUUID) + engineClientProxy.Close() + if err != nil { + return errors.Wrapf(err, "failed to delete the v2 copy on diskUUID %v", v2DiskUUID) + } + delete(bi.Status.DiskFileStatusMap, v2DiskUUID) + bic.v2CopyBackoff.DeleteEntry(v2DiskUUID) + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonDelete, "Deleted failed v2 backing image %v on disk %v on node %v", bi.Name, v2DiskUUID, node.Name) + } + + for _, v2DiskUUID := range v2CopiesFailed { + node, _, err := bic.ds.GetReadyDiskNodeRO(v2DiskUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", v2DiskUUID) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + + log.Infof("Deleting the failed v2 copy on disk %v", v2DiskUUID) + err = engineClientProxy.SPDKBackingImageDelete(bi.Name, v2DiskUUID) + engineClientProxy.Close() + if err != nil { + return errors.Wrapf(err, "failed to delete the v2 copy on diskUUID %v", v2DiskUUID) + } + + delete(bi.Status.DiskFileStatusMap, v2DiskUUID) + bic.v2CopyBackoff.DeleteEntry(v2DiskUUID) + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonDelete, "Deleted failed v2 backing image %v on disk %v on node %v", bi.Name, v2DiskUUID, node.Name) + } + return nil +} + +func (bic *BackingImageController) cleanupAllV2BackingImageCopies(bi *longhorn.BackingImage) (cleaned bool, err error) { + log := getLoggerForBackingImage(bic.logger, bi) + + if bic.syncV2StatusWithInstanceManager(bi); err != nil { + return false, errors.Wrapf(err, "failed to sync v2 backing image status from instance manager") + } + + // we can delete the backing image when all the v2 copies are deleted + if len(bi.Status.DiskFileStatusMap) == 0 { + return true, nil + } + + allV2CopiesCleaned := true + for diskUUID := range bi.Status.DiskFileStatusMap { + if fileSpec, exists := bi.Spec.DiskFileSpecMap[diskUUID]; exists && fileSpec.DataEngine != longhorn.DataEngineTypeV2 { + continue + } + + allV2CopiesCleaned = false + + node, _, err := bic.ds.GetReadyDiskNodeRO(diskUUID) + if err != nil { + return false, errors.Wrapf(err, "failed to get the ready disk node for disk %v", diskUUID) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + return false, errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + return false, errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + + log.Infof("Deleting the v2 copy on disk %v for backing image %v", diskUUID, bi.Name) + err = engineClientProxy.SPDKBackingImageDelete(bi.Name, diskUUID) + engineClientProxy.Close() + if err != nil { + return false, errors.Wrapf(err, "failed to delete the v2 backing image on diskUUID %v", diskUUID) + } + + delete(bi.Status.DiskFileStatusMap, diskUUID) + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonDelete, "Deleted v2 backing image %v on disk %v on node %v", bi.Name, diskUUID, node.Name) + } + // only return true when there is no v2 disk file status after syncing the status from instance manager + return allV2CopiesCleaned, nil +} + +func (bic *BackingImageController) prepareV2Copy(bi *longhorn.BackingImage) (err error) { + log := getLoggerForBackingImage(bic.logger, bi) + + if bi.Status.V2FirstCopyStatus != longhorn.BackingImageStateReady { + return nil + } + + readyV2CopyDiskUUIDs := []string{} + requireV2CopyDiskUUIDs := []string{} + + for v2DiskUUID := range bi.Spec.DiskFileSpecMap { + biStatus, exists := bi.Status.DiskFileStatusMap[v2DiskUUID] + if exists { + if biStatus.State == longhorn.BackingImageStateReady { + readyV2CopyDiskUUIDs = append(readyV2CopyDiskUUIDs, v2DiskUUID) + } + if backingImageInProgress(biStatus.State) { + log.Infof("There is one v2 backing image copy in progress, prepare the backing image one at a time", bi.Name) + return nil + } + } else { + requireV2CopyDiskUUIDs = append(requireV2CopyDiskUUIDs, v2DiskUUID) + } + } + + if len(readyV2CopyDiskUUIDs) == 0 { + log.Infof("Only sync the backing image to other disks when there is one ready v2 backing image copy") + return nil + } + + sourceV2DiskUUID := readyV2CopyDiskUUIDs[0] + // Only sync one backing image copy at a time, so we can control the concurrent limit of syncing for the backing image manager. + for _, v2DiskUUID := range requireV2CopyDiskUUIDs { + if bic.v2CopyBackoff.IsInBackOffSinceUpdate(v2DiskUUID, time.Now()) { + log.Debugf("Skip syncing backing image copy to disk %v immediately since it is still in the backoff window", v2DiskUUID) + continue + } + + node, _, err := bic.ds.GetReadyDiskNodeRO(v2DiskUUID) + if err != nil { + bic.v2CopyBackoff.Next(v2DiskUUID, time.Now()) + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", v2DiskUUID) + } + + instanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(node.Name, longhorn.DataEngineTypeV2) + if err != nil { + bic.v2CopyBackoff.Next(v2DiskUUID, time.Now()) + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + engineClientProxy, err := engineapi.NewEngineClientProxy(instanceManager, log, bic.proxyConnCounter) + if err != nil { + bic.v2CopyBackoff.Next(v2DiskUUID, time.Now()) + return errors.Wrapf(err, "failed to get the engine client proxy for instance manager %v", instanceManager.Name) + } + + log.Infof("Syncing the backing image to disk %v for backing image %v from disk: %v", v2DiskUUID, bi.Name, sourceV2DiskUUID) + srcNode, _, err := bic.ds.GetReadyDiskNodeRO(sourceV2DiskUUID) + if err != nil { + return errors.Wrapf(err, "failed to get the ready disk node for disk %v", v2DiskUUID) + } + srcInstanceManager, err := bic.ds.GetRunningInstanceManagerByNodeRO(srcNode.Name, longhorn.DataEngineTypeV2) + if err != nil { + return errors.Wrapf(err, "failed to get the default instance manager for node %v", node.Name) + } + + // Create the backing image by syncing the backing image data from the SPDK server inside the instance manager holding the source disk + _, err = engineClientProxy.SPDKBackingImageCreate(bi.Name, bi.Status.UUID, v2DiskUUID, bi.Status.Checksum, fmt.Sprintf("%v:%v", srcInstanceManager.Status.IP, engineapi.InstanceManagerSpdkServiceDefaultPort), sourceV2DiskUUID, uint64(bi.Status.Size)) + engineClientProxy.Close() + if err != nil { + bic.v2CopyBackoff.Next(v2DiskUUID, time.Now()) + return errors.Wrapf(err, "failed to create the v2 backing image on diskUUID %v", v2DiskUUID) + } + + // To prevent it from requesting the same disk again, the status will be updated later based on the status from the instance manager + bi.Status.DiskFileStatusMap[v2DiskUUID] = &longhorn.BackingImageDiskFileStatus{ + State: longhorn.BackingImageStateInProgress, + } + bic.v2CopyBackoff.Next(v2DiskUUID, time.Now()) + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonCreate, "Created v2 backing image %v on disk %v on node %v", bi.Name, v2DiskUUID, node.Name) + return nil + } + + return nil +} + +// ExtractBackingImageAndDiskUUID extracts the BackingImageName and DiskUUID from the string pattern "bi-${BackingImageName}-disk-${DiskUUID}" +func extractBackingImageAndDiskUUID(biNameDiskUUID string) (string, string, error) { + // Define the regular expression pattern + // This captures the BackingImageName and DiskUUID while allowing for hyphens in both. + re := regexp.MustCompile(`^bi-([a-zA-Z0-9-]+)-disk-([a-zA-Z0-9-]+)$`) + + // Try to find a match + matches := re.FindStringSubmatch(biNameDiskUUID) + if matches == nil { + return "", "", fmt.Errorf("biNameDiskUUID does not match the expected pattern") + } + + // Extract BackingImageName and DiskUUID from the matches + backingImageName := matches[1] + diskUUID := matches[2] + + return backingImageName, diskUUID, nil +} + +func (bic *BackingImageController) syncV2StatusWithInstanceManager(bi *longhorn.BackingImage) error { + ims, err := bic.ds.ListInstanceManagersRO() + if err != nil { + return errors.Wrap(err, "failed to list instance managers") + } + + updatedCopyDiskUUIDs := map[string]bool{} + for _, im := range ims { + if im.Spec.DataEngine != longhorn.DataEngineTypeV2 { + continue + } + if im.DeletionTimestamp != nil { + continue + } + + // If the instance manager is not in ready state + // update the v2 copy in that instance manager to unknown. + if im.Status.CurrentState != longhorn.InstanceManagerStateRunning { + continue + } + + for biNameDiskUUID, statusInfo := range im.Status.V2BackingImages { + biName, diskUUID, err := extractBackingImageAndDiskUUID(biNameDiskUUID) + if err != nil { + logrus.WithError(err).Warnf("failed to parse the backing image name and disk uuid from %v", biNameDiskUUID) + continue + } + + if biName != bi.Name { + continue + } + + if statusInfo.UUID != bi.Status.UUID { + continue + } + + if err := bic.updateStatusWithFileInfo(bi, + diskUUID, statusInfo.Message, statusInfo.CurrentChecksum, statusInfo.State, statusInfo.Progress, longhorn.DataEngineTypeV2); err != nil { + return err + } + if statusInfo.Size > 0 { + if bi.Status.Size == 0 { + bi.Status.Size = statusInfo.Size + bic.eventRecorder.Eventf(bi, corev1.EventTypeNormal, constant.EventReasonUpdate, "Set size to %v", bi.Status.Size) + } + if bi.Status.Size != statusInfo.Size { + if bi.Status.DiskFileStatusMap[diskUUID].State != longhorn.BackingImageStateFailed { + msg := fmt.Sprintf("found mismatching size %v reported by instance manager %v in disk %v, the size recorded in status is %v", + statusInfo.Size, im.Name, diskUUID, bi.Status.Size) + bic.eventRecorder.Eventf(bi, corev1.EventTypeWarning, constant.EventReasonUpdate, msg) + bi.Status.DiskFileStatusMap[diskUUID].State = longhorn.BackingImageStateFailed + bi.Status.DiskFileStatusMap[diskUUID].Message = msg + } + } + } + + updatedCopyDiskUUIDs[diskUUID] = true + } + } + + for diskUUID, copyStatus := range bi.Status.DiskFileStatusMap { + // only handle v2 copies + if fileSpec, exists := bi.Spec.DiskFileSpecMap[diskUUID]; exists && fileSpec.DataEngine != longhorn.DataEngineTypeV2 { + continue + } + + if copyStatus.State == longhorn.BackingImageStateReady { + bic.v2CopyBackoff.DeleteEntry(diskUUID) + } + + // Update the status to unknow if the instance manager is unknown or the status is not found from the instance manager + if _, exists := updatedCopyDiskUUIDs[diskUUID]; !exists { + bi.Status.DiskFileStatusMap[diskUUID].State = longhorn.BackingImageStateUnknown + bi.Status.DiskFileStatusMap[diskUUID].Message = "The copy status is unknown because the status is not found from the instance manager" + } + } return nil } func (bic *BackingImageController) replenishBackingImageCopies(bi *longhorn.BackingImage) (err error) { + biDataEngine := bi.Spec.DataEngine + bids, err := bic.ds.GetBackingImageDataSource(bi.Name) if err != nil { if apierrors.IsNotFound(err) { @@ -321,11 +931,18 @@ func (bic *BackingImageController) replenishBackingImageCopies(bi *longhorn.Back } return errors.Wrap(err, "failed to get the backing image data source") } - // only maintain the minNumberOfReplicas after BackingImage is transferred to BackingImageManager + // Only maintain the minNumberOfReplicas after BackingImage is transferred to BackingImageManager if !bids.Spec.FileTransferred { return nil } + // Only maintain the minNumberOfReplicas after v2 BackingImage is prepared + if bi.Spec.DataEngine == longhorn.DataEngineTypeV2 { + if bi.Status.V2FirstCopyDisk == "" || bi.Status.V2FirstCopyStatus != longhorn.BackingImageStateReady { + return nil + } + } + concurrentReplenishLimit, err := bic.ds.GetSettingAsInt(types.SettingNameConcurrentBackingImageCopyReplenishPerNodeLimit) if err != nil { return err @@ -362,14 +979,16 @@ func (bic *BackingImageController) replenishBackingImageCopies(bi *longhorn.Back } if nonEvictingCount < bi.Spec.MinNumberOfCopies { - readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi) + readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi, biDataEngine) logrus.Infof("replicate the copy to node: %v, disk: %v", readyNode, readyDiskName) if err != nil { logrus.WithError(err).Warnf("failed to create the backing image copy") return nil } // BackingImageManager will then sync the BackingImage to the disk - bi.Spec.DiskFileSpecMap[readyNode.Status.DiskStatus[readyDiskName].DiskUUID] = &longhorn.BackingImageDiskFileSpec{} + bi.Spec.DiskFileSpecMap[readyNode.Status.DiskStatus[readyDiskName].DiskUUID] = &longhorn.BackingImageDiskFileSpec{ + DataEngine: biDataEngine, + } } } @@ -523,7 +1142,7 @@ func (bic *BackingImageController) handleBackingImageDataSource(bi *longhorn.Bac } if !foundReadyDisk { - readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi) + readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi, longhorn.DataEngineTypeV1) if err != nil { return err } @@ -652,6 +1271,12 @@ func (bic *BackingImageController) handleBackingImageDataSource(bi *longhorn.Bac bids.Spec.NodeID = "" bids.Spec.DiskUUID = "" bids.Spec.DiskPath = "" + + if bi.Spec.DataEngine == longhorn.DataEngineTypeV2 { + log.Info("Prepare to re-prepare the first v2 copy") + bi.Status.V2FirstCopyDisk = "" + bi.Status.V2FirstCopyStatus = longhorn.BackingImageStatePending + } default: log.Warnf("Failed to recover backing image after all existing files becoming unavailable, since the data source with type %v doesn't support restarting", bids.Spec.SourceType) } @@ -663,7 +1288,7 @@ func (bic *BackingImageController) handleBackingImageDataSource(bi *longhorn.Bac changeNodeDisk := err != nil || node.Name != bids.Spec.NodeID || node.Spec.Disks[diskName].Path != bids.Spec.DiskPath || node.Status.DiskStatus[diskName].DiskUUID != bids.Spec.DiskUUID if changeNodeDisk { log.Warn("Backing image data source current node and disk is not ready, need to switch to another ready node and disk") - readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi) + readyNode, readyDiskName, err := bic.ds.GetReadyNodeDiskForBackingImage(bi, longhorn.DataEngineTypeV1) if err != nil { return err } @@ -701,7 +1326,12 @@ func (bic *BackingImageController) handleBackingImageManagers(bi *longhorn.Backi return err } - for diskUUID := range bi.Spec.DiskFileSpecMap { + for diskUUID, fileSpec := range bi.Spec.DiskFileSpecMap { + // v2 backing image is not managed by backing image manager + if fileSpec.DataEngine == longhorn.DataEngineTypeV2 { + continue + } + noDefaultBIM := true requiredBIs := map[string]string{} @@ -777,7 +1407,7 @@ func (bic *BackingImageController) syncBackingImageFileInfo(bi *longhorn.Backing bi.Status.Size = bids.Status.Size } if err := bic.updateStatusWithFileInfo(bi, - bids.Spec.DiskUUID, bids.Status.Message, bids.Status.Checksum, bids.Status.CurrentState, bids.Status.Progress); err != nil { + bids.Spec.DiskUUID, bids.Status.Message, bids.Status.Checksum, bids.Status.CurrentState, bids.Status.Progress, longhorn.DataEngineTypeV1); err != nil { return err } } @@ -807,7 +1437,7 @@ func (bic *BackingImageController) syncBackingImageFileInfo(bi *longhorn.Backing } currentDiskFiles[bim.Spec.DiskUUID] = struct{}{} if err := bic.updateStatusWithFileInfo(bi, - bim.Spec.DiskUUID, info.Message, info.CurrentChecksum, info.State, info.Progress); err != nil { + bim.Spec.DiskUUID, info.Message, info.CurrentChecksum, info.State, info.Progress, longhorn.DataEngineTypeV1); err != nil { return err } if info.Size > 0 { @@ -857,13 +1487,21 @@ func (bic *BackingImageController) syncBackingImageFileInfo(bi *longhorn.Backing } } - for diskUUID := range bi.Status.DiskFileStatusMap { + for diskUUID, fileStatus := range bi.Status.DiskFileStatusMap { + // only handle v1 file status in this function + if fileStatus.DataEngine == longhorn.DataEngineTypeV2 { + continue + } if _, exists := currentDiskFiles[diskUUID]; !exists { delete(bi.Status.DiskFileStatusMap, diskUUID) } } - for diskUUID := range bi.Status.DiskFileStatusMap { + for diskUUID, fileStatus := range bi.Status.DiskFileStatusMap { + // only handle v1 file status in this function + if fileStatus.DataEngine == longhorn.DataEngineTypeV2 { + continue + } if bi.Status.DiskFileStatusMap[diskUUID].LastStateTransitionTime == "" { bi.Status.DiskFileStatusMap[diskUUID].LastStateTransitionTime = util.Now() } @@ -873,11 +1511,14 @@ func (bic *BackingImageController) syncBackingImageFileInfo(bi *longhorn.Backing } func (bic *BackingImageController) updateStatusWithFileInfo(bi *longhorn.BackingImage, - diskUUID, message, checksum string, state longhorn.BackingImageState, progress int) error { + diskUUID, message, checksum string, state longhorn.BackingImageState, progress int, dataEngine longhorn.DataEngineType) error { log := getLoggerForBackingImage(bic.logger, bi) if _, exists := bi.Status.DiskFileStatusMap[diskUUID]; !exists { - bi.Status.DiskFileStatusMap[diskUUID] = &longhorn.BackingImageDiskFileStatus{} + // status from backing-image-manager and backing-image-data-source will be v1 disk + bi.Status.DiskFileStatusMap[diskUUID] = &longhorn.BackingImageDiskFileStatus{ + DataEngine: dataEngine, + } } if bi.Status.DiskFileStatusMap[diskUUID].State != state { bi.Status.DiskFileStatusMap[diskUUID].LastStateTransitionTime = util.Now() @@ -986,6 +1627,10 @@ func (bic *BackingImageController) enqueueBackingImageForBackingImageDataSource( bic.enqueueBackingImage(obj) } +func (bic *BackingImageController) enqueueBackingImageForInstanceManagerUpdate(obj interface{}) { + bic.enqueueBackingImage(obj) +} + func (bic *BackingImageController) enqueueBackingImageForNodeUpdate(oldObj, currObj interface{}) { oldNode, ok := oldObj.(*longhorn.Node) if !ok { diff --git a/controller/backing_image_data_source_controller.go b/controller/backing_image_data_source_controller.go index a160f5b10a..b488766a15 100644 --- a/controller/backing_image_data_source_controller.go +++ b/controller/backing_image_data_source_controller.go @@ -401,7 +401,10 @@ func (c *BackingImageDataSourceController) syncBackingImage(bids *longhorn.Backi if !bids.Spec.FileTransferred { if _, exists := bi.Spec.DiskFileSpecMap[bids.Spec.DiskUUID]; !exists { - bi.Spec.DiskFileSpecMap[bids.Spec.DiskUUID] = &longhorn.BackingImageDiskFileSpec{} + // backing image data source always prepare the backing image on the v1 disk + bi.Spec.DiskFileSpecMap[bids.Spec.DiskUUID] = &longhorn.BackingImageDiskFileSpec{ + DataEngine: longhorn.DataEngineTypeV1, + } } } @@ -690,6 +693,11 @@ func (c *BackingImageDataSourceController) generateBackingImageDataSourcePodMani for key, value := range bids.Status.RunningParameters { cmd = append(cmd, "--parameters", fmt.Sprintf("%s=%s", key, value)) } + + if bi.Spec.DataEngine == longhorn.DataEngineTypeV2 { + cmd = append(cmd, "--parameters", fmt.Sprintf("%s=%s", longhorn.DataSourceTypeParameterDataEngine, longhorn.DataEngineTypeV2)) + } + if bids.Spec.Checksum != "" { cmd = append(cmd, "--checksum", bids.Spec.Checksum) } diff --git a/controller/controller_manager.go b/controller/controller_manager.go index 984e041b81..21e837c237 100644 --- a/controller/controller_manager.go +++ b/controller/controller_manager.go @@ -85,7 +85,7 @@ func StartControllers(logger logrus.FieldLogger, clients *client.Clients, if err != nil { return nil, err } - instanceManagerController, err := NewInstanceManagerController(logger, ds, scheme, kubeClient, namespace, controllerID, serviceAccount) + instanceManagerController, err := NewInstanceManagerController(logger, ds, scheme, kubeClient, namespace, controllerID, serviceAccount, proxyConnCounter) if err != nil { return nil, err } @@ -93,7 +93,7 @@ func StartControllers(logger logrus.FieldLogger, clients *client.Clients, if err != nil { return nil, err } - backingImageController, err := NewBackingImageController(logger, ds, scheme, kubeClient, namespace, controllerID, serviceAccount, backingImageManagerImage) + backingImageController, err := NewBackingImageController(logger, ds, scheme, kubeClient, namespace, controllerID, serviceAccount, backingImageManagerImage, proxyConnCounter) if err != nil { return nil, err } diff --git a/controller/instance_manager_controller.go b/controller/instance_manager_controller.go index 7ad59550cc..50bfac2d3a 100644 --- a/controller/instance_manager_controller.go +++ b/controller/instance_manager_controller.go @@ -59,6 +59,8 @@ type InstanceManagerController struct { instanceManagerMonitorMutex *sync.Mutex instanceManagerMonitorMap map[string]chan struct{} + proxyConnCounter util.Counter + // for unit test versionUpdater func(*longhorn.InstanceManager) error } @@ -67,6 +69,7 @@ type InstanceManagerMonitor struct { logger logrus.FieldLogger Name string + imName string controllerID string ds *datastore.DataStore @@ -79,7 +82,8 @@ type InstanceManagerMonitor struct { nodeCallback func(nodeName string) - client *engineapi.InstanceManagerClient + client *engineapi.InstanceManagerClient + proxyClient engineapi.EngineClientProxy } func updateInstanceManagerVersion(im *longhorn.InstanceManager) error { @@ -104,7 +108,7 @@ func NewInstanceManagerController( ds *datastore.DataStore, scheme *runtime.Scheme, kubeClient clientset.Interface, - namespace, controllerID, serviceAccount string, + namespace, controllerID, serviceAccount string, proxyConnCounter util.Counter, ) (*InstanceManagerController, error) { eventBroadcaster := record.NewBroadcaster() @@ -126,6 +130,8 @@ func NewInstanceManagerController( instanceManagerMonitorMutex: &sync.Mutex{}, instanceManagerMonitorMap: map[string]chan struct{}{}, + proxyConnCounter: proxyConnCounter, + versionUpdater: updateInstanceManagerVersion, } @@ -495,6 +501,7 @@ func (imc *InstanceManagerController) syncInstanceStatus(im *longhorn.InstanceMa im.Status.Instances = nil // nolint: staticcheck im.Status.InstanceEngines = nil im.Status.InstanceReplicas = nil + im.Status.V2BackingImages = nil } return nil } @@ -803,8 +810,14 @@ func (imc *InstanceManagerController) syncMonitor(im *longhorn.InstanceManager) if isMonitorRequired { imc.startMonitoring(im) + if im.Spec.DataEngine == longhorn.DataEngineTypeV2 { + imc.startBackingImageMonitoring(im) + } } else { imc.stopMonitoring(im.Name) + if im.Spec.DataEngine == longhorn.DataEngineTypeV2 { + imc.stopBackingImageMonitoring(im.Name) + } } return nil @@ -1243,6 +1256,9 @@ func (imc *InstanceManagerController) enqueueSettingChange(obj interface{}) { func (imc *InstanceManagerController) cleanupInstanceManager(imName string) error { imc.stopMonitoring(imName) + backingImageMonitorName := types.GetBackingImageMonitorName(imName) + imc.stopMonitoring(backingImageMonitorName) + pod, err := imc.ds.GetPodRO(imc.namespace, imName) if err != nil { return err @@ -1563,6 +1579,189 @@ func (imc *InstanceManagerController) createInstanceManagerPodSpec(im *longhorn. return podSpec, nil } +func (imc *InstanceManagerController) startBackingImageMonitoring(im *longhorn.InstanceManager) { + log := imc.logger.WithField("instance manager", im.Name) + + backingImageMonitorName := types.GetBackingImageMonitorName(im.Name) + + if im.Status.IP == "" { + log.Errorf("IP is not set before monitoring") + return + } + imc.instanceManagerMonitorMutex.Lock() + defer imc.instanceManagerMonitorMutex.Unlock() + + if _, ok := imc.instanceManagerMonitorMap[backingImageMonitorName]; ok { + return + } + + engineClientProxy, err := engineapi.NewEngineClientProxy(im, log, imc.proxyConnCounter) + if err != nil { + log.Errorf("failed to get the engine client proxy for instance manager %v", im.Name) + return + } + + stopCh := make(chan struct{}, 1) + monitorVoluntaryStopCh := make(chan struct{}) + monitor := &InstanceManagerMonitor{ + logger: log, + Name: backingImageMonitorName, + imName: im.Name, + controllerID: imc.controllerID, + ds: imc.ds, + lock: &sync.RWMutex{}, + stopCh: stopCh, + done: false, + monitorVoluntaryStopCh: monitorVoluntaryStopCh, + // notify monitor to update the instance map + updateNotification: true, + proxyClient: engineClientProxy, + + nodeCallback: imc.enqueueInstanceManagersForNode, + } + + imc.instanceManagerMonitorMap[backingImageMonitorName] = stopCh + go monitor.BackingImageMonitorRun() + + go func() { + <-monitorVoluntaryStopCh + engineClientProxy.Close() + imc.instanceManagerMonitorMutex.Lock() + delete(imc.instanceManagerMonitorMap, backingImageMonitorName) + imc.instanceManagerMonitorMutex.Unlock() + }() +} + +func (imc *InstanceManagerController) stopBackingImageMonitoring(imName string) { + imc.instanceManagerMonitorMutex.Lock() + defer imc.instanceManagerMonitorMutex.Unlock() + + backingImageMonitorName := types.GetBackingImageMonitorName(imName) + stopCh, ok := imc.instanceManagerMonitorMap[backingImageMonitorName] + if !ok { + return + } + + select { + case <-stopCh: + // stopCh channel is already closed + default: + close(stopCh) + } +} + +func (m *InstanceManagerMonitor) BackingImageMonitorRun() { + m.logger.Infof("Start SPDK backing image monitoring %v", m.Name) + + ctx, cancel := context.WithCancel(context.TODO()) + notifier, err := m.proxyClient.SPDKBackingImageWatch(ctx) + if err != nil { + m.logger.WithError(err).Errorf("Failed to get the notifier for monitoring") + cancel() + close(m.monitorVoluntaryStopCh) + return + } + + defer func() { + m.logger.Infof("Stop monitoring spdk backing image %v", m.Name) + cancel() + m.StopMonitorWithLock() + close(m.monitorVoluntaryStopCh) + }() + + go func() { + continuousFailureCount := 0 + for { + if continuousFailureCount >= engineapi.MaxMonitorRetryCount { + m.logger.Errorf("Instance manager SPDK backing image monitor streaming continuously errors receiving items for %v times, will stop the monitor itself", engineapi.MaxMonitorRetryCount) + m.StopMonitorWithLock() + } + + if m.CheckMonitorStoppedWithLock() { + return + } + + _, err = notifier.Recv() + if err != nil { + m.logger.WithError(err).Error("Failed to receive next item in spdk backing image watch") + continuousFailureCount++ + time.Sleep(engineapi.MinPollCount * engineapi.PollInterval) + } else { + m.lock.Lock() + m.updateNotification = true + m.lock.Unlock() + } + } + }() + + timer := 0 + ticker := time.NewTicker(engineapi.MinPollCount * engineapi.PollInterval) + defer ticker.Stop() + for { + select { + case <-ticker.C: + if m.CheckMonitorStoppedWithLock() { + return + } + + needUpdate := false + + m.lock.Lock() + timer++ + if timer >= engineapi.MaxPollCount || m.updateNotification { + needUpdate = true + m.updateNotification = false + timer = 0 + } + m.lock.Unlock() + + if !needUpdate { + continue + } + if needStop := m.pollAndUpdateV2BackingImageMap(); needStop { + return + } + case <-m.stopCh: + return + } + } +} + +func (m *InstanceManagerMonitor) pollAndUpdateV2BackingImageMap() (needStop bool) { + im, err := m.ds.GetInstanceManager(m.imName) + if err != nil { + if datastore.ErrorIsNotFound(err) { + m.logger.Warn("stop monitoring because the instance manager no longer exists") + return true + } + utilruntime.HandleError(errors.Wrapf(err, "failed to get instance manager %v for monitoring", m.Name)) + return false + } + + if im.Status.OwnerID != m.controllerID { + m.logger.Warnf("stop monitoring the instance manager on this node (%v) because the instance manager has new ownerID %v", m.controllerID, im.Status.OwnerID) + return true + } + + // the key in the resp is in the form of "bi-%s-disk-%s" so we can distinguish the different disks in the same instance manager + resp, err := m.proxyClient.SPDKBackingImageList() + if err != nil { + utilruntime.HandleError(errors.Wrapf(err, "failed to poll spdk backing image info to update instance manager %v", m.Name)) + return false + } + + if reflect.DeepEqual(im.Status.V2BackingImages, resp) { + return false + } + + im.Status.V2BackingImages = resp + if _, err := m.ds.UpdateInstanceManagerStatus(im); err != nil { + utilruntime.HandleError(errors.Wrapf(err, "failed to update v2 backing image map for instance manager %v", m.Name)) + return false + } + return false +} + func (imc *InstanceManagerController) startMonitoring(im *longhorn.InstanceManager) { log := imc.logger.WithField("instance manager", im.Name) diff --git a/controller/instance_manager_controller_test.go b/controller/instance_manager_controller_test.go index fad60b4537..6e96a15028 100644 --- a/controller/instance_manager_controller_test.go +++ b/controller/instance_manager_controller_test.go @@ -63,7 +63,8 @@ func newTestInstanceManagerController(lhClient *lhfake.Clientset, kubeClient *fa logger := logrus.StandardLogger() - imc, err := NewInstanceManagerController(logger, ds, scheme.Scheme, kubeClient, TestNamespace, controllerID, TestServiceAccount) + proxyConnCounter := util.NewAtomicCounter() + imc, err := NewInstanceManagerController(logger, ds, scheme.Scheme, kubeClient, TestNamespace, controllerID, TestServiceAccount, proxyConnCounter) if err != nil { return nil, err } diff --git a/controller/replica_controller.go b/controller/replica_controller.go index 2930827080..96a13ac8ad 100644 --- a/controller/replica_controller.go +++ b/controller/replica_controller.go @@ -425,7 +425,9 @@ func (rc *ReplicaController) GetBackingImagePathForReplicaStarting(r *longhorn.R if !res { return "", fmt.Errorf("The backing image %v can not be put on the disk %v", bi.Name, r.Spec.DiskID) } - bi.Spec.DiskFileSpecMap[r.Spec.DiskID] = &longhorn.BackingImageDiskFileSpec{} + bi.Spec.DiskFileSpecMap[r.Spec.DiskID] = &longhorn.BackingImageDiskFileSpec{ + DataEngine: r.Spec.DataEngine, + } log.Infof("Asking backing image %v to download file to node %v disk %v", bi.Name, r.Spec.NodeID, r.Spec.DiskID) if _, err := rc.ds.UpdateBackingImage(bi); err != nil { return "", err diff --git a/datastore/longhorn.go b/datastore/longhorn.go index 9799d3b571..90604fa377 100644 --- a/datastore/longhorn.go +++ b/datastore/longhorn.go @@ -2893,7 +2893,7 @@ func (s *DataStore) ListReadyNodesContainingEngineImageRO(image string) (map[str // GetReadyNodeDiskForBackingImage a list of all Node the in the given namespace and // returns the first Node && the first Disk of the Node marked with condition ready and allow scheduling -func (s *DataStore) GetReadyNodeDiskForBackingImage(backingImage *longhorn.BackingImage) (*longhorn.Node, string, error) { +func (s *DataStore) GetReadyNodeDiskForBackingImage(backingImage *longhorn.BackingImage, dataEngine longhorn.DataEngineType) (*longhorn.Node, string, error) { logrus.Info("Preparing to find a random ready node disk") nodes, err := s.ListNodesRO() if err != nil { @@ -2931,11 +2931,16 @@ func (s *DataStore) GetReadyNodeDiskForBackingImage(backingImage *longhorn.Backi if !types.IsSelectorsInTags(diskSpec.Tags, backingImage.Spec.DiskSelector, allowEmptyDiskSelectorVolume) { continue } - if _, exists := backingImage.Spec.DiskFileSpecMap[diskStatus.DiskUUID]; exists { - continue + if dataEngine == longhorn.DataEngineTypeV2 { + if diskSpec.Type != longhorn.DiskTypeBlock { + continue + } + } else { + if diskSpec.Type != longhorn.DiskTypeFilesystem { + continue + } } - // TODO: Jack add block type disk for spdk version BackingImage - if diskSpec.Type != longhorn.DiskTypeFilesystem { + if _, exists := backingImage.Spec.DiskFileSpecMap[diskStatus.DiskUUID]; exists { continue } if !diskSpec.AllowScheduling { diff --git a/engineapi/enginesim.go b/engineapi/enginesim.go index 18f6a3f223..cf461c923e 100644 --- a/engineapi/enginesim.go +++ b/engineapi/enginesim.go @@ -1,6 +1,7 @@ package engineapi import ( + "context" "fmt" "sync" @@ -8,6 +9,7 @@ import ( "github.com/sirupsen/logrus" etypes "github.com/longhorn/longhorn-engine/pkg/types" + imapi "github.com/longhorn/longhorn-instance-manager/pkg/api" "github.com/longhorn/longhorn-manager/datastore" longhorn "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" @@ -284,3 +286,23 @@ func (e *EngineSimulator) CleanupBackupMountPoints() error { func (e *EngineSimulator) RemountReadOnlyVolume(*longhorn.Engine) error { return errors.New(ErrNotImplement) } + +func (e *EngineSimulator) SPDKBackingImageCreate(name, backingImageUUID, diskUUID, checksum, fromAddress, srcDiskUUID string, size uint64) (*imapi.BackingImage, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineSimulator) SPDKBackingImageDelete(name, diskUUID string) error { + return errors.New(ErrNotImplement) +} + +func (e *EngineSimulator) SPDKBackingImageGet(name, diskUUID string) (*imapi.BackingImage, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineSimulator) SPDKBackingImageList() (map[string]longhorn.BackingImageV2CopyInfo, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineSimulator) SPDKBackingImageWatch(ctx context.Context) (*imapi.BackingImageStream, error) { + return nil, errors.New(ErrNotImplement) +} diff --git a/engineapi/instance_manager.go b/engineapi/instance_manager.go index 3f7d00a67c..cfe7844e4f 100644 --- a/engineapi/instance_manager.go +++ b/engineapi/instance_manager.go @@ -573,8 +573,9 @@ func (c *InstanceManagerClient) ReplicaInstanceCreate(req *ReplicaInstanceCreate BinaryArgs: args, Replica: imclient.ReplicaCreateRequest{ - DiskName: req.DiskName, - DiskUUID: req.Replica.Spec.DiskID, + DiskName: req.DiskName, + DiskUUID: req.Replica.Spec.DiskID, + BackingImageName: req.Replica.Spec.BackingImage, }, }) if err != nil { diff --git a/engineapi/proxy_backing_image.go b/engineapi/proxy_backing_image.go new file mode 100644 index 0000000000..2d7a097575 --- /dev/null +++ b/engineapi/proxy_backing_image.go @@ -0,0 +1,75 @@ +package engineapi + +import ( + "context" + + imapi "github.com/longhorn/longhorn-instance-manager/pkg/api" + "github.com/pkg/errors" + + longhorn "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" +) + +func (p *Proxy) SPDKBackingImageCreate(name, backingImageUUID, diskUUID, checksum, fromAddress, srcDiskUUID string, size uint64) (*imapi.BackingImage, error) { + return p.grpcClient.SPDKBackingImageCreate(name, backingImageUUID, diskUUID, checksum, fromAddress, srcDiskUUID, size) +} + +func (p *Proxy) SPDKBackingImageDelete(name, diskUUID string) error { + return p.grpcClient.SPDKBackingImageDelete(name, diskUUID) +} + +func (p *Proxy) SPDKBackingImageGet(name, diskUUID string) (*imapi.BackingImage, error) { + return p.grpcClient.SPDKBackingImageGet(name, diskUUID) +} + +func (p *Proxy) SPDKBackingImageList() (map[string]longhorn.BackingImageV2CopyInfo, error) { + result := map[string]longhorn.BackingImageV2CopyInfo{} + + v2BackingImages, err := p.grpcClient.SPDKBackingImageList() + if err != nil { + return nil, err + } + + for name, backingImage := range v2BackingImages { + result[name] = *parseBackingImage(backingImage) + } + + return result, nil +} + +func (p *Proxy) SPDKBackingImageWatch(ctx context.Context) (*imapi.BackingImageStream, error) { + return p.grpcClient.SPDKBackingImageWatch(ctx) +} + +func parseBackingImage(bi *imapi.BackingImage) *longhorn.BackingImageV2CopyInfo { + return &longhorn.BackingImageV2CopyInfo{ + Name: bi.Name, + UUID: bi.BackingImageUUID, + DiskUUID: bi.DiskUUID, + Size: int64(bi.Size), + + Progress: bi.Status.Progress, + State: longhorn.BackingImageState(bi.Status.State), + CurrentChecksum: bi.Status.CurrentChecksum, + Message: bi.Status.ErrorMsg, + } +} + +func (e *EngineBinary) SPDKBackingImageCreate(name, backingImageUUID, diskUUID, checksum, fromAddress, srcDiskUUID string, size uint64) (*imapi.BackingImage, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineBinary) SPDKBackingImageDelete(name, diskUUID string) error { + return errors.New(ErrNotImplement) +} + +func (e *EngineBinary) SPDKBackingImageGet(name, diskUUID string) (*imapi.BackingImage, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineBinary) SPDKBackingImageList() (map[string]longhorn.BackingImageV2CopyInfo, error) { + return nil, errors.New(ErrNotImplement) +} + +func (e *EngineBinary) SPDKBackingImageWatch(ctx context.Context) (*imapi.BackingImageStream, error) { + return nil, errors.New(ErrNotImplement) +} diff --git a/engineapi/types.go b/engineapi/types.go index 7854bffd0d..7451ad5238 100644 --- a/engineapi/types.go +++ b/engineapi/types.go @@ -1,12 +1,14 @@ package engineapi import ( + "context" "fmt" "strings" "time" iscsidevtypes "github.com/longhorn/go-iscsi-helper/types" spdkdevtypes "github.com/longhorn/go-spdk-helper/pkg/types" + imapi "github.com/longhorn/longhorn-instance-manager/pkg/api" emeta "github.com/longhorn/longhorn-engine/pkg/meta" etypes "github.com/longhorn/longhorn-engine/pkg/types" @@ -109,6 +111,12 @@ type EngineClient interface { BackupRestore(engine *longhorn.Engine, backupTarget, backupName, backupVolume, lastRestored string, credential map[string]string, concurrentLimit int) error BackupRestoreStatus(engine *longhorn.Engine) (map[string]*longhorn.RestoreStatus, error) + SPDKBackingImageCreate(name, backingImageUUID, diskUUID, checksum, fromAddress, srcDiskUUID string, size uint64) (*imapi.BackingImage, error) + SPDKBackingImageDelete(name, diskUUID string) error + SPDKBackingImageGet(name, diskUUID string) (*imapi.BackingImage, error) + SPDKBackingImageList() (map[string]longhorn.BackingImageV2CopyInfo, error) + SPDKBackingImageWatch(ctx context.Context) (*imapi.BackingImageStream, error) + CleanupBackupMountPoints() error MetricsGet(engine *longhorn.Engine) (*Metrics, error) diff --git a/go.mod b/go.mod index 4344238467..a2950545ae 100644 --- a/go.mod +++ b/go.mod @@ -4,6 +4,10 @@ go 1.22.7 toolchain go1.23.3 +replace github.com/longhorn/longhorn-instance-manager v1.8.0-dev-20241117 => github.com/chanyilin/longhorn-instance-manager v1.4.0-rc1.0.20241127091208-1b70e21ae176 + +replace github.com/longhorn/types v0.0.0-20241123075624-48c550af4eab => github.com/chanyilin/types v0.0.0-20241127083912-9de1ee948ab1 + // Replace directives are required for dependencies in this section because: // - This module imports k8s.io/kubernetes. // - The development for all of these dependencies is done at kubernetes/staging and then synced to other repos. @@ -102,7 +106,6 @@ require ( github.com/distribution/reference v0.5.0 // indirect github.com/fxamacker/cbor/v2 v2.7.0 // indirect github.com/go-ole/go-ole v1.3.0 // indirect - github.com/godbus/dbus/v5 v5.1.0 // indirect github.com/golang/protobuf v1.5.4 // indirect github.com/google/gnostic-models v0.6.8 // indirect github.com/jonboulle/clockwork v0.4.0 // indirect @@ -110,9 +113,9 @@ require ( github.com/kylelemons/godebug v1.1.0 // indirect github.com/longhorn/types v0.0.0-20241123075624-48c550af4eab // indirect github.com/mitchellh/go-ps v1.0.0 // indirect + github.com/moby/sys/userns v0.1.0 // indirect github.com/moby/term v0.5.0 // indirect - github.com/opencontainers/runc v1.1.14 // indirect - github.com/opencontainers/runtime-spec v1.1.0 // indirect + github.com/opencontainers/runc v1.2.2 // indirect github.com/power-devops/perfstat v0.0.0-20240221224432-82ca36839d55 // indirect github.com/shirou/gopsutil/v3 v3.24.5 // indirect github.com/x448/float16 v0.8.4 // indirect @@ -120,7 +123,7 @@ require ( golang.org/x/exp v0.0.0-20241108190413-2d47ceb2692f // indirect golang.org/x/tools v0.27.0 // indirect google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 // indirect - google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 // indirect + google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 // indirect gopkg.in/evanphx/json-patch.v4 v4.12.0 // indirect gopkg.in/yaml.v3 v3.0.1 // indirect ) @@ -143,7 +146,7 @@ require ( github.com/evanphx/json-patch v5.9.0+incompatible // indirect github.com/felixge/httpsnoop v1.0.4 // indirect github.com/fsnotify/fsnotify v1.7.0 // indirect - github.com/gammazero/deque v0.2.1 // indirect + github.com/gammazero/deque v1.0.0 // indirect github.com/gammazero/workerpool v1.1.3 // indirect github.com/go-logr/logr v1.4.2 // indirect github.com/go-logr/stdr v1.2.2 // indirect @@ -168,7 +171,7 @@ require ( github.com/mailru/easyjson v0.7.7 // indirect github.com/mitchellh/copystructure v1.2.0 // indirect github.com/mitchellh/reflectwalk v1.0.2 // indirect - github.com/moby/sys/mountinfo v0.7.1 // indirect + github.com/moby/sys/mountinfo v0.7.2 // indirect github.com/modern-go/concurrent v0.0.0-20180306012644-bacd9c7ef1dd // indirect github.com/modern-go/reflect2 v1.0.2 // indirect github.com/munnerz/goautoneg v0.0.0-20191010083416-a7dc8b61c822 // indirect @@ -176,11 +179,11 @@ require ( github.com/pierrec/lz4/v4 v4.1.21 // indirect github.com/pmezard/go-difflib v1.0.1-0.20181226105442-5d4384ee4fb2 // indirect github.com/prometheus/client_model v0.6.1 // indirect - github.com/prometheus/common v0.55.0 // indirect + github.com/prometheus/common v0.60.1 // indirect github.com/prometheus/procfs v0.15.1 // indirect github.com/rancher/lasso v0.0.0-20241115003608-cbc3210bd004 github.com/robfig/cron/v3 v3.0.1 // indirect - github.com/rogpeppe/go-internal v1.12.0 // indirect + github.com/rogpeppe/go-internal v1.13.1 // indirect github.com/russross/blackfriday/v2 v2.1.0 // indirect github.com/slok/goresilience v0.2.0 // indirect github.com/spf13/cobra v1.8.1 // indirect diff --git a/go.sum b/go.sum index a9824da597..85f2356034 100644 --- a/go.sum +++ b/go.sum @@ -22,6 +22,10 @@ github.com/cenkalti/backoff/v4 v4.3.0 h1:MyRJ/UdXutAwSAT+s3wNd7MfTIcy71VQueUuFK3 github.com/cenkalti/backoff/v4 v4.3.0/go.mod h1:Y3VNntkOUPxTVeUxJ/G5vcM//AlwfmyYozVcomhLiZE= github.com/cespare/xxhash/v2 v2.3.0 h1:UL815xU9SqsFlibzuggzjXhog7bL6oX9BbNZnL2UFvs= github.com/cespare/xxhash/v2 v2.3.0/go.mod h1:VGX0DQ3Q6kWi7AoAeZDth3/j3BFtOZR5XLFGgcrjCOs= +github.com/chanyilin/longhorn-instance-manager v1.4.0-rc1.0.20241127091208-1b70e21ae176 h1:1yLJ/xNYFD4KkkBUKzoMqIzsgN1l69Yfp0iP2EgM8tY= +github.com/chanyilin/longhorn-instance-manager v1.4.0-rc1.0.20241127091208-1b70e21ae176/go.mod h1:Hp4jrRE5/TtfNAyY/M/vKvpHPyqU247HU/R5vMI2h6A= +github.com/chanyilin/types v0.0.0-20241127083912-9de1ee948ab1 h1:Hf87oqhMvhnT0onTZw/BL4r4ORk0avl5QDqrPjdoFMI= +github.com/chanyilin/types v0.0.0-20241127083912-9de1ee948ab1/go.mod h1:dIPa2yMBPOa514bn3pohT7kbVAQcZbZSWItpgxunuPs= github.com/container-storage-interface/spec v1.11.0 h1:H/YKTOeUZwHtyPOr9raR+HgFmGluGCklulxDYxSdVNM= github.com/container-storage-interface/spec v1.11.0/go.mod h1:DtUvaQszPml1YJfIK7c00mlv6/g4wNMLanLgiUbKFRI= github.com/coreos/go-semver v0.3.1 h1:yi21YpKnrx1gt5R+la8n5WgS0kCrsPp33dmEyHReZr4= @@ -54,8 +58,8 @@ github.com/fsnotify/fsnotify v1.7.0 h1:8JEhPFa5W2WU7YfeZzPNqzMP6Lwt7L2715Ggo0nos github.com/fsnotify/fsnotify v1.7.0/go.mod h1:40Bi/Hjc2AVfZrqy+aj+yEI+/bRxZnMJyTJwOpGvigM= github.com/fxamacker/cbor/v2 v2.7.0 h1:iM5WgngdRBanHcxugY4JySA0nk1wZorNOpTgCMedv5E= github.com/fxamacker/cbor/v2 v2.7.0/go.mod h1:pxXPTn3joSm21Gbwsv0w9OSA2y1HFR9qXEeXQVeNoDQ= -github.com/gammazero/deque v0.2.1 h1:qSdsbG6pgp6nL7A0+K/B7s12mcCY/5l5SIUpMOl+dC0= -github.com/gammazero/deque v0.2.1/go.mod h1:LFroj8x4cMYCukHJDbxFCkT+r9AndaJnFMuZDV34tuU= +github.com/gammazero/deque v1.0.0 h1:LTmimT8H7bXkkCy6gZX7zNLtkbz4NdS2z8LZuor3j34= +github.com/gammazero/deque v1.0.0/go.mod h1:iflpYvtGfM3U8S8j+sZEKIak3SAKYpA5/SQewgfXDKo= github.com/gammazero/workerpool v1.1.3 h1:WixN4xzukFoN0XSeXF6puqEqFTl2mECI9S6W44HWy9Q= github.com/gammazero/workerpool v1.1.3/go.mod h1:wPjyBLDbyKnUn2XwwyD3EEwo9dHutia9/fwNmSHWACc= github.com/go-co-op/gocron v1.37.0 h1:ZYDJGtQ4OMhTLKOKMIch+/CY70Brbb1dGdooLEhh7b0= @@ -82,8 +86,6 @@ github.com/go-openapi/swag v0.23.0/go.mod h1:esZ8ITTYEsH1V2trKHjAN8Ai7xHb8RV+YSZ github.com/go-task/slim-sprig/v3 v3.0.0 h1:sUs3vkvUymDpBKi3qH1YSqBQk9+9D/8M2mN1vB6EwHI= github.com/go-task/slim-sprig/v3 v3.0.0/go.mod h1:W848ghGpv3Qj3dhTPRyJypKRiqCdHZiAzKg9hl15HA8= github.com/godbus/dbus/v5 v5.0.4/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= -github.com/godbus/dbus/v5 v5.1.0 h1:4KLkAxT3aOY8Li4FRJe/KvhoNFFxo0m6fNuFUO8QJUk= -github.com/godbus/dbus/v5 v5.1.0/go.mod h1:xhWf0FNVPg57R7Z0UbKHbJfkEywrmjJnf7w5xrFpKfA= github.com/gogo/protobuf v1.3.2 h1:Ov1cvc58UF3b5XjBnZv7+opcTcQFZebYjWzi34vdm4Q= github.com/gogo/protobuf v1.3.2/go.mod h1:P1XiOD3dCwIKUDQYPy72D8LYyHL2YPYrpS2s69NZV8Q= github.com/golang-jwt/jwt/v4 v4.5.0 h1:7cYmW1XlMY7h7ii7UhUyChSgS5wUJEnm9uZVTGqOWzg= @@ -169,12 +171,8 @@ github.com/longhorn/go-spdk-helper v0.0.0-20241124090314-c396ae715a7f h1:P/XoCIN github.com/longhorn/go-spdk-helper v0.0.0-20241124090314-c396ae715a7f/go.mod h1:F4KxpqJXmghH0E3GH0XRSr7M0v5DROFEwaecPbhnGY4= github.com/longhorn/longhorn-engine v1.8.0-dev-20241117 h1:2sW/dPj69/XrMDjsh2LBZ2cyOXb5MQBI7HJCuDc2vjs= github.com/longhorn/longhorn-engine v1.8.0-dev-20241117/go.mod h1:3VnMqI8YqJ76XdRvnMWJNp1U1ITAyOwOjKtbYPqLqQw= -github.com/longhorn/longhorn-instance-manager v1.8.0-dev-20241117 h1:dReWXt6gh5mpL27fEhDFCLVK7iZwGWCNK3rH4BGP29M= -github.com/longhorn/longhorn-instance-manager v1.8.0-dev-20241117/go.mod h1:HCh2gA5CNMpZ+b2j7uUgV8RMJ8BwB22cjiTbjWeQ5p4= github.com/longhorn/longhorn-share-manager v1.7.0-rc1 h1:LsSkSajhG8tCfORKKfwK+8XHVrT/8rI9DRWb7fuoVls= github.com/longhorn/longhorn-share-manager v1.7.0-rc1/go.mod h1:R6+NscPU4lAV5ueO7//lBCAO3en0aDbZi5KkkOSUJvk= -github.com/longhorn/types v0.0.0-20241123075624-48c550af4eab h1:vW/sSHB0U/GPnornD1cdoKSEe9bdBwVKP68cZUs1Xh0= -github.com/longhorn/types v0.0.0-20241123075624-48c550af4eab/go.mod h1:dIPa2yMBPOa514bn3pohT7kbVAQcZbZSWItpgxunuPs= github.com/mailru/easyjson v0.7.7 h1:UGYAvKxe3sBsEDzO8ZeWOSlIQfWFlxbzLZe7hwFURr0= github.com/mailru/easyjson v0.7.7/go.mod h1:xzfreul335JAWq5oZzymOObrkdz5UnU4kGfJJLY9Nlc= github.com/matttproud/golang_protobuf_extensions v1.0.1/go.mod h1:D8He9yQNgCq6Z5Ld7szi9bcBfOoFv/3dc6xSMkL2PC0= @@ -184,8 +182,10 @@ github.com/mitchellh/go-ps v1.0.0 h1:i6ampVEEF4wQFF+bkYfwYgY+F/uYJDktmvLPf7qIgjc github.com/mitchellh/go-ps v1.0.0/go.mod h1:J4lOc8z8yJs6vUwklHw2XEIiT4z4C40KtWVN3nvg8Pg= github.com/mitchellh/reflectwalk v1.0.2 h1:G2LzWKi524PWgd3mLHV8Y5k7s6XUvT0Gef6zxSIeXaQ= github.com/mitchellh/reflectwalk v1.0.2/go.mod h1:mSTlrgnPZtwu0c4WaC2kGObEpuNDbx0jmZXqmk4esnw= -github.com/moby/sys/mountinfo v0.7.1 h1:/tTvQaSJRr2FshkhXiIpux6fQ2Zvc4j7tAhMTStAG2g= -github.com/moby/sys/mountinfo v0.7.1/go.mod h1:IJb6JQeOklcdMU9F5xQ8ZALD+CUr5VlGpwtX+VE0rpI= +github.com/moby/sys/mountinfo v0.7.2 h1:1shs6aH5s4o5H2zQLn796ADW1wMrIwHsyJ2v9KouLrg= +github.com/moby/sys/mountinfo v0.7.2/go.mod h1:1YOa8w8Ih7uW0wALDUgT1dTTSBrZ+HiBLGws92L2RU4= +github.com/moby/sys/userns v0.1.0 h1:tVLXkFOxVu9A64/yh59slHVv9ahO9UIev4JZusOLG/g= +github.com/moby/sys/userns v0.1.0/go.mod h1:IHUYgu/kao6N8YZlp9Cf444ySSvCmDlmzUcYfDHOl28= github.com/moby/term v0.5.0 h1:xt8Q1nalod/v7BqbG21f8mQPqH+xAaC9C3N3wfWbVP0= github.com/moby/term v0.5.0/go.mod h1:8FzsFHVUBGZdbDsJw/ot+X+d5HLUbvklYLJ9uGfcI3Y= github.com/modern-go/concurrent v0.0.0-20180228061459-e0a39a4cb421/go.mod h1:6dJC0mAP4ikYIbvyc7fijjWJddQyLn8Ig3JB5CqoB9Q= @@ -201,10 +201,8 @@ github.com/onsi/gomega v1.33.1 h1:dsYjIxxSR755MDmKVsaFQTE22ChNBcuuTWgkUDSubOk= github.com/onsi/gomega v1.33.1/go.mod h1:U4R44UsT+9eLIaYRB2a5qajjtQYn0hauxvRm16AVYg0= github.com/opencontainers/go-digest v1.0.0 h1:apOUWs51W5PlhuyGyz9FCeeBIOUDA/6nW8Oi/yOhh5U= github.com/opencontainers/go-digest v1.0.0/go.mod h1:0JzlMkj0TRzQZfJkVvzbP0HBR3IKzErnv2BNG4W4MAM= -github.com/opencontainers/runc v1.1.14 h1:rgSuzbmgz5DUJjeSnw337TxDbRuqjs6iqQck/2weR6w= -github.com/opencontainers/runc v1.1.14/go.mod h1:E4C2z+7BxR7GHXp0hAY53mek+x49X1LjPNeMTfRGvOA= -github.com/opencontainers/runtime-spec v1.1.0 h1:HHUyrt9mwHUjtasSbXSMvs4cyFxh+Bll4AjJ9odEGpg= -github.com/opencontainers/runtime-spec v1.1.0/go.mod h1:jwyrGlmzljRJv/Fgzds9SsS/C5hL+LL3ko9hs6T5lQ0= +github.com/opencontainers/runc v1.2.2 h1:jTg3Vw2A5f0N9PoxFTEwUhvpANGaNPT3689Yfd/zaX0= +github.com/opencontainers/runc v1.2.2/go.mod h1:/PXzF0h531HTMsYQnmxXkBD7YaGShm/2zcRB79dksUc= github.com/pierrec/lz4/v4 v4.1.21 h1:yOVMLb6qSIDP67pl/5F7RepeKYu/VmTyEXvuMI5d9mQ= github.com/pierrec/lz4/v4 v4.1.21/go.mod h1:gZWDp/Ze/IJXGXf23ltt2EXimqmTUXEy0GFuRQyBid4= github.com/pkg/diff v0.0.0-20210226163009-20ebb0f2a09e/go.mod h1:pJLUxLENpZxwdsKMEsNbx1VGcRFpLqf3715MtcvvzbA= @@ -222,8 +220,8 @@ github.com/prometheus/client_model v0.0.0-20180712105110-5c3871d89910/go.mod h1: github.com/prometheus/client_model v0.6.1 h1:ZKSh/rekM+n3CeS952MLRAdFwIKqeY8b62p8ais2e9E= github.com/prometheus/client_model v0.6.1/go.mod h1:OrxVMOVHjw3lKMa8+x6HeMGkHMQyHDk9E3jmP2AmGiY= github.com/prometheus/common v0.0.0-20181126121408-4724e9255275/go.mod h1:daVV7qP5qjZbuso7PdcryaAu0sAZbrN9i7WWcTMWvro= -github.com/prometheus/common v0.55.0 h1:KEi6DK7lXW/m7Ig5i47x0vRzuBsHuvJdi5ee6Y3G1dc= -github.com/prometheus/common v0.55.0/go.mod h1:2SECS4xJG1kd8XF9IcM1gMX6510RAEL65zxzNImwdc8= +github.com/prometheus/common v0.60.1 h1:FUas6GcOw66yB/73KC+BOZoFJmbo/1pojoILArPAaSc= +github.com/prometheus/common v0.60.1/go.mod h1:h0LYf1R1deLSKtD4Vdg8gy4RuOvENW2J/h19V5NADQw= github.com/prometheus/procfs v0.0.0-20181204211112-1dc9a6cbc91a/go.mod h1:c3At6R/oaqEKCNdg8wHV1ftS6bRYblBhIjjI8uT2IGk= github.com/prometheus/procfs v0.15.1 h1:YagwOFzUgYfKKHX6Dr+sHT7km/hxC76UB0learggepc= github.com/prometheus/procfs v0.15.1/go.mod h1:fB45yRUv8NstnjriLhBQLuOUt+WW4BsoGhij/e3PBqk= @@ -242,8 +240,8 @@ github.com/robfig/cron/v3 v3.0.1/go.mod h1:eQICP3HwyT7UooqI/z+Ov+PtYAWygg1TEWWzG github.com/rogpeppe/go-internal v1.6.1/go.mod h1:xXDCJY+GAPziupqXw64V24skbSoqbTEfhy4qGm1nDQc= github.com/rogpeppe/go-internal v1.8.1/go.mod h1:JeRgkft04UBgHMgCIwADu4Pn6Mtm5d4nPKWu0nJ5d+o= github.com/rogpeppe/go-internal v1.9.0/go.mod h1:WtVeX8xhTBvf0smdhujwtBcq4Qrzq/fJaraNFVN+nFs= -github.com/rogpeppe/go-internal v1.12.0 h1:exVL4IDcn6na9z1rAb56Vxr+CgyK3nn3O+epU5NdKM8= -github.com/rogpeppe/go-internal v1.12.0/go.mod h1:E+RYuTGaKKdloAfM02xzb0FW3Paa99yedzYV+kq4uf4= +github.com/rogpeppe/go-internal v1.13.1 h1:KvO1DLK/DRN07sQ1LQKScxyZJuNnedQ5/wKSR38lUII= +github.com/rogpeppe/go-internal v1.13.1/go.mod h1:uMEvuHeurkdAXX61udpOXGD/AzZDWNMNyH2VO9fmH0o= github.com/russross/blackfriday/v2 v2.1.0 h1:JIOH55/0cWyOuilr9/qlrm0BSXldqnqwMsf35Ld67mk= github.com/russross/blackfriday/v2 v2.1.0/go.mod h1:+Rmxgy9KzJVeS9/2gXHxylqXiyQDYRxCVz55jmeOWTM= github.com/shirou/gopsutil/v3 v3.24.5 h1:i0t8kL+kQTvpAYToeuiVk3TgDeKOFioZO3Ztz/iZ9pI= @@ -368,7 +366,6 @@ golang.org/x/sys v0.0.0-20190916202348-b4ddaad3f8a3/go.mod h1:h1NjWce9XRLGQEsW7w golang.org/x/sys v0.0.0-20200930185726-fdedc70b468f/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20201204225414-ed752295db88/go.mod h1:h1NjWce9XRLGQEsW7wpKNCjG9DtNlClVuFLEZdDNbEs= golang.org/x/sys v0.0.0-20210616094352-59db8d763f22/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= -golang.org/x/sys v0.0.0-20220520151302-bc2c85ada10a/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.0.0-20220715151400-c0bba94af5f8/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.1.0/go.mod h1:oPkhp1MJrh7nUepCBck5+mAzfO9JrbApNNgaTdGDITg= golang.org/x/sys v0.27.0 h1:wBqf8DvsY9Y/2P8gAfPDEYNuS30J4lPHJxXSb/nJZ+s= @@ -395,8 +392,8 @@ google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda h1:wu/KJm9KJwpfHWh google.golang.org/genproto v0.0.0-20240401170217-c3f982113cda/go.mod h1:g2LLCvCeCSir/JJSWosk19BR4NVxGqHUC6rxIRsd7Aw= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1 h1:hjSy6tcFQZ171igDaN5QHOw2n6vx40juYbC/x67CEhc= google.golang.org/genproto/googleapis/api v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:qpvKtACPCQhAdu3PyQgV4l3LMXZEtft7y8QcarRsp9I= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1 h1:pPJltXNxVzT4pK9yD8vR9X75DaWYYmLGMsEvBfFQZzQ= -google.golang.org/genproto/googleapis/rpc v0.0.0-20240903143218-8af14fe29dc1/go.mod h1:UqMtugtsSgubUsoxbuAoiCXvqvErP7Gf0so0mK9tHxU= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697 h1:LWZqQOEjDyONlF1H6afSWpAL/znlREo2tHfLoe+8LMA= +google.golang.org/genproto/googleapis/rpc v0.0.0-20241118233622-e639e219e697/go.mod h1:5uTbfoYQed2U9p3KIj2/Zzm02PYhndfdmML0qC3q3FU= google.golang.org/grpc v1.68.0 h1:aHQeeJbo8zAkAa3pRzrVjZlbz6uSfeOXlJNQM0RAbz0= google.golang.org/grpc v1.68.0/go.mod h1:fmSPC5AsjSBCK54MyHRx48kpOti1/jRfOlwEWywNjWA= google.golang.org/protobuf v1.35.2 h1:8Ar7bF+apOIoThw1EdZl0p1oWvMqTHmpA2fRTyZO8io= diff --git a/k8s/crds.yaml b/k8s/crds.yaml index 2b57b5ddcb..7a1be1b2a5 100644 --- a/k8s/crds.yaml +++ b/k8s/crds.yaml @@ -496,9 +496,16 @@ spec: properties: checksum: type: string + dataEngine: + enum: + - v1 + - v2 + type: string diskFileSpecMap: additionalProperties: properties: + dataEngine: + type: string evictionRequested: type: boolean type: object @@ -545,6 +552,8 @@ spec: diskFileStatusMap: additionalProperties: properties: + dataEngine: + type: string lastStateTransitionTime: type: string message: @@ -573,6 +582,10 @@ spec: type: integer uuid: type: string + v2FirstCopyDisk: + type: string + v2FirstCopyStatus: + type: string virtualSize: description: Virtual size of image in bytes, which may be larger than physical size. Will be zero until known (e.g. while a backing image is uploading) @@ -2264,6 +2277,28 @@ spec: type: integer proxyApiVersion: type: integer + v2BackingImages: + additionalProperties: + properties: + currentChecksum: + type: string + diskUUID: + type: string + message: + type: string + name: + type: string + progress: + type: integer + size: + format: int64 + type: integer + state: + type: string + uuid: + type: string + type: object + type: object type: object type: object served: true diff --git a/k8s/generate_code.sh b/k8s/generate_code.sh index d5ea0749bb..256e8f7e12 100755 --- a/k8s/generate_code.sh +++ b/k8s/generate_code.sh @@ -37,14 +37,14 @@ if [[ ! -d "${GOPATH}/src/k8s.io/code-generator" ]]; then fi # https://github.com/kubernetes-sigs/controller-tools/tree/${CONTROLLER_TOOLS_VERSION}/cmd/controller-gen -if ! command -v controller-gen > /dev/null; then +if ! command -v ${GOPATH}/bin/controller-gen > /dev/null; then echo "controller-gen is missing" echo "Prepare to install controller-gen" go install sigs.k8s.io/controller-tools/cmd/controller-gen@${CONTROLLER_TOOLS_VERSION} fi # https://github.com/kubernetes-sigs/kustomize/tree/kustomize/${KUSTOMIZE_VERSION}/kustomize -if ! command -v kustomize > /dev/null; then +if ! command -v ${GOPATH}/bin/kustomize > /dev/null; then echo "kustomize is missing" echo "Prepare to install kustomize" mkdir -p ${GOPATH}/src/github.com/kubernetes-sigs @@ -65,16 +65,16 @@ bash ${GOPATH}/src/k8s.io/code-generator/generate-groups.sh \ $@ echo Generating CRD -controller-gen crd paths=${APIS_DIR}/... output:crd:dir=${CRDS_DIR} +${GOPATH}/bin/controller-gen crd paths=${APIS_DIR}/... output:crd:dir=${CRDS_DIR} pushd ${CRDS_DIR} -kustomize create --autodetect 2>/dev/null || true -kustomize edit add label longhorn-manager: 2>/dev/null || true +${GOPATH}/bin/kustomize create --autodetect 2>/dev/null || true +${GOPATH}/bin/kustomize edit add label longhorn-manager: 2>/dev/null || true if [ -e ${GOPATH}/src/${LH_MANAGER_DIR}/k8s/patches/crd ]; then cp -a ${GOPATH}/src/${LH_MANAGER_DIR}/k8s/patches/crd patches - find patches -type f | xargs -i sh -c 'kustomize edit add patch --path {}' + find patches -type f | xargs -i sh -c '${GOPATH}/bin/kustomize edit add patch --path {}' fi popd echo "# Generated by the CRDs from ${APIS_DIR}" > ${GOPATH}/src/${LH_MANAGER_DIR}/k8s/crds.yaml -kustomize build ${CRDS_DIR} >> ${GOPATH}/src/${LH_MANAGER_DIR}/k8s/crds.yaml +${GOPATH}/bin/kustomize build ${CRDS_DIR} >> ${GOPATH}/src/${LH_MANAGER_DIR}/k8s/crds.yaml rm -r ${CRDS_DIR} diff --git a/k8s/pkg/apis/longhorn/v1beta2/backingimage.go b/k8s/pkg/apis/longhorn/v1beta2/backingimage.go index 79272e5cf2..093f6063e7 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/backingimage.go +++ b/k8s/pkg/apis/longhorn/v1beta2/backingimage.go @@ -29,6 +29,8 @@ const ( ) type BackingImageDiskFileStatus struct { + // +optional + DataEngine DataEngineType `json:"dataEngine"` // +optional State BackingImageState `json:"state"` // +optional @@ -42,6 +44,8 @@ type BackingImageDiskFileStatus struct { type BackingImageDiskFileSpec struct { // +optional EvictionRequested bool `json:"evictionRequested"` + // +optional + DataEngine DataEngineType `json:"dataEngine"` } // BackingImageSpec defines the desired state of the Longhorn backing image @@ -67,6 +71,9 @@ type BackingImageSpec struct { Secret string `json:"secret"` // +optional SecretNamespace string `json:"secretNamespace"` + // +kubebuilder:validation:Enum=v1;v2 + // +optional + DataEngine DataEngineType `json:"dataEngine"` } // BackingImageStatus defines the observed state of the Longhorn backing image status @@ -91,6 +98,11 @@ type BackingImageStatus struct { // +optional // +nullable DiskLastRefAtMap map[string]string `json:"diskLastRefAtMap"` + // It is pending -> in-progress -> ready/failed + // +optional + V2FirstCopyStatus BackingImageState `json:"v2FirstCopyStatus"` + // +optional + V2FirstCopyDisk string `json:"v2FirstCopyDisk"` } // +genclient diff --git a/k8s/pkg/apis/longhorn/v1beta2/backingimagedatasource.go b/k8s/pkg/apis/longhorn/v1beta2/backingimagedatasource.go index a592795234..8a13fc8fac 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/backingimagedatasource.go +++ b/k8s/pkg/apis/longhorn/v1beta2/backingimagedatasource.go @@ -30,6 +30,7 @@ const ( DataSourceTypeCloneParameterEncryption = "encryption" DataSourceTypeCloneParameterSecret = "secret" DataSourceTypeCloneParameterSecretNamespace = "secret-namespace" + DataSourceTypeParameterDataEngine = "data-engine" ) // BackingImageDataSourceSpec defines the desired state of the Longhorn backing image data source diff --git a/k8s/pkg/apis/longhorn/v1beta2/backingimagemanager.go b/k8s/pkg/apis/longhorn/v1beta2/backingimagemanager.go index 7d0ed0aaeb..6c69a0b14e 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/backingimagemanager.go +++ b/k8s/pkg/apis/longhorn/v1beta2/backingimagemanager.go @@ -12,6 +12,25 @@ const ( BackingImageManagerStateUnknown = BackingImageManagerState("unknown") ) +type BackingImageV2CopyInfo struct { + // +optional + Name string `json:"name"` + // +optional + UUID string `json:"uuid"` + // +optional + DiskUUID string `json:"diskUUID"` + // +optional + Size int64 `json:"size"` + // +optional + Progress int `json:"progress"` + // +optional + State BackingImageState `json:"state"` + // +optional + CurrentChecksum string `json:"currentChecksum"` + // +optional + Message string `json:"message"` +} + type BackingImageFileInfo struct { // +optional Name string `json:"name"` diff --git a/k8s/pkg/apis/longhorn/v1beta2/instancemanager.go b/k8s/pkg/apis/longhorn/v1beta2/instancemanager.go index bcb6ddf041..1894cf391c 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/instancemanager.go +++ b/k8s/pkg/apis/longhorn/v1beta2/instancemanager.go @@ -192,6 +192,8 @@ type InstanceManagerStatus struct { // +nullable InstanceReplicas map[string]InstanceProcess `json:"instanceReplicas,omitempty"` // +optional + V2BackingImages map[string]BackingImageV2CopyInfo `json:"v2BackingImages"` + // +optional IP string `json:"ip"` // +optional APIMinVersion int `json:"apiMinVersion"` diff --git a/k8s/pkg/apis/longhorn/v1beta2/zz_generated.deepcopy.go b/k8s/pkg/apis/longhorn/v1beta2/zz_generated.deepcopy.go index 83ae61fbdb..f4bdf33382 100644 --- a/k8s/pkg/apis/longhorn/v1beta2/zz_generated.deepcopy.go +++ b/k8s/pkg/apis/longhorn/v1beta2/zz_generated.deepcopy.go @@ -485,6 +485,22 @@ func (in *BackingImageStatus) DeepCopy() *BackingImageStatus { return out } +// DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. +func (in *BackingImageV2CopyInfo) DeepCopyInto(out *BackingImageV2CopyInfo) { + *out = *in + return +} + +// DeepCopy is an autogenerated deepcopy function, copying the receiver, creating a new BackingImageV2CopyInfo. +func (in *BackingImageV2CopyInfo) DeepCopy() *BackingImageV2CopyInfo { + if in == nil { + return nil + } + out := new(BackingImageV2CopyInfo) + in.DeepCopyInto(out) + return out +} + // DeepCopyInto is an autogenerated deepcopy function, copying the receiver, writing into out. in must be non-nil. func (in *Backup) DeepCopyInto(out *Backup) { *out = *in @@ -1503,6 +1519,13 @@ func (in *InstanceManagerStatus) DeepCopyInto(out *InstanceManagerStatus) { (*out)[key] = *val.DeepCopy() } } + if in.V2BackingImages != nil { + in, out := &in.V2BackingImages, &out.V2BackingImages + *out = make(map[string]BackingImageV2CopyInfo, len(*in)) + for key, val := range *in { + (*out)[key] = val + } + } out.DataEngineStatus = in.DataEngineStatus if in.Instances != nil { in, out := &in.Instances, &out.Instances diff --git a/manager/backingimage.go b/manager/backingimage.go index 58cab22d5e..873304c7fb 100644 --- a/manager/backingimage.go +++ b/manager/backingimage.go @@ -86,7 +86,7 @@ func (m *VolumeManager) GetDefaultBackingImageManagersByDiskUUID(diskUUID string return nil, fmt.Errorf("default backing image manager for disk %v is not found", diskUUID) } -func (m *VolumeManager) CreateBackingImage(name, checksum, sourceType string, parameters map[string]string, minNumberOfCopies int, nodeSelector, diskSelector []string, secret, secretNamespace string) (bi *longhorn.BackingImage, err error) { +func (m *VolumeManager) CreateBackingImage(name, checksum, sourceType string, parameters map[string]string, minNumberOfCopies int, nodeSelector, diskSelector []string, secret, secretNamespace string, DataEngine string) (bi *longhorn.BackingImage, err error) { if secret != "" || secretNamespace != "" { _, err := m.ds.GetSecretRO(secretNamespace, secret) if err != nil { @@ -94,6 +94,10 @@ func (m *VolumeManager) CreateBackingImage(name, checksum, sourceType string, pa } } + if DataEngine == "" { + DataEngine = string(longhorn.DataEngineTypeV1) + } + bi = &longhorn.BackingImage{ ObjectMeta: metav1.ObjectMeta{ Name: name, @@ -110,6 +114,7 @@ func (m *VolumeManager) CreateBackingImage(name, checksum, sourceType string, pa DiskSelector: diskSelector, Secret: secret, SecretNamespace: secretNamespace, + DataEngine: longhorn.DataEngineType(DataEngine), }, } if bi, err = m.ds.CreateBackingImage(bi); err != nil { diff --git a/scripts/build b/scripts/build index f7b7091ab9..3ce7a92491 100755 --- a/scripts/build +++ b/scripts/build @@ -21,4 +21,4 @@ cd $(dirname $0)/.. mkdir -p bin CGO_ENABLED=0 GOARCH=amd64 go build -o bin/longhorn-manager-amd64 -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" $COVER $COVERPKG -CGO_ENABLED=0 GOARCH=arm64 go build -o bin/longhorn-manager-arm64 -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" $COVER $COVERPKG +# CGO_ENABLED=0 GOARCH=arm64 go build -o bin/longhorn-manager-arm64 -ldflags "$LINKFLAGS $OTHER_LINKFLAGS" $COVER $COVERPKG diff --git a/types/types.go b/types/types.go index e3adfd8430..2713f6248a 100644 --- a/types/types.go +++ b/types/types.go @@ -753,6 +753,10 @@ func GetReplicaMountedDataPath(dataPath string) string { return dataPath } +func ErrorRecordNotFoundButLvolFound(err error) bool { + return strings.Contains(err.Error(), "lvol found") +} + func ErrorIsNotFound(err error) bool { return strings.Contains(err.Error(), "cannot find") } @@ -1262,3 +1266,11 @@ func MergeStringMaps(baseMap, overwriteMap map[string]string) map[string]string } return result } + +func GetBackingImageMonitorName(imName string) string { + return fmt.Sprintf("backing-image-monitor-%s", imName) +} + +func GetV2BackingImageWithDiskUUIDName(biName, v2DiskUUID string) string { + return fmt.Sprintf("%v-%v", biName, v2DiskUUID) +} diff --git a/upgrade/v17xto180/upgrade.go b/upgrade/v17xto180/upgrade.go index 2948aa8f5c..1072bb7690 100644 --- a/upgrade/v17xto180/upgrade.go +++ b/upgrade/v17xto180/upgrade.go @@ -8,6 +8,7 @@ import ( "github.com/longhorn/longhorn-manager/types" + longhorn "github.com/longhorn/longhorn-manager/k8s/pkg/apis/longhorn/v1beta2" lhclientset "github.com/longhorn/longhorn-manager/k8s/pkg/client/clientset/versioned" upgradeutil "github.com/longhorn/longhorn-manager/upgrade/util" ) @@ -37,6 +38,23 @@ func upgradeBackingImages(namespace string, lhClient *lhclientset.Clientset, res if bi.Spec.MinNumberOfCopies == 0 { bi.Spec.MinNumberOfCopies = types.DefaultMinNumberOfCopies } + if string(bi.Spec.DataEngine) == "" { + bi.Spec.DataEngine = longhorn.DataEngineTypeV1 + } + + // before v1.8.0, there should not have any v2 data engine disk in the backing image. + for bi.Spec.DiskFileSpecMap != nil { + for diskUUID := range bi.Spec.DiskFileSpecMap { + bi.Spec.DiskFileSpecMap[diskUUID].DataEngine = longhorn.DataEngineTypeV1 + } + } + + // before v1.8.0, there should not have any v2 data engine disk in the backing image. + for bi.Status.DiskFileStatusMap != nil { + for diskUUID := range bi.Status.DiskFileStatusMap { + bi.Status.DiskFileStatusMap[diskUUID].DataEngine = longhorn.DataEngineTypeV1 + } + } } return nil diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go b/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go deleted file mode 100644 index 147f756fe2..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/dbus.go +++ /dev/null @@ -1,266 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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. - -// Integration with the systemd D-Bus API. See http://www.freedesktop.org/wiki/Software/systemd/dbus/ -package dbus - -import ( - "context" - "encoding/hex" - "fmt" - "os" - "strconv" - "strings" - "sync" - - "github.com/godbus/dbus/v5" -) - -const ( - alpha = `abcdefghijklmnopqrstuvwxyzABCDEFGHIJKLMNOPQRSTUVWXYZ` - num = `0123456789` - alphanum = alpha + num - signalBuffer = 100 -) - -// needsEscape checks whether a byte in a potential dbus ObjectPath needs to be escaped -func needsEscape(i int, b byte) bool { - // Escape everything that is not a-z-A-Z-0-9 - // Also escape 0-9 if it's the first character - return strings.IndexByte(alphanum, b) == -1 || - (i == 0 && strings.IndexByte(num, b) != -1) -} - -// PathBusEscape sanitizes a constituent string of a dbus ObjectPath using the -// rules that systemd uses for serializing special characters. -func PathBusEscape(path string) string { - // Special case the empty string - if len(path) == 0 { - return "_" - } - n := []byte{} - for i := 0; i < len(path); i++ { - c := path[i] - if needsEscape(i, c) { - e := fmt.Sprintf("_%x", c) - n = append(n, []byte(e)...) - } else { - n = append(n, c) - } - } - return string(n) -} - -// pathBusUnescape is the inverse of PathBusEscape. -func pathBusUnescape(path string) string { - if path == "_" { - return "" - } - n := []byte{} - for i := 0; i < len(path); i++ { - c := path[i] - if c == '_' && i+2 < len(path) { - res, err := hex.DecodeString(path[i+1 : i+3]) - if err == nil { - n = append(n, res...) - } - i += 2 - } else { - n = append(n, c) - } - } - return string(n) -} - -// Conn is a connection to systemd's dbus endpoint. -type Conn struct { - // sysconn/sysobj are only used to call dbus methods - sysconn *dbus.Conn - sysobj dbus.BusObject - - // sigconn/sigobj are only used to receive dbus signals - sigconn *dbus.Conn - sigobj dbus.BusObject - - jobListener struct { - jobs map[dbus.ObjectPath]chan<- string - sync.Mutex - } - subStateSubscriber struct { - updateCh chan<- *SubStateUpdate - errCh chan<- error - sync.Mutex - ignore map[dbus.ObjectPath]int64 - cleanIgnore int64 - } - propertiesSubscriber struct { - updateCh chan<- *PropertiesUpdate - errCh chan<- error - sync.Mutex - } -} - -// Deprecated: use NewWithContext instead. -func New() (*Conn, error) { - return NewWithContext(context.Background()) -} - -// NewWithContext establishes a connection to any available bus and authenticates. -// Callers should call Close() when done with the connection. -func NewWithContext(ctx context.Context) (*Conn, error) { - conn, err := NewSystemConnectionContext(ctx) - if err != nil && os.Geteuid() == 0 { - return NewSystemdConnectionContext(ctx) - } - return conn, err -} - -// Deprecated: use NewSystemConnectionContext instead. -func NewSystemConnection() (*Conn, error) { - return NewSystemConnectionContext(context.Background()) -} - -// NewSystemConnectionContext establishes a connection to the system bus and authenticates. -// Callers should call Close() when done with the connection. -func NewSystemConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - return dbusAuthHelloConnection(ctx, dbus.SystemBusPrivate) - }) -} - -// Deprecated: use NewUserConnectionContext instead. -func NewUserConnection() (*Conn, error) { - return NewUserConnectionContext(context.Background()) -} - -// NewUserConnectionContext establishes a connection to the session bus and -// authenticates. This can be used to connect to systemd user instances. -// Callers should call Close() when done with the connection. -func NewUserConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - return dbusAuthHelloConnection(ctx, dbus.SessionBusPrivate) - }) -} - -// Deprecated: use NewSystemdConnectionContext instead. -func NewSystemdConnection() (*Conn, error) { - return NewSystemdConnectionContext(context.Background()) -} - -// NewSystemdConnectionContext establishes a private, direct connection to systemd. -// This can be used for communicating with systemd without a dbus daemon. -// Callers should call Close() when done with the connection. -func NewSystemdConnectionContext(ctx context.Context) (*Conn, error) { - return NewConnection(func() (*dbus.Conn, error) { - // We skip Hello when talking directly to systemd. - return dbusAuthConnection(ctx, func(opts ...dbus.ConnOption) (*dbus.Conn, error) { - return dbus.Dial("unix:path=/run/systemd/private", opts...) - }) - }) -} - -// Close closes an established connection. -func (c *Conn) Close() { - c.sysconn.Close() - c.sigconn.Close() -} - -// Connected returns whether conn is connected -func (c *Conn) Connected() bool { - return c.sysconn.Connected() && c.sigconn.Connected() -} - -// NewConnection establishes a connection to a bus using a caller-supplied function. -// This allows connecting to remote buses through a user-supplied mechanism. -// The supplied function may be called multiple times, and should return independent connections. -// The returned connection must be fully initialised: the org.freedesktop.DBus.Hello call must have succeeded, -// and any authentication should be handled by the function. -func NewConnection(dialBus func() (*dbus.Conn, error)) (*Conn, error) { - sysconn, err := dialBus() - if err != nil { - return nil, err - } - - sigconn, err := dialBus() - if err != nil { - sysconn.Close() - return nil, err - } - - c := &Conn{ - sysconn: sysconn, - sysobj: systemdObject(sysconn), - sigconn: sigconn, - sigobj: systemdObject(sigconn), - } - - c.subStateSubscriber.ignore = make(map[dbus.ObjectPath]int64) - c.jobListener.jobs = make(map[dbus.ObjectPath]chan<- string) - - // Setup the listeners on jobs so that we can get completions - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal', interface='org.freedesktop.systemd1.Manager', member='JobRemoved'") - - c.dispatch() - return c, nil -} - -// GetManagerProperty returns the value of a property on the org.freedesktop.systemd1.Manager -// interface. The value is returned in its string representation, as defined at -// https://developer.gnome.org/glib/unstable/gvariant-text.html. -func (c *Conn) GetManagerProperty(prop string) (string, error) { - variant, err := c.sysobj.GetProperty("org.freedesktop.systemd1.Manager." + prop) - if err != nil { - return "", err - } - return variant.String(), nil -} - -func dbusAuthConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { - conn, err := createBus(dbus.WithContext(ctx)) - if err != nil { - return nil, err - } - - // Only use EXTERNAL method, and hardcode the uid (not username) - // to avoid a username lookup (which requires a dynamically linked - // libc) - methods := []dbus.Auth{dbus.AuthExternal(strconv.Itoa(os.Getuid()))} - - err = conn.Auth(methods) - if err != nil { - conn.Close() - return nil, err - } - - return conn, nil -} - -func dbusAuthHelloConnection(ctx context.Context, createBus func(opts ...dbus.ConnOption) (*dbus.Conn, error)) (*dbus.Conn, error) { - conn, err := dbusAuthConnection(ctx, createBus) - if err != nil { - return nil, err - } - - if err = conn.Hello(); err != nil { - conn.Close() - return nil, err - } - - return conn, nil -} - -func systemdObject(conn *dbus.Conn) dbus.BusObject { - return conn.Object("org.freedesktop.systemd1", dbus.ObjectPath("/org/freedesktop/systemd1")) -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go b/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go deleted file mode 100644 index 074148cb4d..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/methods.go +++ /dev/null @@ -1,864 +0,0 @@ -// Copyright 2015, 2018 CoreOS, Inc. -// -// 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 dbus - -import ( - "context" - "errors" - "fmt" - "path" - "strconv" - - "github.com/godbus/dbus/v5" -) - -// Who can be used to specify which process to kill in the unit via the KillUnitWithTarget API -type Who string - -const ( - // All sends the signal to all processes in the unit - All Who = "all" - // Main sends the signal to the main process of the unit - Main Who = "main" - // Control sends the signal to the control process of the unit - Control Who = "control" -) - -func (c *Conn) jobComplete(signal *dbus.Signal) { - var id uint32 - var job dbus.ObjectPath - var unit string - var result string - dbus.Store(signal.Body, &id, &job, &unit, &result) - c.jobListener.Lock() - out, ok := c.jobListener.jobs[job] - if ok { - out <- result - delete(c.jobListener.jobs, job) - } - c.jobListener.Unlock() -} - -func (c *Conn) startJob(ctx context.Context, ch chan<- string, job string, args ...interface{}) (int, error) { - if ch != nil { - c.jobListener.Lock() - defer c.jobListener.Unlock() - } - - var p dbus.ObjectPath - err := c.sysobj.CallWithContext(ctx, job, 0, args...).Store(&p) - if err != nil { - return 0, err - } - - if ch != nil { - c.jobListener.jobs[p] = ch - } - - // ignore error since 0 is fine if conversion fails - jobID, _ := strconv.Atoi(path.Base(string(p))) - - return jobID, nil -} - -// Deprecated: use StartUnitContext instead. -func (c *Conn) StartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.StartUnitContext(context.Background(), name, mode, ch) -} - -// StartUnitContext enqueues a start job and depending jobs, if any (unless otherwise -// specified by the mode string). -// -// Takes the unit to activate, plus a mode string. The mode needs to be one of -// replace, fail, isolate, ignore-dependencies, ignore-requirements. If -// "replace" the call will start the unit and its dependencies, possibly -// replacing already queued jobs that conflict with this. If "fail" the call -// will start the unit and its dependencies, but will fail if this would change -// an already queued job. If "isolate" the call will start the unit in question -// and terminate all units that aren't dependencies of it. If -// "ignore-dependencies" it will start a unit but ignore all its dependencies. -// If "ignore-requirements" it will start a unit but only ignore the -// requirement dependencies. It is not recommended to make use of the latter -// two options. -// -// If the provided channel is non-nil, a result string will be sent to it upon -// job completion: one of done, canceled, timeout, failed, dependency, skipped. -// done indicates successful execution of a job. canceled indicates that a job -// has been canceled before it finished execution. timeout indicates that the -// job timeout was reached. failed indicates that the job failed. dependency -// indicates that a job this job has been depending on failed and the job hence -// has been removed too. skipped indicates that a job was skipped because it -// didn't apply to the units current state. -// -// If no error occurs, the ID of the underlying systemd job will be returned. There -// does exist the possibility for no error to be returned, but for the returned job -// ID to be 0. In this case, the actual underlying ID is not 0 and this datapoint -// should not be considered authoritative. -// -// If an error does occur, it will be returned to the user alongside a job ID of 0. -func (c *Conn) StartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartUnit", name, mode) -} - -// Deprecated: use StopUnitContext instead. -func (c *Conn) StopUnit(name string, mode string, ch chan<- string) (int, error) { - return c.StopUnitContext(context.Background(), name, mode, ch) -} - -// StopUnitContext is similar to StartUnitContext, but stops the specified unit -// rather than starting it. -func (c *Conn) StopUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StopUnit", name, mode) -} - -// Deprecated: use ReloadUnitContext instead. -func (c *Conn) ReloadUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadUnitContext(context.Background(), name, mode, ch) -} - -// ReloadUnitContext reloads a unit. Reloading is done only if the unit -// is already running, and fails otherwise. -func (c *Conn) ReloadUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadUnit", name, mode) -} - -// Deprecated: use RestartUnitContext instead. -func (c *Conn) RestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.RestartUnitContext(context.Background(), name, mode, ch) -} - -// RestartUnitContext restarts a service. If a service is restarted that isn't -// running it will be started. -func (c *Conn) RestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.RestartUnit", name, mode) -} - -// Deprecated: use TryRestartUnitContext instead. -func (c *Conn) TryRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.TryRestartUnitContext(context.Background(), name, mode, ch) -} - -// TryRestartUnitContext is like RestartUnitContext, except that a service that -// isn't running is not affected by the restart. -func (c *Conn) TryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.TryRestartUnit", name, mode) -} - -// Deprecated: use ReloadOrRestartUnitContext instead. -func (c *Conn) ReloadOrRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadOrRestartUnitContext(context.Background(), name, mode, ch) -} - -// ReloadOrRestartUnitContext attempts a reload if the unit supports it and use -// a restart otherwise. -func (c *Conn) ReloadOrRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrRestartUnit", name, mode) -} - -// Deprecated: use ReloadOrTryRestartUnitContext instead. -func (c *Conn) ReloadOrTryRestartUnit(name string, mode string, ch chan<- string) (int, error) { - return c.ReloadOrTryRestartUnitContext(context.Background(), name, mode, ch) -} - -// ReloadOrTryRestartUnitContext attempts a reload if the unit supports it, -// and use a "Try" flavored restart otherwise. -func (c *Conn) ReloadOrTryRestartUnitContext(ctx context.Context, name string, mode string, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.ReloadOrTryRestartUnit", name, mode) -} - -// Deprecated: use StartTransientUnitContext instead. -func (c *Conn) StartTransientUnit(name string, mode string, properties []Property, ch chan<- string) (int, error) { - return c.StartTransientUnitContext(context.Background(), name, mode, properties, ch) -} - -// StartTransientUnitContext may be used to create and start a transient unit, which -// will be released as soon as it is not running or referenced anymore or the -// system is rebooted. name is the unit name including suffix, and must be -// unique. mode is the same as in StartUnitContext, properties contains properties -// of the unit. -func (c *Conn) StartTransientUnitContext(ctx context.Context, name string, mode string, properties []Property, ch chan<- string) (int, error) { - return c.startJob(ctx, ch, "org.freedesktop.systemd1.Manager.StartTransientUnit", name, mode, properties, make([]PropertyCollection, 0)) -} - -// Deprecated: use KillUnitContext instead. -func (c *Conn) KillUnit(name string, signal int32) { - c.KillUnitContext(context.Background(), name, signal) -} - -// KillUnitContext takes the unit name and a UNIX signal number to send. -// All of the unit's processes are killed. -func (c *Conn) KillUnitContext(ctx context.Context, name string, signal int32) { - c.KillUnitWithTarget(ctx, name, All, signal) -} - -// KillUnitWithTarget is like KillUnitContext, but allows you to specify which -// process in the unit to send the signal to. -func (c *Conn) KillUnitWithTarget(ctx context.Context, name string, target Who, signal int32) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.KillUnit", 0, name, string(target), signal).Store() -} - -// Deprecated: use ResetFailedUnitContext instead. -func (c *Conn) ResetFailedUnit(name string) error { - return c.ResetFailedUnitContext(context.Background(), name) -} - -// ResetFailedUnitContext resets the "failed" state of a specific unit. -func (c *Conn) ResetFailedUnitContext(ctx context.Context, name string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ResetFailedUnit", 0, name).Store() -} - -// Deprecated: use SystemStateContext instead. -func (c *Conn) SystemState() (*Property, error) { - return c.SystemStateContext(context.Background()) -} - -// SystemStateContext returns the systemd state. Equivalent to -// systemctl is-system-running. -func (c *Conn) SystemStateContext(ctx context.Context) (*Property, error) { - var err error - var prop dbus.Variant - - obj := c.sysconn.Object("org.freedesktop.systemd1", "/org/freedesktop/systemd1") - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, "org.freedesktop.systemd1.Manager", "SystemState").Store(&prop) - if err != nil { - return nil, err - } - - return &Property{Name: "SystemState", Value: prop}, nil -} - -// getProperties takes the unit path and returns all of its dbus object properties, for the given dbus interface. -func (c *Conn) getProperties(ctx context.Context, path dbus.ObjectPath, dbusInterface string) (map[string]interface{}, error) { - var err error - var props map[string]dbus.Variant - - if !path.IsValid() { - return nil, fmt.Errorf("invalid unit name: %v", path) - } - - obj := c.sysconn.Object("org.freedesktop.systemd1", path) - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.GetAll", 0, dbusInterface).Store(&props) - if err != nil { - return nil, err - } - - out := make(map[string]interface{}, len(props)) - for k, v := range props { - out[k] = v.Value() - } - - return out, nil -} - -// Deprecated: use GetUnitPropertiesContext instead. -func (c *Conn) GetUnitProperties(unit string) (map[string]interface{}, error) { - return c.GetUnitPropertiesContext(context.Background(), unit) -} - -// GetUnitPropertiesContext takes the (unescaped) unit name and returns all of -// its dbus object properties. -func (c *Conn) GetUnitPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") -} - -// Deprecated: use GetUnitPathPropertiesContext instead. -func (c *Conn) GetUnitPathProperties(path dbus.ObjectPath) (map[string]interface{}, error) { - return c.GetUnitPathPropertiesContext(context.Background(), path) -} - -// GetUnitPathPropertiesContext takes the (escaped) unit path and returns all -// of its dbus object properties. -func (c *Conn) GetUnitPathPropertiesContext(ctx context.Context, path dbus.ObjectPath) (map[string]interface{}, error) { - return c.getProperties(ctx, path, "org.freedesktop.systemd1.Unit") -} - -// Deprecated: use GetAllPropertiesContext instead. -func (c *Conn) GetAllProperties(unit string) (map[string]interface{}, error) { - return c.GetAllPropertiesContext(context.Background(), unit) -} - -// GetAllPropertiesContext takes the (unescaped) unit name and returns all of -// its dbus object properties. -func (c *Conn) GetAllPropertiesContext(ctx context.Context, unit string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "") -} - -func (c *Conn) getProperty(ctx context.Context, unit string, dbusInterface string, propertyName string) (*Property, error) { - var err error - var prop dbus.Variant - - path := unitPath(unit) - if !path.IsValid() { - return nil, errors.New("invalid unit name: " + unit) - } - - obj := c.sysconn.Object("org.freedesktop.systemd1", path) - err = obj.CallWithContext(ctx, "org.freedesktop.DBus.Properties.Get", 0, dbusInterface, propertyName).Store(&prop) - if err != nil { - return nil, err - } - - return &Property{Name: propertyName, Value: prop}, nil -} - -// Deprecated: use GetUnitPropertyContext instead. -func (c *Conn) GetUnitProperty(unit string, propertyName string) (*Property, error) { - return c.GetUnitPropertyContext(context.Background(), unit, propertyName) -} - -// GetUnitPropertyContext takes an (unescaped) unit name, and a property name, -// and returns the property value. -func (c *Conn) GetUnitPropertyContext(ctx context.Context, unit string, propertyName string) (*Property, error) { - return c.getProperty(ctx, unit, "org.freedesktop.systemd1.Unit", propertyName) -} - -// Deprecated: use GetServicePropertyContext instead. -func (c *Conn) GetServiceProperty(service string, propertyName string) (*Property, error) { - return c.GetServicePropertyContext(context.Background(), service, propertyName) -} - -// GetServiceProperty returns property for given service name and property name. -func (c *Conn) GetServicePropertyContext(ctx context.Context, service string, propertyName string) (*Property, error) { - return c.getProperty(ctx, service, "org.freedesktop.systemd1.Service", propertyName) -} - -// Deprecated: use GetUnitTypePropertiesContext instead. -func (c *Conn) GetUnitTypeProperties(unit string, unitType string) (map[string]interface{}, error) { - return c.GetUnitTypePropertiesContext(context.Background(), unit, unitType) -} - -// GetUnitTypePropertiesContext returns the extra properties for a unit, specific to the unit type. -// Valid values for unitType: Service, Socket, Target, Device, Mount, Automount, Snapshot, Timer, Swap, Path, Slice, Scope. -// Returns "dbus.Error: Unknown interface" error if the unitType is not the correct type of the unit. -func (c *Conn) GetUnitTypePropertiesContext(ctx context.Context, unit string, unitType string) (map[string]interface{}, error) { - path := unitPath(unit) - return c.getProperties(ctx, path, "org.freedesktop.systemd1."+unitType) -} - -// Deprecated: use SetUnitPropertiesContext instead. -func (c *Conn) SetUnitProperties(name string, runtime bool, properties ...Property) error { - return c.SetUnitPropertiesContext(context.Background(), name, runtime, properties...) -} - -// SetUnitPropertiesContext may be used to modify certain unit properties at runtime. -// Not all properties may be changed at runtime, but many resource management -// settings (primarily those in systemd.cgroup(5)) may. The changes are applied -// instantly, and stored on disk for future boots, unless runtime is true, in which -// case the settings only apply until the next reboot. name is the name of the unit -// to modify. properties are the settings to set, encoded as an array of property -// name and value pairs. -func (c *Conn) SetUnitPropertiesContext(ctx context.Context, name string, runtime bool, properties ...Property) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.SetUnitProperties", 0, name, runtime, properties).Store() -} - -// Deprecated: use GetUnitTypePropertyContext instead. -func (c *Conn) GetUnitTypeProperty(unit string, unitType string, propertyName string) (*Property, error) { - return c.GetUnitTypePropertyContext(context.Background(), unit, unitType, propertyName) -} - -// GetUnitTypePropertyContext takes a property name, a unit name, and a unit type, -// and returns a property value. For valid values of unitType, see GetUnitTypePropertiesContext. -func (c *Conn) GetUnitTypePropertyContext(ctx context.Context, unit string, unitType string, propertyName string) (*Property, error) { - return c.getProperty(ctx, unit, "org.freedesktop.systemd1."+unitType, propertyName) -} - -type UnitStatus struct { - Name string // The primary unit name as string - Description string // The human readable description string - LoadState string // The load state (i.e. whether the unit file has been loaded successfully) - ActiveState string // The active state (i.e. whether the unit is currently started or not) - SubState string // The sub state (a more fine-grained version of the active state that is specific to the unit type, which the active state is not) - Followed string // A unit that is being followed in its state by this unit, if there is any, otherwise the empty string. - Path dbus.ObjectPath // The unit object path - JobId uint32 // If there is a job queued for the job unit the numeric job id, 0 otherwise - JobType string // The job type as string - JobPath dbus.ObjectPath // The job object path -} - -type storeFunc func(retvalues ...interface{}) error - -func (c *Conn) listUnitsInternal(f storeFunc) ([]UnitStatus, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - status := make([]UnitStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } - - err = dbus.Store(resultInterface, statusInterface...) - if err != nil { - return nil, err - } - - return status, nil -} - -// GetUnitByPID returns the unit object path of the unit a process ID -// belongs to. It takes a UNIX PID and returns the object path. The PID must -// refer to an existing system process -func (c *Conn) GetUnitByPID(ctx context.Context, pid uint32) (dbus.ObjectPath, error) { - var result dbus.ObjectPath - - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.GetUnitByPID", 0, pid).Store(&result) - - return result, err -} - -// GetUnitNameByPID returns the name of the unit a process ID belongs to. It -// takes a UNIX PID and returns the object path. The PID must refer to an -// existing system process -func (c *Conn) GetUnitNameByPID(ctx context.Context, pid uint32) (string, error) { - path, err := c.GetUnitByPID(ctx, pid) - if err != nil { - return "", err - } - - return unitName(path), nil -} - -// Deprecated: use ListUnitsContext instead. -func (c *Conn) ListUnits() ([]UnitStatus, error) { - return c.ListUnitsContext(context.Background()) -} - -// ListUnitsContext returns an array with all currently loaded units. Note that -// units may be known by multiple names at the same time, and hence there might -// be more unit names loaded than actual units behind them. -// Also note that a unit is only loaded if it is active and/or enabled. -// Units that are both disabled and inactive will thus not be returned. -func (c *Conn) ListUnitsContext(ctx context.Context) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnits", 0).Store) -} - -// Deprecated: use ListUnitsFilteredContext instead. -func (c *Conn) ListUnitsFiltered(states []string) ([]UnitStatus, error) { - return c.ListUnitsFilteredContext(context.Background(), states) -} - -// ListUnitsFilteredContext returns an array with units filtered by state. -// It takes a list of units' statuses to filter. -func (c *Conn) ListUnitsFilteredContext(ctx context.Context, states []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsFiltered", 0, states).Store) -} - -// Deprecated: use ListUnitsByPatternsContext instead. -func (c *Conn) ListUnitsByPatterns(states []string, patterns []string) ([]UnitStatus, error) { - return c.ListUnitsByPatternsContext(context.Background(), states, patterns) -} - -// ListUnitsByPatternsContext returns an array with units. -// It takes a list of units' statuses and names to filter. -// Note that units may be known by multiple names at the same time, -// and hence there might be more unit names loaded than actual units behind them. -func (c *Conn) ListUnitsByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByPatterns", 0, states, patterns).Store) -} - -// Deprecated: use ListUnitsByNamesContext instead. -func (c *Conn) ListUnitsByNames(units []string) ([]UnitStatus, error) { - return c.ListUnitsByNamesContext(context.Background(), units) -} - -// ListUnitsByNamesContext returns an array with units. It takes a list of units' -// names and returns an UnitStatus array. Comparing to ListUnitsByPatternsContext -// method, this method returns statuses even for inactive or non-existing -// units. Input array should contain exact unit names, but not patterns. -// -// Requires systemd v230 or higher. -func (c *Conn) ListUnitsByNamesContext(ctx context.Context, units []string) ([]UnitStatus, error) { - return c.listUnitsInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitsByNames", 0, units).Store) -} - -type UnitFile struct { - Path string - Type string -} - -func (c *Conn) listUnitFilesInternal(f storeFunc) ([]UnitFile, error) { - result := make([][]interface{}, 0) - err := f(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - files := make([]UnitFile, len(result)) - fileInterface := make([]interface{}, len(files)) - for i := range files { - fileInterface[i] = &files[i] - } - - err = dbus.Store(resultInterface, fileInterface...) - if err != nil { - return nil, err - } - - return files, nil -} - -// Deprecated: use ListUnitFilesContext instead. -func (c *Conn) ListUnitFiles() ([]UnitFile, error) { - return c.ListUnitFilesContext(context.Background()) -} - -// ListUnitFiles returns an array of all available units on disk. -func (c *Conn) ListUnitFilesContext(ctx context.Context) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFiles", 0).Store) -} - -// Deprecated: use ListUnitFilesByPatternsContext instead. -func (c *Conn) ListUnitFilesByPatterns(states []string, patterns []string) ([]UnitFile, error) { - return c.ListUnitFilesByPatternsContext(context.Background(), states, patterns) -} - -// ListUnitFilesByPatternsContext returns an array of all available units on disk matched the patterns. -func (c *Conn) ListUnitFilesByPatternsContext(ctx context.Context, states []string, patterns []string) ([]UnitFile, error) { - return c.listUnitFilesInternal(c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListUnitFilesByPatterns", 0, states, patterns).Store) -} - -type LinkUnitFileChange EnableUnitFileChange - -// Deprecated: use LinkUnitFilesContext instead. -func (c *Conn) LinkUnitFiles(files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - return c.LinkUnitFilesContext(context.Background(), files, runtime, force) -} - -// LinkUnitFilesContext links unit files (that are located outside of the -// usual unit search paths) into the unit search path. -// -// It takes a list of absolute paths to unit files to link and two -// booleans. -// -// The first boolean controls whether the unit shall be -// enabled for runtime only (true, /run), or persistently (false, -// /etc). -// -// The second controls whether symlinks pointing to other units shall -// be replaced if necessary. -// -// This call returns a list of the changes made. The list consists of -// structures with three strings: the type of the change (one of symlink -// or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) LinkUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]LinkUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.LinkUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]LinkUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -// Deprecated: use EnableUnitFilesContext instead. -func (c *Conn) EnableUnitFiles(files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { - return c.EnableUnitFilesContext(context.Background(), files, runtime, force) -} - -// EnableUnitFilesContext may be used to enable one or more units in the system -// (by creating symlinks to them in /etc or /run). -// -// It takes a list of unit files to enable (either just file names or full -// absolute paths if the unit files are residing outside the usual unit -// search paths), and two booleans: the first controls whether the unit shall -// be enabled for runtime only (true, /run), or persistently (false, /etc). -// The second one controls whether symlinks pointing to other units shall -// be replaced if necessary. -// -// This call returns one boolean and an array with the changes made. The -// boolean signals whether the unit files contained any enablement -// information (i.e. an [Install]) section. The changes list consists of -// structures with three strings: the type of the change (one of symlink -// or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) EnableUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) (bool, []EnableUnitFileChange, error) { - var carries_install_info bool - - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.EnableUnitFiles", 0, files, runtime, force).Store(&carries_install_info, &result) - if err != nil { - return false, nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]EnableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return false, nil, err - } - - return carries_install_info, changes, nil -} - -type EnableUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use DisableUnitFilesContext instead. -func (c *Conn) DisableUnitFiles(files []string, runtime bool) ([]DisableUnitFileChange, error) { - return c.DisableUnitFilesContext(context.Background(), files, runtime) -} - -// DisableUnitFilesContext may be used to disable one or more units in the -// system (by removing symlinks to them from /etc or /run). -// -// It takes a list of unit files to disable (either just file names or full -// absolute paths if the unit files are residing outside the usual unit -// search paths), and one boolean: whether the unit was enabled for runtime -// only (true, /run), or persistently (false, /etc). -// -// This call returns an array with the changes made. The changes list -// consists of structures with three strings: the type of the change (one of -// symlink or unlink), the file name of the symlink and the destination of the -// symlink. -func (c *Conn) DisableUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]DisableUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.DisableUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]DisableUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type DisableUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use MaskUnitFilesContext instead. -func (c *Conn) MaskUnitFiles(files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - return c.MaskUnitFilesContext(context.Background(), files, runtime, force) -} - -// MaskUnitFilesContext masks one or more units in the system. -// -// The files argument contains a list of units to mask (either just file names -// or full absolute paths if the unit files are residing outside the usual unit -// search paths). -// -// The runtime argument is used to specify whether the unit was enabled for -// runtime only (true, /run/systemd/..), or persistently (false, -// /etc/systemd/..). -func (c *Conn) MaskUnitFilesContext(ctx context.Context, files []string, runtime bool, force bool) ([]MaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.MaskUnitFiles", 0, files, runtime, force).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]MaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type MaskUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use UnmaskUnitFilesContext instead. -func (c *Conn) UnmaskUnitFiles(files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - return c.UnmaskUnitFilesContext(context.Background(), files, runtime) -} - -// UnmaskUnitFilesContext unmasks one or more units in the system. -// -// It takes the list of unit files to mask (either just file names or full -// absolute paths if the unit files are residing outside the usual unit search -// paths), and a boolean runtime flag to specify whether the unit was enabled -// for runtime only (true, /run/systemd/..), or persistently (false, -// /etc/systemd/..). -func (c *Conn) UnmaskUnitFilesContext(ctx context.Context, files []string, runtime bool) ([]UnmaskUnitFileChange, error) { - result := make([][]interface{}, 0) - err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.UnmaskUnitFiles", 0, files, runtime).Store(&result) - if err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - changes := make([]UnmaskUnitFileChange, len(result)) - changesInterface := make([]interface{}, len(changes)) - for i := range changes { - changesInterface[i] = &changes[i] - } - - err = dbus.Store(resultInterface, changesInterface...) - if err != nil { - return nil, err - } - - return changes, nil -} - -type UnmaskUnitFileChange struct { - Type string // Type of the change (one of symlink or unlink) - Filename string // File name of the symlink - Destination string // Destination of the symlink -} - -// Deprecated: use ReloadContext instead. -func (c *Conn) Reload() error { - return c.ReloadContext(context.Background()) -} - -// ReloadContext instructs systemd to scan for and reload unit files. This is -// an equivalent to systemctl daemon-reload. -func (c *Conn) ReloadContext(ctx context.Context) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.Reload", 0).Store() -} - -func unitPath(name string) dbus.ObjectPath { - return dbus.ObjectPath("/org/freedesktop/systemd1/unit/" + PathBusEscape(name)) -} - -// unitName returns the unescaped base element of the supplied escaped path. -func unitName(dpath dbus.ObjectPath) string { - return pathBusUnescape(path.Base(string(dpath))) -} - -// JobStatus holds a currently queued job definition. -type JobStatus struct { - Id uint32 // The numeric job id - Unit string // The primary unit name for this job - JobType string // The job type as string - Status string // The job state as string - JobPath dbus.ObjectPath // The job object path - UnitPath dbus.ObjectPath // The unit object path -} - -// Deprecated: use ListJobsContext instead. -func (c *Conn) ListJobs() ([]JobStatus, error) { - return c.ListJobsContext(context.Background()) -} - -// ListJobsContext returns an array with all currently queued jobs. -func (c *Conn) ListJobsContext(ctx context.Context) ([]JobStatus, error) { - return c.listJobsInternal(ctx) -} - -func (c *Conn) listJobsInternal(ctx context.Context) ([]JobStatus, error) { - result := make([][]interface{}, 0) - if err := c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ListJobs", 0).Store(&result); err != nil { - return nil, err - } - - resultInterface := make([]interface{}, len(result)) - for i := range result { - resultInterface[i] = result[i] - } - - status := make([]JobStatus, len(result)) - statusInterface := make([]interface{}, len(status)) - for i := range status { - statusInterface[i] = &status[i] - } - - if err := dbus.Store(resultInterface, statusInterface...); err != nil { - return nil, err - } - - return status, nil -} - -// Freeze the cgroup associated with the unit. -// Note that FreezeUnit and ThawUnit are only supported on systems running with cgroup v2. -func (c *Conn) FreezeUnit(ctx context.Context, unit string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.FreezeUnit", 0, unit).Store() -} - -// Unfreeze the cgroup associated with the unit. -func (c *Conn) ThawUnit(ctx context.Context, unit string) error { - return c.sysobj.CallWithContext(ctx, "org.freedesktop.systemd1.Manager.ThawUnit", 0, unit).Store() -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go b/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go deleted file mode 100644 index fb42b62733..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/properties.go +++ /dev/null @@ -1,237 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 dbus - -import ( - "github.com/godbus/dbus/v5" -) - -// From the systemd docs: -// -// The properties array of StartTransientUnit() may take many of the settings -// that may also be configured in unit files. Not all parameters are currently -// accepted though, but we plan to cover more properties with future release. -// Currently you may set the Description, Slice and all dependency types of -// units, as well as RemainAfterExit, ExecStart for service units, -// TimeoutStopUSec and PIDs for scope units, and CPUAccounting, CPUShares, -// BlockIOAccounting, BlockIOWeight, BlockIOReadBandwidth, -// BlockIOWriteBandwidth, BlockIODeviceWeight, MemoryAccounting, MemoryLimit, -// DevicePolicy, DeviceAllow for services/scopes/slices. These fields map -// directly to their counterparts in unit files and as normal D-Bus object -// properties. The exception here is the PIDs field of scope units which is -// used for construction of the scope only and specifies the initial PIDs to -// add to the scope object. - -type Property struct { - Name string - Value dbus.Variant -} - -type PropertyCollection struct { - Name string - Properties []Property -} - -type execStart struct { - Path string // the binary path to execute - Args []string // an array with all arguments to pass to the executed command, starting with argument 0 - UncleanIsFailure bool // a boolean whether it should be considered a failure if the process exits uncleanly -} - -// PropExecStart sets the ExecStart service property. The first argument is a -// slice with the binary path to execute followed by the arguments to pass to -// the executed command. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#ExecStart= -func PropExecStart(command []string, uncleanIsFailure bool) Property { - execStarts := []execStart{ - { - Path: command[0], - Args: command, - UncleanIsFailure: uncleanIsFailure, - }, - } - - return Property{ - Name: "ExecStart", - Value: dbus.MakeVariant(execStarts), - } -} - -// PropRemainAfterExit sets the RemainAfterExit service property. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#RemainAfterExit= -func PropRemainAfterExit(b bool) Property { - return Property{ - Name: "RemainAfterExit", - Value: dbus.MakeVariant(b), - } -} - -// PropType sets the Type service property. See -// http://www.freedesktop.org/software/systemd/man/systemd.service.html#Type= -func PropType(t string) Property { - return Property{ - Name: "Type", - Value: dbus.MakeVariant(t), - } -} - -// PropDescription sets the Description unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit#Description= -func PropDescription(desc string) Property { - return Property{ - Name: "Description", - Value: dbus.MakeVariant(desc), - } -} - -func propDependency(name string, units []string) Property { - return Property{ - Name: name, - Value: dbus.MakeVariant(units), - } -} - -// PropRequires sets the Requires unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requires= -func PropRequires(units ...string) Property { - return propDependency("Requires", units) -} - -// PropRequiresOverridable sets the RequiresOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresOverridable= -func PropRequiresOverridable(units ...string) Property { - return propDependency("RequiresOverridable", units) -} - -// PropRequisite sets the Requisite unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Requisite= -func PropRequisite(units ...string) Property { - return propDependency("Requisite", units) -} - -// PropRequisiteOverridable sets the RequisiteOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequisiteOverridable= -func PropRequisiteOverridable(units ...string) Property { - return propDependency("RequisiteOverridable", units) -} - -// PropWants sets the Wants unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Wants= -func PropWants(units ...string) Property { - return propDependency("Wants", units) -} - -// PropBindsTo sets the BindsTo unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#BindsTo= -func PropBindsTo(units ...string) Property { - return propDependency("BindsTo", units) -} - -// PropRequiredBy sets the RequiredBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredBy= -func PropRequiredBy(units ...string) Property { - return propDependency("RequiredBy", units) -} - -// PropRequiredByOverridable sets the RequiredByOverridable unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiredByOverridable= -func PropRequiredByOverridable(units ...string) Property { - return propDependency("RequiredByOverridable", units) -} - -// PropWantedBy sets the WantedBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#WantedBy= -func PropWantedBy(units ...string) Property { - return propDependency("WantedBy", units) -} - -// PropBoundBy sets the BoundBy unit property. See -// http://www.freedesktop.org/software/systemd/main/systemd.unit.html#BoundBy= -func PropBoundBy(units ...string) Property { - return propDependency("BoundBy", units) -} - -// PropConflicts sets the Conflicts unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Conflicts= -func PropConflicts(units ...string) Property { - return propDependency("Conflicts", units) -} - -// PropConflictedBy sets the ConflictedBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#ConflictedBy= -func PropConflictedBy(units ...string) Property { - return propDependency("ConflictedBy", units) -} - -// PropBefore sets the Before unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Before= -func PropBefore(units ...string) Property { - return propDependency("Before", units) -} - -// PropAfter sets the After unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#After= -func PropAfter(units ...string) Property { - return propDependency("After", units) -} - -// PropOnFailure sets the OnFailure unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#OnFailure= -func PropOnFailure(units ...string) Property { - return propDependency("OnFailure", units) -} - -// PropTriggers sets the Triggers unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#Triggers= -func PropTriggers(units ...string) Property { - return propDependency("Triggers", units) -} - -// PropTriggeredBy sets the TriggeredBy unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#TriggeredBy= -func PropTriggeredBy(units ...string) Property { - return propDependency("TriggeredBy", units) -} - -// PropPropagatesReloadTo sets the PropagatesReloadTo unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#PropagatesReloadTo= -func PropPropagatesReloadTo(units ...string) Property { - return propDependency("PropagatesReloadTo", units) -} - -// PropRequiresMountsFor sets the RequiresMountsFor unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.unit.html#RequiresMountsFor= -func PropRequiresMountsFor(units ...string) Property { - return propDependency("RequiresMountsFor", units) -} - -// PropSlice sets the Slice unit property. See -// http://www.freedesktop.org/software/systemd/man/systemd.resource-control.html#Slice= -func PropSlice(slice string) Property { - return Property{ - Name: "Slice", - Value: dbus.MakeVariant(slice), - } -} - -// PropPids sets the PIDs field of scope units used in the initial construction -// of the scope only and specifies the initial PIDs to add to the scope object. -// See https://www.freedesktop.org/wiki/Software/systemd/ControlGroupInterface/#properties -func PropPids(pids ...uint32) Property { - return Property{ - Name: "PIDs", - Value: dbus.MakeVariant(pids), - } -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/set.go deleted file mode 100644 index 17c5d48565..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/set.go +++ /dev/null @@ -1,47 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 dbus - -type set struct { - data map[string]bool -} - -func (s *set) Add(value string) { - s.data[value] = true -} - -func (s *set) Remove(value string) { - delete(s.data, value) -} - -func (s *set) Contains(value string) (exists bool) { - _, exists = s.data[value] - return -} - -func (s *set) Length() int { - return len(s.data) -} - -func (s *set) Values() (values []string) { - for val := range s.data { - values = append(values, val) - } - return -} - -func newSet() *set { - return &set{make(map[string]bool)} -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go deleted file mode 100644 index 7e370fea21..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription.go +++ /dev/null @@ -1,333 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 dbus - -import ( - "errors" - "log" - "time" - - "github.com/godbus/dbus/v5" -) - -const ( - cleanIgnoreInterval = int64(10 * time.Second) - ignoreInterval = int64(30 * time.Millisecond) -) - -// Subscribe sets up this connection to subscribe to all systemd dbus events. -// This is required before calling SubscribeUnits. When the connection closes -// systemd will automatically stop sending signals so there is no need to -// explicitly call Unsubscribe(). -func (c *Conn) Subscribe() error { - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal',interface='org.freedesktop.systemd1.Manager',member='UnitNew'") - c.sigconn.BusObject().Call("org.freedesktop.DBus.AddMatch", 0, - "type='signal',interface='org.freedesktop.DBus.Properties',member='PropertiesChanged'") - - return c.sigobj.Call("org.freedesktop.systemd1.Manager.Subscribe", 0).Store() -} - -// Unsubscribe this connection from systemd dbus events. -func (c *Conn) Unsubscribe() error { - return c.sigobj.Call("org.freedesktop.systemd1.Manager.Unsubscribe", 0).Store() -} - -func (c *Conn) dispatch() { - ch := make(chan *dbus.Signal, signalBuffer) - - c.sigconn.Signal(ch) - - go func() { - for { - signal, ok := <-ch - if !ok { - return - } - - if signal.Name == "org.freedesktop.systemd1.Manager.JobRemoved" { - c.jobComplete(signal) - } - - if c.subStateSubscriber.updateCh == nil && - c.propertiesSubscriber.updateCh == nil { - continue - } - - var unitPath dbus.ObjectPath - switch signal.Name { - case "org.freedesktop.systemd1.Manager.JobRemoved": - unitName := signal.Body[2].(string) - c.sysobj.Call("org.freedesktop.systemd1.Manager.GetUnit", 0, unitName).Store(&unitPath) - case "org.freedesktop.systemd1.Manager.UnitNew": - unitPath = signal.Body[1].(dbus.ObjectPath) - case "org.freedesktop.DBus.Properties.PropertiesChanged": - if signal.Body[0].(string) == "org.freedesktop.systemd1.Unit" { - unitPath = signal.Path - - if len(signal.Body) >= 2 { - if changed, ok := signal.Body[1].(map[string]dbus.Variant); ok { - c.sendPropertiesUpdate(unitPath, changed) - } - } - } - } - - if unitPath == dbus.ObjectPath("") { - continue - } - - c.sendSubStateUpdate(unitPath) - } - }() -} - -// SubscribeUnits returns two unbuffered channels which will receive all changed units every -// interval. Deleted units are sent as nil. -func (c *Conn) SubscribeUnits(interval time.Duration) (<-chan map[string]*UnitStatus, <-chan error) { - return c.SubscribeUnitsCustom(interval, 0, func(u1, u2 *UnitStatus) bool { return *u1 != *u2 }, nil) -} - -// SubscribeUnitsCustom is like SubscribeUnits but lets you specify the buffer -// size of the channels, the comparison function for detecting changes and a filter -// function for cutting down on the noise that your channel receives. -func (c *Conn) SubscribeUnitsCustom(interval time.Duration, buffer int, isChanged func(*UnitStatus, *UnitStatus) bool, filterUnit func(string) bool) (<-chan map[string]*UnitStatus, <-chan error) { - old := make(map[string]*UnitStatus) - statusChan := make(chan map[string]*UnitStatus, buffer) - errChan := make(chan error, buffer) - - go func() { - for { - timerChan := time.After(interval) - - units, err := c.ListUnits() - if err == nil { - cur := make(map[string]*UnitStatus) - for i := range units { - if filterUnit != nil && filterUnit(units[i].Name) { - continue - } - cur[units[i].Name] = &units[i] - } - - // add all new or changed units - changed := make(map[string]*UnitStatus) - for n, u := range cur { - if oldU, ok := old[n]; !ok || isChanged(oldU, u) { - changed[n] = u - } - delete(old, n) - } - - // add all deleted units - for oldN := range old { - changed[oldN] = nil - } - - old = cur - - if len(changed) != 0 { - statusChan <- changed - } - } else { - errChan <- err - } - - <-timerChan - } - }() - - return statusChan, errChan -} - -type SubStateUpdate struct { - UnitName string - SubState string -} - -// SetSubStateSubscriber writes to updateCh when any unit's substate changes. -// Although this writes to updateCh on every state change, the reported state -// may be more recent than the change that generated it (due to an unavoidable -// race in the systemd dbus interface). That is, this method provides a good -// way to keep a current view of all units' states, but is not guaranteed to -// show every state transition they go through. Furthermore, state changes -// will only be written to the channel with non-blocking writes. If updateCh -// is full, it attempts to write an error to errCh; if errCh is full, the error -// passes silently. -func (c *Conn) SetSubStateSubscriber(updateCh chan<- *SubStateUpdate, errCh chan<- error) { - if c == nil { - msg := "nil receiver" - select { - case errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } - - c.subStateSubscriber.Lock() - defer c.subStateSubscriber.Unlock() - c.subStateSubscriber.updateCh = updateCh - c.subStateSubscriber.errCh = errCh -} - -func (c *Conn) sendSubStateUpdate(unitPath dbus.ObjectPath) { - c.subStateSubscriber.Lock() - defer c.subStateSubscriber.Unlock() - - if c.subStateSubscriber.updateCh == nil { - return - } - - isIgnored := c.shouldIgnore(unitPath) - defer c.cleanIgnore() - if isIgnored { - return - } - - info, err := c.GetUnitPathProperties(unitPath) - if err != nil { - select { - case c.subStateSubscriber.errCh <- err: - default: - log.Printf("full error channel while reporting: %s\n", err) - } - return - } - defer c.updateIgnore(unitPath, info) - - name, ok := info["Id"].(string) - if !ok { - msg := "failed to cast info.Id" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", err) - } - return - } - substate, ok := info["SubState"].(string) - if !ok { - msg := "failed to cast info.SubState" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } - - update := &SubStateUpdate{name, substate} - select { - case c.subStateSubscriber.updateCh <- update: - default: - msg := "update channel is full" - select { - case c.subStateSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } -} - -// The ignore functions work around a wart in the systemd dbus interface. -// Requesting the properties of an unloaded unit will cause systemd to send a -// pair of UnitNew/UnitRemoved signals. Because we need to get a unit's -// properties on UnitNew (as that's the only indication of a new unit coming up -// for the first time), we would enter an infinite loop if we did not attempt -// to detect and ignore these spurious signals. The signal themselves are -// indistinguishable from relevant ones, so we (somewhat hackishly) ignore an -// unloaded unit's signals for a short time after requesting its properties. -// This means that we will miss e.g. a transient unit being restarted -// *immediately* upon failure and also a transient unit being started -// immediately after requesting its status (with systemctl status, for example, -// because this causes a UnitNew signal to be sent which then causes us to fetch -// the properties). - -func (c *Conn) shouldIgnore(path dbus.ObjectPath) bool { - t, ok := c.subStateSubscriber.ignore[path] - return ok && t >= time.Now().UnixNano() -} - -func (c *Conn) updateIgnore(path dbus.ObjectPath, info map[string]interface{}) { - loadState, ok := info["LoadState"].(string) - if !ok { - return - } - - // unit is unloaded - it will trigger bad systemd dbus behavior - if loadState == "not-found" { - c.subStateSubscriber.ignore[path] = time.Now().UnixNano() + ignoreInterval - } -} - -// without this, ignore would grow unboundedly over time -func (c *Conn) cleanIgnore() { - now := time.Now().UnixNano() - if c.subStateSubscriber.cleanIgnore < now { - c.subStateSubscriber.cleanIgnore = now + cleanIgnoreInterval - - for p, t := range c.subStateSubscriber.ignore { - if t < now { - delete(c.subStateSubscriber.ignore, p) - } - } - } -} - -// PropertiesUpdate holds a map of a unit's changed properties -type PropertiesUpdate struct { - UnitName string - Changed map[string]dbus.Variant -} - -// SetPropertiesSubscriber writes to updateCh when any unit's properties -// change. Every property change reported by systemd will be sent; that is, no -// transitions will be "missed" (as they might be with SetSubStateSubscriber). -// However, state changes will only be written to the channel with non-blocking -// writes. If updateCh is full, it attempts to write an error to errCh; if -// errCh is full, the error passes silently. -func (c *Conn) SetPropertiesSubscriber(updateCh chan<- *PropertiesUpdate, errCh chan<- error) { - c.propertiesSubscriber.Lock() - defer c.propertiesSubscriber.Unlock() - c.propertiesSubscriber.updateCh = updateCh - c.propertiesSubscriber.errCh = errCh -} - -// we don't need to worry about shouldIgnore() here because -// sendPropertiesUpdate doesn't call GetProperties() -func (c *Conn) sendPropertiesUpdate(unitPath dbus.ObjectPath, changedProps map[string]dbus.Variant) { - c.propertiesSubscriber.Lock() - defer c.propertiesSubscriber.Unlock() - - if c.propertiesSubscriber.updateCh == nil { - return - } - - update := &PropertiesUpdate{unitName(unitPath), changedProps} - - select { - case c.propertiesSubscriber.updateCh <- update: - default: - msg := "update channel is full" - select { - case c.propertiesSubscriber.errCh <- errors.New(msg): - default: - log.Printf("full error channel while reporting: %s\n", msg) - } - return - } -} diff --git a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go b/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go deleted file mode 100644 index 5b408d5847..0000000000 --- a/vendor/github.com/coreos/go-systemd/v22/dbus/subscription_set.go +++ /dev/null @@ -1,57 +0,0 @@ -// Copyright 2015 CoreOS, Inc. -// -// 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 dbus - -import ( - "time" -) - -// SubscriptionSet returns a subscription set which is like conn.Subscribe but -// can filter to only return events for a set of units. -type SubscriptionSet struct { - *set - conn *Conn -} - -func (s *SubscriptionSet) filter(unit string) bool { - return !s.Contains(unit) -} - -// Subscribe starts listening for dbus events for all of the units in the set. -// Returns channels identical to conn.SubscribeUnits. -func (s *SubscriptionSet) Subscribe() (<-chan map[string]*UnitStatus, <-chan error) { - // TODO: Make fully evented by using systemd 209 with properties changed values - return s.conn.SubscribeUnitsCustom(time.Second, 0, - mismatchUnitStatus, - func(unit string) bool { return s.filter(unit) }, - ) -} - -// NewSubscriptionSet returns a new subscription set. -func (conn *Conn) NewSubscriptionSet() *SubscriptionSet { - return &SubscriptionSet{newSet(), conn} -} - -// mismatchUnitStatus returns true if the provided UnitStatus objects -// are not equivalent. false is returned if the objects are equivalent. -// Only the Name, Description and state-related fields are used in -// the comparison. -func mismatchUnitStatus(u1, u2 *UnitStatus) bool { - return u1.Name != u2.Name || - u1.Description != u2.Description || - u1.LoadState != u2.LoadState || - u1.ActiveState != u2.ActiveState || - u1.SubState != u2.SubState -} diff --git a/vendor/github.com/gammazero/deque/README.md b/vendor/github.com/gammazero/deque/README.md index eb06369eba..ee2dbb8886 100644 --- a/vendor/github.com/gammazero/deque/README.md +++ b/vendor/github.com/gammazero/deque/README.md @@ -22,9 +22,9 @@ Deque generalizes a queue and a stack, to efficiently add and remove items at ei ## Ring-buffer Performance -This deque implementation is optimized for CPU and GC performance. The circular buffer automatically re-sizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used, and uses bitwise arithmetic for all calculations. Since growth is by powers of two, adding elements will only cause O(log n) allocations. A minimum capacity can be set so that there is no resizing at or below that specified amount. +This deque implementation is optimized for CPU and GC performance. The circular buffer automatically re-sizes by powers of two, growing when additional capacity is needed and shrinking when only a quarter of the capacity is used, and uses bitwise arithmetic for all calculations. Since growth is by powers of two, adding elements will only cause O(log n) allocations. A base capacity can be set, with `SetBaseCap`, so that there is no resizing at or below that specified amount. The Deque can also be grown, using `Grow`, to ensure sufficient storage for n additional items, to prevent resizing when adding a number of itmes. -The ring-buffer implementation improves memory and time performance with fewer GC pauses, compared to implementations based on slices and linked lists. By wrapping around the buffer, previously used space is reused, making allocation unnecessary until all buffer capacity is used. If the deque is only filled and then completely emptied before being filled again, then the ring structure offers little benefit for memory reuse over a slice. +The ring-buffer implementation improves memory and time performance with fewer GC pauses, compared to implementations based on slices or linked lists. By wrapping around the buffer, previously used space is reused, making allocation unnecessary until all buffer capacity is used. The ring buffer implementation performs best when resizes are infrequest, as is the case when items moving in and out of the Deque are balanced or when the base capacity is large enough to rarely require a resize. For maximum speed, this deque implementation leaves concurrency safety up to the application to provide, however the application chooses, if needed at all. @@ -34,9 +34,9 @@ Since it is OK for the deque to contain a `nil` value, it is necessary to either ## Generics -Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type argument to New or with the variable declaration. For example: +Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type argument with the `Deque` variable declaration. For example: ```go - stringDeque := deque.New[string]() + stringDeque := new(deque.Deque[string]) var intDeque deque.Deque[int] ``` diff --git a/vendor/github.com/gammazero/deque/deque.go b/vendor/github.com/gammazero/deque/deque.go index a1743c161a..ff10996205 100644 --- a/vendor/github.com/gammazero/deque/deque.go +++ b/vendor/github.com/gammazero/deque/deque.go @@ -8,59 +8,33 @@ const minCapacity = 16 // Deque represents a single instance of the deque data structure. A Deque // instance contains items of the type specified by the type argument. -type Deque[T any] struct { - buf []T - head int - tail int - count int - minCap int -} - -// New creates a new Deque, optionally setting the current and minimum capacity -// when non-zero values are given for these. The Deque instance returns -// operates on items of the type specified by the type argument. For example, -// to create a Deque that contains strings, // -// stringDeque := deque.New[string]() +// For example, to create a Deque that contains strings do one of the +// following: // -// To create a Deque with capacity to store 2048 ints without resizing, and -// that will not resize below space for 32 items when removing items: +// var stringDeque deque.Deque[string] +// stringDeque := new(deque.Deque[string]) +// stringDeque := &deque.Deque[string]{} // -// d := deque.New[int](2048, 32) +// To create a Deque that will never resize to have space for less than 64 +// items, specify a base capacity: // -// To create a Deque that has not yet allocated memory, but after it does will -// never resize to have space for less than 64 items: +// var d deque.Deque[int] +// d.SetBaseCap(64) // -// d := deque.New[int](0, 64) +// To ensure the Deque can store 1000 items without needing to resize while +// items are added: // -// Any size values supplied here are rounded up to the nearest power of 2. -func New[T any](size ...int) *Deque[T] { - var capacity, minimum int - if len(size) >= 1 { - capacity = size[0] - if len(size) >= 2 { - minimum = size[1] - } - } - - minCap := minCapacity - for minCap < minimum { - minCap <<= 1 - } - - var buf []T - if capacity != 0 { - bufSize := minCap - for bufSize < capacity { - bufSize <<= 1 - } - buf = make([]T, bufSize) - } - - return &Deque[T]{ - buf: buf, - minCap: minCap, - } +// d.Grow(1000) +// +// Any values supplied to SetBaseCap and Grow are rounded up to the nearest +// power of 2, since the Deque grows by powers of 2. +type Deque[T any] struct { + buf []T + head int + tail int + count int + minCap int } // Cap returns the current capacity of the Deque. If q is nil, q.Cap() is zero. @@ -72,7 +46,7 @@ func (q *Deque[T]) Cap() int { } // Len returns the number of elements currently stored in the queue. If q is -// nil, q.Len() is zero. +// nil, q.Len() returns zero. func (q *Deque[T]) Len() int { if q == nil { return 0 @@ -172,9 +146,7 @@ func (q *Deque[T]) Back() T { // and when full the oldest is popped from the other end. All the log entries // in the buffer must be readable without altering the buffer contents. func (q *Deque[T]) At(i int) T { - if i < 0 || i >= q.count { - panic(outOfRangeText(i, q.Len())) - } + q.checkRange(i) // bitwise modulus return q.buf[(q.head+i)&(len(q.buf)-1)] } @@ -183,9 +155,7 @@ func (q *Deque[T]) At(i int) T { // as At but perform the opposite operation. If the index is invalid, the call // panics. func (q *Deque[T]) Set(i int, item T) { - if i < 0 || i >= q.count { - panic(outOfRangeText(i, q.Len())) - } + q.checkRange(i) // bitwise modulus q.buf[(q.head+i)&(len(q.buf)-1)] = item } @@ -207,6 +177,37 @@ func (q *Deque[T]) Clear() { q.count = 0 } +// Grow grows deque's capacity, if necessary, to guarantee space for another n +// items. After Grow(n), at least n items can be written to the deque without +// another allocation. If n is negative, Grow panics. +func (q *Deque[T]) Grow(n int) { + if n < 0 { + panic("deque.Grow: negative count") + } + c := q.Cap() + l := q.Len() + // If already big enough. + if n <= c-l { + return + } + + if c == 0 { + c = minCapacity + } + + newLen := l + n + for c < newLen { + c <<= 1 + } + if l == 0 { + q.buf = make([]T, c) + q.head = 0 + q.tail = 0 + } else { + q.resize(c) + } +} + // Rotate rotates the deque n steps front-to-back. If n is negative, rotates // back-to-front. Having Deque provide Rotate avoids resizing that could happen // if implementing rotation using only Pop and Push methods. If q.Len() is one @@ -288,16 +289,21 @@ func (q *Deque[T]) RIndex(f func(T) bool) int { // Insert is used to insert an element into the middle of the queue, before the // element at the specified index. Insert(0,e) is the same as PushFront(e) and -// Insert(Len(),e) is the same as PushBack(e). Accepts only non-negative index -// values, and panics if index is out of range. +// Insert(Len(),e) is the same as PushBack(e). Out of range indexes result in +// pushing the item onto the front of back of the deque. // // Important: Deque is optimized for O(1) operations at the ends of the queue, // not for operations in the the middle. Complexity of this function is // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Insert(at int, item T) { - if at < 0 || at > q.count { - panic(outOfRangeText(at, q.Len())) + if at <= 0 { + q.PushFront(item) + return + } + if at >= q.Len() { + q.PushBack(item) + return } if at*2 < q.count { q.PushFront(item) @@ -329,10 +335,7 @@ func (q *Deque[T]) Insert(at int, item T) { // constant plus linear in the lesser of the distances between the index and // either of the ends of the queue. func (q *Deque[T]) Remove(at int) T { - if at < 0 || at >= q.Len() { - panic(outOfRangeText(at, q.Len())) - } - + q.checkRange(at) rm := (q.head + at) & (len(q.buf) - 1) if at*2 < q.count { for i := 0; i < at; i++ { @@ -351,18 +354,33 @@ func (q *Deque[T]) Remove(at int) T { return q.PopBack() } -// SetMinCapacity sets a minimum capacity of 2^minCapacityExp. If the value of -// the minimum capacity is less than or equal to the minimum allowed, then -// capacity is set to the minimum allowed. This may be called at anytime to set -// a new minimum capacity. -// -// Setting a larger minimum capacity may be used to prevent resizing when the -// number of stored items changes frequently across a wide range. -func (q *Deque[T]) SetMinCapacity(minCapacityExp uint) { - if 1< minCapacity { - q.minCap = 1 << minCapacityExp - } else { - q.minCap = minCapacity +// SetBaseCap sets a base capacity so that at least the specified number of +// items can always be stored without resizing. +func (q *Deque[T]) SetBaseCap(baseCap int) { + minCap := minCapacity + for minCap < baseCap { + minCap <<= 1 + } + q.minCap = minCap +} + +// Swap exchanges the two values at idxA and idxB. It panics if either index is +// out of range. +func (q *Deque[T]) Swap(idxA, idxB int) { + q.checkRange(idxA) + q.checkRange(idxB) + if idxA == idxB { + return + } + + realA := (q.head + idxA) & (len(q.buf) - 1) + realB := (q.head + idxB) & (len(q.buf) - 1) + q.buf[realA], q.buf[realB] = q.buf[realB], q.buf[realA] +} + +func (q *Deque[T]) checkRange(i int) { + if i < 0 || i >= q.count { + panic(fmt.Sprintf("deque: index out of range %d with length %d", i, q.Len())) } } @@ -388,21 +406,21 @@ func (q *Deque[T]) growIfFull() { q.buf = make([]T, q.minCap) return } - q.resize() + q.resize(q.count << 1) } // shrinkIfExcess resize down if the buffer 1/4 full. func (q *Deque[T]) shrinkIfExcess() { if len(q.buf) > q.minCap && (q.count<<2) == len(q.buf) { - q.resize() + q.resize(q.count << 1) } } // resize resizes the deque to fit exactly twice its current contents. This is // used to grow the queue when it is full, and also to shrink it when it is // only a quarter full. -func (q *Deque[T]) resize() { - newBuf := make([]T, q.count<<1) +func (q *Deque[T]) resize(newSize int) { + newBuf := make([]T, newSize) if q.tail > q.head { copy(newBuf, q.buf[q.head:q.tail]) } else { @@ -414,7 +432,3 @@ func (q *Deque[T]) resize() { q.tail = q.count q.buf = newBuf } - -func outOfRangeText(i, len int) string { - return fmt.Sprintf("deque: index out of range %d with length %d", i, len) -} diff --git a/vendor/github.com/gammazero/deque/doc.go b/vendor/github.com/gammazero/deque/doc.go index 6cfead994b..dfff00adb4 100644 --- a/vendor/github.com/gammazero/deque/doc.go +++ b/vendor/github.com/gammazero/deque/doc.go @@ -33,6 +33,6 @@ Deque.Len() before reading from the deque. Deque uses generics to create a Deque that contains items of the type specified. To create a Deque that holds a specific type, provide a type -argument to New or with the variable declaration. +argument with the Deque variable declaration. */ package deque diff --git a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md b/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md deleted file mode 100644 index c88f9b2bdd..0000000000 --- a/vendor/github.com/godbus/dbus/v5/CONTRIBUTING.md +++ /dev/null @@ -1,50 +0,0 @@ -# How to Contribute - -## Getting Started - -- Fork the repository on GitHub -- Read the [README](README.markdown) for build and test instructions -- Play with the project, submit bugs, submit patches! - -## Contribution Flow - -This is a rough outline of what a contributor's workflow looks like: - -- Create a topic branch from where you want to base your work (usually master). -- Make commits of logical units. -- Make sure your commit messages are in the proper format (see below). -- Push your changes to a topic branch in your fork of the repository. -- Make sure the tests pass, and add any new tests as appropriate. -- Submit a pull request to the original repository. - -Thanks for your contributions! - -### Format of the Commit Message - -We follow a rough convention for commit messages that is designed to answer two -questions: what changed and why. The subject line should feature the what and -the body of the commit should describe the why. - -``` -scripts: add the test-cluster command - -this uses tmux to setup a test cluster that you can easily kill and -start for debugging. - -Fixes #38 -``` - -The format can be described more formally as follows: - -``` -: - - - -