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 }