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
Add new PortAllocator #2579
Add new PortAllocator #2579
Conversation
const ( | ||
// DynamicPortStart is the start of the dynamic port range from which node | ||
// ports will be allocated when the user did not specify a port. | ||
DynamicPortStart uint32 = 30000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I think you might be able to get away with an int16
, although I wouldn't recommend using an unsigned here.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Rather, you can just use a regular int
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I use uint32
because that's the type of PortConfig.PublishPort
.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it using a uint32
in the first place? TCP ports are limited to the uint16
range. I don't find any reason to use a higher capacity type.
} | ||
|
||
// Commit commits a change to the PortAllocator. There are no safety checks. | ||
func (pa *PortAllocator) Commit(p *Proposal) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would recommend moving the Commit
method to the Proposal
object itself. Checkout the bolt db api for an example of how this would work.
Can this method have an error?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I would like to avoid this method having an error. I'd like to avoid having this method. It's literally just because there's no easy way to roll back to a previous PortAllocator state if you abandon allocation.
// as a map key | ||
type port struct { | ||
// protocol represented by this port space | ||
protocol api.PortConfig_Protocol |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You can save a little space by having a struct with a port vector for each protocol:
type PortAllocator struct {
udp map[port]struct{}
tcp map[port]struct{}
sctp map[port]struct{}
}
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's possible map overhead removes this benefit, however.
Like we discussed, this is probably sparse. I did some experimentation with using big.Int
as a bit set and that is pretty minimal.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
It's easier to reason in the code about ports-as-a-value than it is to reason different-protocols-different-spaces.
The overhead of the extra data in each port object is minimal compared to the overhead of literally all the other dumb inefficient crap I'm doing, so even if we optimized here, it would be the tiniest win.
48c10ac
to
6659c83
Compare
Updated with more tests, changed the Proposal to have a Commit method. |
If we're agreed on adding Ginkgo, please add it as a separate commit (and maybe PR). |
fe03354
to
a683ac2
Compare
I realized that mutating the endpoint object in |
I don't think adding Ginkgo as a separate PR is helpful. I could agree that putting the vendoring into a separate commit might be though. |
6b6d186
to
ee3d8ab
Compare
Codecov Report
@@ Coverage Diff @@
## master #2579 +/- ##
==========================================
+ Coverage 61.74% 61.83% +0.08%
==========================================
Files 134 135 +1
Lines 21763 21893 +130
==========================================
+ Hits 13438 13537 +99
- Misses 6869 6905 +36
+ Partials 1456 1451 -5 |
ee3d8ab
to
5cb1087
Compare
It's always a good feeling when you're writing tests to catch an error in your code. |
5cb1087
to
9f81f45
Compare
91.8% test coverage of the new code. I just need to figure out what currently isn't tested. |
9f81f45
to
5443a00
Compare
100.0% test coverage. My work here is done. |
I wrote a utility to spit out the specs of what's being tested. Here's the summary:
|
5443a00
to
442b6bb
Compare
rebased and added |
442b6bb
to
d2bf98b
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Thanks for working on this @dperny and adding so much test coverage!!
I have a couple questions - what happens in the following cases:
- If I had a previously allocated port, but changed the publish mode to host? Is the previously allocated port never deallocated?
- If we're near exhausting the port space, and I update the endpoint to change the name of the dynamically allocated ports, will it allocate me the new ports or will I get an exhaustion error?
Another question, which is I think something that may be outside the scope of the port allocator, and perhaps would just be useful to comment on in doc.go
- what happens if someone specifies a conflicting host port? Do you know if this is handled by some other part of swarm?
// no PublishedPort. | ||
// | ||
// Notably, the allocator only keeps track of which ports are in use, not which | ||
// servies they are in use by. For the port allocator to function correctly, |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Spelling nit: "servies" -> "services"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
good catch.
Commit() | ||
|
||
// IsNoop returns true if the Proposal doesn't actually modify allocator | ||
// state This does not mean the endpoint object has not been modified, just |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Punctuation nit: "state This" -> "state. This"
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Also further down, "it hasn't gained or lost any allocated published ports": do you mean "the allocator hasn't gained or lost any allocated published ports"? Or do you specifically mean the endpoint? Could you also give an example of when this could happen, not in a test?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Another good catch, will clarify.
return prop.ports | ||
} | ||
|
||
// Commit commits the proposal to the port allocator. Part of our |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Comment here ends with a half-finished sentence.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I have no idea what you
(this is a joke i'll fix it)
// Commit commits the proposal to the port allocator. Part of our | ||
func (prop *proposal) Commit() { | ||
if prop.IsNoop() { | ||
// cannot commit with nil port allocator |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to check prop.pa
for nil
, nor does prop.IsNoop()
or anything that it calls - should we add a check for prop.pa
? It seems like only Allocate
and Deallocate
construct prop.pa
, and the proposal is non-exported, so not sure if we need to actually do the check - maybe just remove the comment?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You're right. I'll remove the comment. I probably had the check in IsNoop and removed it.
} | ||
|
||
func (prop *proposal) IsNoop() bool { | ||
// if every port in allocate is in deallocate, and every port in deallocate |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Can we just check that they both have the same length, rather than do 2 loops (e.g. check that allocate has the same keys as deallocate)? I found this statement slightly more confusing than just saying that the ports in allocate and deallocate are the same.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
We can't check the length, because the actual ports in use may have changed. The comment can be reworded though.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oops sorry I meant do a length check, then a single loop as opposed to 2 loops. If they are the same length and every port in allocate is also in reallocate they must have the same keys I think
// Allocate does _not_ change the state of the Allocator. To | ||
// prevent the caller from having to do bulky rollback logic, we return a | ||
// "Proposal" has all the information required. In order to correctly allocate | ||
// another endpoint with this same Allocator, the caller must later call |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
To be clear, given that the allocator is not concurrency-safe, do you mean that if Commit()
is not called before Allocate
is called on a different endpoint, the two proposals may conflict? If so, would it make sense to clarify this case (where two endpoint proposals for the same set of ports exist?)
// one available | ||
for i := DynamicPortStart; i <= DynamicPortEnd; i++ { | ||
portObj.port = i | ||
if _, ok := pa.ports[portObj]; !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
This doesn't seem to take into account any deallocated ports. If we're near almost exhausting the port space, and this update deallocates 3 ports but needs to allocate 3 different ports, we should be able to do so (at least, by share number) but we can't, since this loop isn't checking to see if the taken port has been deallocated.
I can also see how maybe that makes sense to do because if we're changing the name of the port or something else about the port config, etc., maybe it'd be confusing if the changed port config gets dynamically allocated the same port on update.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Actually, yeah, you're right. I'll need to figure out the correct solution.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Fixed this issue by adding another loop further down checking if any dynamic ports are freed.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally non-blocking nit - that loop looks fine and definitely fixes the issue - I was just wondering if it'd be easier to understand as an extra else block here? Since this loop already iterates over only the dynamic ports, that would mean that you may not need an extra check for whether the deallocated port is in the dynamic ports?
Also you already have a port object you can use to index into the deallocate
map.
This is probably just a disfunction of how my brain works, but I find goto
(or continue <label>
) control flow a little more confusing to follow than just if else blocks.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doing this in two loops preserves the behavior of preferring to not reassign the same dynamic ports that were just freed over again. i don't think that that behavior is required, and i don't want to make that behavior part of the api contract, but it is rather nice. i can add a comment explaining that we try this but not to rely on it.
Also you already have a port object you can use to index into the deallocate map.
I'm brain farting on what you mean by this.
You're right about continue
, though, it is a slightly-less-harmful goto.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
doing this in two loops preserves the behavior of preferring to not reassign the same dynamic ports that were just freed over again. i don't think that that behavior is required, and i don't want to make that behavior part of the api contract, but it is rather nice. i can add a comment explaining that we try this but not to rely on it.
Ah that makes sense.
I'm brain farting on what you mean by this.
Er sorry, I meant if you had the one loop, you could just index into deallocated
with https://github.com/docker/swarmkit/pull/2579/files#diff-50dd126d07c8b730e5bae674f9542aa2R384.
Not really relevant if we want a separate loop as you say to try to change the dynamically allocated ports each time.
endpointCopy := endpoint.Copy() | ||
endpointCopy.Spec = spec | ||
endpointCopy.Ports = p.Ports() | ||
p.Commit() |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: would it make sense to add an extra check here before the commit to assert it's not a noop?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Sure, this test can actually probably be reworked a bit to verify that allocator state changes AFTER commit as well, but I thought I had that test someplace else...
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added a check for not noop to the "should succeed" case.
}) | ||
// Pending, we need to test this but I'm not sure what the appropriate | ||
// behavior should be | ||
PContext("to change publish mode", func() { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Are we still deciding what the appropriate behavior should be? It seems like nothing gets deallocated or allocated (so if it previously had an allocated port, changing the publish mode to host would not deallocate the previous port).
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Must've missed this when I was going through, I will add these test cases, thanks!
Expect(e).ToNot(HaveOccurred()) | ||
Expect(p).ToNot(BeNil()) | ||
Expect(p.IsNoop()).ToNot(BeTrue()) | ||
Expect(p.Ports()).ToNot(BeEmpty()) |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should we add more assertions that the previous dynamic allocation was freed?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Added this assertion.
Thanks for the excellent review!
It should be, actually, but there's no test case for it, so I'll add one and make sure.
You're right, actually, that it will give an exhaustion error, I think. I'm not sure, so I'll write a test case. |
d2bf98b
to
382d7fb
Compare
Updated PR:
|
382d7fb
to
e7e5503
Compare
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
LGTM, thanks for this very thoroughly tested module @dperny! Definitely easier to understand with all the test cases written!
The one other stylistic nitpick I have which does not actually need to be done is that the Allocate
function is very long - would it be easier to read if it were broken up into smaller non-exported functions, so that reading the function read a little more like pseudocode (e.g. the names of the helper functions), rather than the longer loop code? I'm totally fine with not doing this, as it's just a personal style preference.
// object, which contains the proposed changes to the Allocator. this | ||
// means if there is a failure in the caller after calling Allocate, the | ||
// caller can discard the changes | ||
prop := &proposal{ |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Non-blocking: I was wondering if we could just call Deallocate
here, and modify the proposal that it returned with an allocate
map? Since otherwise the code is the same.
// one available | ||
for i := DynamicPortStart; i <= DynamicPortEnd; i++ { | ||
portObj.port = i | ||
if _, ok := pa.ports[portObj]; !ok { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Totally non-blocking nit - that loop looks fine and definitely fixes the issue - I was just wondering if it'd be easier to understand as an extra else block here? Since this loop already iterates over only the dynamic ports, that would mean that you may not need an extra check for whether the deallocated port is in the dynamic ports?
Also you already have a port object you can use to index into the deallocate
map.
This is probably just a disfunction of how my brain works, but I find goto
(or continue <label>
) control flow a little more confusing to follow than just if else blocks.
Adds the ginkgo BDD testing framework. Signed-off-by: Drew Erny <drew.erny@docker.com>
Adds a new, more robust implementation of assigning ports. Tested with the ginkgo testing framework. Part of a series of changes rewriting the network allocator. Signed-off-by: Drew Erny <drew.erny@docker.com>
e7e5503
to
2ccbca9
Compare
split into two commits, one vendoring Ginkgo and the other adding the port allocator. |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I made some comments arround using uint16
but this may need to go in another place.
Additionally, port 0 is usually used when you don't mind which port it uses, it may have sense to have this same behaviour (when port 0 is specified select one in the dynamic range).
const ( | ||
// DynamicPortStart is the start of the dynamic port range from which node | ||
// ports will be allocated when the user did not specify a port. | ||
DynamicPortStart uint32 = 30000 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Why is it using a uint32
in the first place? TCP ports are limited to the uint16
range. I don't find any reason to use a higher capacity type.
|
||
// The end of master port range which will hold all the allocation state of | ||
// ports allocated so far regardless of whether it was user defined or not. | ||
masterPortEnd uint32 = 65535 |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Using an uint16
, you may not need these two variables as they are the type limits.
// | ||
// Used mostly for testing, and should probably not be relied on outside of | ||
// this package. | ||
IsNoop() bool |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
If you think it should not be trusted from outside, maybe not exporting it would be wise.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I'm going to change this comment actually. It is probably a useless function outside of the tests, but it should be able to be relied on just fine.
|
||
// Deallocate takes an endpoint and provides a Proposal that will deallocate | ||
// all of its ports. | ||
func (pa *Allocator) Deallocate(endpoint *api.Endpoint) Proposal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Should this function admit variadic arguments? func (pa *Allocator) Deallocate(endpoints ...*api.Endpoint) Proposal {
Would only need using a for
loop wrapping the current one and will allow current behaviour but would optimize deallocating multiple endpoints by grouping them in a single Proposal
. May you ever need multiple endpoints?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Nah, because you very rarely are deallocating many endpoints at once. You're usually deallocating them one-by-one. To make use of a variadic, you'd have to write the caller to do batching. It's not worthwhile.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood
} | ||
if spec.TargetPort > masterPortEnd { | ||
return nil, ErrInvalidEndpoint{fmt.Sprintf("target port %v isn't in the valid port range", spec.TargetPort)} | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
These are the checks that could be avoided if the underlying type was uint16
.
|
||
type proposal struct { | ||
pa *Allocator | ||
ports []*api.PortConfig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Is ports
and the getter function *proposal.Ports()
being used for anything? It seems they are not needed, maybe some deprecated code?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Yes. it's the most important part, used by the caller. The alternative to Ports()
would be to return (Proposal, []*api.PortConfig, error)
from Allocate
. That might be cleaner. WDYT?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see, you need to modify the given []*api.PortConfig (e.g. dynamic assigned ports) but you can not do it over the given pointer as the whole two-phase model would get broken.
Between the two proposed options, I would suggest sticking to the first because all the information is contained in the proposal, you do not have to keep two variables to keep information that does not have any meaning by its own.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Tell me what you think, mainly about extracting general allocator and proposal interfaces so that every allocator needs to implement them, as I think this will open the possibility to "plugable" allocators. Maybe this conversation is a best fit for #2516 so free fill to mention me there if you prefer to have it there.
// protocol represented by this port space | ||
protocol api.PortConfig_Protocol | ||
port uint32 | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I've seen a struct very similar to this in the scheduler code:
type hostPortSpec struct {
protocol api.PortConfig_Protocol
publishedPort uint32
}
Are you following any pattern to avoid this type duplications?
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
no, i'm not. the allocator code is entirely contained within itself. i don't think there's anything to be gained by deduplicating this small struct across those two components, though.
|
||
type proposal struct { | ||
pa *Allocator | ||
ports []*api.PortConfig |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Oh, I see, you need to modify the given []*api.PortConfig (e.g. dynamic assigned ports) but you can not do it over the given pointer as the whole two-phase model would get broken.
Between the two proposed options, I would suggest sticking to the first because all the information is contained in the proposal, you do not have to keep two variables to keep information that does not have any meaning by its own.
|
||
// Restore adds the current endpoints to the local state of the port allocator | ||
// but does not perform any new allocation. | ||
func (pa *Allocator) Restore(endpoints []*api.Endpoint) { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Am I correct thinking that this method is only used just after creating the allocator? Taking it to a normal function instead of a "method", creating the allocator inside and returning the allocator looks nicer in my head, as it will ensure that it only gets called once on an empty allocator.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
there are a few good reasons not to do it like this:
-
the constructor never returns an error, which means that anything that relies on it can in turn avoid returning errors from its constructor, all the way to the top. this is handy because when everything is said and done, it means you can have the top-level allocator component initialize the subcomponents in its constructor, and then run the Restore method in its Run function. it's hard to explain
-
you can call Restore multiple times, which isn't always necessary, but which is useful for testing because subtests can call Restore again with more state.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
I won't mention the second reason as I consider it a nice thing but that shouldn't be taken into account while making design decissions. If you want something for the tests you can always implement non-exported methods in the test files. Don't get me wrong, it's a nice side-effect, but not one that should be looked for while taking design decissions.
About the first point, I think I understood you. I'm not suggesting removing the constructor. Further constructors could use this Restore
function and fallback to New
if the previous gave an error. Or you could make Restore
error free by giving a blank allocator. But I get your point, offering it as a method lets the higher abstraction layer choose how to behave if unable to restore instead of giving this behaviour here. On the other side with the method version you are open to errors if it is getting called on allocators that already have some ports reserved.
|
||
// Deallocate takes an endpoint and provides a Proposal that will deallocate | ||
// all of its ports. | ||
func (pa *Allocator) Deallocate(endpoint *api.Endpoint) Proposal { |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Understood
// Used mostly for testing, and should probably not be relied on outside of | ||
// this package. | ||
IsNoop() bool | ||
} |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
You are creating an interface in order not to export the type itself so that proposals
can not be created directly. I would go even further and create an Allocator
interface and extract both interfaces to the allocator
package so that all allocators need to follow these interfaces. That would requirte some generalization.
Something in the lines of:
type Allocator interface {
Allocate(old, new *api.Task) (Proposal, error)
Deallocate(*api.Task) (Proposal, error)
}
type Proposal interface {
Commit()
IsNOOP() bool
}
And then if you want to still hide the proposal object you can make a specialized interface inside the ports
subpackage:
type PortProposal interface {
allocator.Proposal
Ports() []*api.PortConfig
}
portObj.port = deallocated.port | ||
p.PublishedPort = deallocated.port | ||
prop.allocate[portObj] = struct{}{} | ||
continue ports |
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
What about removing it from deallocate
instead of inserting it into allocate
?
This would remove the need to check if it is in the allocate map, reduce the iterations in this loop to get to a solution and also simplify the commit by removing one add and one remove task.
There was a problem hiding this comment.
Choose a reason for hiding this comment
The reason will be displayed to describe this comment to others. Learn more.
Extra comment
Moves all of the error types into a separate package, which can be shared across all allocator components. Signed-off-by: Drew Erny <drew.erny@docker.com>
Adds a subcomponent to the network allocator with the purpose of interfacing with the IPAM drivers and allocating IP addresses for swarmkit objects. Signed-off-by: Drew Erny <drew.erny@docker.com>
Adds a subcomponent for the network allocator that has the responsibility of allocating network resources and driver state from network drivers. Signed-off-by: Drew Erny <drew.erny@docker.com>
Adds the highest-level component charged with allocating network resources from from the underlying libraries. The network allocator provides an interface between the higher level swarmkit objects (services, tasks and nodes), and the lower level swarmkit objects (endpoints, attachments) that the lower level-allocator components operate on. Signed-off-by: Drew Erny <drew.erny@docker.com>
Adds the new top level allocator object. This component is intended to replace the existing Allocator component and exactly replicate its functionality, except without the bugs. It is responsible for translating between the swarmkit event stream and object store, and the network allocator component. Signed-off-by: Drew Erny <drew.erny@docker.com>
Ah, shit, I pushed to the wrong branch. My naming system is terrible. Anyway, closing this in favor of a pending PR. |
This is the first commit in a series of commits rewriting the network allocator (#2516).
This is a rewrite of the PortAllocator component. The PortAllocator is a very simple component that keeps track of which ingress network ports are in use in the cluster.
This new PortAllocator is not yet wired up, because it exposes a completely different interface compared to the existing one. It will be wired up as the rewrite progresses. It has been put here for a review, and can be reviewed as if it is to be merged, but it should not be merged at this time.
Because the PortAllocator is a simple, serial object, with a well-defined interface, I've chosen to use Ginkgo to write tests, as it has helped me enumerate the test cases I need to write and focus on testing those cases. I'm not suggesting we use Ginkgo anywhere else, and I am especially not suggesting we port any existing tests to Ginkgo. It just happens to be the right tool for this job.
At this time, the main design of PortAllocator is finished, and it only needs some more tests written.
Includes a vendoring of Ginkgo, which makes this PR look bigger than it is.