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 }