#84 Add job scheduling and ResultsDB reporting for Fedora CoreOS
Merged 3 years ago by adamwill. Opened 3 years ago by adamwill.

file modified
+1 -1
@@ -4,5 +4,5 @@ 

  setuptools

  six

  resultsdb_api

- resultsdb_conventions>=2.0.2

+ resultsdb_conventions>=2.1.0

  wikitcms

file modified
+29 -2
@@ -75,6 +75,19 @@ 

      logger.debug("finished")

      sys.exit()

  

+ def command_fcosbuild(args):

+     """Schedule openQA jobs for a Fedora CoreOS build (current build

+     from specified stream). Currently assumes x86_64 as that's all

+     Fedora CoreOS builds for.

+     """

+     flavors = None

+     if args.flavors:

+         flavors = args.flavors.split(',')

+ 

+     jobs = schedule.jobs_from_fcosbuild(stream=args.stream, flavors=flavors, force=args.force,

+                                             openqa_hostname=args.openqa_hostname)

+     print("Scheduled jobs: {0}".format(', '.join((str(job) for job in jobs))))

+     sys.exit()

  

  def command_update_task(args):

      """Schedule openQA jobs for a specified update or task."""
@@ -174,13 +187,13 @@ 

      parser_compose.add_argument(

          '--updates', '-u', help="URL to an updates image to load for all tests. The tests that "

          "test updates image loading will fail when you use this", metavar='UPDATE_IMAGE_URL')

-     parser_compose.set_defaults(func=command_compose)

      parser_compose.add_argument(

          "--arches", '-a', help="Comma-separated list of arches to schedule jobs for (if not specified, "

          "all arches will be scheduled)", metavar='ARCHES')

      parser_compose.add_argument(

          "--flavors", help="Comma-separated list of flavors to schedule jobs for (if not specified, "

          "all flavors will be scheduled)", metavar='FLAVORS')

+     parser_compose.set_defaults(func=command_compose)

  

      # parser_update and parser_task are nearly the same, so...

      parser_update = subparsers.add_parser('update', description="Schedule jobs for a specific update.")
@@ -207,13 +220,26 @@ 

      parser_update.set_defaults(func=command_update_task)

      parser_task.set_defaults(func=command_update_task)

  

+     parser_fcosbuild = subparsers.add_parser(

+         "fcosbuild", description="Schedule jobs for a Fedora CoreOS build stream."

+     )

+     parser_fcosbuild.add_argument("--stream", "-s", default="next", help="The stream to test the current build "

+                                   "from", metavar="STREAM", choices=("next", "testing", "stable"))

+     parser_fcosbuild.add_argument("--flavors", help="Comma-separated list of flavors to schedule jobs for "

+                                   "(if not specified, all flavors will be scheduled)", metavar="FLAVORS")

+     parser_fcosbuild.add_argument("--openqa-hostname", help="openQA host to schedule jobs on (default: "

+                                   "client library default)", metavar="HOSTNAME")

+     parser_fcosbuild.add_argument("--force", "-f", help="For each ISO/flavor combination, schedule jobs even "

+                                   "if there are existing, non-cancelled jobs for that combination",

+                                   action="store_true")

+     parser_fcosbuild.set_defaults(func=command_fcosbuild)

+ 

      parser_report = subparsers.add_parser(

          'report', description="Map openQA job results to Wikitcms test results and either log them to output or "

          "submit them to the wiki and/or ResultsDB.")

      parser_report.add_argument(

          'jobs', nargs='+', help="openQA job IDs or builds (e.g. "

          "'Fedora-24-20160113.n.1'). For each build included, the latest jobs will be reported.")

-     parser_report.set_defaults(func=command_report)

      parser_report.add_argument(

          "--openqa-hostname", help="openQA host to query for results (default: client library "

          "default)", metavar='HOSTNAME')
@@ -230,6 +256,7 @@ 

      parser_report.add_argument(

          "--resultsdb-url", help="ResultsDB URL to report to (default: "

          "http://localhost:5001/api/v2.0/)")

+     parser_report.set_defaults(func=command_report)

  

      parser.add_argument(

          '--log-file', '-f', help="If given, log into specified file. When not provided, stdout"

file modified
+44 -6
@@ -36,6 +36,7 @@ 

  from openqa_client.const import JOB_SCENARIO_WITH_MACHINE_KEYS

  from resultsdb_api import ResultsDBapi, ResultsDBapiException

  from resultsdb_conventions.fedora import FedoraImageResult, FedoraComposeResult, FedoraBodhiResult

+ from resultsdb_conventions.fedoracoreos import FedoraCoreOSBuildResult, FedoraCoreOSImageResult

  from wikitcms.wiki import Wiki, ResTuple

  

  # Internal dependencies
@@ -281,11 +282,19 @@ 

      # won't be 'passed'). When the clone completes, the consumer will

      # try again and do the right thing.

      jobs = client.get_jobs(jobs=jobs, build=build, filter_dupes=True)

+     if not jobs:

+         logger.debug("wiki_report: No jobs found!")

+         return []

  

      # cannot do wiki reporting for update jobs

      jobs = [job for job in jobs if 'ADVISORY' not in job['settings'] and 'KOJITASK' not in job['settings']]

      if not jobs:

-         logger.debug("No wiki-reportable jobs: most likely all jobs were update tests")

+         logger.debug("wiki_report: All jobs were update or Koji task jobs, no wiki reporting possible!")

+         return []

+     # there are no CoreOS wiki validation events, currently

+     jobs = [job for job in jobs if "coreos" not in job['settings'].get("SUBVARIANT", "").lower()]

+     if not jobs:

+         logger.debug("wiki_report: All jobs were CoreOS, update or Koji task jobs, no wiki reporting possible!")

          return []

      passed_testcases = get_passed_testcases(jobs, client)

      logger.info("passed testcases: %s", passed_testcases)
@@ -345,9 +354,10 @@ 

                       openqa_hostname=None, openqa_baseurl=None):

      """Report results from openQA jobs to ResultsDB. Either jobs (an

      iterable of job IDs) or build (an openQA BUILD string, usually a

-     Fedora compose ID) is required (if neither is specified, the

-     openQA client will raise TypeError). If do_report is false-y, will

-     just print out list of jobs to report for inspection.

+     Fedora compose ID or Fedora CoreOS version) is required (if neither

+     is specified, the openQA client will raise TypeError). If do_report

+     is false-y, will just print out list of jobs to report for

+     inspection.

      openqa_hostname is the host name of the openQA server to connect

      to; if set to None, the client library's default behaviour is used

      (see library for more details). openqa_baseurl is the public base
@@ -410,6 +420,19 @@ 

              logger.debug("Job %d is a NOREPORT job or was run with extra params! Will not report", job['id'])

              continue

  

+         # derive some CoreOS-specific values if appropriate

+         if job["settings"].get("SUBVARIANT", "").lower() == "coreos":

+             # FIXME: this is hacky, should do it better

+             form = job["settings"]["FLAVOR"].split("-")[-1]

+             # https://docs.fedoraproject.org/en-US/fedora-coreos/faq/#_what_is_the_format_of_the_version_number

+             # FIXME: maybe pass this in at creation?

+             streams = {

+                 "1": "next",

+                 "2": "testing",

+                 "3": "stable"

+             }

+             stream = streams.get(build.split(".")[2], "unknown")

+ 

          # sanitize the test name

          tc_name = sanitize_tcname(job['test'])

  
@@ -440,12 +463,27 @@ 

              # the front of image names in updates-testing composes

              if imagename.startswith('testing-'):

                  imagename = imagename[8:]

-             rdbpartial = partial(FedoraImageResult, imagename, build, tc_name='compose.' + tc_name)

+             if job["settings"].get("SUBVARIANT", "").lower() == "coreos":

+                 rdbpartial = partial(

+                     FedoraCoreOSImageResult,

+                     platform="metal",

+                     filename=imagename,

+                     form=form,

+                     arch=job["settings"]["ARCH"],

+                     build=build,

+                     stream=stream,

+                     tc_name='fcosbuild.' + tc_name

+                 )

+             else:

+                 rdbpartial = partial(FedoraImageResult, imagename, build, tc_name='compose.' + tc_name)

  

          elif ttarget == 'COMPOSE':

              # We have a non-image-specific compose test result

              # 'build' will be the compose ID

-             rdbpartial = partial(FedoraComposeResult, build, tc_name='compose.' + tc_name)

+             if job["settings"].get("SUBVARIANT", "").lower() == "coreos":

+                 rdbpartial = partial(FedoraCoreOSBuildResult, build, stream, tc_name='fcosbuild.' + tc_name)

+             else:

+                 rdbpartial = partial(FedoraComposeResult, build, tc_name='compose.' + tc_name)

  

          # don't report TEST_TARGET=NONE, non-update jobs that are

          # missing TEST_TARGET, or TEST_TARGET values we don't grok

@@ -377,6 +377,49 @@ 

  

      return (rel.cid, jobs)

  

+ 

+ def jobs_from_fcosbuild(stream="next", flavors=None, force=False, extraparams=None, openqa_hostname=None):

+     """Schedule jobs for the current artifacts from the given Fedora

+     CoreOS stream (valid streams are "next", "testing", "stable").

+     flavors can be an iterable of flavors to schedule, otherwise all

+     known CoreOS flavors will be scheduled. If force is False, we

+     will not create jobs if some already exist for the same version

+     and flavor; if it's True, we will always create jobs.

+     """

+     flavdict = {

+         "CoreOS-colive-iso": ("iso", "colive", "ISO_URL"),

+     }

+     url = f"https://builds.coreos.fedoraproject.org/streams/{stream}.json"

+     # assumes x86_64 for now, if CoreOS starts building for other

+     # arches will have to adjust this

+     artifacts = fedfind.helpers.download_json(url)["architectures"]["x86_64"]["artifacts"]["metal"]

+     version = artifacts["release"]

+     relnum = version.split(".")[0]

+     build = f"Fedora-CoreOS-{version}"

+     logger.info("Scheduling jobs for CoreOS release %s", version)

+     jobs = []

+     for (flavor, (form, imagetype, param)) in flavdict.items():

+         if flavors and flavor not in flavors:

+             # filtered out!

+             continue

+         location = artifacts["formats"].get(form, {}).get("disk", {}).get("location")

+         if location:

+             param_urls = {

+                 param: location,

+             }

+         else:

+             # no artifact found, onto the next

+             continue

+         logger.debug("Flavor: %s", flavor)

+         logger.debug("Format: %s", form)

+         logger.debug("Image type: %s", imagetype)

+         logger.debug("Location: %s", location)

+         jobs.extend(run_openqa_jobs(param_urls, flavor, "x86_64", "CoreOS", imagetype, build,

+                                     relnum, "", force=force, extraparams=extraparams,

+                                     openqa_hostname=openqa_hostname))

+     return jobs

+ 

+ 

  def jobs_from_update(update, version, flavors=None, force=False, extraparams=None, openqa_hostname=None, arch=None):

      """Schedule jobs for a specific Fedora update (or scratch build).

      update is the advisory ID or task ID, version is the release

file modified
+96
@@ -299,6 +299,102 @@ 

      }

  

  @pytest.fixture(scope="function")

+ def jobdict04():

+     """Another openQA job dict, for a Fedora CoreOS build test."""

+     return {

+         "assets": {

+             "hdd": [

+                 "00048191-disk_CoreOS-colive-iso_64bit.qcow2"

+             ],

+             "iso": [

+                 "fedora-coreos-32.20200726.3.1-live.x86_64.iso"

+             ]

+         },

+         "assigned_worker_id": 4,

+         "blocked_by_id": None,

+         "children": {

+             "Chained": [],

+             "Directly chained": [],

+             "Parallel": []

+         },

+         "clone_id": None,

+         "group": "fedora",

+         "group_id": 1,

+         "id": 48192,

+         "modules": [

+             {

+                 "category": "fedora/tests",

+                 "flags": [

+                     "important",

+                     "fatal",

+                     "milestone"

+                 ],

+                 "name": "_console_wait_login",

+                 "result": "passed"

+             },

+             {

+                 "category": "fedora/tests",

+                 "flags": [

+                     "important",

+                     "fatal"

+                 ],

+                 "name": "base_services_start",

+                 "result": "passed"

+             }

+         ],

+         "name": "fedora-32-CoreOS-colive-iso-x86_64-BuildFedora-CoreOS-32-20200726.3.1-base_services_start@64bit",

+         "parents": {

+             "Chained": [

+                 48191

+             ],

+             "Directly chained": [],

+             "Parallel": []

+         },

+         "priority": 40,

+         "result": "passed",

+         "settings": {

+             "ARCH": "x86_64",

+             "ARCH_BASE_MACHINE": "64bit",

+             "BACKEND": "qemu",

+             "BOOTFROM": "c",

+             "BUILD": "Fedora-CoreOS-32-20200726.3.1",

+             "CANNED": "1",

+             "DISTRI": "fedora",

+             "FLAVOR": "CoreOS-colive-iso",

+             "HDD_1": "disk_CoreOS-colive-iso_64bit.qcow2",

+             "IMAGETYPE": "colive",

+             "ISO": "fedora-coreos-32.20200726.3.1-live.x86_64.iso",

+             "LOCATION": "https://kojipkgs.fedoraproject.org/compose/cloud/Fedora-Cloud-32-20200819.0/compose",

+             "MACHINE": "64bit",

+             # pylint: disable=line-too-long

+             "NAME": "00048192-fedora-32-CoreOS-colive-iso-x86_64-BuildFedora-CoreOS-32-20200726.3.1-base_services_start@64bit",

+             "NICTYPE_USER_OPTIONS": "net=172.16.2.0/24",

+             "PART_TABLE_TYPE": "mbr",

+             "POSTINSTALL": "base_services_start",

+             "QEMUCPU": "Nehalem",

+             "QEMUCPUS": "2",

+             "QEMURAM": "2048",

+             "QEMUVGA": "virtio",

+             "QEMU_HOST_IP": "172.16.2.2",

+             "QEMU_VIRTIO_RNG": "1",

+             "ROOT_PASSWORD": "weakpassword",

+             "START_AFTER_TEST": "install_default_upload",

+             "SUBVARIANT": "CoreOS",

+             "TEST": "base_services_start",

+             "TEST_SUITE_NAME": "base_services_start",

+             "TEST_TARGET": "ISO",

+             "USER_LOGIN": "false",

+             "VERSION": "32",

+             "WORKER_CLASS": "qemu_x86_64"

+         },

+         "state": "done",

+         "t_finished": "2020-08-21T23:48:23",

+         "t_started": "2020-08-21T23:47:22",

+         "test": "base_services_start"

+     }

+ 

+ 

+ @pytest.fixture(scope="function")

  def ffimg01():

      """A pre-canned fedfind image dict, for the x86_64 Server DVD from

      the 20170207.n.0 Rawhide nightly.

file modified
+210 -157
@@ -132,172 +132,225 @@ 

          # should exit 1

          assert excinfo.value.code == 1

  

- @pytest.mark.parametrize(

-     "target",

-     # update ID or task ID (to test both paths)

-     ['FEDORA-2017-b07d628952', '32099714']

- )

- @mock.patch('fedora_openqa.schedule.jobs_from_update', return_value=[1, 2], autospec=True)

- def test_command_update_task(fakejfu, target, capsys):

-     """Test the command_update_task function."""

-     if target.isdigit():

-         targetarg = 'task'

-     else:

-         targetarg = 'update'

-     args = cli.parse_args(

-         [targetarg, target, '25']

-     )

-     with pytest.raises(SystemExit) as excinfo:

-         cli.command_update_task(args)

-     (out, _) = capsys.readouterr()

-     # first arg should be target

-     assert fakejfu.call_args[0][0] == target

-     # second should be release

-     assert fakejfu.call_args[0][1] == 25

-     # flavors kwarg should be false-y (not, e.g., [None])

-     assert not fakejfu.call_args[1]['flavors']

-     # should print out list of scheduled jobs

-     assert out == "Scheduled jobs: 1, 2\n"

-     # should exit 0

-     assert not excinfo.value.code

-     # shouldn't force

-     assert fakejfu.call_args[1]['force'] is False

  

-     # check 'flavor'

-     args = cli.parse_args(

-         ['update', target, '25', '--flavor', 'server']

+ class TestCommandUpdateTask:

+     """Tests for the command_update_task function."""

+     @pytest.mark.parametrize(

+         "target",

+         # update ID or task ID (to test both paths)

+         ['FEDORA-2017-b07d628952', '32099714']

      )

-     with pytest.raises(SystemExit) as excinfo:

-         cli.command_update_task(args)

-     # should exit 0

-     assert not excinfo.value.code

-     assert fakejfu.call_args[1]['flavors'] == ['server']

+     @mock.patch('fedora_openqa.schedule.jobs_from_update', return_value=[1, 2], autospec=True)

+     def test_command_update_task(self, fakejfu, target, capsys):

+         """General tests for the command_update_task function."""

+         if target.isdigit():

+             targetarg = 'task'

+         else:

+             targetarg = 'update'

+         args = cli.parse_args(

+             [targetarg, target, '25']

+         )

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_update_task(args)

+         (out, _) = capsys.readouterr()

+         # first arg should be target

+         assert fakejfu.call_args[0][0] == target

+         # second should be release

+         assert fakejfu.call_args[0][1] == 25

+         # flavors kwarg should be false-y (not, e.g., [None])

+         assert not fakejfu.call_args[1]['flavors']

+         # should print out list of scheduled jobs

+         assert out == "Scheduled jobs: 1, 2\n"

+         # should exit 0

+         assert not excinfo.value.code

+         # shouldn't force

+         assert fakejfu.call_args[1]['force'] is False

  

-     # check 'force'

-     args = cli.parse_args(

-         ['update', target, '25', '--force']

-     )

-     with pytest.raises(SystemExit) as excinfo:

-         cli.command_update_task(args)

-     # should exit 0

-     assert not excinfo.value.code

-     assert fakejfu.call_args[1]['force'] is True

+         # check 'flavor'

+         args = cli.parse_args(

+             ['update', target, '25', '--flavor', 'server']

+         )

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_update_task(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejfu.call_args[1]['flavors'] == ['server']

  

-     # check 'openqa_hostname'

-     args = cli.parse_args(

-         ['update', target, '25', '--openqa-hostname', 'openqa.example']

-     )

-     with pytest.raises(SystemExit) as excinfo:

-         cli.command_update_task(args)

-     # should exit 0

-     assert not excinfo.value.code

-     assert fakejfu.call_args[1]['openqa_hostname'] == 'openqa.example'

+         # check 'force'

+         args = cli.parse_args(

+             ['update', target, '25', '--force']

+         )

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_update_task(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejfu.call_args[1]['force'] is True

  

- # this should not in fact get hit, but just in case we *do* break the code,

- # let's mock it to be safe...

- @mock.patch('fedora_openqa.schedule.jobs_from_update', return_value=[1, 2], autospec=True)

- def test_command_update_nonint(fakejfu):

-     args = cli.parse_args(

-         ['task', '123abc', '25']

-     )

-     with pytest.raises(SystemExit) as excinfo:

-         cli.command_update_task(args)

-     # should exit 1

-     assert excinfo.value.code == 1

+         # check 'openqa_hostname'

+         args = cli.parse_args(

+             ['update', target, '25', '--openqa-hostname', 'openqa.example']

+         )

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_update_task(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejfu.call_args[1]['openqa_hostname'] == 'openqa.example'

  

- @pytest.mark.parametrize(

-     "jobargs,argname,expecteds",

-     # args, which kwarg we expect report function to get, list of

-     # the expected values of specified kwarg for each expected

-     # call to the report function

-     [

-         (["1", "2", "3"], "jobs", [[1, 2, 3]]),

-         (

-             ["Fedora-Rawhide-20170207.n.0", "Fedora-25-20170207.n.1"],

-             "build",

-             ["Fedora-Rawhide-20170207.n.0", "Fedora-25-20170207.n.1"]

+     # this should not in fact get hit, but just in case we *do* break the code,

+     # let's mock it to be safe...

+     @mock.patch('fedora_openqa.schedule.jobs_from_update', return_value=[1, 2], autospec=True)

+     def test_command_update_nonint(self, fakejfu):

+         """Test we exit 1 on a non-integer 'task' ID."""

+         args = cli.parse_args(

+             ['task', '123abc', '25']

          )

-     ]

- )

- @pytest.mark.parametrize(

-     "targargs,repwiki,reprdb",

-     # args, should report_wiki be called, should report_resultsdb

-     # be called

-     [

-         ([], False, False),

-         (["--wiki"], True, False),

-         (["--resultsdb"], False, True),

-         (["--wiki", "--resultsdb"], True, True)

-     ]

- )

- @pytest.mark.parametrize(

-     "oqaargs,oqah,oqau",

-     # args, expected openqa_hostname, expected openqa_baseurl

-     [

-         ([], None, None),

-         (["--openqa-hostname", "test.ing"], "test.ing", None),

-         (["--openqa-baseurl", "https://test.ing"], None, "https://test.ing"),

-         (["--openqa-hostname", "test.ing", "--openqa-baseurl", "https://test.ing"], "test.ing", "https://test.ing")

-     ]

- )

- @pytest.mark.parametrize(

-     "wikiargs,wikih",

-     # args, expected wiki_hostname

-     [

-         ([], None),

-         (["--wiki-hostname", "test2.ing"], "test2.ing")

-     ]

- )

- @pytest.mark.parametrize(

-     "rdbargs,rdbu",

-     # args, expected resultsdb_url

-     [

-         ([], None),

-         (["--resultsdb-url", "test3.ing"], "test3.ing")

-     ]

- )

- @mock.patch('fedora_openqa.report.wiki_report', autospec=True)

- @mock.patch('fedora_openqa.report.resultsdb_report', autospec=True)

- def test_variations(fakerdb, fakewiki, jobargs, argname, expecteds, targargs, repwiki, reprdb,

-                     oqaargs, oqah, oqau, wikiargs, wikih, rdbargs, rdbu):

-     """Okay, that was a lot of parametrization! But really we're

-     just testing all possible arg combinations: both types of job

-     arg (job IDs and build IDs), all four possible report targets

-     (wiki, resultsdb, both, neither), all combinations of openqa

-     hostname and baseurl, and both possibilities for wiki hostname

-     and ResultsDB URL (specified or not). We test all possible

-     combinations of these with each other by stacking decorators.

-     """

-     fakerdb.reset_mock()

-     fakewiki.reset_mock()

-     args = cli.parse_args(["report"] + targargs + oqaargs + wikiargs + rdbargs + jobargs)

-     cli.command_report(args)

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_update_task(args)

+         # should exit 1

+         assert excinfo.value.code == 1

+ 

+ 

+ class TestCommandFCOSBuild:

+     """Tests for the command_fcosbuild function."""

+     @mock.patch('fedora_openqa.schedule.jobs_from_fcosbuild', return_value=[1, 2], autospec=True)

+     def test_fcosbuild(self, fakejff, capsys):

+         """General tests for command_fcosbuild."""

+         args = cli.parse_args(["fcosbuild"])

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_fcosbuild(args)

+         (out, _) = capsys.readouterr()

+         # stream kwarg should be default stream ("next")

+         assert fakejff.call_args[1]["stream"] == "next"

+         # flavors kwarg should be false-y (not, e.g., [None])

+         assert not fakejff.call_args[1]["flavors"]

+         # shouldn't force

+         assert fakejff.call_args[1]["force"] is False

+         # should print out list of scheduled jobs

+         assert out == "Scheduled jobs: 1, 2\n"

+         # should exit 0

+         assert not excinfo.value.code

+ 

+         # check explicit stream selection

+         args = cli.parse_args(["fcosbuild", "--stream", "testing"])

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_fcosbuild(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejff.call_args[1]["stream"] == "testing"

+ 

+         # check 'flavors'

+         args = cli.parse_args(["fcosbuild", "--flavors", "foo,bar"])

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_fcosbuild(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejff.call_args[1]["flavors"] == ["foo", "bar"]

+ 

+         # check 'force'

+         args = cli.parse_args(["fcosbuild", "--force"])

+         with pytest.raises(SystemExit) as excinfo:

+             cli.command_fcosbuild(args)

+         # should exit 0

+         assert not excinfo.value.code

+         assert fakejff.call_args[1]["force"] is True

+ 

+ 

+ class TestCommandReport:

+     """Tests for the command_report function."""

+     @pytest.mark.parametrize(

+         "jobargs,argname,expecteds",

+         # args, which kwarg we expect report function to get, list of

+         # the expected values of specified kwarg for each expected

+         # call to the report function

+         [

+             (["1", "2", "3"], "jobs", [[1, 2, 3]]),

+             (

+                 ["Fedora-Rawhide-20170207.n.0", "Fedora-25-20170207.n.1"],

+                 "build",

+                 ["Fedora-Rawhide-20170207.n.0", "Fedora-25-20170207.n.1"]

+             )

+         ]

+     )

+     @pytest.mark.parametrize(

+         "targargs,repwiki,reprdb",

+         # args, should report_wiki be called, should report_resultsdb

+         # be called

+         [

+             ([], False, False),

+             (["--wiki"], True, False),

+             (["--resultsdb"], False, True),

+             (["--wiki", "--resultsdb"], True, True)

+         ]

+     )

+     @pytest.mark.parametrize(

+         "oqaargs,oqah,oqau",

+         # args, expected openqa_hostname, expected openqa_baseurl

+         [

+             ([], None, None),

+             (["--openqa-hostname", "test.ing"], "test.ing", None),

+             (["--openqa-baseurl", "https://test.ing"], None, "https://test.ing"),

+             (["--openqa-hostname", "test.ing", "--openqa-baseurl", "https://test.ing"],

+              "test.ing", "https://test.ing")

+         ]

+     )

+     @pytest.mark.parametrize(

+         "wikiargs,wikih",

+         # args, expected wiki_hostname

+         [

+             ([], None),

+             (["--wiki-hostname", "test2.ing"], "test2.ing")

+         ]

+     )

+     @pytest.mark.parametrize(

+         "rdbargs,rdbu",

+         # args, expected resultsdb_url

+         [

+             ([], None),

+             (["--resultsdb-url", "test3.ing"], "test3.ing")

+         ]

+     )

+     @mock.patch('fedora_openqa.report.wiki_report', autospec=True)

+     @mock.patch('fedora_openqa.report.resultsdb_report', autospec=True)

+     def test_variations(self, fakerdb, fakewiki, jobargs, argname, expecteds, targargs, repwiki, reprdb,

+                         oqaargs, oqah, oqau, wikiargs, wikih, rdbargs, rdbu):

+         """Okay, that was a lot of parametrization! But really we're

+         just testing all possible arg combinations: both types of job

+         arg (job IDs and build IDs), all four possible report targets

+         (wiki, resultsdb, both, neither), all combinations of openqa

+         hostname and baseurl, and both possibilities for wiki hostname

+         and ResultsDB URL (specified or not). We test all possible

+         combinations of these with each other by stacking decorators.

+         """

+         fakerdb.reset_mock()

+         fakewiki.reset_mock()

+         args = cli.parse_args(["report"] + targargs + oqaargs + wikiargs + rdbargs + jobargs)

+         cli.command_report(args)

  

-     # find the appropriate mock(s) and check they were called the

-     # expected number of times

-     fakes = []

-     if repwiki:

-         assert fakewiki.call_count == len(expecteds)

-         fakes.append(fakewiki)

-     if reprdb:

-         assert fakerdb.call_count == len(expecteds)

-         fakes.append(fakerdb)

+         # find the appropriate mock(s) and check they were called the

+         # expected number of times

+         fakes = []

+         if repwiki:

+             assert fakewiki.call_count == len(expecteds)

+             fakes.append(fakewiki)

+         if reprdb:

+             assert fakerdb.call_count == len(expecteds)

+             fakes.append(fakerdb)

  

-     # check job arg parsed appropriately. for jobs, we expect one

-     # call with a list of the job IDs as the jobs arg. For builds,

-     # we expect as many calls as we included build IDs, with each

-     # call specifying one build ID as the build arg.

-     for fake in fakes:

-         for (idx, expected) in enumerate(expecteds):

-             assert fake.call_args_list[idx][1][argname] == expected

+         # check job arg parsed appropriately. for jobs, we expect one

+         # call with a list of the job IDs as the jobs arg. For builds,

+         # we expect as many calls as we included build IDs, with each

+         # call specifying one build ID as the build arg.

+         for fake in fakes:

+             for (idx, expected) in enumerate(expecteds):

+                 assert fake.call_args_list[idx][1][argname] == expected

  

-     # check the openQA, wiki and rdb args

-     for fake in fakes:

-         assert fake.call_args[1]['openqa_hostname'] == oqah

-         assert fake.call_args[1]['openqa_baseurl'] == oqau

-     if fakewiki in fakes:

-         assert fakewiki.call_args[1]['wiki_hostname'] == wikih

-     if fakerdb in fakes:

-         assert fake.call_args[1]['resultsdb_url'] == rdbu

+         # check the openQA, wiki and rdb args

+         for fake in fakes:

+             assert fake.call_args[1]['openqa_hostname'] == oqah

+             assert fake.call_args[1]['openqa_baseurl'] == oqau

+         if fakewiki in fakes:

+             assert fakewiki.call_args[1]['wiki_hostname'] == wikih

+         if fakerdb in fakes:

+             assert fake.call_args[1]['resultsdb_url'] == rdbu

  

  # vim: set textwidth=120 ts=8 et sw=4:

file modified
+36 -1
@@ -370,7 +370,7 @@ 

          # check results didn't happen

          assert mockinst.report_validation_results.call_args is None

  

-     def  test_update_noreport(self, fake_getpassed, wikimock, oqaclientmock, jobdict02):

+     def test_update_noreport(self, fake_getpassed, wikimock, oqaclientmock, jobdict02):

          """Check we do no reporting (but don't crash or do anything

          else odd) for an update test job.

          """
@@ -383,6 +383,29 @@ 

          assert mockinst.report_validation_results.call_args is None

          assert ret == []

  

+     def test_coreos_noreport(self, fake_getpassed, wikimock, oqaclientmock, jobdict04):

+         """Check we do no reporting for Fedora CoreOS jobs."""

+         # adjust the OpenQA_Client instance mock to return jobdict04

+         instmock = oqaclientmock[1]

+         instmock.get_jobs.return_value = [jobdict04]

+         (_, mockinst) = wikimock

+         ret = fosreport.wiki_report(jobs=[1])

+         # check results didn't happen

+         assert mockinst.report_validation_results.call_args is None

+         assert ret == []

+ 

+     def test_no_jobs_noreport(self, fake_getpassed, wikimock, oqaclientmock):

+         """Check we do no reporting if we find no jobs."""

+         # adjust the OpenQA_Client instance mock to return nothing

+         instmock = oqaclientmock[1]

+         instmock.get_jobs.return_value = []

+         (_, mockinst) = wikimock

+         ret = fosreport.wiki_report(jobs=[1])

+         # check results didn't happen

+         assert mockinst.report_validation_results.call_args is None

+         assert ret == []

+ 

+ 

  @mock.patch.object(resultsdb_api.ResultsDBapi, 'create_result')

  @pytest.mark.usefixtures("ffmock", "oqaclientmock")

  class TestResultsDBReport:
@@ -413,6 +436,18 @@ 

          assert fakeres.call_args[1]['firmware'] == 'bios'

          assert fakeres.call_args[1]['outcome'] == 'PASSED'

  

+     def test_coreos(self, fakeres, oqaclientmock, jobdict04):

+         """Check resultsdb_report behaviour with a CoreOS job."""

+         # adjust the OpenQA_Client instance mock to return jobdict04

+         instmock = oqaclientmock[1]

+         instmock.get_jobs.return_value = [jobdict04]

+         fosreport.resultsdb_report(jobs=[1])

+         assert fakeres.call_args[1]['item'] == "fedora-coreos-32.20200726.3.1-live.x86_64.iso"

+         assert fakeres.call_args[1]['ref_url'] == "https://some.url/tests/48192"

+         assert fakeres.call_args[1]['testcase']['name'] == "fcosbuild.base_services_start"

+         assert fakeres.call_args[1]['firmware'] == "bios"

+         assert fakeres.call_args[1]['outcome'] == "PASSED"

+ 

      def test_uefi(self, fakeres, oqaclientmock):

          """Check resultsdb_report with UEFI test."""

          # modify the job dict used by the mock fixture

file modified
+107
@@ -60,6 +60,63 @@ 

          ]

      }

  }

+ # trimmed CoreOS build metadata JSON, used for scheduling CoreOS jobs

+ COREOSJSON = {

+     # pylint: disable=line-too-long

+     "stream": "next",

+     "metadata": {

+         "last-modified": "2020-08-25T19:20:43Z"

+     },

+     "architectures": {

+         "x86_64": {

+             "artifacts": {

+                 "metal": {

+                     "release": "32.20200824.1.0",

+                     "formats": {

+                         "4k.raw.xz": {

+                             "disk": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-metal4k.x86_64.raw.xz",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-metal4k.x86_64.raw.xz.sig",

+                                 "sha256": "124b9ef7aa58d4c85c59259a6eae6e55175f1f8aa00f9df4e28f6e1fe77170df"

+                             }

+                         },

+                         "iso": {

+                             "disk": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live.x86_64.iso",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live.x86_64.iso.sig",

+                                 "sha256": "23b979b1675fcd1cc1972f77cce802e585eb123f4ef1770448a6085b574527fb"

+                             }

+                         },

+                         "pxe": {

+                             "kernel": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-kernel-x86_64",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-kernel-x86_64.sig",

+                                 "sha256": "249d9f9b59bc96904de096a5b6a15a7892a596bdc109e6b7b123fe69f5969b9c"

+                             },

+                             "initramfs": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-initramfs.x86_64.img",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-initramfs.x86_64.img.sig",

+                                 "sha256": "92ef4de1daa0d65c8981a1f5f6e9b80cb3f95ba5a08f25f1621ac0ecb934f9b5"

+                             },

+                             "rootfs": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-rootfs.x86_64.img",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live-rootfs.x86_64.img.sig",

+                                 "sha256": "8581ac303b9f0465f910229863505a0ca8d937924bfa5ee2f4db61daa29d94da"

+                             }

+                         },

+                         "raw.xz": {

+                             "disk": {

+                                 "location": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-metal.x86_64.raw.xz",

+                                 "signature": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-metal.x86_64.raw.xz.sig",

+                                 "sha256": "8fc5dd1ab58acebd69b90b4d8dc46bc8e7166ace101fdd092e6526941632ece2"

+                             }

+                         }

+                     }

+                 }

+             }

+         }

+     }

+ }

  

  @pytest.mark.usefixtures("ffmock02")

  class TestGetImages:
@@ -829,4 +886,54 @@ 

          'UP2REL': '26',

      }

  

+ @mock.patch("fedfind.helpers.download_json", return_value=COREOSJSON)

+ @mock.patch("fedfind.helpers.get_current_stables", return_value=[31, 32])

+ @mock.patch("fedfind.helpers.get_current_release", return_value=32)

+ @mock.patch("fedora_openqa.schedule.OpenQA_Client", autospec=True)

+ def test_jobs_from_fcosbuild(fakeclient, fakecurrr, fakecurrs, fakejson):

+     """Test scheduling jobs from a Fedora CoreOS build."""

+     # the OpenQA_Client instance mock

+     fakeinst = fakeclient.return_value

+     # for now, return no 'jobs' (for the dupe query), one 'id' (for

+     # the post request)

+     fakeinst.openqa_request.return_value = {"jobs": [], "ids": [1]}

+     # simple case, default stream is next

+     ret = schedule.jobs_from_fcosbuild()

+     # should get one job and the build back

+     assert ret == [1]

+     # find the POST calls

+     posts = [call for call in fakeinst.openqa_request.call_args_list if call[0][0] == "POST"]

+     # one flavor, one call

+     assert len(posts) == 1

+     parmdict = posts[0][0][2]

+     assert parmdict == {

+         "DISTRI": "fedora",

+         "VERSION": "32",

+         "ARCH": "x86_64",

+         "BUILD": "Fedora-CoreOS-32.20200824.1.0",

+         "_OBSOLETE": "1",

+         "_ONLY_OBSOLETE_SAME_BUILD": "1",

+         "QEMU_HOST_IP": "172.16.2.2",

+         "NICTYPE_USER_OPTIONS": "net=172.16.2.0/24",

+         "FLAVOR": "CoreOS-colive-iso",

+         "CURRREL": "32",

+         "PREVREL": "31",

+         "RAWREL": "33",

+         "UP1REL": "31",

+         "UP2REL": "30",

+         "IMAGETYPE": "colive",

+         # pylint: disable=line-too-long

+         "ISO_URL": "https://builds.coreos.fedoraproject.org/prod/streams/next/builds/32.20200824.1.0/x86_64/fedora-coreos-32.20200824.1.0-live.x86_64.iso",

+         "LOCATION": "",

+         "SUBVARIANT": "CoreOS",

+     }

+ 

+     # test we get no jobs if we specify a non-found flavor

+     ret = schedule.jobs_from_fcosbuild(flavors=["nonexistent"])

+     assert ret == []

+     posts = [call for call in fakeinst.openqa_request.call_args_list if call[0][0] == "POST"]

+     # should still only have one call

+     assert len(posts) == 1

+ 

+ 

  # vim: set textwidth=120 ts=8 et sw=4:

This adds the ability to schedule jobs and report results to
ResultsDB for Fedora CoreOS builds. These are not Pungi composes
and so don't have productmd-style metadata or messages, so we
handle them entirely separately. Currently you can only schedule
jobs for the current build from one of the streams, and only for
x86_64 and the "live" ISO. This requires a new version of
resultsdb_conventions that adds conventions for Fedora CoreOS
build results.

Also refactors test_cli a bit as it was confusing that some tests
were in classes and others weren't. Think I did this to make
pylint happy but I'd rather make humans happy than a script,
really...

Signed-off-by: Adam Williamson awilliam@redhat.com

note, we don't do anything in the messaging consumer as there are currently no messages to use. I plan to just have the infra ansible scripts deploy a cron job that runs the CLI for each stream once an hour - if there's a new build it'll test it, if not, it'll do nothing (since we won't pass --force).

Build succeeded.

Only skimmed but seems sane to me!

Pull-Request has been merged by adamwill

3 years ago