kubernetes/pkg/cloudprovider/providers/vsphere/vsphere.go
Davanum Srinivas ee8507a5ae Use Infof/Warningf when appropriate
When we use a format string, we should use Infof/Warningf instead
of Info/Warning
2016-07-19 12:10:53 -04:00

815 lines
21 KiB
Go

/*
Copyright 2016 The Kubernetes Authors.
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 vsphere
import (
"errors"
"fmt"
"io"
"net"
"net/url"
"path"
"strings"
"github.com/vmware/govmomi"
"github.com/vmware/govmomi/find"
"github.com/vmware/govmomi/object"
"github.com/vmware/govmomi/property"
"github.com/vmware/govmomi/vim25/mo"
"github.com/vmware/govmomi/vim25/types"
"golang.org/x/net/context"
"gopkg.in/gcfg.v1"
"github.com/golang/glog"
"k8s.io/kubernetes/pkg/api"
"k8s.io/kubernetes/pkg/cloudprovider"
"k8s.io/kubernetes/pkg/util/runtime"
)
const ProviderName = "vsphere"
const ActivePowerState = "poweredOn"
const DefaultDiskController = "scsi"
const DefaultSCSIControllerType = "lsilogic-sas"
// Controller types that are currently supported for hot attach of disks
// lsilogic driver type is currently not supported because,when a device gets detached
// it fails to remove the device from the /dev path (which should be manually done)
// making the subsequent attaches to the node to fail.
// TODO: Add support for lsilogic driver type
var supportedSCSIControllerType = []string{"lsilogic-sas", "pvscsi"}
var ErrNoDiskUUIDFound = errors.New("No disk UUID found")
var ErrNoDiskIDFound = errors.New("No vSphere disk ID found")
var ErrNoDevicesFound = errors.New("No devices found")
// VSphere is an implementation of cloud provider Interface for VSphere.
type VSphere struct {
cfg *VSphereConfig
// InstanceID of the server where this VSphere object is instantiated.
localInstanceID string
}
type VSphereConfig struct {
Global struct {
User string `gcfg:"user"`
Password string `gcfg:"password"`
VCenterIP string `gcfg:"server"`
VCenterPort string `gcfg:"port"`
InsecureFlag bool `gcfg:"insecure-flag"`
Datacenter string `gcfg:"datacenter"`
Datastore string `gcfg:"datastore"`
WorkingDir string `gcfg:"working-dir"`
}
Network struct {
PublicNetwork string `gcfg:"public-network"`
}
Disk struct {
SCSIControllerType string `dcfg:"scsicontrollertype"`
}
}
func readConfig(config io.Reader) (VSphereConfig, error) {
if config == nil {
err := fmt.Errorf("no vSphere cloud provider config file given")
return VSphereConfig{}, err
}
var cfg VSphereConfig
err := gcfg.ReadInto(&cfg, config)
return cfg, err
}
func init() {
cloudprovider.RegisterCloudProvider(ProviderName, func(config io.Reader) (cloudprovider.Interface, error) {
cfg, err := readConfig(config)
if err != nil {
return nil, err
}
return newVSphere(cfg)
})
}
func readInstanceID(cfg *VSphereConfig) (string, error) {
addrs, err := net.InterfaceAddrs()
if err != nil {
return "", err
}
if len(addrs) == 0 {
return "", fmt.Errorf("unable to retrieve Instance ID")
}
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(cfg, ctx)
if err != nil {
return "", err
}
defer c.Logout(ctx)
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
if err != nil {
return "", err
}
f.SetDatacenter(dc)
s := object.NewSearchIndex(c.Client)
var svm object.Reference
for _, v := range addrs {
ip, _, err := net.ParseCIDR(v.String())
if err != nil {
return "", fmt.Errorf("unable to parse cidr from ip")
}
svm, err = s.FindByIp(ctx, dc, ip.String(), true)
if err == nil && svm != nil {
break
}
}
if svm == nil {
return "", fmt.Errorf("unable to retrieve vm reference from vSphere")
}
var vm mo.VirtualMachine
err = s.Properties(ctx, svm.Reference(), []string{"name"}, &vm)
if err != nil {
return "", err
}
return vm.Name, nil
}
func newVSphere(cfg VSphereConfig) (*VSphere, error) {
id, err := readInstanceID(&cfg)
if err != nil {
return nil, err
}
if cfg.Disk.SCSIControllerType == "" {
cfg.Disk.SCSIControllerType = DefaultSCSIControllerType
} else if !checkControllerSupported(cfg.Disk.SCSIControllerType) {
glog.Errorf("%v is not a supported SCSI Controller type. Please configure 'lsilogic-sas' OR 'pvscsi'", cfg.Disk.SCSIControllerType)
return nil, errors.New("Controller type not supported. Please configure 'lsilogic-sas' OR 'pvscsi'")
}
if cfg.Global.WorkingDir != "" {
cfg.Global.WorkingDir = path.Clean(cfg.Global.WorkingDir) + "/"
}
vs := VSphere{
cfg: &cfg,
localInstanceID: id,
}
return &vs, nil
}
func checkControllerSupported(ctrlType string) bool {
for _, c := range supportedSCSIControllerType {
if ctrlType == c {
return true
}
}
return false
}
func vsphereLogin(cfg *VSphereConfig, ctx context.Context) (*govmomi.Client, error) {
// Parse URL from string
u, err := url.Parse(fmt.Sprintf("https://%s:%s/sdk", cfg.Global.VCenterIP, cfg.Global.VCenterPort))
if err != nil {
return nil, err
}
// set username and password for the URL
u.User = url.UserPassword(cfg.Global.User, cfg.Global.Password)
// Connect and log in to ESX or vCenter
c, err := govmomi.NewClient(ctx, u, cfg.Global.InsecureFlag)
if err != nil {
return nil, err
}
return c, nil
}
func getVirtualMachineByName(cfg *VSphereConfig, ctx context.Context, c *govmomi.Client, name string) (*object.VirtualMachine, error) {
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
if err != nil {
return nil, err
}
f.SetDatacenter(dc)
vmRegex := cfg.Global.WorkingDir + name
// Retrieve vm by name
//TODO: also look for vm inside subfolders
vm, err := f.VirtualMachine(ctx, vmRegex)
if err != nil {
return nil, err
}
return vm, nil
}
func getVirtualMachineManagedObjectReference(ctx context.Context, c *govmomi.Client, vm *object.VirtualMachine, field string, dst interface{}) error {
collector := property.DefaultCollector(c.Client)
// Retrieve required field from VM object
err := collector.RetrieveOne(ctx, vm.Reference(), []string{field}, dst)
if err != nil {
return err
}
return nil
}
func getInstances(cfg *VSphereConfig, ctx context.Context, c *govmomi.Client, filter string) ([]string, error) {
f := find.NewFinder(c.Client, true)
dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
if err != nil {
return nil, err
}
f.SetDatacenter(dc)
vmRegex := cfg.Global.WorkingDir + filter
//TODO: get all vms inside subfolders
vms, err := f.VirtualMachineList(ctx, vmRegex)
if err != nil {
return nil, err
}
var vmRef []types.ManagedObjectReference
for _, vm := range vms {
vmRef = append(vmRef, vm.Reference())
}
pc := property.DefaultCollector(c.Client)
var vmt []mo.VirtualMachine
err = pc.Retrieve(ctx, vmRef, []string{"name", "summary"}, &vmt)
if err != nil {
return nil, err
}
var vmList []string
for _, vm := range vmt {
if vm.Summary.Runtime.PowerState == ActivePowerState {
vmList = append(vmList, vm.Name)
} else if vm.Summary.Config.Template == false {
glog.Warningf("VM %s, is not in %s state", vm.Name, ActivePowerState)
}
}
return vmList, nil
}
type Instances struct {
cfg *VSphereConfig
localInstanceID string
}
// Instances returns an implementation of Instances for vSphere.
func (vs *VSphere) Instances() (cloudprovider.Instances, bool) {
return &Instances{vs.cfg, vs.localInstanceID}, true
}
// List is an implementation of Instances.List.
func (i *Instances) List(filter string) ([]string, error) {
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
c, err := vsphereLogin(i.cfg, ctx)
if err != nil {
return nil, err
}
defer c.Logout(ctx)
vmList, err := getInstances(i.cfg, ctx, c, filter)
if err != nil {
return nil, err
}
glog.V(3).Infof("Found %s instances matching %s: %s",
len(vmList), filter, vmList)
return vmList, nil
}
// NodeAddresses is an implementation of Instances.NodeAddresses.
func (i *Instances) NodeAddresses(name string) ([]api.NodeAddress, error) {
addrs := []api.NodeAddress{}
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(i.cfg, ctx)
if err != nil {
return nil, err
}
defer c.Logout(ctx)
vm, err := getVirtualMachineByName(i.cfg, ctx, c, name)
if err != nil {
return nil, err
}
var mvm mo.VirtualMachine
err = getVirtualMachineManagedObjectReference(ctx, c, vm, "guest.net", &mvm)
if err != nil {
return nil, err
}
// retrieve VM's ip(s)
for _, v := range mvm.Guest.Net {
var addressType api.NodeAddressType
if i.cfg.Network.PublicNetwork == v.Network {
addressType = api.NodeExternalIP
} else {
addressType = api.NodeInternalIP
}
for _, ip := range v.IpAddress {
api.AddToNodeAddresses(&addrs,
api.NodeAddress{
Type: addressType,
Address: ip,
},
)
}
}
return addrs, nil
}
func (i *Instances) AddSSHKeyToAllInstances(user string, keyData []byte) error {
return errors.New("unimplemented")
}
func (i *Instances) CurrentNodeName(hostname string) (string, error) {
return i.localInstanceID, nil
}
// ExternalID returns the cloud provider ID of the specified instance (deprecated).
func (i *Instances) ExternalID(name string) (string, error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(i.cfg, ctx)
if err != nil {
return "", err
}
defer c.Logout(ctx)
vm, err := getVirtualMachineByName(i.cfg, ctx, c, name)
if err != nil {
return "", err
}
var mvm mo.VirtualMachine
err = getVirtualMachineManagedObjectReference(ctx, c, vm, "summary", &mvm)
if err != nil {
return "", err
}
if mvm.Summary.Runtime.PowerState == ActivePowerState {
return vm.InventoryPath, nil
}
if mvm.Summary.Config.Template == false {
glog.Warningf("VM %s, is not in %s state", name, ActivePowerState)
} else {
glog.Warningf("VM %s, is a template", name)
}
return "", cloudprovider.InstanceNotFound
}
// InstanceID returns the cloud provider ID of the specified instance.
func (i *Instances) InstanceID(name string) (string, error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(i.cfg, ctx)
if err != nil {
return "", err
}
defer c.Logout(ctx)
vm, err := getVirtualMachineByName(i.cfg, ctx, c, name)
var mvm mo.VirtualMachine
err = getVirtualMachineManagedObjectReference(ctx, c, vm, "summary", &mvm)
if err != nil {
return "", err
}
if mvm.Summary.Runtime.PowerState == ActivePowerState {
return "/" + vm.InventoryPath, nil
}
if mvm.Summary.Config.Template == false {
glog.Warningf("VM %s, is not in %s state", name, ActivePowerState)
} else {
glog.Warningf("VM %s, is a template", name)
}
return "", cloudprovider.InstanceNotFound
}
func (i *Instances) InstanceType(name string) (string, error) {
return "", nil
}
func (vs *VSphere) Clusters() (cloudprovider.Clusters, bool) {
return nil, true
}
// ProviderName returns the cloud provider ID.
func (vs *VSphere) ProviderName() string {
return ProviderName
}
// LoadBalancer returns an implementation of LoadBalancer for vSphere.
func (vs *VSphere) LoadBalancer() (cloudprovider.LoadBalancer, bool) {
return nil, false
}
// Zones returns an implementation of Zones for Google vSphere.
func (vs *VSphere) Zones() (cloudprovider.Zones, bool) {
glog.V(4).Info("Claiming to support Zones")
return vs, true
}
func (vs *VSphere) GetZone() (cloudprovider.Zone, error) {
glog.V(4).Infof("Current zone is %v", vs.cfg.Global.Datacenter)
return cloudprovider.Zone{Region: vs.cfg.Global.Datacenter}, nil
}
// Routes returns a false since the interface is not supported for vSphere.
func (vs *VSphere) Routes() (cloudprovider.Routes, bool) {
return nil, false
}
// ScrubDNS filters DNS settings for pods.
func (vs *VSphere) ScrubDNS(nameservers, searches []string) (nsOut, srchOut []string) {
return nameservers, searches
}
func getVirtualMachineDevices(cfg *VSphereConfig, ctx context.Context, c *govmomi.Client, name string) (*object.VirtualMachine, object.VirtualDeviceList, *object.Datastore, error) {
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, cfg.Global.Datacenter)
if err != nil {
return nil, nil, nil, err
}
f.SetDatacenter(dc)
// Find datastores
ds, err := f.Datastore(ctx, cfg.Global.Datastore)
if err != nil {
return nil, nil, nil, err
}
vmRegex := cfg.Global.WorkingDir + name
vm, err := f.VirtualMachine(ctx, vmRegex)
if err != nil {
return nil, nil, nil, err
}
// Get devices from VM
vmDevices, err := vm.Device(ctx)
if err != nil {
return nil, nil, nil, err
}
return vm, vmDevices, ds, nil
}
//cleaning up the controller
func cleanUpController(newSCSIController types.BaseVirtualDevice, vmDevices object.VirtualDeviceList, vm *object.VirtualMachine, ctx context.Context) error {
ctls := vmDevices.SelectByType(newSCSIController)
if len(ctls) < 1 {
return ErrNoDevicesFound
}
newScsi := ctls[len(ctls)-1]
err := vm.RemoveDevice(ctx, true, newScsi)
if err != nil {
return err
}
return nil
}
// Attaches given virtual disk volume to the compute running kubelet.
func (vs *VSphere) AttachDisk(vmDiskPath string, nodeName string) (diskID string, diskUUID string, err error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return "", "", err
}
defer c.Logout(ctx)
// Find virtual machine to attach disk to
var vSphereInstance string
if nodeName == "" {
vSphereInstance = vs.localInstanceID
} else {
vSphereInstance = nodeName
}
// Get VM device list
vm, vmDevices, ds, err := getVirtualMachineDevices(vs.cfg, ctx, c, vSphereInstance)
if err != nil {
return "", "", err
}
var diskControllerType = vs.cfg.Disk.SCSIControllerType
// find SCSI controller of particular type from VM devices
var diskController = getSCSIController(vmDevices, diskControllerType)
var newSCSICreated = false
var newSCSIController types.BaseVirtualDevice
// creating a scsi controller as there is none found of controller type defined
if diskController == nil {
glog.V(4).Infof("Creating a SCSI controller of %v type", diskControllerType)
newSCSIController, err := vmDevices.CreateSCSIController(diskControllerType)
if err != nil {
runtime.HandleError(fmt.Errorf("error creating new SCSI controller: %v", err))
return "", "", err
}
configNewSCSIController := newSCSIController.(types.BaseVirtualSCSIController).GetVirtualSCSIController()
hotAndRemove := true
configNewSCSIController.HotAddRemove = &hotAndRemove
configNewSCSIController.SharedBus = types.VirtualSCSISharing(types.VirtualSCSISharingNoSharing)
// add the scsi controller to virtual machine
err = vm.AddDevice(context.TODO(), newSCSIController)
if err != nil {
glog.V(3).Infof("cannot add SCSI controller to vm - %v", err)
// attempt clean up of scsi controller
if vmDevices, err := vm.Device(ctx); err == nil {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
// verify scsi controller in virtual machine
vmDevices, err = vm.Device(ctx)
if err != nil {
//cannot cleanup if there is no device list
return "", "", err
}
diskController = getSCSIController(vmDevices, vs.cfg.Disk.SCSIControllerType)
if diskController == nil {
glog.Errorf("cannot find SCSI controller in VM - %v", err)
// attempt clean up of scsi controller
cleanUpController(newSCSIController, vmDevices, vm, ctx)
return "", "", err
}
newSCSICreated = true
}
disk := vmDevices.CreateDisk(diskController, ds.Reference(), vmDiskPath)
backing := disk.Backing.(*types.VirtualDiskFlatVer2BackingInfo)
backing.DiskMode = string(types.VirtualDiskModeIndependent_persistent)
// Attach disk to the VM
err = vm.AddDevice(ctx, disk)
if err != nil {
glog.Errorf("cannot attach disk to the vm - %v", err)
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
vmDevices, err = vm.Device(ctx)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", err
}
devices := vmDevices.SelectByType(disk)
if len(devices) < 1 {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
return "", "", ErrNoDevicesFound
}
// get new disk id
newDevice := devices[len(devices)-1]
deviceName := devices.Name(newDevice)
// get device uuid
diskUUID, err = getVirtualDiskUUID(newDevice)
if err != nil {
if newSCSICreated {
cleanUpController(newSCSIController, vmDevices, vm, ctx)
}
vs.DetachDisk(deviceName, vSphereInstance)
return "", "", err
}
return deviceName, diskUUID, nil
}
func getSCSIController(vmDevices object.VirtualDeviceList, scsiType string) *types.VirtualController {
// get virtual scsi controller of passed argument type
for _, device := range vmDevices {
devType := vmDevices.Type(device)
if devType == scsiType {
if c, ok := device.(types.BaseVirtualController); ok {
return c.GetVirtualController()
}
}
}
return nil
}
func getVirtualDiskUUID(newDevice types.BaseVirtualDevice) (string, error) {
vd := newDevice.GetVirtualDevice()
if b, ok := vd.Backing.(*types.VirtualDiskFlatVer2BackingInfo); ok {
uuidWithNoHypens := strings.Replace(b.Uuid, "-", "", -1)
return strings.ToLower(uuidWithNoHypens), nil
}
return "", ErrNoDiskUUIDFound
}
func getVirtualDiskID(volPath string, vmDevices object.VirtualDeviceList) (string, error) {
// filter vm devices to retrieve disk ID for the given vmdk file
for _, device := range vmDevices {
if vmDevices.TypeName(device) == "VirtualDisk" {
d := device.GetVirtualDevice()
if b, ok := d.Backing.(types.BaseVirtualDeviceFileBackingInfo); ok {
fileName := b.GetVirtualDeviceFileBackingInfo().FileName
if fileName == volPath {
return vmDevices.Name(device), nil
}
}
}
}
return "", ErrNoDiskIDFound
}
// Detaches given virtual disk volume from the compute running kubelet.
func (vs *VSphere) DetachDisk(volPath string, nodeName string) error {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return err
}
defer c.Logout(ctx)
// Find VM to detach disk from
var vSphereInstance string
if nodeName == "" {
vSphereInstance = vs.localInstanceID
} else {
vSphereInstance = nodeName
}
vm, vmDevices, _, err := getVirtualMachineDevices(vs.cfg, ctx, c, vSphereInstance)
if err != nil {
return err
}
diskID, err := getVirtualDiskID(volPath, vmDevices)
if err != nil {
glog.Warningf("disk ID not found for %v ", volPath)
return err
}
// Remove disk from VM
device := vmDevices.Find(diskID)
if device == nil {
return fmt.Errorf("device '%s' not found", diskID)
}
err = vm.RemoveDevice(ctx, true, device)
if err != nil {
return err
}
return nil
}
// Create a volume of given size (in KiB).
func (vs *VSphere) CreateVolume(name string, size int, tags *map[string]string) (volumePath string, err error) {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return "", err
}
defer c.Logout(ctx)
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, vs.cfg.Global.Datacenter)
f.SetDatacenter(dc)
// Create a virtual disk manager
vmDiskPath := "[" + vs.cfg.Global.Datastore + "] " + name + ".vmdk"
virtualDiskManager := object.NewVirtualDiskManager(c.Client)
// Create specification for new virtual disk
vmDiskSpec := &types.FileBackedVirtualDiskSpec{
VirtualDiskSpec: types.VirtualDiskSpec{
AdapterType: (*tags)["adapterType"],
DiskType: (*tags)["diskType"],
},
CapacityKb: int64(size),
}
// Create virtual disk
task, err := virtualDiskManager.CreateVirtualDisk(ctx, vmDiskPath, dc, vmDiskSpec)
if err != nil {
return "", err
}
err = task.Wait(ctx)
if err != nil {
return "", err
}
return vmDiskPath, nil
}
// Deletes a volume given volume name.
func (vs *VSphere) DeleteVolume(vmDiskPath string) error {
// Create context
ctx, cancel := context.WithCancel(context.Background())
defer cancel()
// Create vSphere client
c, err := vsphereLogin(vs.cfg, ctx)
if err != nil {
return err
}
defer c.Logout(ctx)
// Create a new finder
f := find.NewFinder(c.Client, true)
// Fetch and set data center
dc, err := f.Datacenter(ctx, vs.cfg.Global.Datacenter)
f.SetDatacenter(dc)
// Create a virtual disk manager
virtualDiskManager := object.NewVirtualDiskManager(c.Client)
// Delete virtual disk
task, err := virtualDiskManager.DeleteVirtualDisk(ctx, vmDiskPath, dc)
if err != nil {
return err
}
return task.Wait(ctx)
}