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 coverage for improved countme system age #1501

Merged
merged 8 commits into from
Aug 29, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion dnf-behave-tests/common/lib/cmd.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,7 +31,7 @@ def run(cmd, shell=True, cwd=None):

def run_in_context(context, cmd, can_fail=False, expected_exit_code=None, **run_args):
if getattr(context, "faketime", None) is not None:
cmd = context.faketime + cmd
cmd = 'NO_FAKE_STAT=1 ' + context.faketime + cmd

if getattr(context, "fake_kernel_release", None) is not None:
cmd = context.fake_kernel_release + cmd
Expand Down
123 changes: 79 additions & 44 deletions dnf-behave-tests/dnf/countme.feature
Original file line number Diff line number Diff line change
@@ -1,3 +1,4 @@
@dnf5
Feature: Better user counting

@destructive
Expand All @@ -7,7 +8,7 @@ Feature: Better user counting
Given I am running a system identified as the "<system>"
And I use repository "dnf-ci-fedora" as http
And I start capturing outbound HTTP requests
When I execute dnf with args "makecache"
When I execute dnf with args "makecache --refresh"
Then every HTTP GET request should match:
| header | value |
| User-Agent | <agent> |
Expand All @@ -25,7 +26,7 @@ Feature: Better user counting
Given I remove the os-release file
And I use repository "dnf-ci-fedora" as http
And I start capturing outbound HTTP requests
When I execute dnf with args "makecache"
When I execute dnf with args "makecache --refresh"
Then the exit code is 0
And every HTTP GET request should match:
| header | value |
Expand All @@ -35,86 +36,120 @@ Feature: Better user counting
Given I use repository "dnf-ci-fedora" as http
And I set config option "user_agent" to "'Agent 007'"
And I start capturing outbound HTTP requests
When I execute dnf with args "makecache"
When I execute dnf with args "makecache --refresh"
Then every HTTP GET request should match:
| header | value |
| User-Agent | Agent 007 |

Scenario: Countme flag is sent once per week
Given I set config option "countme" to "1"
And today is Wednesday, August 07, 2019
@destructive
Scenario Outline: Countme flag is sent once per calendar week
Given the machine-id file is <machine-id> as of <epoch>
And I set config option "countme" to "1"
And I set releasever to "39"
And I copy repository "dnf-ci-fedora" for modification
And I use repository "dnf-ci-fedora" as http
And I set up metalink for repository "dnf-ci-fedora"
And I start capturing outbound HTTP requests
# First week (bucket 1)

# First calendar week
# Note: One in the first 4 requests is randomly chosen to include the
# flag (see COUNTME_BUDGET=4 in libdnf/repo/Repo.cpp for details)
When I execute dnf with args "makecache" 4 times
When today is <date>
When I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=1 |
# Same week (should not be sent)
When today is Friday, August 09, 2019
| path |
| */metalink.xml*&countme=<age#1> |

# Same calendar week (should not be sent)
When today is <date> + 3 days
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache" 4 times
And I execute dnf with args "makecache --refresh" 4 times
Then no HTTP GET request should match:
| path |
| */metalink.xml?countme=* |
# Next week (bucket 1)
When today is Tuesday, August 13, 2019
| path |
| */metalink.xml*&countme=* |

# Next calendar week
When today is <date> + 8 days
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml*&countme=<age#2> |

# Next calendar week
When today is <date> + 15 days
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache" 4 times
And I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=1 |
# Next week (bucket 2)
When today is Tuesday, August 21, 2019
| path |
| */metalink.xml*&countme=<age#3> |

# Next calendar month
When today is <date> + 40 days
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache" 4 times
And I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=2 |
# 1 month later (bucket 3)
When today is Tuesday, September 16, 2019
| path |
| */metalink.xml*&countme=<age#4> |

# 6 calendar months later
When today is <date> + 182 days
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache" 4 times
And I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=3 |
# 6 months later (bucket 4)
When today is Tuesday, March 15, 2020
| path |
| */metalink.xml*&countme=<age#5> |

# Even later, after a system upgrade
When today is <date> + 365 days
And I set releasever to "40"
And I forget any HTTP requests captured so far
And I execute dnf with args "makecache" 4 times
And I execute dnf with args "makecache --refresh" 4 times
Then exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=4 |
| path |
| */metalink.xml*&countme=<age#6> |

Examples:
| machine-id | epoch | date | age#1 | age#2 | age#3 | age#4 | age#5 | age#6 |
# Absolute age counting (since "epoch")
| initialized | Aug 06, 2019 | Aug 07, 2019 | 1 | 1 | 2 | 3 | 4 | 4 |
| initialized | Aug 06, 2019 | Aug 20, 2019 | 2 | 2 | 2 | 3 | 4 | 4 |
| initialized | Aug 06, 2019 | Sep 12, 2019 | 3 | 3 | 3 | 3 | 4 | 4 |
| initialized | Aug 06, 2019 | Jun 18, 2020 | 4 | 4 | 4 | 4 | 4 | 4 |
# Relative age counting (since "date")
| uninitialized | Aug 06, 2019 | Jun 18, 2020 | 1 | 1 | 2 | 3 | 4 | 1 |
| empty | --- | Jun 18, 2020 | 1 | 1 | 2 | 3 | 4 | 1 |
| absent | --- | Jun 18, 2020 | 1 | 1 | 2 | 3 | 4 | 1 |

@destructive
Scenario: Countme flag is not sent repeatedly on retries
Given I set config option "countme" to "1"
Given the machine-id file is initialized as of today
And I set config option "countme" to "1"
And I copy repository "dnf-ci-fedora" for modification
And I use repository "dnf-ci-fedora" as http
And I set up metalink for repository "dnf-ci-fedora"
# This triggers the retry mechanism in librepo, 4 retries by default
And the server starts responding with HTTP status code 503
And I start capturing outbound HTTP requests
When I execute dnf with args "makecache" 4 times
# 48 = 4 * makecache = 4 * (3 metalink attempts * 4 low-level retries)
When I execute dnf with args "makecache --refresh" 4 times
# 48 = 4 * makecache --refresh = 4 * (3 metalink attempts * 4 low-level retries)
# See librepo commits 15adfb31 and 12d0b4ad for details
Then exactly 48 HTTP GET requests should match:
| path |
| */metalink.xml* |
And exactly one HTTP GET request should match:
| path |
| */metalink.xml?countme=1 |
| path |
| */metalink.xml*&countme=1 |

@destructive
Scenario: Countme feature is disabled
Given I set config option "countme" to "0"
Given the machine-id file is initialized as of today
And I set config option "countme" to "0"
And I copy repository "dnf-ci-fedora" for modification
And I use repository "dnf-ci-fedora" as http
And I set up metalink for repository "dnf-ci-fedora"
And I start capturing outbound HTTP requests
When I execute dnf with args "makecache" 4 times
When I execute dnf with args "makecache --refresh" 4 times
Then no HTTP GET request should match:
| path |
| */metalink.xml?countme=1 |
| path |
| */metalink.xml*&countme=* |
63 changes: 63 additions & 0 deletions dnf-behave-tests/dnf/steps/fixtures/machineid.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,63 @@
# -*- coding: utf-8 -*-

from __future__ import absolute_import
from __future__ import print_function

from behave import fixture
from datetime import datetime
import os


class MachineId(object):
"""Represents the machine-id(5) file."""
def __init__(self, path):
self._path = path
self._backup = path + '.bak'
if os.path.exists(path):
os.rename(path, self._backup)

def _set_mtime(self, value):
"""Set the given mtime on this file."""
times = None
if value is not None and value != 'today':
ts = int(datetime.strptime(value, '%b %d, %Y').timestamp())
times = (ts, ts)
os.utime(self._path, times=times)

def initialize(self, mtime):
"""Initialize the file and set the given mtime."""
with open(self._path, 'w') as f:
f.write('dummy\n')
self._set_mtime(mtime)

def uninitialize(self, mtime):
"""Uninitialize the file and set the given mtime."""
with open(self._path, 'w') as f:
f.write('uninitialized\n')
self._set_mtime(mtime)

def empty(self):
"""Empty the file."""
open(self._path, 'w').close()

def delete(self):
"""Delete the file."""
if os.path.exists(self._path):
os.remove(self._path)

def __del__(self):
"""Restore the backup."""
if os.path.exists(self._backup):
os.rename(self._backup, self._path)


@fixture
def machineid_fixture(context):
try:
if not hasattr(context, "machineid"):
path = os.path.realpath('/etc/machine-id')
context.scenario.machineid = MachineId(path)

yield context.scenario.machineid
finally:
del context.scenario.machineid
24 changes: 22 additions & 2 deletions dnf-behave-tests/dnf/steps/repo.py
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
)
from fixtures import start_server_based_on_type, stop_server_type
from fixtures.osrelease import osrelease_fixture
from fixtures.machineid import machineid_fixture


def repo_config(repo, new={}):
Expand Down Expand Up @@ -137,7 +138,7 @@ def generate_metalink(destdir, url):
f.write(data + '\n')


@behave.given("I set releasever to \"{releasever}\"")
@behave.step("I set releasever to \"{releasever}\"")
def step_impl(context, releasever):
context.dnf._set("releasever", releasever)
for repo, info in context.dnf.repos.items():
Expand Down Expand Up @@ -356,7 +357,7 @@ def step_set_up_metalink_for_repository(context, repo):
generate_metalink(repo_info.path, url)
repo_info.update_config({
"baseurl": "",
"metalink": url + "metalink.xml",
"metalink": url + "metalink.xml?releasever=$releasever",
})
create_repo_conf(context, repo)

Expand All @@ -369,7 +370,10 @@ def given_system(context, system):
variant = None
if len(system) > 1:
variant = system[1]
variant = '"{0}"'.format(variant.strip())
name, version = distro.rsplit(' ', 1)
name = '"{0}"'.format(name.strip())
version = '"{0}"'.format(version.strip())
data = dict(zip(('NAME', 'VERSION_ID', 'VARIANT_ID'),
(name, version, variant)))
context.scenario.osrelease.set(data)
Expand All @@ -381,6 +385,22 @@ def given_no_osrelease(context):
context.scenario.osrelease.delete()


@behave.step("the machine-id file is {what} as of {when}")
def step_machine_id_file(context, what, when):
behave.use_fixture(machineid_fixture, context)
machineid = context.scenario.machineid
if when.startswith('on '):
when = when[3:]
if what == 'initialized':
machineid.initialize(when)
elif what == 'uninitialized':
machineid.uninitialize(when)
elif what == 'empty':
machineid.empty()
elif what == 'absent':
machineid.delete()


@behave.step("I invalidate solvfile version of \"{path}\"")
def rewrite_solvfile_version(context, path):
path = path.format(context=context)
Expand Down
Loading