forked from rook/rook
226 lines
6.5 KiB
Go
226 lines
6.5 KiB
Go
/*
|
|
Copyright 2017 The Rook Authors. All rights reserved.
|
|
|
|
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 operator
|
|
|
|
import (
|
|
"fmt"
|
|
"strings"
|
|
|
|
"github.com/kubernetes-incubator/external-storage/lib/controller"
|
|
"github.com/rook/rook/pkg/model"
|
|
"github.com/rook/rook/pkg/operator/k8sutil"
|
|
rookclient "github.com/rook/rook/pkg/rook/client"
|
|
metav1 "k8s.io/apimachinery/pkg/apis/meta/v1"
|
|
"k8s.io/apimachinery/pkg/util/uuid"
|
|
"k8s.io/client-go/pkg/api/v1"
|
|
)
|
|
|
|
const (
|
|
imageNameMaxLen = 100 // image name should be under 100 chars to support kernels older than 4.7
|
|
imageNamePrefix = "k8s-dynamic"
|
|
rbdIDPrefix = "rbd_id."
|
|
)
|
|
|
|
type rookVolumeProvisioner struct {
|
|
clusterManager *clusterManager
|
|
|
|
// Configuration of rook volume provisioner
|
|
provConfig provisionerConfig
|
|
}
|
|
|
|
type provisionerConfig struct {
|
|
// Required: the pool name to provision volumes from.
|
|
pool string
|
|
|
|
// Optional: Namespace of the cluster. Default is `rook`
|
|
clusterNamespace string
|
|
|
|
// Optional: Name of the cluster. Default is `rook`
|
|
clusterName string
|
|
}
|
|
|
|
func newRookVolumeProvisioner(clusterManager *clusterManager) controller.Provisioner {
|
|
return &rookVolumeProvisioner{
|
|
clusterManager: clusterManager,
|
|
}
|
|
}
|
|
|
|
// Provision creates a storage asset and returns a PV object representing it.
|
|
func (p *rookVolumeProvisioner) Provision(options controller.VolumeOptions) (*v1.PersistentVolume, error) {
|
|
|
|
var err error
|
|
if options.PVC.Spec.Selector != nil {
|
|
return nil, fmt.Errorf("claim Selector is not supported")
|
|
}
|
|
|
|
logger.Infof("VolumeOptions %v", options)
|
|
|
|
cfg, err := parseClassParameters(options.Parameters)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
p.provConfig = *cfg
|
|
|
|
logger.Infof("creating volume with configuration %+v", p.provConfig)
|
|
|
|
capacity := options.PVC.Spec.Resources.Requests[v1.ResourceName(v1.ResourceStorage)]
|
|
requestBytes := capacity.Value()
|
|
|
|
imageName := createImageName(options.PVName)
|
|
|
|
rookClient, err := p.clusterManager.getRookClient(p.provConfig.clusterNamespace)
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to get rook client: %v", err)
|
|
}
|
|
|
|
res, err := createVolume(imageName, p.provConfig.pool, requestBytes, rookClient)
|
|
if err != nil {
|
|
return nil, err
|
|
}
|
|
logger.Infof("Rook block image created: %s", res)
|
|
|
|
rookClientInfo, err := rookClient.GetClientAccessInfo()
|
|
if err != nil {
|
|
return nil, fmt.Errorf("Failed to get rook client information: %v", err)
|
|
}
|
|
monitors := processMonAddresses(rookClientInfo.MonAddresses)
|
|
radosUser := fmt.Sprintf("%s-rook-user", p.provConfig.clusterName)
|
|
secretRef := new(v1.LocalObjectReference)
|
|
secretRef.Name = fmt.Sprintf("%s-rook-user", p.provConfig.clusterName)
|
|
|
|
pv := &v1.PersistentVolume{
|
|
ObjectMeta: metav1.ObjectMeta{
|
|
Name: options.PVName,
|
|
},
|
|
Spec: v1.PersistentVolumeSpec{
|
|
PersistentVolumeReclaimPolicy: options.PersistentVolumeReclaimPolicy,
|
|
AccessModes: options.PVC.Spec.AccessModes,
|
|
Capacity: v1.ResourceList{
|
|
v1.ResourceName(v1.ResourceStorage): capacity,
|
|
},
|
|
PersistentVolumeSource: v1.PersistentVolumeSource{
|
|
RBD: &v1.RBDVolumeSource{
|
|
RBDImage: imageName,
|
|
RBDPool: p.provConfig.pool,
|
|
CephMonitors: monitors,
|
|
RadosUser: radosUser,
|
|
SecretRef: secretRef,
|
|
FSType: "ext4",
|
|
ReadOnly: false,
|
|
},
|
|
},
|
|
},
|
|
}
|
|
logger.Infof("successfully created Rook Block volume %+v", pv.Spec.PersistentVolumeSource.RBD)
|
|
return pv, nil
|
|
}
|
|
|
|
// createVolume creates a rook block volume.
|
|
func createVolume(image, pool string, size int64, client rookclient.RookRestClient) (string, error) {
|
|
newImage := model.BlockImage{
|
|
Name: image,
|
|
PoolName: pool,
|
|
Size: uint64(size),
|
|
}
|
|
|
|
res, err := client.CreateBlockImage(newImage)
|
|
if err != nil {
|
|
return "", fmt.Errorf("Failed to create rook block image %s/%s: %v", pool, image, err)
|
|
}
|
|
|
|
return res, nil
|
|
}
|
|
|
|
// Delete removes the storage asset that was created by Provision represented
|
|
// by the given PV.
|
|
func (p *rookVolumeProvisioner) Delete(volume *v1.PersistentVolume) error {
|
|
rookClient, err := p.clusterManager.getRookClient(p.provConfig.clusterNamespace)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to get rook client: %v", err)
|
|
}
|
|
|
|
image := model.BlockImage{
|
|
Name: volume.Spec.PersistentVolumeSource.RBD.RBDImage,
|
|
PoolName: p.provConfig.pool,
|
|
}
|
|
|
|
_, err = rookClient.DeleteBlockImage(image)
|
|
if err != nil {
|
|
return fmt.Errorf("Failed to delete rook block image %s/%s: %v", p.provConfig.pool, volume.Name, err)
|
|
}
|
|
return nil
|
|
}
|
|
|
|
func parseClassParameters(params map[string]string) (*provisionerConfig, error) {
|
|
var cfg provisionerConfig
|
|
|
|
// Namespace and cluster name have the same default name
|
|
defaultCluster := k8sutil.Namespace
|
|
|
|
for k, v := range params {
|
|
switch strings.ToLower(k) {
|
|
case "pool":
|
|
cfg.pool = v
|
|
case "clusternamespace":
|
|
cfg.clusterNamespace = v
|
|
case "clustername":
|
|
cfg.clusterName = v
|
|
default:
|
|
return nil, fmt.Errorf("invalid option %q for volume plugin %s", k, "rookVolumeProvisioner")
|
|
}
|
|
}
|
|
|
|
if len(cfg.pool) == 0 {
|
|
return nil, fmt.Errorf("StorageClass for provisioner %s must contain 'pool' parameter", "rookVolumeProvisioner")
|
|
}
|
|
|
|
if len(cfg.clusterNamespace) == 0 {
|
|
cfg.clusterNamespace = defaultCluster
|
|
}
|
|
|
|
if len(cfg.clusterName) == 0 {
|
|
cfg.clusterName = defaultCluster
|
|
}
|
|
|
|
return &cfg, nil
|
|
}
|
|
|
|
func processMonAddresses(monAddresses []string) []string {
|
|
monAddrs := make([]string, len(monAddresses))
|
|
for i, addr := range monAddresses {
|
|
mon := strings.Split(addr, "/")
|
|
monAddrs[i] = mon[0]
|
|
}
|
|
return monAddrs
|
|
}
|
|
|
|
func createImageName(pvName string) string {
|
|
// generate a UUID for our image name
|
|
u := string(uuid.NewUUID())
|
|
|
|
// image name should be under 100 chars to support kernels older than 4.7
|
|
// when the RBD kernel module converts the image name to an OID, it will use "rbd_id.<imageName>",
|
|
// so we have to leave room for that "rbd_id." prefix too.
|
|
pvNameMaxLen := imageNameMaxLen - len(rbdIDPrefix) - len(imageNamePrefix) - len(u) - 2 // 2 hyphens
|
|
if len(pvName) > pvNameMaxLen {
|
|
// the PV name is too long, truncate it before including it in the final image name
|
|
pvName = pvName[:pvNameMaxLen]
|
|
}
|
|
|
|
return fmt.Sprintf("%s-%s-%s", imageNamePrefix, pvName, u)
|
|
}
|