def test_copy_nbd_to_nbd(tmpdir, src_fmt, dst_fmt, zero, hole): # Default cluser size with qcow2 format. cluster_size = 64 * 1024 extents = [ ("data", cluster_size), ("zero", cluster_size), ("data", cluster_size), ("hole", cluster_size + io.MAX_ZERO_SIZE), ("data", cluster_size + io.BUFFER_SIZE), ("hole", cluster_size), ("data", cluster_size), ] size = sum(length for _, length in extents) src = str(tmpdir.join("src." + src_fmt)) qemu_img.create(src, src_fmt, size=size) populate_image(src, src_fmt, extents) src_sock = UnixAddress(tmpdir.join("src.sock")) src_url = urlparse(src_sock.url()) dst = str(tmpdir.join("dst." + dst_fmt)) qemu_img.create(dst, dst_fmt, size=size) dst_sock = UnixAddress(tmpdir.join("dst.sock")) dst_url = urlparse(dst_sock.url()) # Note: We need extra worker for reading extents for source. max_workers = 2 with qemu_nbd.run( src, src_fmt, src_sock, read_only=True, shared=max_workers + 1), \ qemu_nbd.run( dst, dst_fmt, dst_sock, shared=max_workers), \ nbd.open(src_url, "r") as src_backend, \ nbd.open(dst_url, "r+", sparse=True) as dst_backend: # Because we copy to new image, we can always use zero=False, but we # test both to verify that the result is the same. io.copy(src_backend, dst_backend, max_workers=max_workers, zero=zero, hole=hole) # Compare image content - must match. qemu_img.compare(src, dst) # Allocation can be compared only with qcow2 images when we write zeroes to # zero extents and skip holes. if src_fmt == "qcow2" and dst_fmt == "qcow2" and zero and not hole: qemu_img.compare(src, dst, strict=True)
def test_size(nbd_server, fmt): size = 150 * 1024**2 nbd_server.fmt = fmt qemu_img.create(nbd_server.image, fmt, size=size) nbd_server.start() with nbd.open(nbd_server.url) as b: assert b.size() == size
def test_extents_zero(nbd_server, user_file, fmt): size = 6 * 1024**3 qemu_img.create(user_file.path, fmt, size=size) nbd_server.image = user_file.path nbd_server.fmt = fmt nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: # qcow2 extents resolution is cluster size. data = b"x" * 64 * 1024 b.write(data) # The second extent length is bigger than NBD maximum length, testing # that our extent length is not limited by NBD limits. The backend # sends multiple block status commands and merge the returned extents. b.seek(5 * 1024**3) b.write(data) # Holes can be reported only for qcow2 images. hole = fmt == "qcow2" assert list(b.extents()) == [ extent.ZeroExtent(0, len(data), False, False), extent.ZeroExtent(len(data), 5 * 1024**3 - len(data), True, hole), extent.ZeroExtent(5 * 1024**3, len(data), False, False), extent.ZeroExtent(5 * 1024**3 + len(data), 1024**3 - len(data), True, hole), ]
def test_debugging_interface(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: assert b.readable() assert b.writable() assert not b.sparse assert b.name == "nbd"
def test_dirty(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: # backend created clean assert not b.dirty # write and zero dirty the backend b.write(b"01234") assert b.dirty b.flush() assert not b.dirty b.zero(5) assert b.dirty b.flush() assert not b.dirty # readinto, seek do not affect dirty. b.seek(0) assert not b.dirty with closing(util.aligned_buffer(10)) as buf: b.readinto(buf) assert not b.dirty
def test_extents_dirty_not_availabe(nbd_server, fmt): qemu_img.create(nbd_server.image, fmt, 65536) nbd_server.fmt = fmt nbd_server.start() with nbd.open(nbd_server.url, "r+", dirty=True) as b: with pytest.raises(errors.UnsupportedOperation): list(b.extents(context="dirty"))
def test_readinto_end(nbd_server, end_offset): nbd_server.start() buf = bytearray(512) with nbd.open(nbd_server.url, "r+") as f: offset = f.size() + end_offset f.seek(offset) n = f.readinto(buf) assert n == 0 assert f.tell() == offset
def test_zero_sparse(nbd_server, user_file, sparse): size = 10 * 1024**2 qemu_img.create(user_file.path, "raw", size=size) nbd_server.image = user_file.path nbd_server.start() with nbd.open(nbd_server.url, "r+", sparse=sparse) as b: b.zero(b.size()) b.flush() actual_size = os.stat(user_file.path).st_blocks * 512 assert actual_size == 0 if sparse else b.size()
def test_zero_middle(nbd_server, sparse): nbd_server.start() with nbd.open(nbd_server.url, "r+", sparse=sparse) as b: b.write(b"xxxxxxxxxxxx") b.seek(4) assert b.zero(4) == 4 with closing(util.aligned_buffer(12)) as buf: b.seek(0) assert b.readinto(buf) == 12 assert buf[:] == b"xxxx\x00\x00\x00\x00xxxx"
def test_open_writeonly(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "w") as b: assert not b.readable() assert b.writable() data = b"data" b.write(data) assert b.tell() == len(data) with pytest.raises(IOError): with closing(util.aligned_buffer(100)) as buf: b.readinto(buf) b.flush()
def test_close(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: pass # Closing twice does not do anything. b.close() # But other operations should fail now with: # socket.error: Bad file descriptor with pytest.raises(IOError): b.write("more") with pytest.raises(IOError): with closing(util.aligned_buffer(100)) as buf: b.readinto(buf)
def test_readinto(nbd_server, img_size, buf_size): with open(nbd_server.image, "wb") as f: f.write(b"x" * img_size) nbd_server.start() buf = bytearray(buf_size) with nbd.open(nbd_server.url, "r+") as f: # When image size is not aligned to 512 bytes, the last read is # extended to the next multiple of 512. effective_size = f.size() n = f.readinto(buf) assert n == effective_size assert f.tell() == effective_size assert buf[:img_size] == b"x" * img_size assert buf[img_size:] == b"\0" * (buf_size - img_size)
def test_open_read_write(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: assert b.readable() assert b.writable() data = b"data" b.write(data) assert b.tell() == len(data) b.zero(4) size = len(data) + 4 assert b.tell() == size with closing(util.aligned_buffer(size)) as buf: b.seek(0) assert b.readinto(buf) == size assert buf[:] == data + b"\0" * 4 b.flush()
def test_close(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: pass # Closing twice does not do anything. b.close() # But other operations should fail. error = "Operation on closed backend" with pytest.raises(ValueError) as e: b.write("more") assert str(e.value) == error with pytest.raises(ValueError) as e: with closing(util.aligned_buffer(100)) as buf: b.readinto(buf) assert str(e.value) == error
def test_clone(nbd_server, fmt): qemu_img.create(nbd_server.image, fmt, 65536) nbd_server.fmt = fmt nbd_server.start() with nbd.open(nbd_server.url, "r+") as a, \ a.clone() as b: # Backends are indentical when created. assert a.size() == b.size() assert a.tell() == b.tell() assert a.block_size == b.block_size # Modifying one backend does not change the other. a.write(b"x" * 4096) assert b.tell() == 0 # Both backends expoose the same content. buf = bytearray(4096) b.readinto(buf) assert buf == b"x" * 4096
def test_open_readonly(nbd_server): nbd_server.read_only = True nbd_server.start() with nbd.open(nbd_server.url) as b: assert b.readable() assert not b.writable() with pytest.raises(IOError): b.write(b"data") assert b.tell() == 0 with pytest.raises(IOError): b.zero(4) assert b.tell() == 0 with closing(util.aligned_buffer(100)) as buf: buf.write(b"x" * 100) assert b.readinto(buf) == len(buf) assert buf[:] == b"\0" * len(buf) b.flush()
def test_invalid_mode(nbd_server): nbd_server.start() with pytest.raises(ValueError): nbd.open(nbd_server.url, "invalid")
def test_context_manager(nbd_server): nbd_server.start() with nbd.open(nbd_server.url, "r+") as b: b.write(b"data") with pytest.raises(ValueError): b.write("more")