Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Updates RangeSize error message and tests for IPv6. #47621

Merged
merged 1 commit into from
Oct 13, 2017

Conversation

danehans
Copy link

@danehans danehans commented Jun 15, 2017

What this PR does / why we need it:
Updates the RangeSize function's error message and tests for IPv6. Converts RangeSize unit test to a table test and tests for success and failure cases. This is needed to support IPv6. Previously, it was unclear whether RangeSize supported IPv6 CIDRs. These updates make IPv6 support explicit.

Which issue this PR fixes
Partially fixes Issue #1443

Special notes for your reviewer:
/area ipv6

Release note:

@k8s-ci-robot k8s-ci-robot added area/ipv6 cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. labels Jun 15, 2017
@k8s-github-robot k8s-github-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. release-note-none Denotes a PR that doesn't merit a release note. labels Jun 15, 2017
@@ -254,7 +254,7 @@ func calculateIPOffset(base *big.Int, ip net.IP) int {
func RangeSize(subnet *net.IPNet) int64 {
ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
panic("masks smaller than /2 for IPv4 or /98 for IPv6 are not supported.")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Just curious, does everyone say /1 is "smaller" than /2? Will it be ambiguous?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

That's a good point. Maybe shorter and longer is more appropriate for describing the "length" of the subnet mask. Let me know if you find these descriptions acceptable.

}, {
cidr: "192.168.1.0/28",
addrs: 999,
expect: false,
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why can't we use the correct addrs and test addrs==RangeSize(cidr)?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

OK. I'll update.

@caesarxuchao
Copy link
Member

/assign @MrHohn

@MrHohn
Copy link
Member

MrHohn commented Jun 17, 2017

Reassigning ipv6 PR.

/unassign

/assign @bowei

@caesarxuchao
Copy link
Member

lgtm, but will let someone from the networking team take a look.

@@ -254,7 +254,7 @@ func calculateIPOffset(base *big.Int, ip net.IP) int {
func RangeSize(subnet *net.IPNet) int64 {
ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
panic("masks shorter than /2 for IPv4 or /98 for IPv6 are not supported.")
Copy link
Contributor

@pmichali pmichali Jun 19, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If I understand this, the function is returning the number of hosts that can be expressed by the IPNet. That said, should the message reflect that, instead of in terms of the mask size? Maybe something like "Cannot support more than 2^30 hosts for IPNet 2001::4/64", for example.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In my experience, subnet masks are expressed using "length". I prefer using shorter or longer with a / notation to describe a subnet mask.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Sure. I'm just pointing out (the subtle point) that the method is returning the number of hosts available for the subnet, and wondering if the error message that would get returned, should focus on that, rather than on mask size. For example:

err: = fmt.Sprintf("too many hosts allowed for subnet (%v). Must be /%d or longer", subnet, bits-30)

However, see my last comment on L254, where maybe this should be redesigned to check for valid subnet lengths, rather than returning the number of hosts and having the caller doing some of the checking, with the rest done here. Then, the error messages could be in terms of mask size (from tests for it being too short or too long).

}, {
cidr: "2001:db8::/127",
addrs: 2,
},
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Only concern is that only passing cases are covered.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Agreed. I had fail cases in my original commit but I then removed them. As you mention in your other comment, I think the func needs to be updated to return an error.

Copy link
Contributor

@pmichali pmichali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some suggestions on the RangeSize function.

@@ -254,7 +254,7 @@ func calculateIPOffset(base *big.Int, ip net.IP) int {
func RangeSize(subnet *net.IPNet) int64 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Should the function signature be changed to return an error indication? This way, you could unit test for failure cases (I don't know how you can UT panics).

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Alternatively, should the function just return the # networks, 0+, and then the caller can check for validity of the number of the range? Right now, it determines the range of hosts AND checks for exceeding a maximum, but not a minimum. Looks like some callers are doing checking.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm open to making the changes. Maybe it's best to implement the changes and the failure case UT's in a follow-on PR? Lets see what @MrHohn has to say.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

My vote would be to address it now, since the submit queue is blocked. BTW the current assignment has @bowei, instead of MrHohn.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If possible I would change this function to return an error, we should try to avoid panic() in long running server code.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looking at the usage of this, the function panics (should be returning error, IMHO), if the subnet is too large, and if not, returns the number of hosts available, and the caller then checks if the subnet is too small. Maybe this should be changed to CheckSubnetSize() and return error if too large or too small, and caller can just check return (error) for non-nil?

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Looks like it is not too much work to propagate an error instead of panic here. Would be great to fix.

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@bowei I removed the panic. @thockin asked to return 0 instead of an error/panic.

@danehans
Copy link
Author

danehans commented Jul 6, 2017

@bowei when you have time, do you mind taking a look at this PR?

@bowei
Copy link
Member

bowei commented Jul 7, 2017

taking a look

@k8s-github-robot k8s-github-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/M Denotes a PR that changes 30-99 lines, ignoring generated files. labels Jul 11, 2017
@@ -156,13 +156,16 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
}

ServiceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface {
ServiceClusterIPAllocator, err := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface {
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

is there a reason why this ServiceClusterIPAllocator is Capitalized?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be capitalized. I updated to lowercase.

@@ -83,11 +87,11 @@ func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.Allocator
max: maximum(0, int(max-2)), // don't use the network broadcast,
}
r.alloc = allocatorFactory(r.max, rangeSpec)
return &r
return &r, err
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

shouldn't this be return &r, nil?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I have updated the return.

ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
return 0, fmt.Errorf("Subnet mask for %q is shorter than /2 for IPv4 or /98 for IPv6.", subnet)
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

"subnet mask must be >= /2 for IPv4 or /98 for IPv6" (change message to be about valid values)

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the error message.

Copy link
Author

@danehans danehans left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Updated based on @bowei comments.

@@ -156,13 +156,16 @@ func (c LegacyRESTStorageProvider) NewLegacyRESTStorage(restOptionsGetter generi
return LegacyRESTStorage{}, genericapiserver.APIGroupInfo{}, err
}

ServiceClusterIPAllocator := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface {
ServiceClusterIPAllocator, err := ipallocator.NewAllocatorCIDRRange(&serviceClusterIPRange, func(max int, rangeSpec string) allocator.Interface {
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It should not be capitalized. I updated to lowercase.

@@ -83,11 +87,11 @@ func NewAllocatorCIDRRange(cidr *net.IPNet, allocatorFactory allocator.Allocator
max: maximum(0, int(max-2)), // don't use the network broadcast,
}
r.alloc = allocatorFactory(r.max, rangeSpec)
return &r
return &r, err
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thanks for catching this. I have updated the return.

ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
return 0, fmt.Errorf("Subnet mask for %q is shorter than /2 for IPv4 or /98 for IPv6.", subnet)
Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I have updated the error message.

Copy link
Member

@bowei bowei left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

otherwise looks ok to me

ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
return 0, fmt.Errorf("subnet mask must be longer than /1 for IPv4 or /97 for IPv6")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Put exported error at the top:

ErrMaskTooSmall = error.New("subnet mask must be longer than /1 for IPv4 or /97 for IPv6")

then

return 0, ErrMaskTooSmall

numAddresses := ipallocator.RangeSize(svcSubnet)
numAddresses, err := ipallocator.RangeSize(svcSubnet)
if err != nil {
allErrs = append(allErrs, field.Invalid(fldPath, subnet, "subnet mask is too short"))
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

A bit more future proof:

if err != nil {
  if err == ipallocator.ErrMaskTooSmall {
    allErrs = append(allErrs, field.Invalid(fldPath, subnet, "subnet mask is too short"))
  } else {
        allErrs = append(allErrs, field.Invalid(fldPath, subnet, "invalid service subnet"))
  }
}

@danehans
Copy link
Author

@bowei sorry it took me so long to address your feedback. I have updated the PR based on your feedback and tests pass. Let me know if you need anything for a lgtm. Thanks for your help.

@bowei
Copy link
Member

bowei commented Aug 15, 2017

/lgtm thanks for carrying this over the finish line!

@bowei
Copy link
Member

bowei commented Aug 15, 2017

/approve

Copy link
Contributor

@pmichali pmichali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@dims
Copy link
Member

dims commented Sep 29, 2017

/retest

@danehans
Copy link
Author

@bowei @mikedanese do you mind reviewing this PR when you have a moment?

@danehans
Copy link
Author

danehans commented Oct 5, 2017

@caesarxuchao do you mind reviewing this PR when you have a moment? This needed for adding IPv6 support in the 1.9 release.

/assign @thockin

name string
cidr string
addrs int64
expect bool
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The name expect isn't clear to me. What does it expect?

@caesarxuchao
Copy link
Member

A nit. I don't see other problems, but I'm not experienced in the network world. @bowei @dnardo could you take a look?

/unassign @caesarxuchao

@@ -42,6 +42,7 @@ var (
ErrFull = errors.New("range is full")
ErrAllocated = errors.New("provided IP is already allocated")
ErrMismatchedNetwork = errors.New("the provided network does not match the current range")
ErrMaskTooSmall = errors.New("subnet mask must be longer than /1 for IPv4 or longer than /65 for IPv6")
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure what /1 means here - do you mean /31 ? 10.0.0.0/1 is nonsense?

Copy link
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I mean the mask must be /2 or longer (/8, /16, etc.). I did not change any of the existing IPv4 logic in this PR. I updated the error message because it was incorrect.

Old Error:masks greater than 31 bits are not supported

As you can see in the RangeSize function, (bits - ones) >= 31. For IPv4, bits = 32 and ones is the number of masked bits in the 32-bit address. For a /1, the math would trigger the error ``(32 - 1) >= 31`. I am happy to submit a PR to implement a fix if this is not the expected behavior, unless you prefer the fix to be implemented in this PR.

@thockin
Copy link
Member

thockin commented Oct 9, 2017 via email

@danehans
Copy link
Author

danehans commented Oct 9, 2017

I can change the notation in the error message from a /, to specifying the number of masked bits supported. For example:

ErrMaskTooSmall = errors.New("subnet mask must be longer than 1 bit for IPv4 or longer than 65 bits for IPv6")

I used the / notation in the error message since a CIDR is given as parameter to the function, i.e. 10.10.0.0/16. It's my understanding that subnet masks are typically described using "length" as the number of masked bits (1's) in an address and I believe the Size() method follows this understanding.

Here is a Go Playground example: https://play.golang.org/p/6r4JwMs1TL

I apologize if I'm not following you.

@thockin
Copy link
Member

thockin commented Oct 9, 2017

I think there are a couple things being conflated.

The panic() test for > 31 is pretty horrible, I admit, and I don't actually recall why it is needed. It certainly was not about policy of "too big" or "too small". I don't see why it can't simply return 0, as in "the range you specified has 0 available addresses". The policy of how to handle that should be at the callers, I think?

NewAllocatorCIDRRange() wants to subtract 2, so it should probably check that the values from RangeSize() is at least 2, otherwise return an error (or fake it like it does today).

Basically, could teh whole PR be reduced to removing that panic and just allowing it to return 0 ?

@danehans
Copy link
Author

danehans commented Oct 9, 2017

@thockin I like the idea of simply returning 0 and removing the error return. I can check max >= 2 in NewAllocatorCIDRRange. If so, should NewAllocatorCIDRRange return an empty *range or add an error return to the function?

@k8s-ci-robot k8s-ci-robot added size/M Denotes a PR that changes 30-99 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Oct 9, 2017
@danehans
Copy link
Author

@thockin the PR has been greatly simplified and is passing tests.

@danehans
Copy link
Author

@bowei when you have a moment, do you mind reviewing this PR? The PR has been refactored based on recent feedback from @thockin

Copy link
Contributor

@pmichali pmichali left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

/lgtm

@@ -263,8 +263,8 @@ func calculateIPOffset(base *big.Int, ip net.IP) int {
// RangeSize returns the size of a range in valid addresses.
func RangeSize(subnet *net.IPNet) int64 {
ones, bits := subnet.Mask.Size()
if (bits - ones) >= 31 {
panic("masks greater than 31 bits are not supported")
if bits == 32 && (bits-ones) >= 31 || bits == 128 && (bits-ones) >= 63 {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As a future clean-up, we should look at making this easier to understand (or supplement it with a comment to indicate that if the network bits are less than 2 for IPv4 or less than 65 for IPv6 it fails. OK for now.

@k8s-ci-robot k8s-ci-robot added the lgtm "Looks good to me", indicates that a PR is ready to be merged. label Oct 11, 2017
@bowei
Copy link
Member

bowei commented Oct 11, 2017

/lgtm

@bowei
Copy link
Member

bowei commented Oct 11, 2017

/approve no-issue

@danehans
Copy link
Author

@thockin this PR needs one more approver. Do you mind taking a look when you have a few minutes to spare?

@bowei
Copy link
Member

bowei commented Oct 13, 2017

@wojtek-t for approval

@wojtek-t
Copy link
Member

/approve no-issue

@k8s-github-robot
Copy link

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: bowei, danehans, leblancd, pmichali, wojtek-t

Associated issue: 1443

The full list of commands accepted by this bot can be found here.

Needs approval from an approver in each of these OWNERS Files:

You can indicate your approval by writing /approve in a comment
You can cancel your approval by writing /approve cancel in a comment

@k8s-github-robot k8s-github-robot added the approved Indicates a PR has been approved by an approver from all required OWNERS files. label Oct 13, 2017
@k8s-github-robot
Copy link

/test all [submit-queue is verifying that this PR is safe to merge]

@k8s-github-robot
Copy link

Automatic merge from submit-queue. If you want to cherry-pick this change to another branch, please follow the instructions here.

@k8s-github-robot k8s-github-robot merged commit e51e714 into kubernetes:master Oct 13, 2017
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. area/ipv6 cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. lgtm "Looks good to me", indicates that a PR is ready to be merged. release-note-none Denotes a PR that doesn't merit a release note. size/M Denotes a PR that changes 30-99 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.