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

Add multi source IP support #1682

Merged
merged 14 commits into from
Oct 28, 2020
Merged

Add multi source IP support #1682

merged 14 commits into from
Oct 28, 2020

Conversation

mstoykov
Copy link
Contributor

fixes #476

@mstoykov mstoykov requested review from imiric and na-- October 22, 2020 15:43
cmd/options.go Outdated
@@ -93,6 +93,7 @@ func optionFlagSet() *pflag.FlagSet {
flags.StringSlice("tag", nil, "add a `tag` to be applied to all samples, as `[name]=[value]`")
flags.String("console-output", "", "redirects the console logging to the provided output file")
flags.Bool("discard-response-bodies", false, "Read but don't process or save HTTP response bodies")
flags.String("client-ips", "", "Client IP Ranges and/or CIDRs from which each VU will be making requests")
Copy link
Member

Choose a reason for hiding this comment

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

--local-ips or --local-addr seems slightly better as a name. This is also what Go uses, see https://golang.org/pkg/net/#IPConn.LocalAddr, https://golang.org/pkg/net/#DialTCP

Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

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

LGTM, besides the option name and the few minor nitpicks I noted inline.

return ipBlockFromCIDR(s)
default:
if net.ParseIP(s) == nil {
return nil, fmt.Errorf("%s is not a valid ip, range ip or CIDR", s)
Copy link
Member

Choose a reason for hiding this comment

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

Suggested change
return nil, fmt.Errorf("%s is not a valid ip, range ip or CIDR", s)
return nil, fmt.Errorf("%s is not a valid IP, IP range or CIDR", s)

lib/types/ipblock.go Outdated Show resolved Hide resolved
block.firstIP.SetBytes(ip0.To4())
block.count.SetBytes(ip1.To4())
}
block.count.Sub(block.count, block.firstIP)
Copy link
Member

Choose a reason for hiding this comment

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

Please add a comment that ip0<ip1 is validated elsewhere, I missed the negative ip range check on the first reading

Copy link
Contributor

Choose a reason for hiding this comment

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

Also having some tests for this would be good.

lib/types/ipblock.go Show resolved Hide resolved
lib/types/ipblock.go Outdated Show resolved Hide resolved
block.firstIP.SetBytes(ip0.To4())
block.count.SetBytes(ip1.To4())
}
block.count.Sub(block.count, block.firstIP)
Copy link
Contributor

Choose a reason for hiding this comment

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

Also having some tests for this would be good.

lib/types/ipblock.go Outdated Show resolved Hide resolved
lib/types/ipblock.go Outdated Show resolved Hide resolved
lib/types/ipblock.go Outdated Show resolved Hide resolved
@codecov-io
Copy link

codecov-io commented Oct 26, 2020

Codecov Report

Merging #1682 into master will increase coverage by 0.27%.
The diff coverage is 77.84%.

Impacted file tree graph

@@            Coverage Diff             @@
##           master    #1682      +/-   ##
==========================================
+ Coverage   72.10%   72.37%   +0.27%     
==========================================
  Files         167      176       +9     
  Lines       12882    13536     +654     
==========================================
+ Hits         9288     9797     +509     
- Misses       3031     3126      +95     
- Partials      563      613      +50     
Flag Coverage Δ
#ubuntu 72.33% <77.84%> (+0.24%) ⬆️
#windows 70.97% <77.84%> (+0.41%) ⬆️

Flags with carried forward coverage won't be shown. Click here to find out more.

Impacted Files Coverage Δ
js/bundle.go 90.20% <ø> (ø)
lib/testutils/httpmultibin/httpmultibin.go 89.88% <ø> (-2.52%) ⬇️
lib/types/dns_policy_gen.go 36.36% <36.36%> (ø)
lib/types/dns_select_gen.go 45.45% <45.45%> (ø)
lib/netext/dialer.go 93.75% <50.00%> (-2.17%) ⬇️
cmd/options.go 60.46% <52.63%> (-0.98%) ⬇️
lib/types/dns.go 68.29% <68.29%> (ø)
js/runner.go 80.13% <74.28%> (-2.20%) ⬇️
lib/netext/resolver.go 84.21% <84.21%> (ø)
lib/testutils/mockresolver/resolver.go 85.71% <85.71%> (ø)
... and 18 more

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 0e39d45...ef7eb9d. Read the comment docs.

@mstoykov mstoykov requested review from imiric and na-- October 26, 2020 13:39
imiric
imiric previously approved these changes Oct 26, 2020
Copy link
Contributor

@imiric imiric left a comment

Choose a reason for hiding this comment

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

LGTM, just a comment nitpick.

}

// ipPoolBlock is almost the same as ipBlock but is put in a IPPool and knows it's start id indest
// of it's size/count
Copy link
Contributor

Choose a reason for hiding this comment

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

Please revise this comment. I'm not sure what it's start id indest means, and it's should be its in this case.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

"ipPoolBlock is similar to ipBlock but instead of knowing its count/size it knows the first index from which it starts in an IPPool" ?

}

func ipBlockFromCIDR(s string) (*ipBlock, error) {
_, pnet, err := net.ParseCIDR(s)
Copy link
Contributor

Choose a reason for hiding this comment

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

_, pnet, err := net.ParseCIDR(strings.TrimSpace(s))

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 don't think the trimming is needed - I just don't think people should add a random amount of additional space in their argument and then tools to just remove them for them.

I am actually thinking if I shouldn't drop it from the places I have apparently not dropped it from 🤔
cc @na-- @imiric

Copy link
Contributor

Choose a reason for hiding this comment

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

at least it should keep as same as ipBlockFromRange() which is using trimming.

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't have a strong preference either way, but we should keep it consistent, so removing trimming LGTM.

// bigger than startIndex but it will be true always for the first block which is with
// startIndex 0. This can also be fixed by iterating in reverse but this seems better
for i := 0; i < len(pool.list)/2; i++ {
pool.list[i], pool.list[len(pool.list)/2-i] = pool.list[len(pool.list)/2-i], pool.list[i]
Copy link
Contributor

Choose a reason for hiding this comment

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

a reverse should be: pool.list[i], pool.list[len(pool.list)-i] = pool.list[len(pool.list)-i], pool.list[i]

js/runner.go Outdated
@@ -170,6 +170,10 @@ func (r *Runner) newVU(id int64, samplesOut chan<- stats.SampleContainer) (*VU,
BlockedHostnames: r.Bundle.Options.BlockedHostnames.Trie,
Hosts: r.Bundle.Options.Hosts,
}
if r.Bundle.Options.LocalIPs.Valid {
dialer.Dialer.LocalAddr = &net.TCPAddr{IP: r.Bundle.Options.LocalIPs.Pool.GetIP(uint64(id - 1))}
Copy link
Contributor

Choose a reason for hiding this comment

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

From my testing, id will be 0 in the first VU allocation. so a check of id > 0 should be set.

Copy link
Contributor

Choose a reason for hiding this comment

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

if r.Bundle.Options.LocalIPs.Valid && id > 0 {

Copy link
Contributor

Choose a reason for hiding this comment

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

there is a fact: VU (id==0) will never sends requests... I think this is a bug

Copy link
Contributor Author

Choose a reason for hiding this comment

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

VU with id 0 is generally used for stuff that "don't" do requests but ... setup and teardown functions can make them and they need to also use one of the provided IPs to make the requests.

Actually speaking of this ... maybe they should use some "randomish" one but instead the first one? Currently They will use (2**64 -1) % IPPool.count which IMO is fine - they use an IP from the provided ones and arguably every IP should be viable, but maybe they should use the first one instead for more consistancy?
cc @imiric @na--

Copy link
Contributor

Choose a reason for hiding this comment

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

I don't think we should add any special handling for 0 ID VUs, but we should fix the uint64 underflow for the id=0 case. Picking the first one seems fine to me.

Copy link
Contributor

Choose a reason for hiding this comment

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

from my view, take the first IP is not good since the vu id == 0 will take initialising requests - I guess it is DNS resolving?
if skipped, first VU(id == 0) will be using OS interface ip address (OS to decide what interface & its IP address to reach dns servers). this is needed because most of outside dns servers may not have route to responding to k6's --local-ips.

@mstoykov mstoykov requested a review from imiric October 27, 2020 08:58
cmd/options.go Outdated Show resolved Hide resolved
js/runner.go Outdated Show resolved Hide resolved
Copy link
Contributor

@imiric imiric left a comment

Choose a reason for hiding this comment

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

LGTM, just remember to squash.

@@ -388,6 +388,9 @@ type Options struct {

// Redirect console logging to a file
ConsoleOutput null.String `json:"-" envconfig:"K6_CONSOLE_OUTPUT"`

// Specify client IP ranges and/or CIDR from which VUs will make requests
LocalIPs types.NullIPPool `json:"-" envconfig:"K6_LOCAL_IPS"`
Copy link
Member

Choose a reason for hiding this comment

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

I am not sure why this shouldn't be configured via the JS options and from config.json. We can validate so we don't allow it in the cloud, but I don't see to disable it in general.

Copy link
Contributor Author

Choose a reason for hiding this comment

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

Why should it be configurable? I find it very unlikely that will be a needed thing for anyone and setting an environment variable won't be a good enough solution as well.

I am for leaving this for when someone actually asks for it as we can always add it

Copy link
Member

Choose a reason for hiding this comment

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

Why should it be configurable?

Because there's no good reason not to be? And, whenever possible, most things in k6 are configurable in 4 different ways - CLI flags, env vars, JS options, JSON options. When that's not the case, it's usually because there is a good reason for it. For example, we need to know the option's value before we execute the init context. Or we think the option is so minor that it'll overcrowd the CLI flags. Or there's a bug in envconfig, or something like that.

The more inconsistencies we have with the k6 options, the worse the user experience is. It's not a big deal, but constantly breaking expectations in this way is bound to get annoying.

Copy link
Member

@na-- na-- left a comment

Choose a reason for hiding this comment

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

LGTM besides the lack of js/json option support, but we can fix that in the future, along with all of the already existing config inconsistencies 😞

Regarding future developments in this direction (weights, randomization and such), I was thinking that shoehorning them in a super-complex global option might not be the best way to implement the feature. I had a brief look at the other PR and it was very difficult to understand things. Rather, given that this is essentially a per-VU setting, why can't we have a JS API to control the source IPs for each VU?

Basically, leave the --local-ips exactly as it is, without any further complications. Instead, in the future, we can add a new JS API that allow users to control different aspects of the VU, including the local IPs. I don't see a problem with setting the local IP in the init context, or even on every iteration... The --local-ips value can be used for validation - if set, allow the JS function to only select IPs from its pool, otherwise allow users to set any local IPs.

More thought obviously has to go into this, but I think it would be better than having increasingly more complex global options. "Pick a random IP out of this pool of IP ranges with different weights" is much more cleanly expressed in a programming language like JS than through some obscure configuration syntax.

And we actually won't have to add this huge complexity to k6 for the few users like @divfor who might need it. We'd just offer a simple API for setting the source IP. Then every user can have an arbitrary complex scheme for picking random or sequential values however they like. The new HTTP client API should also probably have the ability to set a local IP... And this way, local IPs will work much better in conjunction with scenarios - different scenarios can have different pools of local IPs, which seems potentially useful.

@@ -388,6 +388,9 @@ type Options struct {

// Redirect console logging to a file
ConsoleOutput null.String `json:"-" envconfig:"K6_CONSOLE_OUTPUT"`

// Specify client IP ranges and/or CIDR from which VUs will make requests
LocalIPs types.NullIPPool `json:"-" envconfig:"K6_LOCAL_IPS"`
Copy link
Member

Choose a reason for hiding this comment

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

Why should it be configurable?

Because there's no good reason not to be? And, whenever possible, most things in k6 are configurable in 4 different ways - CLI flags, env vars, JS options, JSON options. When that's not the case, it's usually because there is a good reason for it. For example, we need to know the option's value before we execute the init context. Or we think the option is so minor that it'll overcrowd the CLI flags. Or there's a bug in envconfig, or something like that.

The more inconsistencies we have with the k6 options, the worse the user experience is. It's not a big deal, but constantly breaking expectations in this way is bound to get annoying.

@mstoykov mstoykov merged commit bf826ba into master Oct 28, 2020
@mstoykov mstoykov deleted the feature/multiSourceIPs branch October 28, 2020 08:50
@na-- na-- added this to the v0.29.0 milestone Oct 28, 2020
salem84 pushed a commit to salem84/k6 that referenced this pull request Nov 5, 2020
@na-- na-- mentioned this pull request Jan 21, 2021
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.

Support for Multiple Network Card
5 participants