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