#3628 Add checksum API
Merged by tkopecek. Opened by jcupova.
jcupova/koji issue-3627  into  master

Download 3628.patch

Fixes: https://pagure.io/koji/issue/3627

Add foreign key constraint here, please.

mutable field shouldn't be the default value (replace with None and set it in function instead) + checksum_types would be better here if we expect list

More informative would be something like "There is no rpm {nvrea} signed with {sigkey}". I think that "No cached signature" is a bit misleading.

There is no need to error here - just return with noop?

We want to compute checksum for signed rpm itself, not for its content (it is not even possible to get rpm file data this way)

rebased onto 8f17c8a29356f93c738617a606c567621364aa78

rebased onto fa94a30e4f3c80ee0aef3c066b5fe059108b7fc2

rebased onto e4a3ba5ea439c1bd7a2d57ad773c01d545a654fc

rebased onto 2b9f77990f83e0f2254b18182bab020b546c4b32

rebased onto 7a1c0a7a11372de51345a843b212974fea6484d8

rebased onto 7bc8c3958b83aa07833c123573319839a83e21b8

rebased onto 4db50efc2ac9fe3b8efc6f11e4dd48e91814b8e6

rebased onto a06501dafae91ce210b8ee090a90d7497b9d2ecf

rebased onto 3c4c87d9be15b19dc42d2032e673aca07e3cbb29

It works, but for consistency with other records it could be in one line rpm_id INTEGER NOT NULL REFERENCES rpminfo(id)

These can be reverted to original state. There is no change in the method, it is just pulled out of original space (listRPMFiles), same for get_rpm_file

Is there any reason to publish this method via API?

This is not necessary - we want to have default types in config file, not code.

pull it from config

any iterable is ok, so at least (list, tuple)

sighash is not used for anything, it could be replaced with no columns and countOnly.

same nvra could be used in previous error message when there is only rpm_id

checksum_types are now strings. In the db are integers, unify usage (either convert to ints now or expects ints on input. sigkey is completely missing in clauses. Furthermore, next step checks only equivalent sets. In case some of hashes already exist it will fail during the insert.

It could be a bit easier without list encapsulation
chsum_list = {chsum: getattr(hashlib, chsum)() for chsum in checksum_type}

chunk name is used in similar context throughout the code

rebased onto 15cbf3b9dd8e6112867886ae9a334804b25bd873

@tkopecek all fixed

+
CREATE INDEX rpm_checksum_rpm_id ON rpm_checksum(id);
UNIQUE(rpm_id, sigkey, checksum_type)

quotes should not be there + typo in name

checksum=chsum.hexdigest()

mutable list can't be in default values (use None) instead + config says that default is md5+sha256, not sha256

checksum_type_int = [koji.CHECKSUM_TYPES[chsum] for chsum in checksum_types]

There could be subset available.

you should call write_signed_rpm instead of following code. It would create signed/* data, so it could be computed. Otherwise it would be failing in most cases as GC will delete those copies.

generally "s/RPMDefaultCHecksums/RPMDefaultChecksums/g"

s/repo/rpm

It should be renamed everywhere as it is always list -> missing_checksum_types

isinstance(checksum_types, (list, tuple))

if chtype not in koji.CHECKSUM_TYPES

it could be simplifed via set()

koji.CHECKSUM_TYPES[func]

s/friandly/friendly

drop this cycle ^

rebased onto e62295c6049ecca99ff9ed2a035bdc655fb5a53e

rebased onto 7a674468faac1d77389c26875938095e988f9038

rebased onto f3a844e3254e63f483e6c5486a6aa98597c908b7

rebased onto 93dbacda08054c9b199fba8af8f74e0e1ac8dab3

rebased onto 0318ba216284d4c03a70c64f183ef03960b5119e

rebased onto ffbcc0f77570378a867f00bc59abb3b3048f9fee

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

rebased onto df2bfa978d3900f57031c75aef1c0c503b6c8fca

There are a few places we need to clear this data:

  • delete_build
  • reset_build
  • delete_rpm_sig

The rpm_checksum table doesn't currently have a primary key. It would make sense to use (rpm_id, sigkey, checksum_type)

The exception for unsigned rpms seems unnecessary and inconsistent. The call is not looking for a specific key, but for any key. If there are no keys, then a {} result is accurate.

The koji get* calls are generally tolerant unless they are explicitly passed a strict arg. This one should be too (not that I'm suggesting that we add a strict mode here).

I had hoped for more efficient use of IO in the write_signed_rpm hook. We already loop over the new files contents in koji.splice_rpm_sighdr. We could add a callback there to enable the checksum. This is admittedly more complex, but without it we're re-reading every signed copy after writing it. I thought I'd made this more explicit, but looking back I see I only alluded to a few places.

Metadata Update from @jcupova:
- Pull-request untagged with: testing-ready

1 new commit added

  • Fix review

2 new commits added

  • Fix review
  • Add checksum API

2 new commits added

  • Fix review
  • Add checksum API

Apologies for the delay. There's a bit more complexity here than I expected.

1) splice changes

The splice_rpm_sighdr callback changes are not quite what I envisioned, and I don't think we need to expose so much specific data to that function. I made some changes here that are more along the lines of what I was thinking.

https://pagure.io/fork/mikem/koji/commits/pr3628updates

The first commit there, "simplify splice_rpm_sighdr changes", is kind of what I had in mind initially. The second commit takes things a bit further and bears some explanation (see below).

Also, note that the previous callback changes were not capturing the full file contents, but only the part after the signature. This would result in incorrect sums. This is fixed in the above changes.

2) unauthenticated writes

The getRPMChecksums export is an unauthenticated call that can potentially write out signed copies to disc. I don't think we should allow this so freely. The writeSignedRPM call requires special access.

There are a couple ways we might address this. One is to calculate the checksums without writing the copy. This is what led to my second commit above ("generator for reading spliced rpm signatures"). Splitting that logic out of the original splice function should make it fairly easy to generate a checksum for a signed rpm without actually performing the writes.

Alternately, we could simply fail when we have neither cached sums or signed copies. Which leads me to...

3) strict option

Above I write that I was not suggesting adding a strict option for getRPMChecksums. There might be reason to, but I think it bears more thought. However, I do not think that the case where an rpm has no signatures should ever result in an error. In that case, there's a definitive answer to the query and that answer is {}.

That said, the situation above suggests a situation where a strict option might make sense -- whether to raise an error when we have the signature, but neither a cached checksum nor a signed copy. In the non-strict case we'd report the missing checksums as None. This would make it easier for the client to distinguish which sum is missing.

4) Some smaller things.

The variable named chsum_list is not a list, which is unnecessarily confusing.

Seems a little odd to use BulkInsert for such a small number of inserts.

The create_rpm_checksum function has signature *kwargs, which seems unnecessarily generic. We only call it twice, and with the same sort of parameters each time.

The create_rpm_checksum function modifies mutable parameters that are passed to it. This sort of hidden side effect can cause issues that are very hard to debug.

The getRPMChecksums function is appended at the end of the exports class, but it would probably make more sense to have it near the other exports that relate to rpm signatures. E.g. somewhere near writeSignedRPM.

rebased onto 5f9a68a8e365d8c741beb373d2959cd635eb9358

3 new commits added

  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes

5 new commits added

  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

5 new commits added

  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

With rewriting generator to IOStream it could be more usable? We can unify behaviour to deal with it as with signed copy in all places except writing the signed copy (based on https://gist.github.com/mechanical-snail/7688353, not clear what is the license there). Not tested.

def spliced_sig_reader(path, sighdr, bufsize=8192):
    class Stream(io.RawIOBase):
        def __init__(self, path, sighdr):
            self.path = path
            self.sighdr = sighdr
            self.buf = None
            self.gen = self.generator()
        def generator(self):
            (start, size) = find_rpm_sighdr(self.path)
            with open(path, 'rb') as fo:
                # the part before the signature
                yield fo.read(start)
                # the spliced signature
                yield sighdr
                # skip original signature
                fo.seek(size, 1)
                # the part after the signature
                while True:
                    buf = fo.read(bufsize)
                    if not buf:
                        break
                    yield buf
        def readable(self):
            return True
        def readinto(self, b):
            try:
                expected_buf_size = len(b)
                data = self.buf or next(self.gen)
                output = data[:expected_buf_size]
                self.buf = data[expected_buf_size:]
                b[:len(output)] = output
                return len(output)
            except StopIteration:
                return 0    # indicate EOF
    return io.BufferedReader(Stream(path, sighdr), buffer_size=bufsize)

1 new commit added

  • Rewrite generator to IOStream

6 new commits added

  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

6 new commits added

  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

6 new commits added

  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

rebased onto 86a03364edafeeb06f6037a7fa7c4ef0dd8cd478

We should avoid os.path.join in the hub code for safety reasons. We have koji.joinpath for this now.

The BufferedReader implementation is neat, but the class declaration should be moved out of the function (and given a clearer name). Embedding the class definition like this creates a new class each time the function is called.

With the code updates that automatically splice and sum when the signed copy is missing, the strict option doesn't really have much meaning separate from (anti-)cacheonly. Better and simpler to just drop the strict option.

The builddir calculation is incorrect.

        builddir = koji.pathinfo.build(rpm_info)

The rpm info cannot stand in for the build info. The NVR could be completely different.

I have addressed some of these on my branch. I've also added some unit tests for the splicing code.
https://pagure.io/fork/mikem/koji/commits/pr3628updates

3 new commits added

  • Move class out of function
  • use signed copy for checksum if avail, drop strict option
  • simple unit tests for splicing code

9 new commits added

  • Move class out of function
  • use signed copy for checksum if avail, drop strict option
  • simple unit tests for splicing code
  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API
self.checksums[name] = checksum.hexdigest()

Having this function alter the structure of a key instance variable seems fragile and unnecessary. This could could instead return an altered copy and calling code could use the result of that call rather than accessing checksums.

def spliced_sig_reader(path, sighdr, bufsize=8192):
    """A generator that yields the contents of an rpm with signature spliced in"""

minor, but the docstring here is now inaccurate. Should be something like: "returns a file-like object whose contents have the new signature spliced in"
Sorry, meant to note that before.

Your commit message only mentions the class move, but the other changes are also notable.

9 new commits added

  • Move class out of function and create to_hexdigest function
  • use signed copy for checksum if avail, drop strict option
  • simple unit tests for splicing code
  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

9 new commits added

  • Move class out of function and create to_hexdigest function
  • use signed copy for checksum if avail, drop strict option
  • simple unit tests for splicing code
  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

Oh! One more thing.

In create_rpm_checksum we filter out the checksums that we already have in the db. We should probably take the opportunity to check that the values we're ignoring actually match what is in the db. A mismatch here is a "should not happen" situation, so we should raise an error.

1 new commit added

  • Add comparison between checksum from DB and caltulated checksum

Sorry, I keep finding more small things :)

The deepcopy in create_rpm_checksum is not needed at this point, since the data will be a name:digest dictionary. A simple .copy() should suffice.

The error in create_rpm_checksum should indicate the rpm id, sigkey, and checksum type so that this can be debugged if it ever happens.

The checksum_types keyword arg for write_signed_rpm is never used in the current version of the PR. I think we can drop it and just use the default.

10 new commits added

  • Add comparison between checksum from DB and caltulated checksum
  • Move class out of function and create to_hexdigest function
  • use signed copy for checksum if avail, drop strict option
  • simple unit tests for splicing code
  • Rewrite generator to IOStream
  • Use strict for rpm without signed copies or checksums + small review fixes
  • generator for reading spliced rpm signatures
  • simplify splice_rpm_sighdr changes
  • Fix review
  • Add checksum API

Metadata Update from @jcupova:
- Pull-request tagged with: testing-ready

Ok, I think we're done

:thumbsup:

Thanks for all the revisions!

Metadata Update from @relias-redhat:
- Pull-request tagged with: testing-done

Commit 6f068f16 fixes this pull-request

Pull-Request has been merged by tkopecek

Metadata