Skip to content
This repository was archived by the owner on Feb 14, 2024. It is now read-only.

add download method for updater#20

Merged
groob merged 11 commits intokolide:masterfrom
groob:download_method
Jun 27, 2017
Merged

add download method for updater#20
groob merged 11 commits intokolide:masterfrom
groob:download_method

Conversation

@groob
Copy link
Copy Markdown
Contributor

@groob groob commented Jun 26, 2017

Closes #13

In the process of adding the method also discovered and updated a few other concerns:

Closes #16
Closes #18

still a WIP pr for now

Comment thread tuf/client.go Outdated
return &Client{manager: manager}, nil
}

func (c *Client) Update() (files map[targetNameType]FileIntegrityMeta, latest bool, err error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

// update refreshes local state, returning all updated targets.

Comment thread tuf/client.go
return <-ff, latest, nil
}

func (c *Client) Download(targetName string, destination io.Writer) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Download securely copies a target to an io.Writer, doing all the checks on that file.

Comment thread tuf/client.go Outdated
}
currentMeta, ok := files[targetNameType(targetName)]
if !ok {
// return errors.Errorf("targetName %s not found", targetName)
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO

Comment thread tuf/roles.go Outdated
return nil
}

func (fim FileIntegrityMeta) verifyIO(r io.Reader, size int64) error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

This method works like fim.verify() but attempts to work on a stream in stead of []byte, addressing #18

Comment thread tuf/roles.go Outdated
}

func (fim FileIntegrityMeta) verifyIO(r io.Reader, size int64) error {
if size != int64(fim.Length) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

@murphybytes I had to convert int to int64 a few times. Would it make sense to just set fim.Length to an int64 type, so it's decoded from JSON into int64? Would probably be easier to work with.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Yes. I don't think that will cause any issues?

Comment thread tuf/tuf.go
targets *Targets
client *http.Client

actionc chan func()
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

the Snapshot, Timestamp, Targets and Root fields on this structure need to be safe for concurrency. To accomplish that, I created a function type channel which I use in the new refresh method, avoiding race conditions and ensuring files are updated/read in order.

Comment thread tuf/tuf.go
func (rs *repoMan) refresSafe() (bool, error) {
errc := make(chan error)
var isLatest bool
rs.actionc <- func() {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

example action channel function.

Comment thread tuf/tuf.go Outdated
<-quit
}

func (rs *repoMan) refresSafe() (bool, error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

need to remove the old refresh() method and rename this one to refresh.

Comment thread tuf/tuf.go Outdated
return stagePath, nil
}

func (rs *repoMan) downloatTargetToWriter() error {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

TODO: delete

Comment thread tuf/tuf.go
}

func (rs *repoMan) refreshTargets(root *Root, snapshot *Snapshot) (*Targets, string, error) {
func (rs *repoMan) refreshTargets(root *Root, snapshot *Snapshot) (*Targets, bool, error) {
Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

In the previous method, refreshTargets downloaded the settings.Target.
Sometimes we want to refresh without downloading to the staging path. I let the caller decide if they need to update.

There's a problem here, that someone calling Download() will update the local repository, but that won't be seen by the looping update function... need to think/test better.

@murphybytes
Copy link
Copy Markdown
Contributor

murphybytes commented Jun 26, 2017

My general sense is that you're adding a lot of complexity here by adding concurrency and I don't see the need for it given what our needs are today. Concurrency will make it more probable that we'll miss a test case for a situation that might cause a panic in a production environment. If you have a really really good reason for going down this path I'd like to hear it.

@groob
Copy link
Copy Markdown
Contributor Author

groob commented Jun 26, 2017

I'm not adding concurrency, just trying to limit the potential for race conditions in already existing situations.

Today there's a loop which checks for new updates every X minutes. That loop ends up calling refresh() which updates a bunch of pointers on a struct.

That same method is also needed in order to Download something. There's no way to keep update running safely while also calling Download somewhere without ensuring that the code is thread safe.

@murphybytes
Copy link
Copy Markdown
Contributor

I see your point. Another way to do this would be to add a Download method to the updater, that uses a channel to send a message to the goroutine in the updater, this would perform the download, blocking refresh until you're done, at which point you send a message back to the Download method with the results. Or you could send a function that would get executed in the updater goroutine and get the results as arguments.

@murphybytes
Copy link
Copy Markdown
Contributor

If you could perform the operations in the updater goroutine you eliminate the possibility of race conditions since that goroutine is the only thing that touches the tuf repository

@groob
Copy link
Copy Markdown
Contributor Author

groob commented Jun 26, 2017

That doesn't fix a problem that exists right now:

the update method cares about a specific target, like latest/target. It downloads that target if the targets file was updated.

But the intention of the Download method is to download a specifc target, instead of the one in the settings. Say the updater is watching for latest/target, but I need to download previous/target. When that happens, the state of the repository will get updated, and the update loop will not realize that it needs to perform an update.

There needs to be a way to watch for updates to a specific file rather than to the entire targets file..

@groob
Copy link
Copy Markdown
Contributor Author

groob commented Jun 26, 2017

what I'm saying is that the current updater method assumes that if the targets.json has been updated there was an update to a specific target, which is not true.

@murphybytes
Copy link
Copy Markdown
Contributor

Ahh, got it. I see what you're up to.

Comment thread updater.go
actionc chan func()
checkFrequency time.Duration
notificationHandler NotificationHandler
client *tuf.Client
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

Maybe the client would be better if it were instantiated inside the loop and was not a member of the Updater struct since your sharing it's state between the main process and the loop goroutine unless you have a reason to share that state.

Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

If you instantiated the client in the goroutine you could pass it to an argument the func you send over the channel.

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

I need to call download on the client itself so it has to be available on the updater struct.

Comment thread updater.go
select {
case <-ticker:
case f := <-u.actionc:
f()
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

👍 I love this pattern

Comment thread updater.go
// goroutine.
errc := make(chan error)
u.actionc <- func() {
_, _, err := u.client.Update()
Copy link
Copy Markdown
Contributor

@murphybytes murphybytes Jun 27, 2017

Choose a reason for hiding this comment

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

u.actionc <- func(client *tuf.Client) {
   _, _, err := client.Update() 
   if err != nil {
        errc <- err 
        return 
    } 
    errc <- client.Download(target, destination)
    return <-errc
}

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

Not sure I follow

Copy link
Copy Markdown
Contributor Author

Choose a reason for hiding this comment

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

That would limit the action channel to only be usable with a func(client) signature. I don't see the benefit. We'll just have to change it next time we want to do something in the loop.

Comment thread updater.go
@@ -124,21 +128,72 @@ func (u *Updater) Stop() {
func (u *Updater) loop() {
ticker := time.NewTicker(u.checkFrequency).C
Copy link
Copy Markdown
Contributor

Choose a reason for hiding this comment

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

So you'd instantiate the tuf.Client here.

@groob
Copy link
Copy Markdown
Contributor Author

groob commented Jun 27, 2017

@murphybytes this branch is now ready to merge. I've updated the tests and verified manually that the example works with both continually monitoring a target and with using Download directly.

Once this is merged, I'll work on #22 where I'll only expose the current updater methods and make everything except the types private.

Copy link
Copy Markdown
Contributor

@murphybytes murphybytes left a comment

Choose a reason for hiding this comment

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

Looks great.

@groob groob merged commit 04e45b3 into kolide:master Jun 27, 2017
@groob groob deleted the download_method branch June 27, 2017 20:23
Sign up for free to subscribe to this conversation on GitHub. Already have an account? Sign in.

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

2 participants