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

Streaming qcow2->raw conversion and resource limits on qemu-img subprocess #317

Merged
merged 16 commits into from
Sep 7, 2018

Conversation

mhenriks
Copy link
Member

@mhenriks mhenriks commented Aug 6, 2018

Two goals here:

Support streaming qcow2 to raw conversion. Don't want to have to create temporary file. Currently only for http or https source images. But maybe we want to avoid the extra copy for non gzipped/tarred as well?

Put resource limits on qemu-img. Basically, it is possible to craft a qcow2 file in such a way that it'll DOS your system. Initial checkin uses prlimit() to set the same cpu/memory limits used in Nova. Subsequent update will include call to qemu-img info to get details on source image to detect bad images before attempting conversion.
See this thread for more details about streaming conversion and DOS prevention: https://groups.google.com/forum/#!msg/kubevirt-dev/4h7R8fPq0eU/fyL2IaiSBAAJ

  • streaming qcow2 to raw conversion
  • cpu/memory limits for qemu-img
  • pre-validate qcow2 with qemu-img info
  • tests

Fixes #254

@jeffvance jeffvance added the WIP Work in Progress label Aug 6, 2018
@jeffvance
Copy link
Contributor

should we also use fedors28 in hack/build/docker/builder/Dockerfile?

)

// ConvertQcow2ToRaw converts a local qcou2 image to raw format
Copy link
Contributor

Choose a reason for hiding this comment

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

nit: typo

return nil
}

// ConvertQcow2ToRawStream converts an http accessible qcow2 image to raf format without locally caching the qcow2 image
Copy link
Contributor

@jeffvance jeffvance Aug 6, 2018

Choose a reason for hiding this comment

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

nit: typo: assume "raw"?

@@ -467,8 +483,18 @@ func closeReaders(readers []Reader) (rtnerr error) {
return rtnerr
}

func (d dataStream) isHTTPQcow2() bool {
// assuming len(d.Readers) == 3 is [http, mr, mr]
// should prob get rid of the redundant MultiReader
Copy link
Contributor

Choose a reason for hiding this comment

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

agree. Where is the extra mr coming from?

Copy link
Member Author

Choose a reason for hiding this comment

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

the loop in constructReaders() always adds a MultiReader to the stack, when matchHeader() returns a match. This is a problem in the qcow2 case because unlike the other possible matches, no qcow2 reader is added to the stack.

I think there is a pretty straightforward fix for this.

Copy link
Contributor

Choose a reason for hiding this comment

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

Ok, my bad and thanks for finding this. If you want to fix this in a separate pr let me know asap, else I'll fix it. Thanks.

Copy link
Member Author

Choose a reason for hiding this comment

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

@jeffvance I spent a bit of time looking into this earlier. Turns out a fix is not as simple as I thought. I think a separate PR would be appropriate.

Copy link
Contributor

Choose a reason for hiding this comment

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

I just added pr #339. Thanks for mentioning this as it turned out to be a bug in constructReaders and the unit test too.

func (d dataStream) isHTTPQcow2() bool {
// assuming len(d.Readers) == 3 is [http, mr, mr]
// should prob get rid of the redundant MultiReader
return (d.Url.Scheme == "http" || d.Url.Scheme == "https") && d.qemu && len(d.Readers) == 3
Copy link
Contributor

@jeffvance jeffvance Aug 6, 2018

Choose a reason for hiding this comment

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

== 3? vs. >=3 (or 2 if mr fix). It looks like we only invoke streaming if the endpoint has not been compressed and/or archived, correct? I thought http(s) supports some form of inline decompression. If this is true, is true, can we use this so that additional endpoint formats based on qemu can be imported via qemu streaming?

Copy link
Member Author

Choose a reason for hiding this comment

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

Yeah, qemu-img can't work directly on compressed/tarred files.

I'm not aware of any http features that can help us here. An http client can request that the server compress data for transmission via the Accept-Encoding header. But I'm not aware of any protocol support for decompressing a request for a gzipped file.

Copy link
Member Author

Choose a reason for hiding this comment

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

Also, qemu-img convert doesn't support reading from stdin so we can't pipe data into it from a qzip reader. If the file is compressed/tarred, I think we have to copy it locally,

// Copy endpoint to dest based on passed-in reader.
func (d dataStream) copy(dest string) error {
if d.isHTTPQcow2() {
glog.V(Vuser).Infoln("Doing streaming qcow2 to raw conversion")
Copy link
Contributor

@jeffvance jeffvance Aug 6, 2018

Choose a reason for hiding this comment

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

perhaps logging level here should be Vadmin? Just thinking that the end user doesn't really care if we use qemu streaming, but the admin might due to SLA or perf guarantees.

Copy link
Member Author

Choose a reason for hiding this comment

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

Makes sense

return nil
}

// ConvertQcow2ToRawStream converts an http accessible qcow2 image to raf format without locally caching the qcow2 image
// ConvertQcow2ToRawStream converts an http accessible qcow2 image to raw format without locally caching the qcow2 image
Copy link
Contributor

@jeffvance jeffvance Aug 8, 2018

Choose a reason for hiding this comment

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

what do you think of having a single ConvertQcow2ToRaw func with an "optional" json arg? This fits the DRY principle...

BackingFile string `json:"backing-filename"`
}

output, err := execWithLimits("qemu-img", "info", "--output=json", image)
Copy link
Contributor

Choose a reason for hiding this comment

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

I do not know how qemu-img does reads on qcow2 files. But, does this func cause the entire (or a lot of?) the qcow2 file to be read?

Copy link
Member Author

Choose a reason for hiding this comment

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

Pretty sure it doesn't read the whole file. Would be surprised if it reads more than the header.

Copy link
Member

Choose a reason for hiding this comment

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

right, it's just reading the header.


func validate(image, format string) error {
type imageInfo struct {
Format string `json:"format"`
Copy link
Contributor

@jeffvance jeffvance Aug 8, 2018

Choose a reason for hiding this comment

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

do these fields need to be exported? Maybe for unmarshaliing? (but the struct type is not exported)?

Copy link
Member Author

Choose a reason for hiding this comment

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

Since the scope of that struct is limited to the function I didn't think much about public/private. I guess I can make members private.

Copy link
Member Author

Choose a reason for hiding this comment

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

Get this warning if I make private: struct field format has json tag but is not exported so will keep exported.

Copy link
Contributor

Choose a reason for hiding this comment

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

hmmm, thanks for checking.

}

if info.Format != format {
return errors.Wrapf(err, "Invalid format %s for image %s", info.Format, image)
Copy link
Contributor

Choose a reason for hiding this comment

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

but err is nil here so no need to wrap.

Copy link
Member Author

Choose a reason for hiding this comment

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

good catch!

}

if len(info.BackingFile) > 0 {
return errors.Wrapf(err, "Image %s is invalid because it has backing file %s", image, info.BackingFile)
Copy link
Contributor

Choose a reason for hiding this comment

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

ditto re nil err

@mhenriks mhenriks changed the title [WIP] Streaming qcow2->raw conversion and resource limits on qemu-img subprocess Streaming qcow2->raw conversion and resource limits on qemu-img subprocess Aug 9, 2018

const killedByTestError = "Had to kill process"

func TestLimits(t *testing.T) {
Copy link
Member Author

Choose a reason for hiding this comment

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

Wondering if these tests are really necessary and if so should they be functional tests?

Copy link
Member

Choose a reason for hiding this comment

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

I am not sure we've ever found a real image in the wild that can cause problems. Maybe we can run using a fake qemu binary that spinloops or something?

return nil
}

// ConvertQcow2ToRawStream converts an http accessible qcow2 image to raw format without locally caching the qcow2 image
func ConvertQcow2ToRawStream(url *url.URL, dest string) error {
Copy link
Member Author

Choose a reason for hiding this comment

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

Should this be a functional test?

Copy link
Member

Choose a reason for hiding this comment

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

I'd say so. Will depend on @copejon 's server container in the environment.

Copy link
Member Author

Choose a reason for hiding this comment

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

@aglitke this test actually starts an embedded http server that serves up image files. but yeah may be redundant in functional test environment

@mhenriks mhenriks force-pushed the streaming-qcow branch 7 times, most recently from 2a9a74e to 422a42a Compare August 9, 2018 19:17

arr=()
for ((i=1; i<=$1; i++)); do
random="$(head -c 4096 /dev/urandom | base64)"
Copy link
Contributor

Choose a reason for hiding this comment

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

why pipe to base64 from the perspective of consuming memory?

Copy link
Member Author

Choose a reason for hiding this comment

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

bash didn't like certain characters (definitely nulls and maybe other stuff) so just base64 encoded everything.

@mhenriks mhenriks force-pushed the streaming-qcow branch 4 times, most recently from ec230db to 6246025 Compare August 9, 2018 20:31
@mhenriks mhenriks force-pushed the streaming-qcow branch 3 times, most recently from 0912a2a to add4373 Compare August 23, 2018 15:19
@aglitke
Copy link
Member

aglitke commented Sep 6, 2018

@mhenriks Can you rebase this? Do the existing functional tests cover this feature satisfactorily?

@aglitke
Copy link
Member

aglitke commented Sep 7, 2018

@mhenriks Can we remove the WIP label?

@mhenriks
Copy link
Member Author

mhenriks commented Sep 7, 2018

@aglitke I honestly have no idea how to remove the WIP label! Tried to do it awhile ago. Maybe because I still have restricted permissions to this repo?

@aglitke aglitke removed the WIP Work in Progress label Sep 7, 2018
@aglitke
Copy link
Member

aglitke commented Sep 7, 2018

@mhenriks Actually the PROW integration will really help with this.

@awels
Copy link
Member

awels commented Sep 7, 2018

lgtm

@aglitke aglitke merged commit 368e676 into kubevirt:master Sep 7, 2018
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

5 participants