github.com/niedbalski/juju@v0.0.0-20190215020005-8ff100488e47/provider/openstack/networking.go (about) 1 // Copyright 2012-2016 Canonical Ltd. 2 // Licensed under the AGPLv3, see LICENCE file for details. 3 4 package openstack 5 6 import ( 7 "fmt" 8 "net" 9 "strings" 10 "sync" 11 12 "github.com/juju/collections/set" 13 "github.com/juju/errors" 14 "github.com/juju/utils" 15 "gopkg.in/goose.v2/neutron" 16 "gopkg.in/goose.v2/nova" 17 18 "github.com/juju/juju/core/instance" 19 "github.com/juju/juju/network" 20 ) 21 22 // Networking is an interface providing networking-related operations 23 // for an OpenStack Environ. 24 type Networking interface { 25 // AllocatePublicIP allocates a public (floating) IP 26 // to the specified instance. 27 AllocatePublicIP(instance.Id) (*string, error) 28 29 // DefaultNetworks returns the set of networks that should be 30 // added by default to all new instances. 31 DefaultNetworks() ([]nova.ServerNetworks, error) 32 33 // ResolveNetwork takes either a network ID or label 34 // with a string to specify whether the network is external 35 // and returns the corresponding network ID. 36 ResolveNetwork(string, bool) (string, error) 37 38 // Subnets returns basic information about subnets known 39 // by OpenStack for the environment. 40 // Needed for Environ.Networking 41 Subnets(instance.Id, []network.Id) ([]network.SubnetInfo, error) 42 43 // NetworkInterfaces requests information about the network 44 // interfaces on the given instance. 45 // Needed for Environ.Networking 46 NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) 47 } 48 49 // NetworkingDecorator is an interface that provides a means of overriding 50 // the default Networking implementation. 51 type NetworkingDecorator interface { 52 // DecorateNetworking can be used to return a new Networking 53 // implementation that overrides the provided, default Networking 54 // implementation. 55 DecorateNetworking(Networking) (Networking, error) 56 } 57 58 // switchingNetworking is an implementation of Networking that delegates 59 // to either Neutron networking (preferred), or legacy Nova networking if 60 // there is no support for Neutron. 61 type switchingNetworking struct { 62 env *Environ 63 64 mu sync.Mutex 65 networking Networking 66 } 67 68 func (n *switchingNetworking) initNetworking() error { 69 n.mu.Lock() 70 defer n.mu.Unlock() 71 if n.networking != nil { 72 return nil 73 } 74 75 client := n.env.client() 76 if !client.IsAuthenticated() { 77 if err := authenticateClient(client); err != nil { 78 return errors.Trace(err) 79 } 80 } 81 82 base := networkingBase{env: n.env} 83 if n.env.supportsNeutron() { 84 n.networking = &NeutronNetworking{base} 85 } else { 86 n.networking = &LegacyNovaNetworking{base} 87 } 88 return nil 89 } 90 91 // AllocatePublicIP is part of the Networking interface. 92 func (n *switchingNetworking) AllocatePublicIP(instId instance.Id) (*string, error) { 93 if err := n.initNetworking(); err != nil { 94 return nil, errors.Trace(err) 95 } 96 return n.networking.AllocatePublicIP(instId) 97 } 98 99 // DefaultNetworks is part of the Networking interface. 100 func (n *switchingNetworking) DefaultNetworks() ([]nova.ServerNetworks, error) { 101 if err := n.initNetworking(); err != nil { 102 return nil, errors.Trace(err) 103 } 104 return n.networking.DefaultNetworks() 105 } 106 107 // ResolveNetwork is part of the Networking interface. 108 func (n *switchingNetworking) ResolveNetwork(name string, external bool) (string, error) { 109 if err := n.initNetworking(); err != nil { 110 return "", errors.Trace(err) 111 } 112 return n.networking.ResolveNetwork(name, external) 113 } 114 115 // Subnets is part of the Networking interface. 116 func (n *switchingNetworking) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 117 if err := n.initNetworking(); err != nil { 118 return nil, errors.Trace(err) 119 } 120 return n.networking.Subnets(instId, subnetIds) 121 } 122 123 // NetworkInterfaces is part of the Networking interface 124 func (n *switchingNetworking) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) { 125 if err := n.initNetworking(); err != nil { 126 return nil, errors.Trace(err) 127 } 128 return n.networking.NetworkInterfaces(instId) 129 } 130 131 type networkingBase struct { 132 env *Environ 133 } 134 135 func processResolveNetworkIds(name string, networkIds []string) (string, error) { 136 switch len(networkIds) { 137 case 1: 138 return networkIds[0], nil 139 case 0: 140 return "", errors.Errorf("no networks exist with label %q", name) 141 } 142 return "", errors.Errorf("multiple networks with label %q: %v", name, networkIds) 143 } 144 145 // NeutronNetworking is an implementation of Networking that uses the Neutron 146 // network APIs. 147 type NeutronNetworking struct { 148 networkingBase 149 } 150 151 // networkFilter returns a neutron.Filter to match Neutron Networks with 152 // the exact given name AND router:external boolean result. 153 func projectIdFilter(projectId string) *neutron.Filter { 154 filter := neutron.NewFilter() 155 filter.Set(neutron.FilterProjectId, projectId) 156 return filter 157 } 158 159 // AllocatePublicIP is part of the Networking interface. 160 func (n *NeutronNetworking) AllocatePublicIP(instId instance.Id) (*string, error) { 161 extNetworkIds := make([]string, 0) 162 neutronClient := n.env.neutron() 163 externalNetwork := n.env.ecfg().externalNetwork() 164 if externalNetwork != "" { 165 // the config specified an external network, try it first. 166 netId, err := resolveNeutronNetwork(neutronClient, externalNetwork, true) 167 if err != nil { 168 logger.Debugf("external network %s not found, search for one", externalNetwork) 169 } else { 170 logger.Debugf("using external network %q", externalNetwork) 171 extNetworkIds = []string{netId} 172 } 173 } 174 175 if len(extNetworkIds) == 0 { 176 // Create slice of network.Ids for external networks in the same AZ as 177 // the instance's network, to find an existing floating ip in, or allocate 178 // a new floating ip from. 179 network := n.env.ecfg().network() 180 netId, err := resolveNeutronNetwork(neutronClient, network, false) 181 netDetails, err := neutronClient.GetNetworkV2(netId) 182 if err != nil { 183 return nil, errors.Trace(err) 184 } 185 186 for _, az := range netDetails.AvailabilityZones { 187 extNetIds, _ := getExternalNeutronNetworksByAZ(n.env, az) 188 if len(extNetIds) > 0 { 189 extNetworkIds = append(extNetworkIds, extNetIds...) 190 } 191 } 192 193 if len(extNetworkIds) == 0 { 194 return nil, errors.NewNotFound(nil, fmt.Sprintf("could not find an external network in availability zone %s", netDetails.AvailabilityZones)) 195 } 196 } 197 198 // Look for FIPs in same project as the credentials. 199 // Admins have visibility into other projects. 200 fips, err := n.env.neutron().ListFloatingIPsV2(projectIdFilter(n.env.client().TenantId())) 201 if err != nil { 202 return nil, errors.Trace(err) 203 } 204 205 // Is there an unused FloatingIP on an external network in the instance's availability zone? 206 for _, fip := range fips { 207 if fip.FixedIP == "" { 208 // Not a perfect solution. If an external network was specified in the 209 // config, it'll be at the top of the extNetworkIds, but may be not used 210 // if the available FIP isn't it in. However the instance and the 211 // FIP will be in the same availability zone. 212 for _, extNetId := range extNetworkIds { 213 if fip.FloatingNetworkId == extNetId { 214 logger.Debugf("found unassigned public ip: %v", fip.IP) 215 return &fip.IP, nil 216 } 217 } 218 } 219 } 220 221 // allocate a new IP and use it 222 var lastErr error 223 for _, extNetId := range extNetworkIds { 224 var newfip *neutron.FloatingIPV2 225 newfip, lastErr = neutronClient.AllocateFloatingIPV2(extNetId) 226 if lastErr == nil { 227 logger.Debugf("allocated new public IP: %s", newfip.IP) 228 return &newfip.IP, nil 229 } 230 } 231 232 logger.Debugf("Unable to allocate a public IP") 233 return nil, lastErr 234 } 235 236 // externalNetworkFilter returns a neutron.Filter to match Neutron Networks with 237 // router:external = true. 238 func externalNetworkFilter() *neutron.Filter { 239 filter := neutron.NewFilter() 240 filter.Set(neutron.FilterRouterExternal, "true") 241 return filter 242 } 243 244 // getExternalNeutronNetworksByAZ returns all external networks within the 245 // given availability zone. If azName is empty, return all external networks. 246 func getExternalNeutronNetworksByAZ(e *Environ, azName string) ([]string, error) { 247 neutron := e.neutron() 248 // Find all external networks in availability zone 249 networks, err := neutron.ListNetworksV2(externalNetworkFilter()) 250 if err != nil { 251 return nil, errors.Trace(err) 252 } 253 netIds := make([]string, 0) 254 for _, network := range networks { 255 for _, netAZ := range network.AvailabilityZones { 256 if azName == netAZ { 257 netIds = append(netIds, network.Id) 258 break 259 } 260 } 261 } 262 if len(netIds) == 0 { 263 return nil, errors.NewNotFound(nil, "No External networks found to allocate a Floating IP") 264 } 265 return netIds, nil 266 } 267 268 // DefaultNetworks is part of the Networking interface. 269 func (n *NeutronNetworking) DefaultNetworks() ([]nova.ServerNetworks, error) { 270 return []nova.ServerNetworks{}, nil 271 } 272 273 // ResolveNetwork is part of the Networking interface. 274 func (n *NeutronNetworking) ResolveNetwork(name string, external bool) (string, error) { 275 return resolveNeutronNetwork(n.env.neutron(), name, external) 276 } 277 278 // networkFilter returns a neutron.Filter to match Neutron Networks with 279 // the exact given name AND router:external boolean result. 280 func networkFilter(name string, external bool) *neutron.Filter { 281 filter := neutron.NewFilter() 282 filter.Set(neutron.FilterNetwork, fmt.Sprintf("%s", name)) 283 filter.Set(neutron.FilterRouterExternal, fmt.Sprintf("%t", external)) 284 return filter 285 } 286 287 func resolveNeutronNetwork(neutron *neutron.Client, name string, external bool) (string, error) { 288 if utils.IsValidUUIDString(name) { 289 return name, nil 290 } 291 networks, err := neutron.ListNetworksV2(networkFilter(name, external)) 292 if err != nil { 293 return "", err 294 } 295 var networkIds []string 296 for _, network := range networks { 297 networkIds = append(networkIds, network.Id) 298 } 299 return processResolveNetworkIds(name, networkIds) 300 } 301 302 func makeSubnetInfo(neutron *neutron.Client, subnet neutron.SubnetV2) (network.SubnetInfo, error) { 303 _, _, err := net.ParseCIDR(subnet.Cidr) 304 if err != nil { 305 return network.SubnetInfo{}, errors.Annotatef(err, "skipping subnet %q, invalid CIDR", subnet.Cidr) 306 } 307 net, err := neutron.GetNetworkV2(subnet.NetworkId) 308 if err != nil { 309 return network.SubnetInfo{}, err 310 } 311 312 // TODO (hml) 2017-03-20: 313 // With goose updates, VLANTag can be updated to be 314 // network.segmentation_id, if network.network_type equals vlan 315 info := network.SubnetInfo{ 316 CIDR: subnet.Cidr, 317 ProviderId: network.Id(subnet.Id), 318 VLANTag: 0, 319 AvailabilityZones: net.AvailabilityZones, 320 SpaceProviderId: "", 321 } 322 logger.Tracef("found subnet with info %#v", info) 323 return info, nil 324 } 325 326 // Subnets returns basic information about the specified subnets known 327 // by the provider for the specified instance or list of ids. subnetIds can be 328 // empty, in which case all known are returned. 329 func (n *NeutronNetworking) Subnets(instId instance.Id, subnetIds []network.Id) ([]network.SubnetInfo, error) { 330 netIds := set.NewStrings() 331 neutron := n.env.neutron() 332 internalNet := n.env.ecfg().network() 333 netId, err := resolveNeutronNetwork(neutron, internalNet, false) 334 if err != nil { 335 // Note: (jam 2018-05-23) We don't treat this as fatal because we used to never pay attention to it anyway 336 if internalNet == "" { 337 logger.Warningf(noNetConfigMsg(err)) 338 } else { 339 logger.Warningf("could not resolve internal network id for %q: %v", internalNet, err) 340 } 341 } else { 342 netIds.Add(netId) 343 // Note, there are cases where we will detect an external 344 // network without it being explicitly configured by the user. 345 // When we get to a point where we start detecting spaces for users 346 // on Openstack, we'll probably need to include better logic here. 347 externalNet := n.env.ecfg().externalNetwork() 348 if externalNet != "" { 349 netId, err := resolveNeutronNetwork(neutron, externalNet, true) 350 if err != nil { 351 logger.Warningf("could not resolve external network id for %q: %v", externalNet, err) 352 } else { 353 netIds.Add(netId) 354 } 355 } 356 } 357 logger.Debugf("finding subnets in networks: %s", strings.Join(netIds.Values(), ", ")) 358 359 subIdSet := set.NewStrings() 360 for _, subId := range subnetIds { 361 subIdSet.Add(string(subId)) 362 } 363 364 var results []network.SubnetInfo 365 if instId != instance.UnknownId { 366 // TODO(hml): 2017-03-20 367 // Implement Subnets() for case where instId is specified 368 return nil, errors.NotSupportedf("neutron subnets with instance Id") 369 } else { 370 // TODO(jam): 2018-05-23 It is likely that ListSubnetsV2 could 371 // take a Filter rather that doing the filtering client side. 372 subnets, err := neutron.ListSubnetsV2() 373 if err != nil { 374 return nil, errors.Annotatef(err, "failed to retrieve subnets") 375 } 376 if len(subnetIds) == 0 { 377 for _, subnet := range subnets { 378 // TODO (manadart 2018-07-17): If there was an error resolving 379 // an internal network ID, then no subnets will be discovered. 380 // The user will get an error attempting to add machines to 381 // this model and will have to update model config with a 382 // network name; but this does not re-discover the subnets. 383 // If subnets/spaces become important, we will have to address 384 // this somehow. 385 if !netIds.Contains(subnet.NetworkId) { 386 logger.Tracef("ignoring subnet %q, part of network %q", subnet.Id, subnet.NetworkId) 387 continue 388 } 389 subIdSet.Add(subnet.Id) 390 } 391 } 392 for _, subnet := range subnets { 393 if !subIdSet.Contains(subnet.Id) { 394 logger.Tracef("subnet %q not in %v, skipping", subnet.Id, subnetIds) 395 continue 396 } 397 subIdSet.Remove(subnet.Id) 398 if info, err := makeSubnetInfo(neutron, subnet); err == nil { 399 // Error will already have been logged. 400 results = append(results, info) 401 } 402 } 403 } 404 if !subIdSet.IsEmpty() { 405 return nil, errors.Errorf("failed to find the following subnet ids: %v", subIdSet.Values()) 406 } 407 return results, nil 408 } 409 410 // noNetConfigMsg is used to present resolution options when an error is 411 // encountered due to missing "network" configuration. 412 // Any error from attempting to resolve a network without network 413 // config set, is likely due to the resolution returning multiple 414 // internal networks. 415 func noNetConfigMsg(err error) string { 416 return fmt.Sprintf( 417 "%s\n\tTo resolve this error, set a value for \"network\" in model-config or model-defaults;"+ 418 "\n\tor supply it via --config when creating a new model", 419 err.Error()) 420 } 421 422 func (n *NeutronNetworking) NetworkInterfaces(instId instance.Id) ([]network.InterfaceInfo, error) { 423 return nil, errors.NotSupportedf("neutron network interfaces") 424 }