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 }