github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/gce/environ_network.go (about) 1 // Copyright 2017 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package gce 5 6 import ( 7 "fmt" 8 "strings" 9 10 "github.com/juju/collections/set" 11 "github.com/juju/errors" 12 "google.golang.org/api/compute/v1" 13 "gopkg.in/juju/names.v2" 14 15 "github.com/juju/juju/core/instance" 16 "github.com/juju/juju/environs" 17 "github.com/juju/juju/environs/context" 18 "github.com/juju/juju/network" 19 "github.com/juju/juju/provider/gce/google" 20 ) 21 22 type subnetMap map[string]network.SubnetInfo 23 type networkMap map[string]*compute.Network 24 25 // Subnets implements environs.NetworkingEnviron. 26 func (e *environ) Subnets(ctx context.ProviderCallContext, inst instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 27 // In GCE all the subnets are in all AZs. 28 zones, err := e.zoneNames(ctx) 29 if err != nil { 30 return nil, errors.Trace(err) 31 } 32 ids := makeIncludeSet(subnetIds) 33 var results []network.SubnetInfo 34 if inst == instance.UnknownId { 35 results, err = e.getMatchingSubnets(ctx, ids, zones) 36 } else { 37 results, err = e.getInstanceSubnets(ctx, inst, ids, zones) 38 } 39 if err != nil { 40 return nil, errors.Trace(err) 41 } 42 43 if missing := ids.Missing(); len(missing) != 0 { 44 return nil, errors.NotFoundf("subnets %v", formatMissing(missing)) 45 } 46 47 return results, nil 48 } 49 50 func (e *environ) zoneNames(ctx context.ProviderCallContext) ([]string, error) { 51 zones, err := e.AvailabilityZones(ctx) 52 if err != nil { 53 return nil, errors.Trace(err) 54 } 55 names := make([]string, len(zones)) 56 for i, zone := range zones { 57 names[i] = zone.Name() 58 } 59 return names, nil 60 } 61 62 func (e *environ) networksByURL(ctx context.ProviderCallContext) (networkMap, error) { 63 networks, err := e.gce.Networks() 64 if err != nil { 65 return nil, google.HandleCredentialError(errors.Trace(err), ctx) 66 } 67 results := make(networkMap) 68 for _, network := range networks { 69 results[network.SelfLink] = network 70 } 71 return results, nil 72 } 73 74 func (e *environ) getMatchingSubnets(ctx context.ProviderCallContext, subnetIds IncludeSet, zones []string) ([]network.SubnetInfo, error) { 75 allSubnets, err := e.gce.Subnetworks(e.cloud.Region) 76 if err != nil { 77 return nil, google.HandleCredentialError(errors.Trace(err), ctx) 78 } 79 networks, err := e.networksByURL(ctx) 80 if err != nil { 81 return nil, errors.Trace(err) 82 } 83 var results []network.SubnetInfo 84 for _, subnet := range allSubnets { 85 netwk, ok := networks[subnet.Network] 86 if !ok { 87 return nil, errors.NotFoundf("network %q for subnet %q", subnet.Network, subnet.Name) 88 } 89 if subnetIds.Include(subnet.Name) { 90 results = append(results, makeSubnetInfo( 91 network.Id(subnet.Name), 92 network.Id(netwk.Name), 93 subnet.IpCidrRange, 94 zones, 95 )) 96 } 97 } 98 // We have to include networks in 'LEGACY' mode that do not have subnetworks. 99 for _, netwk := range networks { 100 if netwk.IPv4Range != "" && subnetIds.Include(netwk.Name) { 101 results = append(results, makeSubnetInfo( 102 network.Id(netwk.Name), 103 network.Id(netwk.Name), 104 netwk.IPv4Range, 105 zones, 106 )) 107 } 108 } 109 return results, nil 110 } 111 112 func (e *environ) getInstanceSubnets(ctx context.ProviderCallContext, inst instance.Id, subnetIds IncludeSet, zones []string) ([]network.SubnetInfo, error) { 113 ifaces, err := e.NetworkInterfaces(ctx, inst) 114 if err != nil { 115 return nil, errors.Trace(err) 116 } 117 var results []network.SubnetInfo 118 for _, iface := range ifaces { 119 if subnetIds.Include(string(iface.ProviderSubnetId)) { 120 results = append(results, makeSubnetInfo( 121 iface.ProviderSubnetId, 122 iface.ProviderNetworkId, 123 iface.CIDR, 124 zones, 125 )) 126 } 127 } 128 return results, nil 129 } 130 131 // NetworkInterfaces implements environs.NetworkingEnviron. 132 func (e *environ) NetworkInterfaces(ctx context.ProviderCallContext, instId instance.Id) ([]network.InterfaceInfo, error) { 133 insts, err := e.Instances(ctx, []instance.Id{instId}) 134 if err != nil { 135 return nil, errors.Trace(err) 136 } 137 envInst, ok := insts[0].(*environInstance) 138 if !ok { 139 // This shouldn't happen. 140 return nil, errors.Errorf("couldn't extract google instance for %q", instId) 141 } 142 // In GCE all the subnets are in all AZs. 143 zones, err := e.zoneNames(ctx) 144 if err != nil { 145 return nil, errors.Trace(err) 146 } 147 networks, err := e.networksByURL(ctx) 148 if err != nil { 149 return nil, errors.Trace(err) 150 } 151 googleInst := envInst.base 152 ifaces := googleInst.NetworkInterfaces() 153 154 var subnetURLs []string 155 for _, iface := range ifaces { 156 if iface.Subnetwork != "" { 157 subnetURLs = append(subnetURLs, iface.Subnetwork) 158 } 159 } 160 subnets, err := e.subnetsByURL(ctx, subnetURLs, networks, zones) 161 if err != nil { 162 return nil, errors.Trace(err) 163 } 164 // We know there'll be a subnet for each url requested, otherwise 165 // there would have been an error. 166 167 var results []network.InterfaceInfo 168 for i, iface := range ifaces { 169 details, err := findNetworkDetails(iface, subnets, networks) 170 if err != nil { 171 return nil, errors.Annotatef(err, "instance %q", instId) 172 } 173 results = append(results, network.InterfaceInfo{ 174 DeviceIndex: i, 175 CIDR: details.cidr, 176 // The network interface has no id in GCE so it's 177 // identified by the machine's id + its name. 178 ProviderId: network.Id(fmt.Sprintf("%s/%s", instId, iface.Name)), 179 ProviderSubnetId: details.subnet, 180 ProviderNetworkId: details.network, 181 AvailabilityZones: copyStrings(zones), 182 InterfaceName: iface.Name, 183 Address: network.NewScopedAddress(iface.NetworkIP, network.ScopeCloudLocal), 184 InterfaceType: network.EthernetInterface, 185 Disabled: false, 186 NoAutoStart: false, 187 ConfigType: network.ConfigDHCP, 188 }) 189 } 190 return results, nil 191 } 192 193 type networkDetails struct { 194 cidr string 195 subnet network.Id 196 network network.Id 197 } 198 199 // findNetworkDetails looks up the network information we need to 200 // populate an InterfaceInfo - if the interface is on a legacy network 201 // we use information from the network because there'll be no subnet 202 // linked. 203 func findNetworkDetails(iface compute.NetworkInterface, subnets subnetMap, networks networkMap) (networkDetails, error) { 204 var result networkDetails 205 if iface.Subnetwork == "" { 206 // This interface is on a legacy network. 207 netwk, ok := networks[iface.Network] 208 if !ok { 209 return result, errors.NotFoundf("network %q", iface.Network) 210 } 211 result.cidr = netwk.IPv4Range 212 result.subnet = "" 213 result.network = network.Id(netwk.Name) 214 } else { 215 subnet, ok := subnets[iface.Subnetwork] 216 if !ok { 217 return result, errors.NotFoundf("subnet %q", iface.Subnetwork) 218 } 219 result.cidr = subnet.CIDR 220 result.subnet = subnet.ProviderId 221 result.network = subnet.ProviderNetworkId 222 } 223 return result, nil 224 } 225 226 func (e *environ) subnetsByURL(ctx context.ProviderCallContext, urls []string, networks networkMap, zones []string) (subnetMap, error) { 227 if len(urls) == 0 { 228 return make(map[string]network.SubnetInfo), nil 229 } 230 urlSet := includeSet{items: set.NewStrings(urls...)} 231 allSubnets, err := e.gce.Subnetworks(e.cloud.Region) 232 if err != nil { 233 return nil, google.HandleCredentialError(errors.Trace(err), ctx) 234 } 235 results := make(map[string]network.SubnetInfo) 236 for _, subnet := range allSubnets { 237 netwk, ok := networks[subnet.Network] 238 if !ok { 239 return nil, errors.NotFoundf("network %q for subnet %q", subnet.Network, subnet.Name) 240 } 241 if urlSet.Include(subnet.SelfLink) { 242 results[subnet.SelfLink] = makeSubnetInfo( 243 network.Id(subnet.Name), 244 network.Id(netwk.Name), 245 subnet.IpCidrRange, 246 zones, 247 ) 248 } 249 } 250 if missing := urlSet.Missing(); len(missing) != 0 { 251 return nil, errors.NotFoundf("subnets %v", formatMissing(missing)) 252 } 253 return results, nil 254 } 255 256 // SupportsSpaces implements environs.NetworkingEnviron. 257 func (e *environ) SupportsSpaces(ctx context.ProviderCallContext) (bool, error) { 258 return false, nil 259 } 260 261 // SupportsSpaceDiscovery implements environs.NetworkingEnviron. 262 func (e *environ) SupportsSpaceDiscovery(ctx context.ProviderCallContext) (bool, error) { 263 return false, nil 264 } 265 266 // Spaces implements environs.NetworkingEnviron. 267 func (e *environ) Spaces(ctx context.ProviderCallContext) ([]network.SpaceInfo, error) { 268 return nil, errors.NotSupportedf("spaces") 269 } 270 271 // SupportsContainerAddresses implements environs.NetworkingEnviron. 272 func (e *environ) SupportsContainerAddresses(ctx context.ProviderCallContext) (bool, error) { 273 return false, nil 274 } 275 276 // AllocateContainerAddresses implements environs.NetworkingEnviron. 277 func (e *environ) AllocateContainerAddresses(context.ProviderCallContext, instance.Id, names.MachineTag, []network.InterfaceInfo) ([]network.InterfaceInfo, error) { 278 return nil, errors.NotSupportedf("container addresses") 279 } 280 281 // ReleaseContainerAddresses implements environs.NetworkingEnviron. 282 func (e *environ) ReleaseContainerAddresses(context.ProviderCallContext, []network.ProviderInterfaceInfo) error { 283 return errors.NotSupportedf("container addresses") 284 } 285 286 // ProviderSpaceInfo implements environs.NetworkingEnviron. 287 func (*environ) ProviderSpaceInfo(ctx context.ProviderCallContext, space *network.SpaceInfo) (*environs.ProviderSpaceInfo, error) { 288 return nil, errors.NotSupportedf("provider space info") 289 } 290 291 // AreSpacesRoutable implements environs.NetworkingEnviron. 292 func (*environ) AreSpacesRoutable(ctx context.ProviderCallContext, space1, space2 *environs.ProviderSpaceInfo) (bool, error) { 293 return false, nil 294 } 295 296 // SSHAddresses implements environs.SSHAddresses. 297 // For GCE we want to make sure we're returning only one public address, so that probing won't 298 // cause SSHGuard to lock us out 299 func (*environ) SSHAddresses(ctx context.ProviderCallContext, addresses []network.Address) ([]network.Address, error) { 300 bestAddress, ok := network.SelectPublicAddress(addresses) 301 if ok { 302 return []network.Address{bestAddress}, nil 303 } else { 304 // fallback 305 return addresses, nil 306 } 307 } 308 309 // SuperSubnets implements environs.SuperSubnets 310 func (e *environ) SuperSubnets(ctx context.ProviderCallContext) ([]string, error) { 311 subnets, err := e.Subnets(ctx, "", nil) 312 if err != nil { 313 return nil, err 314 } 315 cidrs := make([]string, len(subnets)) 316 for i, subnet := range subnets { 317 cidrs[i] = subnet.CIDR 318 } 319 return cidrs, nil 320 } 321 322 func copyStrings(items []string) []string { 323 if items == nil { 324 return nil 325 } 326 result := make([]string, len(items)) 327 copy(result, items) 328 return result 329 } 330 331 func makeSubnetInfo(subnetId network.Id, networkId network.Id, cidr string, zones []string) network.SubnetInfo { 332 return network.SubnetInfo{ 333 ProviderId: subnetId, 334 ProviderNetworkId: networkId, 335 CIDR: cidr, 336 AvailabilityZones: copyStrings(zones), 337 VLANTag: 0, 338 SpaceProviderId: "", 339 } 340 } 341 342 // IncludeSet represents a set of items that can be crossed off once, 343 // and when you're finished crossing items off then you can see what's 344 // left. 345 type IncludeSet interface { 346 // Include returns whether this item should be included, and 347 // crosses it off. 348 Include(item string) bool 349 // Missing returns any items that haven't been crossed off (as a 350 // sorted slice). 351 Missing() []string 352 } 353 354 // includeAny allows any items and doesn't report any as missing. 355 type includeAny struct{} 356 357 // Include implements IncludeSet. 358 func (includeAny) Include(string) bool { return true } 359 360 // Missing implements IncludeSet. 361 func (includeAny) Missing() []string { return nil } 362 363 // includeSet is a set of items that we want to find in some results. 364 type includeSet struct { 365 items set.Strings 366 } 367 368 // Include implements IncludeSet. 369 func (s *includeSet) Include(item string) bool { 370 if s.items.Contains(item) { 371 s.items.Remove(item) 372 return true 373 } 374 return false 375 } 376 377 // Missing implements IncludeSet. 378 func (s *includeSet) Missing() []string { 379 return s.items.SortedValues() 380 } 381 382 func makeIncludeSet(ids []network.Id) IncludeSet { 383 if len(ids) == 0 { 384 return &includeAny{} 385 } 386 strings := set.NewStrings() 387 for _, id := range ids { 388 strings.Add(string(id)) 389 } 390 return &includeSet{items: strings} 391 } 392 393 func formatMissing(items []string) string { 394 parts := make([]string, len(items)) 395 for i, item := range items { 396 parts[i] = fmt.Sprintf("%q", item) 397 } 398 return "[" + strings.Join(parts, ", ") + "]" 399 }