github.com/cloud-green/juju@v0.0.0-20151002100041-a00291338d3d/provider/vsphere/client.go (about)

     1  // Copyright 2015 Canonical Ltd.
     2  // Licensed under the AGPLv3, see LICENCE file for details.
     3  
     4  // +build !gccgo
     5  
     6  package vsphere
     7  
     8  import (
     9  	"fmt"
    10  	"net"
    11  	"net/url"
    12  	"strconv"
    13  	"strings"
    14  
    15  	"github.com/juju/errors"
    16  	"github.com/juju/govmomi"
    17  	"github.com/juju/govmomi/find"
    18  	"github.com/juju/govmomi/list"
    19  	"github.com/juju/govmomi/object"
    20  	"github.com/juju/govmomi/property"
    21  	"github.com/juju/govmomi/vim25/methods"
    22  	"github.com/juju/govmomi/vim25/mo"
    23  	"github.com/juju/govmomi/vim25/types"
    24  	"golang.org/x/net/context"
    25  
    26  	"github.com/juju/juju/instance"
    27  	"github.com/juju/juju/network"
    28  )
    29  
    30  const (
    31  	metadataKeyIsState   = "juju_is_state_key"
    32  	metadataValueIsState = "juju_is_value_value"
    33  )
    34  
    35  type client struct {
    36  	connection *govmomi.Client
    37  	datacenter *object.Datacenter
    38  	finder     *find.Finder
    39  	recurser   *list.Recurser
    40  }
    41  
    42  var newClient = func(ecfg *environConfig) (*client, error) {
    43  	url, err := ecfg.url()
    44  	if err != nil {
    45  		return nil, err
    46  	}
    47  	connection, err := newConnection(url)
    48  	if err != nil {
    49  		return nil, errors.Trace(err)
    50  	}
    51  	finder := find.NewFinder(connection.Client, true)
    52  	datacenter, err := finder.Datacenter(context.TODO(), ecfg.datacenter())
    53  	if err != nil {
    54  		return nil, errors.Trace(err)
    55  	}
    56  	finder.SetDatacenter(datacenter)
    57  	recurser := &list.Recurser{
    58  		Collector: property.DefaultCollector(connection.Client),
    59  		All:       true,
    60  	}
    61  	return &client{
    62  		connection: connection,
    63  		datacenter: datacenter,
    64  		finder:     finder,
    65  		recurser:   recurser,
    66  	}, nil
    67  }
    68  
    69  var newConnection = func(url *url.URL) (*govmomi.Client, error) {
    70  	return govmomi.NewClient(context.TODO(), url, true)
    71  }
    72  
    73  type instanceSpec struct {
    74  	machineID string
    75  	zone      *vmwareAvailZone
    76  	hwc       *instance.HardwareCharacteristics
    77  	img       *OvaFileMetadata
    78  	userData  []byte
    79  	sshKey    string
    80  	isState   bool
    81  	apiPort   int
    82  }
    83  
    84  // CreateInstance create new vm in vsphere and run it
    85  func (c *client) CreateInstance(ecfg *environConfig, spec *instanceSpec) (*mo.VirtualMachine, error) {
    86  	manager := &ovaImportManager{client: c}
    87  	vm, err := manager.importOva(ecfg, spec)
    88  	if err != nil {
    89  		return nil, errors.Annotatef(err, "Failed to import OVA file")
    90  	}
    91  	task, err := vm.PowerOn(context.TODO())
    92  	if err != nil {
    93  		return nil, errors.Trace(err)
    94  	}
    95  	taskInfo, err := task.WaitForResult(context.TODO(), nil)
    96  	if err != nil {
    97  		return nil, errors.Trace(err)
    98  	}
    99  	if ecfg.externalNetwork() != "" {
   100  		ip, err := vm.WaitForIP(context.TODO())
   101  		if err != nil {
   102  			return nil, errors.Trace(err)
   103  		}
   104  		client := newSshClient(ip)
   105  		err = client.configureExternalIpAddress(spec.apiPort)
   106  		if err != nil {
   107  			return nil, errors.Trace(err)
   108  		}
   109  	}
   110  	var res mo.VirtualMachine
   111  	err = c.connection.RetrieveOne(context.TODO(), *taskInfo.Entity, nil, &res)
   112  	if err != nil {
   113  		return nil, errors.Trace(err)
   114  	}
   115  	return &res, nil
   116  }
   117  
   118  // RemoveInstances removes vms from the system
   119  func (c *client) RemoveInstances(ids ...string) error {
   120  	var lastError error
   121  	tasks := make([]*object.Task, 0, len(ids))
   122  	for _, id := range ids {
   123  		vm, err := c.finder.VirtualMachine(context.TODO(), id)
   124  		if err != nil {
   125  			lastError = err
   126  			logger.Errorf(err.Error())
   127  			continue
   128  		}
   129  		task, err := vm.PowerOff(context.TODO())
   130  		if err != nil {
   131  			lastError = err
   132  			logger.Errorf(err.Error())
   133  			continue
   134  		}
   135  		tasks = append(tasks, task)
   136  		task, err = vm.Destroy(context.TODO())
   137  		if err != nil {
   138  			lastError = err
   139  			logger.Errorf(err.Error())
   140  			continue
   141  		}
   142  		//We don't wait for task completeon here. Instead we want to run all tasks as soon as posible
   143  		//and then wait for them all. such aproach will run all tasks in parallel
   144  		tasks = append(tasks, task)
   145  	}
   146  
   147  	for _, task := range tasks {
   148  		_, err := task.WaitForResult(context.TODO(), nil)
   149  		if err != nil {
   150  			lastError = err
   151  			logger.Errorf(err.Error())
   152  		}
   153  	}
   154  	return errors.Annotatef(lastError, "failed to remowe instances")
   155  }
   156  
   157  // Instances return list of all vms in the system, that match naming convention
   158  func (c *client) Instances(prefix string) ([]*mo.VirtualMachine, error) {
   159  	items, err := c.finder.VirtualMachineList(context.TODO(), "*")
   160  	if err != nil {
   161  		return nil, errors.Trace(err)
   162  	}
   163  
   164  	var vms []*mo.VirtualMachine
   165  	vms = make([]*mo.VirtualMachine, 0, len(vms))
   166  	for _, item := range items {
   167  		var vm mo.VirtualMachine
   168  		err = c.connection.RetrieveOne(context.TODO(), item.Reference(), nil, &vm)
   169  		if err != nil {
   170  			return nil, errors.Trace(err)
   171  		}
   172  		if strings.HasPrefix(vm.Name, prefix) {
   173  			vms = append(vms, &vm)
   174  		}
   175  	}
   176  
   177  	return vms, nil
   178  }
   179  
   180  // Refresh refreshes the virtual machine
   181  func (c *client) Refresh(v *mo.VirtualMachine) error {
   182  	vm, err := c.getVm(v.Name)
   183  	if err != nil {
   184  		return errors.Trace(err)
   185  	}
   186  	*v = *vm
   187  	return nil
   188  }
   189  
   190  func (c *client) getVm(name string) (*mo.VirtualMachine, error) {
   191  	item, err := c.finder.VirtualMachine(context.TODO(), name)
   192  	if err != nil {
   193  		return nil, errors.Trace(err)
   194  	}
   195  	var vm mo.VirtualMachine
   196  	err = c.connection.RetrieveOne(context.TODO(), item.Reference(), nil, &vm)
   197  	if err != nil {
   198  		return nil, errors.Trace(err)
   199  	}
   200  	return &vm, nil
   201  }
   202  
   203  //AvailabilityZones retuns list of all root compute resources in the system
   204  func (c *client) AvailabilityZones() ([]*mo.ComputeResource, error) {
   205  	folders, err := c.datacenter.Folders(context.TODO())
   206  	if err != nil {
   207  		return nil, errors.Trace(err)
   208  	}
   209  	root := list.Element{
   210  		Object: folders.HostFolder,
   211  	}
   212  	es, err := c.recurser.Recurse(context.TODO(), root, []string{"*"})
   213  	if err != nil {
   214  		return nil, err
   215  	}
   216  
   217  	var cprs []*mo.ComputeResource
   218  	for _, e := range es {
   219  		switch o := e.Object.(type) {
   220  		case mo.ClusterComputeResource:
   221  			cprs = append(cprs, &o.ComputeResource)
   222  		case mo.ComputeResource:
   223  			cprs = append(cprs, &o)
   224  		}
   225  	}
   226  
   227  	return cprs, nil
   228  }
   229  
   230  func (c *client) GetNetworkInterfaces(inst instance.Id, ecfg *environConfig) ([]network.InterfaceInfo, error) {
   231  	vm, err := c.getVm(string(inst))
   232  	if err != nil {
   233  		return nil, errors.Trace(err)
   234  	}
   235  	if vm.Guest == nil {
   236  		return nil, errors.Errorf("vm guest is not initialized")
   237  	}
   238  	res := make([]network.InterfaceInfo, 0)
   239  	for _, net := range vm.Guest.Net {
   240  		ipScope := network.ScopeCloudLocal
   241  		if net.Network == ecfg.externalNetwork() {
   242  			ipScope = network.ScopePublic
   243  		}
   244  		res = append(res, network.InterfaceInfo{
   245  			DeviceIndex:      net.DeviceConfigId,
   246  			MACAddress:       net.MacAddress,
   247  			NetworkName:      net.Network,
   248  			Disabled:         !net.Connected,
   249  			ProviderId:       network.Id(fmt.Sprintf("net-device%d", net.DeviceConfigId)),
   250  			ProviderSubnetId: network.Id(net.Network),
   251  			InterfaceName:    fmt.Sprintf("unsupported%d", net.DeviceConfigId),
   252  			ConfigType:       network.ConfigDHCP,
   253  			Address:          network.NewScopedAddress(net.IpAddress[0], ipScope),
   254  		})
   255  	}
   256  	return res, nil
   257  }
   258  
   259  func (c *client) Subnets(inst instance.Id, ids []network.Id) ([]network.SubnetInfo, error) {
   260  	if len(ids) == 0 {
   261  		return nil, errors.Errorf("subnetIds must not be empty")
   262  	}
   263  	vm, err := c.getVm(string(inst))
   264  	if err != nil {
   265  		return nil, errors.Trace(err)
   266  	}
   267  
   268  	res := make([]network.SubnetInfo, 0)
   269  	req := &types.QueryIpPools{
   270  		This: *c.connection.ServiceContent.IpPoolManager,
   271  		Dc:   c.datacenter.Reference(),
   272  	}
   273  	ipPools, err := methods.QueryIpPools(context.TODO(), c.connection.Client, req)
   274  	if err != nil {
   275  		return nil, errors.Trace(err)
   276  	}
   277  	for _, vmNet := range vm.Guest.Net {
   278  		existId := false
   279  		for _, id := range ids {
   280  			if string(id) == vmNet.Network {
   281  				existId = true
   282  				break
   283  			}
   284  		}
   285  		if !existId {
   286  			continue
   287  		}
   288  		var netPool *types.IpPool
   289  		for _, pool := range ipPools.Returnval {
   290  			for _, association := range pool.NetworkAssociation {
   291  				if association.NetworkName == vmNet.Network {
   292  					netPool = &pool
   293  					break
   294  				}
   295  			}
   296  		}
   297  		subnet := network.SubnetInfo{
   298  			ProviderId: network.Id(vmNet.Network),
   299  		}
   300  		if netPool != nil && netPool.Ipv4Config != nil {
   301  			low, high, err := c.ParseNetworkRange(netPool.Ipv4Config.Range)
   302  			if err != nil {
   303  				logger.Warningf(err.Error())
   304  			} else {
   305  				subnet.AllocatableIPLow = low
   306  				subnet.AllocatableIPHigh = high
   307  			}
   308  		}
   309  		res = append(res)
   310  	}
   311  	return res, nil
   312  }
   313  
   314  func (c *client) ParseNetworkRange(netRange string) (net.IP, net.IP, error) {
   315  	//netPool.Range is specified as a set of ranges separated with commas. One range is given by a start address, a hash (#), and the length of the range.
   316  	//For example:
   317  	//192.0.2.235 # 20 is the IPv4 range from 192.0.2.235 to 192.0.2.254
   318  	ranges := strings.Split(netRange, ",")
   319  	if len(ranges) > 0 {
   320  		rangeSplit := strings.Split(ranges[0], "#")
   321  		if len(rangeSplit) == 2 {
   322  			if rangeLen, err := strconv.ParseInt(rangeSplit[1], 10, 8); err == nil {
   323  				ipSplit := strings.Split(rangeSplit[0], ".")
   324  				if len(ipSplit) == 4 {
   325  					if lastSegment, err := strconv.ParseInt(ipSplit[3], 10, 8); err != nil {
   326  						lastSegment += rangeLen - 1
   327  						if lastSegment > 254 {
   328  							lastSegment = 254
   329  						}
   330  						return net.ParseIP(rangeSplit[0]), net.ParseIP(fmt.Sprintf("%s.%s.%s.%d", ipSplit[0], ipSplit[1], ipSplit[2], lastSegment)), nil
   331  					}
   332  				}
   333  			}
   334  		}
   335  	}
   336  	return nil, nil, errors.Errorf("can't parse netRange: %s", netRange)
   337  }