#4033 kojira on demand
Merged by mikem. Opened by mikem.
mikem/koji kojira-on-demand  into  master

Download 4033.patch

This is a major change to the way that repos are generated in Koji, shifting from constant global re-generation of repos as tag content changes to primarily on-demand generation when repos are required. Kojira still exists, but is about 30% smaller with significant parts of the logic moved to the hub.

Related: https://pagure.io/koji/issue/3176
Related: https://pagure.io/koji/issue/3866

Repos are only generated by request. All Koji build processes that need a repo make such a request. Additionally, kojira will regularly make such requests for tags that are specifically configured for automatic generation (by setting repo.auto for the tag).

This does not mean that every build triggers a fresh repo. In many cases, repos will be re-used. By default, a repo request asks for a repo with a minimum event about an hour old. So, the system will prefer to re-use a (very slightly) out of date repo versus regenerating a fresh one. This allowed lag can be controlled with the RepoLag setting on the hub. Otoh, the --wait-repo option to the build command will cause the buid task to request a current repo.

There's a lot of change here. I recommend reading through the included doc first.

Metadata Update from @mikem:
- Pull-request tagged with: discussion

see also: #4027

14 new commits added

  • no more data field
  • use full opts in request entries too
  • ...
  • propagate opts in repo_init
  • include opts in query
  • missing import
  • more opts support
  • handle repo opts in request call
  • fix import
  • fix fields
  • move code into kojihub/repos
  • more unit test fixes
  • unit test
  • flake8

24 new commits added

  • flake8
  • handle deleted tags sanely in tag_last_change_event
  • typo
  • add external_repo_data table. check ext repo tables for first/last tag change events
  • fix newer_than logic in WaitrepoTask
  • todos
  • AllowNewRepo hub config
  • fixes
  • request min_event never null or in future
  • ...
  • default min_event. don't allow future events
  • use BulkUpdateProcessor in update_end_events
  • BulkUpdateProcessor
  • update_end_events()
  • record begin/end events in repo_init
  • QueryView: accept single field value
  • adjust query fields
  • QueryView: adjust special field name handling
  • adjust event fields
  • honor owner opt to make_task
  • ...
  • typo
  • use kojira user for repo tasks
  • hook to fulfill requests when repos are marked ready

rebased onto e107400463679113971daaa400d75ec006f4dca5

35 new commits added

  • don't expire at_event repos
  • typo
  • repos.py: support at_event
  • update repo symlink logic
  • ...
  • block owner opt in makeTask and host.subtask
  • db lock for repo queue
  • ...
  • move new exports around
  • drop noisy debug line
  • ...
  • ...
  • ...
  • ...
  • ...
  • ...
  • typo
  • pruning tweaks
  • kojira: use ordered dict for delete queue
  • rework repo expiration and lifetimes a bit
  • more kojira rework
  • cleanup
  • track update time for repos
  • factor in implicit joins for fields="*"
  • partial repo docs
  • remove some obsolete code from kojira
  • ...
  • do_auto_repos
  • fakehub: option to override config
  • tweak logging
  • adjust archlist for external repo check
  • tweak logging; wait-repo --new
  • typo
  • ...
  • separate get/set for erepo data

This is feeling tantalizingly close, which probably means it's about half done ;)

Also the merge on my branch seems to have confused pagure. The diff displayed is incorrect.

rebased onto 7b9857d6a589f959cc1b22887b5dcb6bad180508

squashed and rebased. pagure seems less confused now

18 new commits added

  • custom opts sorted out?
  • allow containment operator
  • partial
  • fragment: struggling with repo opt timing
  • fine to have default values in the class
  • option renamed
  • flake8
  • more config options in repos.py
  • use requests by default in regen-repo
  • adjust watch_fields
  • adjust event validation
  • wait-repo tweaks
  • cli: wait-repo-request command
  • drop complex request options from wait-repo
  • fix call
  • typo
  • partial...
  • drop event="new" code

1 new commit added

  • fix unit tests

1 new commit added

  • unit tests for RepoWatcher

1 new commit added

  • unit tests for RepoWatcher

1 new commit added

  • more kojira tests

32 new commits added

  • more mock.patch cleanup
  • avoid test failure when running in console
  • adjust valid_repo opts checks
  • extend valid_repo unit test and fix a bug
  • test_request_existing_req_invalid
  • test_request_at_event
  • test_request_new_req
  • test_request_existing_req
  • test_request_existing_repo
  • more stubs
  • fix import
  • stub test
  • link repo doc in toc
  • unused options
  • include new setting
  • test + fixes
  • more kojira unit tests
  • fix unit test
  • refactor repo delete
  • avoid circular import issue
  • some kojira cleanup
  • adjust state transition check
  • update RepoQuery
  • decode query bytes in log
  • sanity check requests before reusing
  • repo.query api call
  • reduce some cli code duplication
  • tweak warnings in external repo check
  • clean repo queue
  • implement retry limit for repo queue
  • cleanup a few TODOs
  • tweak test

rebased onto fb48bfa557175703ff13f9331c3a93a517d7f9c9

1 new commit added

  • fix unit test after rebase

2 new commits added

  • drop unused RawClauses code
  • clean up BulkUpdateProcessor and add tests

6 new commits added

  • more docs
  • support MaxRepoTasksMaven
  • drop unused method
  • api for querying repo queue
  • flake8
  • record custom opts in repo.json

1 new commit added

  • don't trigger regens from scheduler. kojira is enough

5 new commits added

  • unit tests
  • wait-repo compat
  • fixes
  • use old regen-repo code for older hubs
  • fixes

This is nearly complete

--request-repo?

--request-repo?

This is also not accurate. The build handler (and any other handler that calls self.getRepo()) will always end up using the repo.request call (which will either return a matching active repo or an active request entry).

This flag controls affects the parameters of the repo we request. If given, we pass min_event = "last", which means the build requests a repo that is current as of that point in time. Without it, we get the default behavior, which uses a min_event value that is slightly older (based on RepoLag setting).

The default behavior is effectively similar to what we have now, where the build gets whatever the latest repo for the tag is, which may or may not be current depending on timing and kojira backlog.

So, perhaps something like --current-repo?

Hmm, thinking about it a little more, --wait-repo is probably fine if we adjust the help text, plus it matches --wait-build.

1 new commit added

  • clarify --wait-repo help text

3 new commits added

  • adjust watch output; die if request fails
  • handle problem repos
  • fix typos

rebased onto 8a49e0308458c08de428e7f66513b18ce4e18135

Rebased. Minor conflicts with PR#4097

Two more updates (migration + cleanup): https://pagure.io/fork/tkopecek/koji/commits/pr4033a

Maybe (not as a part of this PR) we could merge TaskWatcher and RepoWatcher into some baseclass. Also not sure if they shouldn't be in the same location. Probably both have some use outside of CLI.

Not sure if repo_queue shouldn't contain some priority field. But maybe it is not needed. I was thinking about some situation when we need to run some repo immediately, but we can spawn newRepo tasks without consulting the queue at all.

8 new commits added

  • Apply repo_lifetime to custom repos even if current
  • fix migration script
  • migration script
  • clean up some TODO items
  • update unit tests
  • streamline simple case for tag_first/last_change_event
  • backwards compatible age checks in kojira
  • repowatch unit test fixes

Two more updates (migration + cleanup)

I pulled in the migration one, though the script also needed a fix because of this:

ERROR:  CREATE INDEX CONCURRENTLY cannot run inside a transaction block

queue cleanup is actually already handled in the hub code. See clean_repo_queue.

Maybe (not as a part of this PR) we could merge TaskWatcher and RepoWatcher into some baseclass.

They're quite different code, based on different queries. RepoWatcher never directly queries the task, but uses the small bit of task info that can be returned by repo.checkRequest. There might be some small unification that could be done, but I agree it should probably be separate from this PR.

Not sure if repo_queue shouldn't contain some priority field. But maybe it is not needed. I was thinking about some situation when we need to run some repo immediately, but we can spawn newRepo tasks without consulting the queue at all.

I keep debating adjusting this. For the most part, I think first in first out is the best plan, but you have a good point about manual override, and it would be nice to deprioritize the auto regens.

My hope is that the queue will stay small or empty with on-demand, but it's hard to be sure how that will play out without actually running this with a real production load.

Casual querying in prod suggests that on-demand would trigger less than 25 regens per hour (assuming default RepoLag setting of 1 hour). It's unclear to me how many tags we'd need to set up for auto-regen, though I figure at least all the '*-repo' targets (currently there are 55 distinct build tags there). It's hard to know how often those would actually need to regen, but default RepoAutoLag is 2 hours, so worst case is about 28/hr.

Compared to our frequent spikes of 300+ regens/hour, I'd expect the queue to stay small.

I suppose the x-factor would be user abuse of the request api.

Relevant queries

select count(id), date_trunc('hour', create_time) as bin, count(distinct cast(xpath('//param[2]/value/string/text()', request::xml) as text[]))from task where method in ('build', 'maven', 'chainmaven') and create_time > now() - '1 day'::interval group by bin;
select count(id), date_trunc('hour', create_time) as bin, count(distinct cast(xpath('//param[4]/value/string/text()', request::xml) as text[]))from task where method in ('image', 'appliance', 'livecd', 'livemedia') and create_time > now() - '1 day'::interval group by bin;
select count(id), date_trunc('hour', create_time) as bin, count(distinct cast(xpath('//param[1]/value/string/text()', request::xml) as text[]))from task where method = 'newRepo' and create_time > now() - '1 day'::interval group by bin;

4 new commits added

  • update additional fields in repo_done_hook
  • simplify updateRepos
  • kojira: adjust cleanup of self.repos
  • docs updates

6 new commits added

  • unit tests and fixes
  • use tag name in waitrepo task for readability
  • cleanup
  • better approach to repo lookup from task id
  • support priority for requests
  • track user for request

1 new commit added

  • unit tests

That brings repos.py to full coverage

kojihub/repos.py                        483      0   100%

rebased onto d68a9273e1a768e7b9665bc8551954d7760ebf63

rebased onto d68a927

rebased onto current master and squashed commits again. If anyone is interested, pre-squash branch is saved here. No merge conflicts.

1 new commit added

  • update api check data for host.repoInit

Metadata Update from @mikem:
- Pull-request tagged with: deprecation, feature

Here is the current result of check_api as compared to current master:

Signature changed for koji.tasks.WaitrepoTask.handler:
  old: (self, tag, newer_than=None, nvrs=None)
  new: (self, tag, newer_than=None, nvrs=None, min_event=None)
Args extended for koji.tasks.WaitrepoTask.handler: ['min_event']
Added module global: koji.util.RepoWatcher
Added RPC call: repo.autoRequests
Added RPC call: repo.checkQueue
Added RPC call: repo.checkRequest
Added RPC call: repo.get
Added RPC call: repo.getExternalRepoData
Added RPC call: repo.query
Added RPC call: repo.queryQueue
Added RPC call: repo.references
Added RPC call: repo.request
Added RPC call: repo.setExternalRepoData
Added RPC call: repo.setRequestPriority
Added RPC call: repo.setState
Added RPC call: repo.updateEndEvents
Added RPC call: tagFirstChangeEvent
Added RPC call: tagLastChangeEvent
Signature changed for rpc call getEvent:
  old: (id)
  new: (id, strict=True)
Args extended for rpc call getEvent: ['strict']
Signature changed for rpc call getRepo:
  old: (tag, state=None, event=None, dist=False)
  new: (tag, state=None, event=None, dist=False, min_event=None)
Args extended for rpc call getRepo: ['min_event']
Signature changed for rpc call host.repoInit:
  old: (tag, task_id=None, with_src=False, with_debuginfo=False, event=None, with_separate_src=False)
  new: (tag, task_id=None, event=None, opts=None)
ERROR: Arg count reduced for rpc call host.repoInit
Got 23 warnings and 1 errors

2 new commits added

  • extend repo_init unit test
  • BulkUpdateProcessor: only assign for set_keys

3 new commits added

  • drop shadowbuild case in newRepo
  • use requests in sidetag plugin
  • update koji-shadow to use repo requests

@tkopecek please check the change for the sidetag plugin. It's not strictly required, but probably better to use the requests interface here. The main risk is if some client or script is assuming that the task_id field will be not None.

We could also drop the automatic initial regen option entirely, since the later builds will just make a request, but there is probably some value in triggering it early.

1 new commit added

  • minor doc updates

I've checked rpkg usage and it is safe in regard to their usage. I believe it is safe to update it.

@tkopecek a few more questions for review

  • Do you think the queue cleanup in the hub code is ok, or should we consolidate into the cleanup script?
  • Any concerns about the host.repoInit api change?
  • Do you think we should require a permission to request a repo?
  • repo init callback args changed a bit. Won't affect protonmsg plugin, but should we worry about anything else?
  • Do the docs read ok?
  • does the overall structure of the cli changes make sense?
  • Do you think the queue cleanup in the hub code is ok, or should we consolidate into the cleanup script?

I was thinking about cleanup script since we've introduced scheduler planning. For now I would leave it here. In future I was imagining some hook which could call different cron-like tasks (scheduler planning, cleaning sessions, cleaning repo queue, ...). Hook could know what is the async needed and trigger them, so they don't cause big bursts when they hit each other. Wild idea would be to run async operations really async, so connection to user can be closed and these subtasks can run after that. But that is another major intervention we've not capacity to do now.

  • Any concerns about the host.repoInit api change?

No, I believe it is not used by anyone except koji itself.

  • Do you think we should require a permission to request a repo?

I would start without and see what is the real usage. I don't think it would be bigger load than is with current kojira.

There is now not possibility to request repo with overriding repo.opts via request_repo. But not sure if it has any real-world usage, so it is probably fine (I can still spawn newRepo manually).

  • repo init callback args changed a bit. Won't affect protonmsg plugin, but should we worry about anything else?

I don't know about anything using it.

  • Do the docs read ok?

Yep, I would only expand repo.auto and kojira interaction for people who migrate from older koji. It is probably not clear what kojira do now and that it still needs to be running.

  • does the overall structure of the cli changes make sense?

Yes, only thing I'm not sure if koji build --wait-repo shouldn't file a request automatically.


Not sure if we want to update tag2distrepo to using queue. As it is just spawning the task and not waiting for anything, it could be better approach?

koji wait-repo-request <id> fails on KeyError: 'score'.

Yes, only thing I'm not sure if koji build --wait-repo shouldn't file a request automatically.

Hmm, interesting. I guess it could...

This seems like a relatively small optimization, as in most cases the build task will start reasonably quickly and make the request almost immediately. In cases where that is not true, it might actually be better to defer the load.

Definitely something to think about, but I'm not sure I'd want to implement this optimization in the client. Of course, a user could always just make a separate request-repo call if they feel the need.

Not sure if we want to update tag2distrepo to using queue

dist repos are not covered by the requests feature. They could be eventually, but I think it will be tricky to get it right.

koji wait-repo-request fails on KeyError: 'score'

Ah, I missed that field ref. Will fix!

2 new commits added

  • flake8
  • score field was dropped

Commit f2da4b8e fixes this pull-request

Pull-Request has been merged by mikem

Some testing notes

overview

Since there is a lot of churn here, it is probably best to focus on higher level behavior. I.e.

  • cli behavior
  • kojira behavior
  • ensuring that builds are triggering regenerations as needed

cli

The cli has two new commands: request-repo and wait-repo-request. These should function as expected.

The existing wait-repo and regen-repo commands have been modified in light of the repo requests. Both should work in compat mode with older hubs.

Additionally, taginfo and list-history have some small changes. Both commands should work normally.

Additionally, while the build command itself hasn't really changed, the interpretation of the --wait-repo option has. When this option is given, the system should use a current repo for the build tag. That doesn't always mean a regen, as there might already be a current repo, but the repo that is used match the last change event for the build tag. For example, if you make a change to the build tag (or one of its ancestors in the inheritance), and then trigger a build with this option, you should see the task fire off an appropriate waitrepo subtask.

api

There are a few calls that have been updated and several entirely new calls. Most of the new calls are used by kojira or the cli commands.

The repoInfo, getRepo calls have been rewritten. They should work as they did before.

The getEvent call gained a strict option (default True). The new option should be honored and the call should otherwise work as before.

The newRepo call can be disabled by the AllowNewRepo hub setting. It should otherwise work as before.

The tagLastChangeEvent and tagFirstChangeEvent are fairly technical calls used by the repo code. Both should work for any tag and event combo. In particular

  • after making changes to a tag (or its ancestors), tagLastChange event should correctly report the most recent change
  • tagLastChangeEvent should report None when querying before the first change event for the tag
  • tagFirstChangeEvent should report None when querying after last change event for the tag

All of the repo.* calls are new. Almost all of them are exercised by either the new cli commands or kojira. The exceptions are -- repo.query, repo.setRequestPriority, and repo.queryQueue

Metadata Update from @tkopecek:
- Pull-request untagged with: discussion
- Pull-request tagged with: testing-ready

Metadata