Script 'mail_helper' called by obssrc Hello community, here is the log from the commit of package fortio for openSUSE:Factory checked in at 2022-05-20 17:51:17 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Comparing /work/SRC/openSUSE:Factory/fortio (Old) and /work/SRC/openSUSE:Factory/.fortio.new.1538 (New) ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++
Package is "fortio" Fri May 20 17:51:17 2022 rev:12 rq:978309 version:1.31.0 Changes: -------- --- /work/SRC/openSUSE:Factory/fortio/fortio.changes 2022-05-19 22:49:14.314328627 +0200 +++ /work/SRC/openSUSE:Factory/.fortio.new.1538/fortio.changes 2022-05-20 17:52:17.627311632 +0200 @@ -1,0 +2,7 @@ +Fri May 20 14:57:26 UTC 2022 - ka...@b1-systems.de + +- Update to version 1.31.0: + * add configurable `-dns-method` to resolve dns (#576) + * fix fortio version when used as libray/module (#574) + +------------------------------------------------------------------- Old: ---- fortio-1.30.1.tar.gz New: ---- fortio-1.31.0.tar.gz ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++ Other differences: ------------------ ++++++ fortio.spec ++++++ --- /var/tmp/diff_new_pack.DKr4Ab/_old 2022-05-20 17:52:18.219312171 +0200 +++ /var/tmp/diff_new_pack.DKr4Ab/_new 2022-05-20 17:52:18.227312178 +0200 @@ -19,7 +19,7 @@ %define __arch_install_post export NO_BRP_STRIP_DEBUG=true Name: fortio -Version: 1.30.1 +Version: 1.31.0 Release: 0 Summary: Load testing library, command line tool, advanced echo server and web UI License: Apache-2.0 ++++++ _service ++++++ --- /var/tmp/diff_new_pack.DKr4Ab/_old 2022-05-20 17:52:18.263312211 +0200 +++ /var/tmp/diff_new_pack.DKr4Ab/_new 2022-05-20 17:52:18.267312215 +0200 @@ -3,7 +3,7 @@ <param name="url">https://github.com/fortio/fortio</param> <param name="scm">git</param> <param name="exclude">.git</param> - <param name="revision">v1.30.1</param> + <param name="revision">v1.31.0</param> <param name="versionformat">@PARENT_TAG@</param> <param name="changesgenerate">enable</param> <param name="versionrewrite-pattern">v(.*)</param> @@ -17,7 +17,7 @@ <param name="compression">gz</param> </service> <service name="go_modules" mode="disabled"> - <param name="archive">fortio-1.30.1.tar.gz</param> + <param name="archive">fortio-1.31.0.tar.gz</param> </service> </services> ++++++ _servicedata ++++++ --- /var/tmp/diff_new_pack.DKr4Ab/_old 2022-05-20 17:52:18.291312236 +0200 +++ /var/tmp/diff_new_pack.DKr4Ab/_new 2022-05-20 17:52:18.295312240 +0200 @@ -1,6 +1,6 @@ <servicedata> <service name="tar_scm"> <param name="url">https://github.com/fortio/fortio</param> - <param name="changesrevision">a5dbc975c3cd6585298ecc49361b2d7fcd185528</param></service></servicedata> + <param name="changesrevision">a9397d43c1e2081cc15c6ccae93d08542f9801d5</param></service></servicedata> (No newline at EOF) ++++++ fortio-1.30.1.tar.gz -> fortio-1.31.0.tar.gz ++++++ diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/.github/workflows/main.yml new/fortio-1.31.0/.github/workflows/main.yml --- old/fortio-1.30.1/.github/workflows/main.yml 2022-05-17 00:51:18.000000000 +0200 +++ new/fortio-1.31.0/.github/workflows/main.yml 2022-05-20 04:13:35.000000000 +0200 @@ -4,7 +4,9 @@ on: push: tags: - - '*' + # so a vX.Y.Z-test1 doesn't trigger build + - 'v[0-9]+.[0-9]+.[0-9]+' + - 'v[0-9]+.[0-9]+.[0-9]+-pre*' # A workflow run is made up of one or more jobs that can run sequentially or in parallel jobs: diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/.github/workflows/test.yml new/fortio-1.31.0/.github/workflows/test.yml --- old/fortio-1.30.1/.github/workflows/test.yml 1970-01-01 01:00:00.000000000 +0100 +++ new/fortio-1.31.0/.github/workflows/test.yml 2022-05-20 04:13:35.000000000 +0200 @@ -0,0 +1,20 @@ + +name: Test Tags + +on: + push: + tags: + - 'v[0-9]+.[0-9]+.[0-9]+-test*' + +# A workflow run is made up of one or more jobs that can run sequentially or in parallel +jobs: + # This workflow contains a single job called "build" + build: + # The type of runner that the job will run on + runs-on: ubuntu-latest + + # Steps represent a sequence of tasks that will be executed as part of the job + steps: + # Checks-out your repository under $GITHUB_WORKSPACE, so your job can access it + - name: Just a test step + run: echo it works diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/README.md new/fortio-1.31.0/README.md --- old/fortio-1.30.1/README.md 2022-05-17 00:51:18.000000000 +0200 +++ new/fortio-1.31.0/README.md 2022-05-20 04:13:35.000000000 +0200 @@ -50,13 +50,13 @@ The [releases](https://github.com/fortio/fortio/releases) page has binaries for many OS/architecture combinations (see assets). ```shell -curl -L https://github.com/fortio/fortio/releases/download/v1.30.1/fortio-linux_amd64-1.30.1.tgz \ +curl -L https://github.com/fortio/fortio/releases/download/v1.31.0/fortio-linux_amd64-1.31.0.tgz \ | sudo tar -C / -xvzpf - # or the debian package -wget https://github.com/fortio/fortio/releases/download/v1.30.1/fortio_1.30.1_amd64.deb -dpkg -i fortio_1.30.1_amd64.deb +wget https://github.com/fortio/fortio/releases/download/v1.31.0/fortio_1.31.0_amd64.deb +dpkg -i fortio_1.31.0_amd64.deb # or the rpm -rpm -i https://github.com/fortio/fortio/releases/download/v1.30.1/fortio-1.30.1-1.x86_64.rpm +rpm -i https://github.com/fortio/fortio/releases/download/v1.31.0/fortio-1.31.0-1.x86_64.rpm # and more, see assets in release page ``` @@ -66,7 +66,7 @@ brew install fortio ``` -On Windows, download https://github.com/fortio/fortio/releases/download/v1.30.1/fortio_win_1.30.1.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: +On Windows, download https://github.com/fortio/fortio/releases/download/v1.31.0/fortio_win_1.31.0.zip and extract `fortio.exe` to any location, then using the Windows Command Prompt: ``` fortio.exe server ``` @@ -114,7 +114,7 @@ <details> <!-- use release/updateFlags.sh to update this section --> <pre> -???????????? 1.30.1 usage: +???????????? 1.31.0 usage: where command is one of: load (load testing), server (starts ui, http-echo, redirect, proxies, tcp-echo and grpc ping servers), tcp-echo (only the tcp-echo server), report (report only UI server), redirect (only the redirect server), @@ -169,6 +169,10 @@ output to stdout in curl mode. now stderr by default. -data-dir Directory Directory where JSON results are stored/read (default ".") + -dns-method method + When a name resolves to multiple ip, which method to pick: cached-rr +for cached round robin, rnd for random, first for first answer (pre 1.30 +behavior), rr for round robin. (default cached-rr) -echo-debug-path URI http echo server URI for debug, empty turns off that part (more secure) (default "/debug") diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/fnet/network.go new/fortio-1.31.0/fnet/network.go --- old/fortio-1.30.1/fnet/network.go 2022-05-17 00:51:18.000000000 +0200 +++ new/fortio-1.31.0/fnet/network.go 2022-05-20 04:13:35.000000000 +0200 @@ -27,7 +27,6 @@ "strconv" "strings" "sync" - "sync/atomic" "time" "fortio.org/fortio/dflag" @@ -63,19 +62,43 @@ MaxPayloadSize = 256 * KILOBYTE // Payload that is returned during echo call. Payload []byte - // Atomically incremented counter for dns resolution. - dnsRoundRobin uint32 = 0xffffffff // we want the first one, after increment to be 0 // FlagResolveIPType indicates which IP types to resolve. // With round robin resolution now the default, you are likely to get ipv6 which may not work if // use both type (`ip`). In particular some test environments like the CI do have ipv6 // for localhost but fail to connect. So we made the default ip4 only. FlagResolveIPType = dflag.DynString(flag.CommandLine, "resolve-ip-type", "ip4", "Resolve `type`: ip4 for ipv4, ip6 for ipv6 only, use ip for both") + // FlagResolveMethod decides which method to use when multiple ips are returned for a given name + // default assumes one gets all the ips in the first call and does round robin across these. + // first just picks the first answer, rr rounds robin on each answer. + FlagResolveMethod = dflag.DynString(flag.CommandLine, "dns-method", "cached-rr", + "When a name resolves to multiple ip, which `method` to pick: cached-rr for cached round robin, rnd for random, "+ + "first for first answer (pre 1.30 behavior), rr for round robin.").WithValidator(dnsValidator) + // cache for cached-rr mode. + dnsMutex sync.Mutex + // all below are updated under lock. + dnsHost string + dnsAddrs []net.IP + dnsRoundRobin uint32 = 0 ) +func dnsValidator(inp string) error { + valid := map[string]bool{ + "cached-rr": true, + "rnd": true, + "rr": true, + "first": true, + } + if valid[inp] { + return nil + } + return fmt.Errorf("invalid value for -dns-method, should be one of cached-rr, first, rnd or rr") +} + // nolint: gochecknoinits // needed here (unit change) func init() { ChangeMaxPayloadSize(MaxPayloadSize) + rand.Seed(time.Now().UnixNano()) } // ChangeMaxPayloadSize is used to change max payload size and fill it with pseudorandom content. @@ -296,11 +319,20 @@ return &net.TCPAddr{IP: addr.IP, Port: addr.Port}, nil } +// ClearResolveCache clears the DNS cache for cached-rr resolution mode. +// For instance in case of error, to force re-resolving to potentially changed IPs. +func ClearResolveCache() { + dnsMutex.Lock() + dnsHost = "" + dnsAddrs = nil + dnsMutex.Unlock() +} + // ResolveByProto returns the address of the host,port suitable for net.Dial. // nil in case of errors. works for both "tcp" and "udp" proto. // Limit which address type is returned using `resolve-ip` ip4/ip6/ip (for both, default). -// If the same host is requested, and it has more than 1 IP, returned value will roundrobin -// over the ips. +// If the same host is requested, and it has more than 1 IP, returned value will first, +// random or roundrobin or cached roundrobin over the ips depending on the -dns-method flag value. func ResolveByProto(host string, port string, proto string) (*HostPortAddr, error) { log.Debugf("Resolve() called with host=%s port=%s proto=%s", host, port, proto) dest := &HostPortAddr{} @@ -308,34 +340,63 @@ log.Debugf("host %s looks like an IPv6, stripping []", host) host = host[1 : len(host)-1] } - isAddr := net.ParseIP(host) - filter := FlagResolveIPType.Get() var err error + dest.Port, err = net.LookupPort(proto, port) + if err != nil { + log.Errf("Unable to resolve %s port '%s' : %v", proto, port, err) + return nil, err + } + isAddr := net.ParseIP(host) if isAddr != nil { - log.Debugf("Host already an IP, will go to %s", isAddr) dest.IP = isAddr - } else { - var addrs []net.IP - addrs, err = net.DefaultResolver.LookupIP(context.Background(), filter, host) - if err != nil { - log.Errf("Unable to lookup '%s' : %v", host, err) - return nil, err - } - idx := uint32(0) - l := uint32(len(addrs)) - if l > 1 { - idx = (atomic.AddUint32(&dnsRoundRobin, 1) % l) - log.Debugf("Using address #%d for %s : %v", idx, host, addrs) + log.LogVf("Resolved %s:%s already an IP as addr %+v", host, port, dest) + return dest, nil + } + filter := FlagResolveIPType.Get() + dnsMethod := FlagResolveMethod.Get() + idx := uint32(0) + if dnsMethod == "cached-rr" { + dnsMutex.Lock() + if host == dnsHost { + idx = dnsRoundRobin % uint32(len(dnsAddrs)) + dnsRoundRobin++ + dest.IP = dnsAddrs[idx] + dnsMutex.Unlock() // unlock before IOs + log.LogVf("Resolved %s:%s to cached #%d addr %+v", host, port, idx, dest) + return dest, nil } - log.Debugf("%s will go to %s", proto, addrs[idx]) - dest.IP = addrs[idx] + dnsMutex.Unlock() } - dest.Port, err = net.LookupPort(proto, port) + addrs, err := net.DefaultResolver.LookupIP(context.Background(), filter, host) if err != nil { - log.Errf("Unable to resolve port '%s' : %v", port, err) + log.Errf("Unable to lookup '%s' : %v", host, err) return nil, err } - log.LogVf("Resolved %s:%s to %s %s addr %+v", host, port, proto, filter, dest) + l := uint32(len(addrs)) + if l > 1 { + switch dnsMethod { + case "cached-rr": + // already covered the cache hit case above, so this is a miss/first set + dnsMutex.Lock() + dnsHost = host + dnsAddrs = addrs + idx = 0 + dnsRoundRobin = 1 // next one after 0 + dnsMutex.Unlock() + log.Debugf("First time/new host for caching address for %s : %v", host, addrs) + case "rr": + idx = dnsRoundRobin % uint32(len(addrs)) + dnsRoundRobin++ + log.Debugf("Using rr address #%d for %s : %v", idx, host, addrs) + case "first": + log.Debugf("Using first address for %s : %v", host, addrs) + case "rnd": + idx = uint32(rand.Intn(int(l))) + log.Debugf("Using rnd address #%d for %s : %v", idx, host, addrs) + } + } + dest.IP = addrs[idx] + log.LogVf("Resolved %s:%s to %s %s %s #%d addr %+v", host, port, proto, filter, dnsMethod, idx, dest) return dest, nil } diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/fnet/network_test.go new/fortio-1.31.0/fnet/network_test.go --- old/fortio-1.30.1/fnet/network_test.go 2022-05-17 00:51:18.000000000 +0200 +++ new/fortio-1.31.0/fnet/network_test.go 2022-05-20 04:13:35.000000000 +0200 @@ -450,6 +450,115 @@ } } +// This test relies on google answer 2 ips, first ipv4, second ipv6. +// if that's not the case anymore or in the testing environment, this will fail. +func TestDNSMethods(t *testing.T) { + err := fnet.FlagResolveMethod.Set("first") + if err != nil { + t.Errorf("unexpected error setting method to first: %v", err) + } + fnet.FlagResolveIPType.Set("ip4") + addr4, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip4 resolving google: %v", err) + } + fnet.FlagResolveIPType.Set("ip6") + addr6, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip6 resolving google: %v", err) + } + if addr4.String() == addr6.String() { + t.Errorf("ipv4 %v and ipv6 %v shouldn't be same", addr4, addr6) + } + fnet.FlagResolveIPType.Set("ip") + addrFirst, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving google: %v", err) + } + if addrFirst.String() != addr4.String() { + // dns might change when not in cached mode + log.Warnf("first ip %v not ipv4 %v", addrFirst, addr4) + } + addrSecond, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving (2) google: %v", err) + } + if addrFirst.String() != addrSecond.String() { + log.Warnf("first ip %v not == second %v in first mode", addrFirst, addrSecond) + } + err = fnet.FlagResolveMethod.Set("cached-rr") + if err != nil { + t.Fatalf("error setting back cached-rr mode: %v", err) + } + addrThird, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving (3) google: %v", err) + } + if addrFirst.String() != addrThird.String() { + log.Warnf("first cached ip %v not == first %v in cached-rr mode", addrThird, addrFirst) + } + addrFourth, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving (4) google: %v", err) + } + if addrFourth.String() != addr6.String() { + log.Warnf("second cached ip %v not == ipv6 %v in cached-rr mode", addrFourth, addr6) + } + if addrFourth.String() == addrThird.String() { + t.Errorf("in cached rr mode, 2nd call %v shouldn't be same as first %v for google", addrFourth, addrThird) + } + // back to first (rr) [only if there are only 2 ips] + addrFifth, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving (5) google: %v", err) + } + if addrThird.String() != addrFifth.String() { + log.Warnf("third cached ip %v not == back to first %v in cached-rr mode (if only 2 ips)", addrFifth, addrThird) + } + // clear cache we'll get first again (if we don't get a completely different one that is) + fnet.ClearResolveCache() + addrAfterCache, err := fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("error ip any resolving (6) google: %v", err) + } + if addrAfterCache.String() == addrFourth.String() { + t.Errorf("cache clear failure, we still got 2nd ip: %v", addrAfterCache) + } + if addrAfterCache.String() != addrThird.String() { + log.Warnf("after cache clear we expect to get first %v, we got %v", addrThird, addrAfterCache) + } + // few extra resolve just for coverage + err = fnet.FlagResolveMethod.Set("rnd") + if err != nil { + t.Errorf("unexpected error setting method to rnd: %v", err) + } + _, err = fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("unexpected error in rnd mode for resolve of google: %v", err) + } + fnet.FlagResolveMethod.Set("rr") + if err != nil { + t.Errorf("unexpected error setting method to rr: %v", err) + } + _, err = fnet.Resolve("www.google.com", "80") + if err != nil { + t.Errorf("unexpected error in rr mode for resolve of google: %v", err) + } + // put it back to default + err = fnet.FlagResolveMethod.Set("cached-rr") + if err != nil { + t.Errorf("unexpected error setting method to cached-rr: %v", err) + } + fnet.FlagResolveIPType.Set("ip4") +} + +func TestBadValueForDNSMethod(t *testing.T) { + err := fnet.FlagResolveMethod.Set("foo") + if err == nil { + t.Errorf("passing foo to FlagResolveMethod.Set should error out/fail validation") + } +} + func TestJoinHostAndPort(t *testing.T) { tests := []struct { inputPort string diff -urN '--exclude=CVS' '--exclude=.cvsignore' '--exclude=.svn' '--exclude=.svnignore' old/fortio-1.30.1/version/version.go new/fortio-1.31.0/version/version.go --- old/fortio-1.30.1/version/version.go 2022-05-17 00:51:18.000000000 +0200 +++ new/fortio-1.31.0/version/version.go 2022-05-20 04:13:35.000000000 +0200 @@ -1,4 +1,4 @@ -// Copyright 2017 Istio Authors +// Copyright 2017 Fortio Authors // // Licensed under the Apache License, Version 2.0 (the "License"); // you may not use this file except in compliance with the License. @@ -18,6 +18,7 @@ "fmt" "runtime" "runtime/debug" + "strings" "fortio.org/fortio/log" ) @@ -54,24 +55,49 @@ // automatically added by go 1.18+ when doing `go install project@vX.Y.Z` // and is also used for fortio itself. func FromBuildInfo() (short, long, full string) { + return FromBuildInfoPath("") +} + +func getVersion(binfo *debug.BuildInfo, path string) (short, sum string) { + if path == "" || binfo.Main.Path == path { + // skip leading v, assumes the project use `vX.Y.Z` tags. + short = strings.TrimLeft(binfo.Main.Version, "v") + // '(devel)' messes up the release-tests paths + if short == "(devel)" || short == "" { + short = "dev" + } + sum = binfo.Main.Sum + return + } + // try to find the right module in deps + short = path + " not found in buildinfo" + for _, m := range binfo.Deps { + if path == m.Path { + short = strings.TrimLeft(m.Version, "v") + sum = m.Sum + return + } + } + return +} + +// FromBuildInfoPath returns the version of as specific module if that module isn't already the main one. +// Used by Fortio library version init to remember it's own version. +func FromBuildInfoPath(path string) (short, long, full string) { binfo, ok := debug.ReadBuildInfo() if !ok { - log.Errf("fortio version module: unexpected but no build info available") + full = "fortio version module error, no build info" + log.Errf(full) return } - short = binfo.Main.Version - // '(devel)' messes up the release-tests paths - if short == "(devel)" || short == "" { - short = "dev" - } else { - short = short[1:] // skip leading v, assumes the project use `vX.Y.Z` tags. - } - long = short + " " + binfo.Main.Sum + " " + binfo.GoVersion + " " + runtime.GOARCH + " " + runtime.GOOS + short, sum := getVersion(binfo, path) + long = short + " " + sum + " " + binfo.GoVersion + " " + runtime.GOARCH + " " + runtime.GOOS full = fmt.Sprintf("%s\n%v", long, binfo.String()) return } -// This "burns in" the fortio version. +// This "burns in" the fortio version. we need to get the "right" versions though. +// depending if we are a module or main. func init() { // nolint:gochecknoinits //we do need an init for this - version, longVersion, fullVersion = FromBuildInfo() + version, longVersion, fullVersion = FromBuildInfoPath("fortio.org/fortio") } ++++++ vendor.tar.gz ++++++