Skip to content

Commit

Permalink
Add helpers for infering space from address or CIDR+provider subnetID
Browse files Browse the repository at this point in the history
The infer logic for addresses works by parsing the subnet CIDRs and
attempting to match the specified address to a single CIDR (multiple
matches cause an error to be returned).

The expectation is that the code using the infer helpers will obtain the
SpaceInfos once and then iterate on a list of addresses. In order to
avoid having to parse CIDRs every single time, this commit tweaks
network.SubnetInfo so that it memoizes parsed CIDRs and adds the
ParsedCIDRNetwork helper to get the parsed CIDR instance.
  • Loading branch information
achilleasa committed Dec 4, 2019
1 parent 2a244ae commit 37e32d9
Show file tree
Hide file tree
Showing 3 changed files with 141 additions and 6 deletions.
58 changes: 58 additions & 0 deletions core/network/space.go
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,10 @@ package network

import (
"fmt"
"net"
"strings"

"github.com/juju/errors"
)

const (
Expand Down Expand Up @@ -47,6 +50,13 @@ type SpaceInfo struct {
// SpaceInfos is a collection of spaces.
type SpaceInfos []SpaceInfo

// AllSpaceInfos satisfies the SpaceLookup interface.
// It is useful for passing to conversions where we already have the spaces
// materialised and don't need to pull them from the DB again.
func (s SpaceInfos) AllSpaceInfos() (SpaceInfos, error) {
return s, nil
}

// String returns returns a quoted, comma-delimited names of the spaces in the
// collection, or <none> if the collection is empty.
func (s SpaceInfos) String() string {
Expand Down Expand Up @@ -124,3 +134,51 @@ func (s SpaceInfos) Minus(other SpaceInfos) SpaceInfos {
}
return result
}

func (s SpaceInfos) InferSpaceFromAddress(addr string) (*SpaceInfo, error) {
var (
ip = net.ParseIP(addr)
match *SpaceInfo
)

nextSpace:
for spIndex, space := range s {
for _, subnet := range space.Subnets {
ipNet, err := subnet.ParsedCIDRNetwork()
if err != nil {
// Subnets should always have a valid CIDR
return nil, errors.Trace(err)
}

if ipNet.Contains(ip) {
if match == nil {
match = &s[spIndex]

// We still need to check other spaces
// in case we have multiple networks
// with the same subnet CIDRs
continue nextSpace
}

return nil, errors.Errorf("unable to infer space for address %q: address matches the same CIDR in multiple spaces", addr)
}
}
}

if match == nil {
return nil, errors.NewNotFound(nil, fmt.Sprintf("unable to infer space for address %q", addr))
}
return match, nil
}

func (s SpaceInfos) InferSpaceFromCIDRAndSubnetID(cidr, providerSubnetID string) (*SpaceInfo, error) {
for _, space := range s {
for _, subnet := range space.Subnets {
if subnet.CIDR == cidr && string(subnet.ProviderId) == providerSubnetID {
return &space, nil
}
}
}

return nil, errors.NewNotFound(nil, fmt.Sprintf("unable to infer space for CIDR %q and provider subnet ID %q", cidr, providerSubnetID))
}
62 changes: 62 additions & 0 deletions core/network/space_test.go
Original file line number Diff line number Diff line change
Expand Up @@ -65,3 +65,65 @@ func (s *spaceSuite) TestMinuxNoDiff(c *gc.C) {
result := s.spaces.Minus(infos)
c.Assert(result, gc.DeepEquals, network.SpaceInfos{})
}

func (s *spaceSuite) TestInferSpaceFromAddress(c *gc.C) {
infos := network.SpaceInfos{
{ID: "1", Name: "space1", Subnets: []network.SubnetInfo{{CIDR: "10.0.0.0/24"}}},
{ID: "2", Name: "space2", Subnets: []network.SubnetInfo{{CIDR: "10.0.1.0/24"}}},
{ID: "3", Name: "space3", Subnets: []network.SubnetInfo{{CIDR: "10.0.2.0/24"}}},
}

queries := map[string]network.SpaceName{
"10.0.0.42": "space1",
"10.0.1.1": "space2",
"10.0.2.99": "space3",
}

for addr, expSpaceName := range queries {
si, err := infos.InferSpaceFromAddress(addr)
c.Assert(err, jc.ErrorIsNil, gc.Commentf("infer space for address %q", addr))
c.Assert(si.Name, gc.Equals, expSpaceName, gc.Commentf("infer space for address %q", addr))
}

// Check that CIDR collisions are detected
infos = append(
infos,
network.SpaceInfo{ID: "-3", Name: "inverse", Subnets: []network.SubnetInfo{{CIDR: "10.0.2.0/24"}}},
)

_, err := infos.InferSpaceFromAddress("10.0.2.255")
c.Assert(err, gc.ErrorMatches, ".*address matches the same CIDR in multiple spaces")

// Check for no-match-found
_, err = infos.InferSpaceFromAddress("99.99.99.99")
c.Assert(err, gc.ErrorMatches, ".*unable to infer space for address.*")
}

func (s *spaceSuite) TestInferSpaceFromCIDRAndSubnetID(c *gc.C) {
infos := network.SpaceInfos{
{ID: "1", Name: "space1", Subnets: []network.SubnetInfo{{CIDR: "10.0.0.0/24", ProviderId: "1"}}},
{ID: "2", Name: "space2", Subnets: []network.SubnetInfo{{CIDR: "10.0.1.0/24", ProviderId: "2"}}},
}

si, err := infos.InferSpaceFromCIDRAndSubnetID("10.0.0.0/24", "1")
c.Assert(err, jc.ErrorIsNil)
c.Assert(si.Name, gc.Equals, network.SpaceName("space1"))

// Check for same CIDR/different provider
infos = append(
infos,
network.SpaceInfo{ID: "-2", Name: "inverse", Subnets: []network.SubnetInfo{{CIDR: "10.0.1.0/24", ProviderId: "3"}}},
)

si, err = infos.InferSpaceFromCIDRAndSubnetID("10.0.1.0/24", "2")
c.Assert(err, jc.ErrorIsNil)
c.Assert(si.Name, gc.Equals, network.SpaceName("space2"))

si, err = infos.InferSpaceFromCIDRAndSubnetID("10.0.1.0/24", "3")
c.Assert(err, jc.ErrorIsNil)
c.Assert(si.Name, gc.Equals, network.SpaceName("inverse"))

// Check for no-match-found
_, err = infos.InferSpaceFromCIDRAndSubnetID("10.0.1.0/24", "42")
c.Assert(err, gc.ErrorMatches, ".*unable to infer space.*")
}
27 changes: 21 additions & 6 deletions core/network/subnet.go
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,9 @@ type SubnetInfo struct {
// CIDR of the network, in 123.45.67.89/24 format.
CIDR string

// Memoized value for the parsed network for the above CIDR.
parsedCIDRNetwork *net.IPNet

// ProviderId is a provider-specific subnet ID.
ProviderId Id

Expand Down Expand Up @@ -97,13 +100,10 @@ func (s *SubnetInfo) FanOverlay() string {

// Validate validates the subnet, checking the CIDR, and VLANTag, if present.
func (s *SubnetInfo) Validate() error {
if s.CIDR != "" {
_, _, err := net.ParseCIDR(s.CIDR)
if err != nil {
return errors.Trace(err)
}
} else {
if s.CIDR == "" {
return errors.Errorf("missing CIDR")
} else if _, err := s.ParsedCIDRNetwork(); err != nil {
return errors.Trace(err)
}

if s.VLANTag < 0 || s.VLANTag > 4094 {
Expand All @@ -113,6 +113,21 @@ func (s *SubnetInfo) Validate() error {
return nil
}

// ParsedCIDRNetwork returns the network represented by the CIDR field.
func (s *SubnetInfo) ParsedCIDRNetwork() (*net.IPNet, error) {
// Memoize the CIDR the first time this method is called or if the
// CIDR field has changed.
if s.parsedCIDRNetwork == nil || s.parsedCIDRNetwork.String() != s.CIDR {
_, ipNet, err := net.ParseCIDR(s.CIDR)
if err != nil {
return nil, err
}

s.parsedCIDRNetwork = ipNet
}
return s.parsedCIDRNetwork, nil
}

// IsValidCidr returns whether cidr is a valid subnet CIDR.
func IsValidCidr(cidr string) bool {
_, ipNet, err := net.ParseCIDR(cidr)
Expand Down

0 comments on commit 37e32d9

Please sign in to comment.