github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/caas/kubernetes/provider/storage.go (about)

     1  // Copyright 2018 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  package provider
     5  
     6  import (
     7  	"fmt"
     8  	"strings"
     9  	"sync"
    10  
    11  	"github.com/juju/errors"
    12  	"github.com/juju/schema"
    13  	core "k8s.io/api/core/v1"
    14  	k8serrors "k8s.io/apimachinery/pkg/api/errors"
    15  	"k8s.io/apimachinery/pkg/apis/meta/v1"
    16  
    17  	"github.com/juju/juju/environs/context"
    18  	"github.com/juju/juju/storage"
    19  )
    20  
    21  const (
    22  	// K8s_ProviderType defines the Juju storage type which can be used
    23  	// to provision storage on k8s models.
    24  	K8s_ProviderType = storage.ProviderType("kubernetes")
    25  
    26  	// K8s storage pool attributes.
    27  	storageClass       = "storage-class"
    28  	storageProvisioner = "storage-provisioner"
    29  	storageLabel       = "storage-label"
    30  
    31  	// K8s storage pool attribute default values.
    32  	defaultStorageClass = "juju-unit-storage"
    33  )
    34  
    35  // StorageProviderTypes is defined on the storage.ProviderRegistry interface.
    36  func (k *kubernetesClient) StorageProviderTypes() ([]storage.ProviderType, error) {
    37  	return []storage.ProviderType{K8s_ProviderType}, nil
    38  }
    39  
    40  // StorageProvider is defined on the storage.ProviderRegistry interface.
    41  func (k *kubernetesClient) StorageProvider(t storage.ProviderType) (storage.Provider, error) {
    42  	if t == K8s_ProviderType {
    43  		return &storageProvider{k}, nil
    44  	}
    45  	return nil, errors.NotFoundf("storage provider %q", t)
    46  }
    47  
    48  type storageProvider struct {
    49  	client *kubernetesClient
    50  }
    51  
    52  var _ storage.Provider = (*storageProvider)(nil)
    53  
    54  var storageConfigFields = schema.Fields{
    55  	storageClass:       schema.String(),
    56  	storageLabel:       schema.String(),
    57  	storageProvisioner: schema.String(),
    58  }
    59  
    60  var storageConfigChecker = schema.FieldMap(
    61  	storageConfigFields,
    62  	schema.Defaults{
    63  		storageClass:       schema.Omit,
    64  		storageLabel:       schema.Omit,
    65  		storageProvisioner: schema.Omit,
    66  	},
    67  )
    68  
    69  type storageConfig struct {
    70  	// storageClass defines a storage class
    71  	// which will be created with the specified
    72  	// provisioner and parameters if it doesn't
    73  	// exist.
    74  	storageClass string
    75  
    76  	// storageProvisioner is the provisioner class to use.
    77  	storageProvisioner string
    78  
    79  	// parameters define attributes of the storage class.
    80  	parameters map[string]string
    81  
    82  	// existingStorageClass defines a storage class
    83  	// which if present will be used, but if not
    84  	// will fallback to looking for a storage class
    85  	// based on the specified labels.
    86  	existingStorageClass string
    87  
    88  	// storageLabels define the labels used to
    89  	// search for a storage class.
    90  	storageLabels []string
    91  
    92  	// reclaimPolicy defines the volume reclaim policy.
    93  	reclaimPolicy core.PersistentVolumeReclaimPolicy
    94  }
    95  
    96  func newStorageConfig(attrs map[string]interface{}, defaultStorageClass string) (*storageConfig, error) {
    97  	out, err := storageConfigChecker.Coerce(attrs, nil)
    98  	if err != nil {
    99  		return nil, errors.Annotate(err, "validating storage config")
   100  	}
   101  	coerced := out.(map[string]interface{})
   102  	storageConfig := &storageConfig{
   103  		existingStorageClass: defaultStorageClass,
   104  	}
   105  	if storageClassName, ok := coerced[storageClass].(string); ok {
   106  		storageConfig.storageClass = storageClassName
   107  	}
   108  	if storageProvisioner, ok := coerced[storageProvisioner].(string); ok {
   109  		storageConfig.storageProvisioner = storageProvisioner
   110  	}
   111  	if storageConfig.storageProvisioner != "" && storageConfig.storageClass == "" {
   112  		return nil, errors.New("storage-class must be specified if storage-provisioner is specified")
   113  	}
   114  	// By default, we'll retain volumes used for charm storage.
   115  	storageConfig.reclaimPolicy = core.PersistentVolumeReclaimRetain
   116  	storageConfig.parameters = make(map[string]string)
   117  	for k, v := range attrs {
   118  		k = strings.TrimPrefix(k, "parameters.")
   119  		storageConfig.parameters[k] = fmt.Sprintf("%v", v)
   120  	}
   121  	delete(storageConfig.parameters, storageClass)
   122  	delete(storageConfig.parameters, storageLabel)
   123  	delete(storageConfig.parameters, storageProvisioner)
   124  
   125  	return storageConfig, nil
   126  }
   127  
   128  // ValidateConfig is defined on the storage.Provider interface.
   129  func (g *storageProvider) ValidateConfig(cfg *storage.Config) error {
   130  	_, err := newStorageConfig(cfg.Attrs(), defaultStorageClass)
   131  	return errors.Trace(err)
   132  }
   133  
   134  // Supports is defined on the storage.Provider interface.
   135  func (g *storageProvider) Supports(k storage.StorageKind) bool {
   136  	return k == storage.StorageKindBlock
   137  }
   138  
   139  // Scope is defined on the storage.Provider interface.
   140  func (g *storageProvider) Scope() storage.Scope {
   141  	return storage.ScopeEnviron
   142  }
   143  
   144  // Dynamic is defined on the storage.Provider interface.
   145  func (g *storageProvider) Dynamic() bool {
   146  	return true
   147  }
   148  
   149  // Releasable is defined on the storage.Provider interface.
   150  func (e *storageProvider) Releasable() bool {
   151  	return true
   152  }
   153  
   154  // DefaultPools is defined on the storage.Provider interface.
   155  func (g *storageProvider) DefaultPools() []*storage.Config {
   156  	return nil
   157  }
   158  
   159  // VolumeSource is defined on the storage.Provider interface.
   160  func (g *storageProvider) VolumeSource(cfg *storage.Config) (storage.VolumeSource, error) {
   161  	return &volumeSource{
   162  		client: g.client,
   163  	}, nil
   164  }
   165  
   166  // FilesystemSource is defined on the storage.Provider interface.
   167  func (g *storageProvider) FilesystemSource(providerConfig *storage.Config) (storage.FilesystemSource, error) {
   168  	return nil, errors.NotSupportedf("filesystems")
   169  }
   170  
   171  type volumeSource struct {
   172  	client *kubernetesClient
   173  }
   174  
   175  var _ storage.VolumeSource = (*volumeSource)(nil)
   176  
   177  // CreateVolumes is specified on the storage.VolumeSource interface.
   178  func (v *volumeSource) CreateVolumes(ctx context.ProviderCallContext, params []storage.VolumeParams) (_ []storage.CreateVolumesResult, err error) {
   179  	// noop
   180  	return nil, nil
   181  }
   182  
   183  // ListVolumes is specified on the storage.VolumeSource interface.
   184  func (v *volumeSource) ListVolumes(ctx context.ProviderCallContext) ([]string, error) {
   185  	pVolumes := v.client.CoreV1().PersistentVolumes()
   186  	vols, err := pVolumes.List(v1.ListOptions{})
   187  	if err != nil {
   188  		return nil, errors.Trace(err)
   189  	}
   190  	volumeIds := make([]string, 0, len(vols.Items))
   191  	for _, v := range vols.Items {
   192  		volumeIds = append(volumeIds, v.Name)
   193  	}
   194  	return volumeIds, nil
   195  }
   196  
   197  // DescribeVolumes is specified on the storage.VolumeSource interface.
   198  func (v *volumeSource) DescribeVolumes(ctx context.ProviderCallContext, volIds []string) ([]storage.DescribeVolumesResult, error) {
   199  	pVolumes := v.client.CoreV1().PersistentVolumes()
   200  	vols, err := pVolumes.List(v1.ListOptions{
   201  		// TODO(caas) - filter on volumes for the current model
   202  	})
   203  	if err != nil {
   204  		return nil, errors.Trace(err)
   205  	}
   206  
   207  	byId := make(map[string]core.PersistentVolume)
   208  	for _, vol := range vols.Items {
   209  		byId[vol.Name] = vol
   210  	}
   211  	results := make([]storage.DescribeVolumesResult, len(volIds))
   212  	for i, volId := range volIds {
   213  		vol, ok := byId[volId]
   214  		if !ok {
   215  			results[i].Error = errors.NotFoundf("%s", volId)
   216  			continue
   217  		}
   218  		results[i].VolumeInfo = &storage.VolumeInfo{
   219  			Size:       uint64(vol.Size()),
   220  			VolumeId:   vol.Name,
   221  			Persistent: vol.Spec.PersistentVolumeReclaimPolicy == core.PersistentVolumeReclaimRetain,
   222  		}
   223  	}
   224  	return results, nil
   225  }
   226  
   227  // DestroyVolumes is specified on the storage.VolumeSource interface.
   228  func (v *volumeSource) DestroyVolumes(ctx context.ProviderCallContext, volIds []string) ([]error, error) {
   229  	logger.Debugf("destroy k8s volumes: %v", volIds)
   230  	pVolumes := v.client.CoreV1().PersistentVolumes()
   231  	return foreachVolume(volIds, func(volumeId string) error {
   232  		if err := pVolumes.Delete(
   233  			volumeId,
   234  			&v1.DeleteOptions{PropagationPolicy: &defaultPropagationPolicy},
   235  		); !k8serrors.IsNotFound(err) {
   236  			return errors.Annotate(err, "destroying k8s volumes")
   237  		}
   238  		return nil
   239  	}), nil
   240  }
   241  
   242  // ReleaseVolumes is specified on the storage.VolumeSource interface.
   243  func (v *volumeSource) ReleaseVolumes(ctx context.ProviderCallContext, volIds []string) ([]error, error) {
   244  	// noop
   245  	return make([]error, len(volIds)), nil
   246  }
   247  
   248  // ValidateVolumeParams is specified on the storage.VolumeSource interface.
   249  func (v *volumeSource) ValidateVolumeParams(params storage.VolumeParams) error {
   250  	// TODO(caas) - we need to validate params based on the underlying substrate
   251  	return nil
   252  }
   253  
   254  // AttachVolumes is specified on the storage.VolumeSource interface.
   255  func (v *volumeSource) AttachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]storage.AttachVolumesResult, error) {
   256  	// noop
   257  	return nil, nil
   258  }
   259  
   260  // DetachVolumes is specified on the storage.VolumeSource interface.
   261  func (v *volumeSource) DetachVolumes(ctx context.ProviderCallContext, attachParams []storage.VolumeAttachmentParams) ([]error, error) {
   262  	// noop
   263  	return make([]error, len(attachParams)), nil
   264  }
   265  
   266  func foreachVolume(volumeIds []string, f func(string) error) []error {
   267  	results := make([]error, len(volumeIds))
   268  	var wg sync.WaitGroup
   269  	for i, volumeId := range volumeIds {
   270  		wg.Add(1)
   271  		go func(i int, volumeId string) {
   272  			defer wg.Done()
   273  			results[i] = f(volumeId)
   274  		}(i, volumeId)
   275  	}
   276  	wg.Wait()
   277  	return results
   278  }