-
Notifications
You must be signed in to change notification settings - Fork 10
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
base: main
Are you sure you want to change the base?
Conversation
[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 |
There was a problem hiding this 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.
a0bb384
to
3ba3934
Compare
I added a new tool |
There was a problem hiding this 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 theinterator
package that contains aRun
operation. - Rename or provide an alternative contructor called
New
that will return theIterator
interface. - Rename
iterator.run
tointerator.Run
- Do not embed the iterator in the
verifier
package
pkg/iterator/iter.go
Outdated
type Iterator struct { | ||
Args |
There was a problem hiding this comment.
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
if err != nil { | ||
return err | ||
} | ||
|
||
return err | ||
return iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum) |
There was a problem hiding this comment.
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
There was a problem hiding this comment.
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 ?
There was a problem hiding this comment.
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!
pkg/iterator/iter.go
Outdated
@@ -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 { |
There was a problem hiding this comment.
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)
if err := verifier.VerifySnapshotMetadata(ctx, args); err != nil { | ||
fmt.Fprintf(os.Stderr, "Error: %v\n", err) | ||
os.Exit(1) |
There was a problem hiding this comment.
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.
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. | ||
|
There was a problem hiding this comment.
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?
pkg/verifier/verifier.go
Outdated
if err = a.Clients.Validate(); err != nil { | ||
return err | ||
} |
There was a problem hiding this comment.
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.
pkg/verifier/verifier.go
Outdated
// 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 |
There was a problem hiding this comment.
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!
targetDevice, err := os.OpenFile(args.TargetDevicePath, os.O_RDWR, 0644) | ||
if err != nil { |
There was a problem hiding this comment.
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?
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.") |
There was a problem hiding this comment.
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.
3ba3934
to
19b3041
Compare
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]>
19b3041
to
fbcbded
Compare
Thanks for the insight ! I've modified the pr to further make very minimal changes:
Done method is called only when there is no error. #117 (comment) Please let me know what you think. |
There was a problem hiding this 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 { |
There was a problem hiding this comment.
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. |
There was a problem hiding this comment.
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
.
if err != nil { | ||
return err | ||
} | ||
|
||
return err | ||
return iter.Emitter.SnapshotMetadataIteratorDone(iter.recordNum) |
There was a problem hiding this comment.
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) |
There was a problem hiding this comment.
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
.
What type of PR is this?
/kind feature
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?: