Skip to content

Conversation

@selansen
Copy link
Contributor

This change brings global default address pool feature into
libnetwork. Idea is to reuse same code flow and functions that were
implemented for local scope default address pool.
Function InitNetworks carries most of the changes. local scope default
address pool init should always happen only once. But Global scope
default address pool can be initialized multiple times.

Signed-off-by: selansen elango.siva@docker.com

@selansen selansen changed the title Global Default Address Pool support [WIP]Global Default Address Pool support Jul 25, 2018
@selansen selansen force-pushed the global_add_pool branch 2 times, most recently from 7500c9b to 2a40767 Compare July 25, 2018 20:35
// defaultAddressPool Stores user configured subnet list
defaultAddressPool []*ipamutils.NetworkToSplit
// isGlobalScopePool indicates if it is global or local
isGlobalScopePool bool
Copy link
Member

Choose a reason for hiding this comment

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

How does this work? How can I set default address pools on the daemon (bridge networks), and global address pools (for swarm)?

RUN apt-get update && apt-get -y install iptables \
protobuf-compiler

RUN go get -d github.com/gogo/protobuf/protoc-gen-gogo && \

Choose a reason for hiding this comment

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

code got merged, you can rebase

@selansen selansen force-pushed the global_add_pool branch 2 times, most recently from 1b5e43f to 4b4b319 Compare July 27, 2018 16:14
FROM golang:1.10.2 as dev
RUN apt-get update && apt-get -y install iptables \
protobuf-compiler

Choose a reason for hiding this comment

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

just checkout this file, no need to add these changes


// InitNetworks initializes the broad network pool and the granular network pool
func InitNetworks(defaultAddressPool []*NetworkToSplit) {
func InitNetworks(defaultAddressPool []*NetworkToSplit, isGlobalScope bool) {

Choose a reason for hiding this comment

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

does not seem a clean solution this one.
I would create a Reinit function if needed

@selansen selansen force-pushed the global_add_pool branch 3 times, most recently from 10f1257 to 1e555c7 Compare July 28, 2018 16:13
@selansen
Copy link
Contributor Author

@thaJeztah @fcrisciani @ctelfer As per our discussion I cleaned up the code along with review comments.
Now we have new API which will be used from swarmkit to set global pool address.

}

// ConfigGlobalScopeDefaultNetworks initializes the global scope default network pool
func ConfigGlobalScopeDefaultNetworks(defaultAddressPool []*NetworkToSplit) {

Choose a reason for hiding this comment

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

if you define this function as: func ConfigGlobalScopeDefaultNetworks(defaultAddressPool *NetworkToSplit...) {
You can avoid from other places to call it with nil, but you will be able to call the method as:
ConfigGlobalScopeDefaultNetworks() that is nicer

Copy link
Contributor Author

Choose a reason for hiding this comment

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

swarm init is the only place we are going to call. we can reuse the same function for swarm update. We are not going to call from any other place.

Choose a reason for hiding this comment

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

sorry I was referring to the fact that there is a nil check at line 54. If that is needed, means that this method can be called with nil, I was just suggesting that in that case, would be nicer to have a method that takes 0 arguments instead of 1 argument set to nil

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Added unit test to cover new changes

@selansen selansen force-pushed the global_add_pool branch 4 times, most recently from 1ce1f3e to 20e5a92 Compare August 6, 2018 23:48
@selansen selansen changed the title [WIP]Global Default Address Pool support Global Default Address Pool support Aug 7, 2018
@fcrisciani fcrisciani requested a review from euanh August 9, 2018 06:06
}
mutex.Unlock()
if defaultAddressPool != nil {
logrus.Infof("Swarm init global scope default address pool succeeded")

Choose a reason for hiding this comment

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

Info

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

}
var err error
if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(defaultAddressPool); err != nil {
logrus.WithError(err).Error("InitAddressPools failed to initialize the local scope default address pool")

Choose a reason for hiding this comment

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

don't we want to return the err to the caller?

Copy link
Contributor Author

@selansen selansen Aug 11, 2018

Choose a reason for hiding this comment

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

This is kind of Init function for swarmKit. We dont return error. Ideally this should never fail. Thats why we just log the error message. All the validations are done at CLI level.

Copy link
Contributor

Choose a reason for hiding this comment

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

Hrmm.. I'm not sure that I agree in this case or in the InitNetworks case. But especially in this case. The reason is that both functions take potentially erroneous parameters that can come from user input. Even though it should never fail, by not returning an error here we are implicitly forcing the caller to know all the rules for input validation that this library requires. Clearly the CLI and swarmkit should do some input validation. But IMHO, they should only have to do enough to ensure that the input is parses correctly. Things like invalid parameter combinations should really be handled here. This way, if the design or functionality changes, (e.g. becoming more restrictive or less restrictive) the changes don't have to happen in multiple locations in the code for it to validate the input correctly. I'd also think that the caller should be given the option to panic or abort the code entirely if such an error occurs.

Copy link
Contributor Author

@selansen selansen Aug 13, 2018

Choose a reason for hiding this comment

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

I agree with you on caller should get notified. We dont pass user input while calling Libnetwork API the way we used to do. Instead of passing CLI input deep into stack, I was asked to change APIs params such a way that we pass well defined structure. when we receive input from CLI , now the parser does all validation and converts into []IPNet object. So if there is failure in the user input we reject way before it reaches swarmKit or Libnetwork. When we call, Libnetwork Init we dont pass wrong structure from swarmkit. So it is protected at CLI and swarmKit modules. Like I mentioned existing Init APIs also behave in the same way.

if defaultAddressPool == nil {
defaultAddressPool = globalScopeDefaultNetworks
}
var err error

Choose a reason for hiding this comment

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

why this err is defined here and not in the next line if not used later?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

while re writing , I missed it out. corrected it.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

If I move err inside if block, I am getting below error. So moved it back up.
ipamutils/utils.go:60:2: PredefinedGlobalScopeDefaultNetworks declared and not used

Copy link
Contributor

Choose a reason for hiding this comment

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

Agree. This is one of those ... er ... charming Go scope behaviors with := ... Go will assume that PredefinedGlobalScopeDefaultNetworks is also a local variable in need of fresh declaration. Go allows for partial redeclaration of variables ... except when it doesn't ... by overriding the outer declaration with an inner one.

initNetworksOnce.Do(func() {
// error ingnored should never fail
PredefinedGranularNetworks, _ = splitNetworks(defaultGranularNetwork)
mutex.Lock()

Choose a reason for hiding this comment

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

looks like the lock here can be removed and you can directly call the method ConfigGlobalScopeDefaultNetworks

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Idea is ConfigGlobalScopeDefaultNetworks is invoked only from swarmkit and init network will be invoked only during Daemon Init. Later if we plan to support swarmkit update , we dont can modify ConfigGlobalScopeDefaultNetworks without worrying about InitNetworks calling this API. would like to keep it that way .

Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to keep it this way I think you should add a comment explaining why. Otherwise someone may come along later and refactor InitNetworks() to call ConfigGlobalScopeDefaultNetworks() as the logic is (more or less) duplicated.

// error ignored should never fail
PredefinedGlobalScopeDefaultNetworks, _ = splitNetworks(globalScopeDefaultNetworks)
mutex.Unlock()
if defaultAddressPool == nil {

Choose a reason for hiding this comment

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

do you want to call this one localAddressPool?

Copy link
Contributor Author

Choose a reason for hiding this comment

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

I guess its not required. Daemon Init will be called only once and we will be initializing local pool only one time during Init. So I feel its not required. WDYT?

Copy link
Contributor

Choose a reason for hiding this comment

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

I think the point was more just for code clarity. We are initializing two default address pools in this function. Renaming the parameter makes it clear that one only can modify the local address pool using InitNetworks().

Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, renaming the parameter to localAddressPool would make the intent clearer.

Copy link
Contributor

@ctelfer ctelfer left a comment

Choose a reason for hiding this comment

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

Sorry, wrote this in the wrong browser tab. Will review soon. Pls disregard ... github doesn't seem to want to let me delete the comment.

}
var err error
if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(defaultAddressPool); err != nil {
logrus.WithError(err).Error("InitAddressPools failed to initialize the local scope default address pool")
Copy link
Contributor

Choose a reason for hiding this comment

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

Hrmm.. I'm not sure that I agree in this case or in the InitNetworks case. But especially in this case. The reason is that both functions take potentially erroneous parameters that can come from user input. Even though it should never fail, by not returning an error here we are implicitly forcing the caller to know all the rules for input validation that this library requires. Clearly the CLI and swarmkit should do some input validation. But IMHO, they should only have to do enough to ensure that the input is parses correctly. Things like invalid parameter combinations should really be handled here. This way, if the design or functionality changes, (e.g. becoming more restrictive or less restrictive) the changes don't have to happen in multiple locations in the code for it to validate the input correctly. I'd also think that the caller should be given the option to panic or abort the code entirely if such an error occurs.


assert.Check(t, is.Len(m, 0))

}
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: probably delete at least one of the extra blank lines here.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

done

// error ignored should never fail
PredefinedGlobalScopeDefaultNetworks, _ = splitNetworks(globalScopeDefaultNetworks)
mutex.Unlock()
if defaultAddressPool == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I think the point was more just for code clarity. We are initializing two default address pools in this function. Renaming the parameter makes it clear that one only can modify the local address pool using InitNetworks().

@selansen
Copy link
Contributor Author

@ctelfer somehow I see your comments are duplicated twice in some cases. Not sure why :)

@selansen selansen force-pushed the global_add_pool branch 2 times, most recently from c5b9182 to 69adc43 Compare August 14, 2018 03:57
@selansen
Copy link
Contributor Author

Ping @ctelfer @fcrisciani @abhi

Size int `json:"size"`
}

// InitNetworks initializes the broad network pool and the granular network pool
Copy link
Contributor

Choose a reason for hiding this comment

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

Please change 'broad network' and 'granular network' to 'local scope' and 'global scope' to match the new variable names.

// error ignored should never fail
PredefinedGlobalScopeDefaultNetworks, _ = splitNetworks(globalScopeDefaultNetworks)
mutex.Unlock()
if defaultAddressPool == nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

I agree, renaming the parameter to localAddressPool would make the intent clearer.

initNetworksOnce.Do(func() {
// error ingnored should never fail
PredefinedGranularNetworks, _ = splitNetworks(defaultGranularNetwork)
mutex.Lock()
Copy link
Contributor

Choose a reason for hiding this comment

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

If you want to keep it this way I think you should add a comment explaining why. Otherwise someone may come along later and refactor InitNetworks() to call ConfigGlobalScopeDefaultNetworks() as the logic is (more or less) duplicated.

@ctelfer
Copy link
Contributor

ctelfer commented Aug 15, 2018

@selansen yeah, I have been seeing comments duplicated in other PRs as well. Weird.

@selansen selansen force-pushed the global_add_pool branch 4 times, most recently from d4c567d to 600c483 Compare August 15, 2018 21:30
@selansen
Copy link
Contributor Author

@ctelfer @euanh , refactored init code completely as per our discussion. PTAL. Once we freeze Libnetwork PR, I can go back to swarmkit and update it.

Copy link
Contributor

@ctelfer ctelfer left a comment

Choose a reason for hiding this comment

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

The refactoring generally looks fine. Just a few nits re testing for exceptional conditions. I'll check on this early tomorrow and merge ASAP if we can answer these. BTW, do the unit tests pass cleanly with the changes? (I assume they do, but want to be sure.)

var err error
if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(globalScopeDefaultNetworks); err != nil {
logrus.WithError(err).Error("InitAddressPools failed to initialize the global scope default address pool")
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Since this default initialization only occurs on compile-time constant input it must always work. Therefore, it should be OK to panic() if some kind of error occurs. This is similar to https://golang.org/pkg/regexp/#MustCompile which panics if a regex is malformed. (again, one invokes this on compile-time regex strings that are known to be good.)


if defaultAddressPool == nil {
defaultAddressPool = globalScopeDefaultNetworks
}
Copy link
Contributor

Choose a reason for hiding this comment

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

Since the globalScopeDefaultNetworks are already installed, why not just do:

if defaultAddressPool == nil {
    return nil
}

at the start of the function? (even before the lock/deferred unlock) Re-invoking configureDefaultNetworks() again on globalScopeDefaultNetworks isn't going to change the configuration.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

No. This is one of the issue I found out during testing and added as extra requirement after I & @mark-church discussed.
here is the scenario which I am trying to address

  1. swarm init happens with new "subnet-pool"
  2. tearing down swarm cluster
  3. now bring up swarm init without "subnet-pool" option
  4. we should init global pool back to inbuilt pool as it used to work without this feature.
    so I will have to set it back to in built values and re initialize it again. else swam cluster will have old IPs for overlay networks.

Copy link
Contributor

Choose a reason for hiding this comment

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

ok, got it. leave as is, then.

mutex.Lock()
return configDefaultNetworks(defaultAddressPool, &PredefinedLocalScopeDefaultNetworks)
}
return nil
Copy link
Contributor

Choose a reason for hiding this comment

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

Same could be done here (early return if parameter is nil). I believe this is the preferred form in go. (i.e. keep indentation down by returning early). @abhi please correct me if I'm wrong.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

make sense. will change it.

@selansen
Copy link
Contributor Author

CI unit test failed few places after refactor but fixed them all.

@selansen
Copy link
Contributor Author

@ctelfer All set to go. pls let me know if everything looks good to you

@ctelfer
Copy link
Contributor

ctelfer commented Aug 16, 2018

LGTM

The defer mutex.Unlock()s before the mutex.Lock()s looks a little weird to me, but it isn't wrong per se.

})
func init() {
var err error
if PredefinedGlobalScopeDefaultNetworks, err = splitNetworks(globalScopeDefaultNetworks); err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

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

Can't you use configDefaultNetworks() here as well?

This change brings global default address pool feature into
libnetwork. Idea is to reuse same code flow and functions that were
implemented for local scope default address pool.
Function InitNetworks carries most of the changes. local scope default
address pool init should always happen only once. But Global scope
default address pool can be initialized multiple times.

Signed-off-by: selansen <elango.siva@docker.com>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants