github.com/mirantis/virtlet@v1.5.2-0.20191204181327-1659b8a48e9b/pkg/libvirttools/ceph_flexvolume.go (about)

     1  /*
     2  Copyright 2017 Mirantis
     3  
     4  Licensed under the Apache License, Version 2.0 (the "License");
     5  you may not use this file except in compliance with the License.
     6  You may obtain a copy of the License at
     7  
     8      http://www.apache.org/licenses/LICENSE-2.0
     9  
    10  Unless required by applicable law or agreed to in writing, software
    11  distributed under the License is distributed on an "AS IS" BASIS,
    12  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
    13  See the License for the specific language governing permissions and
    14  limitations under the License.
    15  */
    16  
    17  package libvirttools
    18  
    19  import (
    20  	"encoding/base64"
    21  	"fmt"
    22  	"strings"
    23  
    24  	"github.com/golang/glog"
    25  
    26  	libvirtxml "github.com/libvirt/libvirt-go-xml"
    27  
    28  	"github.com/Mirantis/virtlet/pkg/metadata/types"
    29  	"github.com/Mirantis/virtlet/pkg/utils"
    30  	"github.com/Mirantis/virtlet/pkg/virt"
    31  )
    32  
    33  type cephFlexvolumeOptions struct {
    34  	// Type field is needed here so it gets written during
    35  	// remarshalling after removing the contents of 'Secret' field
    36  	Type     string `json:"type"`
    37  	Monitor  string `json:"monitor"`
    38  	Pool     string `json:"pool"`
    39  	Volume   string `json:"volume"`
    40  	Secret   string `json:"secret"`
    41  	User     string `json:"user"`
    42  	Protocol string `json:"protocol"`
    43  	UUID     string `json:"uuid"`
    44  }
    45  
    46  // cephVolume denotes a Ceph RBD volume
    47  type cephVolume struct {
    48  	volumeBase
    49  	volumeName string
    50  	opts       *cephFlexvolumeOptions
    51  }
    52  
    53  var _ VMVolume = &cephVolume{}
    54  
    55  func newCephVolume(volumeName, configPath string, config *types.VMConfig, owner volumeOwner) (VMVolume, error) {
    56  	v := &cephVolume{
    57  		volumeBase: volumeBase{config, owner},
    58  	}
    59  	if err := utils.ReadJSON(configPath, &v.opts); err != nil {
    60  		return nil, fmt.Errorf("failed to parse ceph flexvolume config %q: %v", configPath, err)
    61  	}
    62  	v.volumeName = volumeName
    63  	// Remove the key from flexvolume options to limit exposure.
    64  	// The file itself will be needed to recreate cephVolume during the teardown,
    65  	// but we don't need secret content at that time anymore
    66  	safeOpts := *v.opts
    67  	safeOpts.Secret = ""
    68  	if err := utils.WriteJSON(configPath, safeOpts, 0700); err != nil {
    69  		return nil, fmt.Errorf("failed to overwrite ceph flexvolume config %q: %v", configPath, err)
    70  	}
    71  	return v, nil
    72  }
    73  
    74  func (v *cephVolume) secretUsageName() string {
    75  	return v.opts.User + "-" + utils.NewUUID5(ContainerNsUUID, v.config.PodSandboxID) + "-" + v.volumeName
    76  }
    77  
    78  func (v *cephVolume) secretDef() *libvirtxml.Secret {
    79  	return &libvirtxml.Secret{
    80  		Ephemeral: "no",
    81  		Private:   "no",
    82  		// Both secret UUID and Usage name must be unique across all definitions
    83  		// As Usage name is a string and can be used to lookup secret
    84  		// it's more convenient to use it for manipulating secrets
    85  		// and preserve using UUIDv5 as part of value
    86  		// UUID value is generated randomly
    87  		UUID:  utils.NewUUID(),
    88  		Usage: &libvirtxml.SecretUsage{Name: v.secretUsageName(), Type: "ceph"},
    89  	}
    90  }
    91  
    92  func (v *cephVolume) IsDisk() bool { return true }
    93  
    94  func (v *cephVolume) UUID() string {
    95  	return v.opts.UUID
    96  }
    97  
    98  func (v *cephVolume) Setup() (*libvirtxml.DomainDisk, *libvirtxml.DomainFilesystem, error) {
    99  	ipPortPair := strings.Split(v.opts.Monitor, ":")
   100  	if len(ipPortPair) != 2 {
   101  		return nil, nil, fmt.Errorf("invalid format of ceph monitor setting: %s. Expected ip:port", v.opts.Monitor)
   102  	}
   103  
   104  	secret, err := v.owner.DomainConnection().DefineSecret(v.secretDef())
   105  	if err != nil {
   106  		return nil, nil, fmt.Errorf("error defining ceph secret: %v", err)
   107  	}
   108  
   109  	key, err := base64.StdEncoding.DecodeString(v.opts.Secret)
   110  	if err != nil {
   111  		return nil, nil, fmt.Errorf("error decoding ceph secret: %v", err)
   112  	}
   113  
   114  	if err := secret.SetValue([]byte(key)); err != nil {
   115  		return nil, nil, fmt.Errorf("error setting value of secret %q: %v", v.secretUsageName(), err)
   116  	}
   117  
   118  	return &libvirtxml.DomainDisk{
   119  		Device: "disk",
   120  		Driver: &libvirtxml.DomainDiskDriver{Name: "qemu", Type: "raw"},
   121  		Auth: &libvirtxml.DomainDiskAuth{
   122  			Username: v.opts.User,
   123  			Secret: &libvirtxml.DomainDiskSecret{
   124  				Type:  "ceph",
   125  				Usage: v.secretUsageName(),
   126  			},
   127  		},
   128  		Source: &libvirtxml.DomainDiskSource{
   129  			Network: &libvirtxml.DomainDiskSourceNetwork{
   130  				Protocol: "rbd",
   131  				Name:     v.opts.Pool + "/" + v.opts.Volume,
   132  				Hosts: []libvirtxml.DomainDiskSourceHost{
   133  					{
   134  						Name: ipPortPair[0],
   135  						Port: ipPortPair[1],
   136  					},
   137  				},
   138  			},
   139  		},
   140  	}, nil, nil
   141  }
   142  
   143  func (v *cephVolume) Teardown() error {
   144  	secret, err := v.owner.DomainConnection().LookupSecretByUsageName("ceph", v.secretUsageName())
   145  	switch {
   146  	case err == virt.ErrSecretNotFound:
   147  		// ok, no need to delete the secret
   148  		glog.V(3).Infof("No secret with usage name %q for ceph volume was found", v.secretUsageName())
   149  		return nil
   150  	case err == nil:
   151  		glog.V(3).Infof("Removing secret with usage name: %q", v.secretUsageName())
   152  		err = secret.Remove()
   153  	}
   154  	if err != nil {
   155  		return fmt.Errorf("error deleting secret with usage name %q: %v", v.secretUsageName(), err)
   156  	}
   157  	return nil
   158  }
   159  
   160  func init() {
   161  	addFlexvolumeSource("ceph", newCephVolume)
   162  }
   163  
   164  // TODO: this file needs a test