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 verify functionality to snapshot-metadata-lister tool #117

Open
wants to merge 1 commit into
base: main
Choose a base branch
from

Conversation

Rakshith-R
Copy link
Contributor

What type of PR is this?

Uncomment only one /kind <> line, hit enter to put that in a new line, and remove leading whitespaces from that line:

/kind api-change
/kind bug
/kind cleanup
/kind design
/kind documentation
/kind failing-test

/kind feature

/kind flake

What this PR does / why we need it:

This commit adds verify functionality which when
enabled copies changed blocks from source device to target device and finally verifies contents of
both the devices to be same.

Which issue(s) this PR fixes:

Fixes #108

Special notes for your reviewer:

Does this PR introduce a user-facing change?:

NONE

@k8s-ci-robot k8s-ci-robot added release-note-none Denotes a PR that doesn't merit a release note. kind/feature Categorizes issue or PR as related to a new feature. labels Jan 29, 2025
@k8s-ci-robot
Copy link
Contributor

[APPROVALNOTIFIER] This PR is APPROVED

This pull-request has been approved by: Rakshith-R

The full list of commands accepted by this bot can be found here.

The pull request process is described here

Needs approval from an approver in each of these files:

Approvers can indicate their approval by writing /approve in a comment
Approvers can cancel approval by writing /approve cancel in a comment

@k8s-ci-robot k8s-ci-robot added approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Jan 29, 2025
Copy link
Contributor

@carlbraganza carlbraganza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

As discussed, a better approach would be to have a separate CLI that invokes the iterator with an emitter that handles the difference check.

@k8s-ci-robot k8s-ci-robot added size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. and removed size/L Denotes a PR that changes 100-499 lines, ignoring generated files. labels Feb 5, 2025
@Rakshith-R
Copy link
Contributor Author

As discussed, a better approach would be to have a separate CLI that invokes the iterator with an emitter that handles the difference check.

I added a new tool snapshot-metadata-verifier as discussed.
Please take a look

Copy link
Contributor

@carlbraganza carlbraganza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I think we're almost aligned. Please see my comments in the code.

I definitely don't approve of the Done method controlling the iterator return error. If you really need it called even in error situations then that's okay.

Apart from that, I suggested the following:

  • Define an Iterator interface in the interator package that contains a Run operation.
  • Rename or provide an alternative contructor called New that will return the Iterator interface.
  • Rename iterator.run to interator.Run
  • Do not embed the iterator in the verifier package

Comment on lines 158 to 159
type Iterator struct {
Args
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I don't think you really want make the iterator data structure visible, because if you did then Args would become visible and you'd have to it from being an embedded field into a private field to a private variable:

type Iterator struct {
  args Args
  ...

and that has a lot of consequences.

I suggest instead, that the New constructor return an interface with the appropriate functionality. In this way the modularity is preserved with a clean, unpenetratable external interface:

type Iterator interface {
   Run(ctx context.Context) error
}

func New(args Args) Iterator {
  return newIterator(args)
}

// rename iter.run to iter.Run and the rest of the logic does not change

Comment on lines +240 to +245
if err != nil {
return err
}

return err
return iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

It breaks the iterator abstraction to allow Done to modify the error!

Why do you even need to change the behavior here? The iterator is returning to your verifier and it has full control after it returns.

If you absolutely must invoke Done even in cases of error, then it should be passed the iterator error value in case it is needed, but under no circumstances can it be allowed to modify the iterator return value.

i.e. Re-define Done to be something like this:

	// SnapshotMetadataIteratorDone is called prior to termination.
	// It is passed the error that the iterator would return.
	SnapshotMetadataIteratorDone(numberRecords int, iterErr error)

and use it thus in the Run method:

	iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum, err)

	return err

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

If you absolutely must invoke Done even in cases of error, then it should be passed the iterator error value in case it is needed, but under no circumstances can it be allowed to modify the iterator return value.

I think there is some confusion here.

Done is invoked only when there is no error.

	if err != nil {
		return err
	}

	return iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum)

Hopefully this should be fine ?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can live with it!

@@ -175,8 +172,8 @@ type iteratorHelpers interface {
getChangedBlocks(ctx context.Context, grpcClient api.SnapshotMetadataClient, securityToken string) error
}

func newIterator(args Args) *iterator {
iter := &iterator{}
func NewIterator(args Args) *Iterator {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

NewIterator stutters! I suggest calling it New so that the external usage is

  iter := iterator.New(args)

instead of

  iter := iterator.NewIterator(args)

Comment on lines 162 to 164
if err := verifier.VerifySnapshotMetadata(ctx, args); err != nil {
fmt.Fprintf(os.Stderr, "Error: %v\n", err)
os.Exit(1)
Copy link
Contributor

@carlbraganza carlbraganza Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I'm not sure why you feel the need to embed the iterator in the verifier package! Would the following invocation not be the same? (Assuming that New is the exposed constructor in the iterator package).

if err := iterator.New(args).Run(ctx); err != nil {
...

i.e. the verifier package has supplied an Emitter that is capable of verifying source/target while the traversal is done by the iterator package.

Comment on lines 50 to 57
usageFmt = `This command displays metadata on the content of a VolumeSnapshot object.
If a previous VolumeSnapshot object is also specified then the metadata
describes the content changes between the two snapshots, which must both
be from the same PersistentVolume.

The command is usually invoked in a Pod in the cluster, as the gRPC client
needs to resolve the DNS address in the SnapshotMetadataService CR.

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Shouldn't this talk about the verification being done?

Comment on lines 66 to 68
if err = a.Clients.Validate(); err != nil {
return err
}
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This is redundant as a.Args.Validate() already checks this.

Comment on lines 47 to 53
// SourceDevicePath is optional, and if specified SourceDevice will be used to copy
// changed blocks to the TargetDevice.
SourceDevicePath string

// TargetDevice is optional, and if specified changed blocks from the SourceDevice
// will be copied to it.
TargetDevicePath string
Copy link
Contributor

@carlbraganza carlbraganza Feb 10, 2025

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

You are passing the device files to VerifyEmitter. Why do you need these paths here? The device files are opened and checked in main.go already!

Comment on lines 147 to 148
targetDevice, err := os.OpenFile(args.TargetDevicePath, os.O_RDWR, 0644)
if err != nil {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

The usage doesn't make it clear whether this target file should exist or not. Is os.O_CREAT needed?

Comment on lines 79 to 80
stringFlag(&args.SourceDevicePath, "source-device-path", "src", "", "The source device to use for verification.")
stringFlag(&args.TargetDevicePath, "target-device-path", "tgt", "", "The target device to use for verification.")
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

What does "verification" entail" ? Please describe in the usage.

@k8s-ci-robot k8s-ci-robot added size/L Denotes a PR that changes 100-499 lines, ignoring generated files. and removed size/XL Denotes a PR that changes 500-999 lines, ignoring generated files. labels Feb 11, 2025
This commit adds verify functionality which when
enabled copies changed blocks from source device to
target device and finally verifies contents of
both the devices to be same.

Signed-off-by: Rakshith R <[email protected]>
@Rakshith-R
Copy link
Contributor Author

Thanks for the insight !

I've modified the pr to further make very minimal changes:

  • add a new verifier emitter
  • emitter methods now return a error
  • main takes 3 more arguments verify, source-device-path and target-device-path (added more info in usage)
    • if verify is true, verifier is set as the emitter and iterator is run again.

I definitely don't approve of the Done method controlling the iterator return error. If you really need it called even in error situations then that's okay.

Done method is called only when there is no error. #117 (comment)

Please let me know what you think.

Copy link
Contributor

@carlbraganza carlbraganza left a comment

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Some questions:

  • Why 2 passes in the CLI?
  • Location of the emitter - in the iterator package or in the cli package?
  • Update doc comments.

@@ -155,6 +168,34 @@ func main() {
os.Exit(1)
}

if !verify {
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I kind-of missed the fact that this verify is happening as a secondary step of the existing lister command. Nothing wrong with that, but why make 2 passes? Why not just use the verify emitter when the --verify flag is set?

return nil
}

// VerifierEmitter formats the metadata as a table.
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please change the comment description!

Anything in pkg/iterator is part of our external interface: I don't think this functionality should be here!

It has very specific semantics which are not part of the published KEP. I suggest it be in the CLI command package as a sibling of main.go.

Comment on lines +240 to +245
if err != nil {
return err
}

return err
return iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

I can live with it!


func (verifierEmitter *VerifierEmitter) SnapshotMetadataIteratorRecord(_ int, metadata IteratorMetadata) error {
for _, bmd := range metadata.BlockMetadata {
buffer := make([]byte, bmd.SizeBytes)
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Why allocate the buffer in the inner loop? This is essentially serialized code and the buffer can be reused.

In fact, you could even stash the buffer in the VerifyEmitter struct itself, as a private variable that gets initialized before use if nil.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
approved Indicates a PR has been approved by an approver from all required OWNERS files. cncf-cla: yes Indicates the PR's author has signed the CNCF CLA. kind/feature Categorizes issue or PR as related to a new feature. release-note-none Denotes a PR that doesn't merit a release note. size/L Denotes a PR that changes 100-499 lines, ignoring generated files.
Projects
None yet
Development

Successfully merging this pull request may close these issues.

Add verify functionality to the testing tool
3 participants