Skip to content

feat(image): implement one.image.resize API#7507

Open
lexfrei wants to merge 18 commits intoOpenNebula:masterfrom
aenix-org:feat/image-resize
Open

feat(image): implement one.image.resize API#7507
lexfrei wants to merge 18 commits intoOpenNebula:masterfrom
aenix-org:feat/image-resize

Conversation

@lexfrei
Copy link
Copy Markdown

@lexfrei lexfrei commented Feb 26, 2026

Description

Implement the one.image.resize XML-RPC/gRPC API method to resize persistent images
in the datastore. This has been a long-requested feature (#5251).

The implementation follows the standard async datastore driver pattern used by
snap_flatten and other image operations:

  • Core: ImageManager::resize_image() validates the request (image type OS/DATABLOCK,
    state READY, new size > current size), reserves datastore quota for the size delta,
    locks the image, and sends RESIZE to the datastore driver. The _resize callback
    updates the image size on success or rolls back quota on failure.

  • API: ImageAPI::resize() with basic_authorization, exposed as XML-RPC method
    one.image.resize (signature A:sis) and gRPC ImageService.Resize.

  • Drivers: Backend-specific resize scripts for fs (qemu-img resize),
    ceph (rbd resize), dummy (echo size), and dev (not supported).

  • Clients: Go goca (ImageController.Resize), Ruby OCA (Image#resize),
    and CLI (oneimage resize <id> <size>).

Only upsizing is supported. The image must be in READY state and of type OS or DATABLOCK.

Branches to which this PR applies

  • master
  • one-X.X

  • Check this if this PR should not be squashed

lexfrei and others added 7 commits February 26, 2026 23:17
Add Resize/ResizeContext methods to ImageController, ImageResize to
RPCCaller interface, and XML-RPC/gRPC client implementations.

Test verifies that resize rejects downsizing (server must return
"greater than current" error).

Part of: OpenNebula#5251

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Implement the ImageManager::resize_image() method and _resize callback
for the new one.image.resize operation. The resize follows the standard
async driver pattern: validate (type, state, size), reserve quota for
the size delta, lock the image, send RESIZE to the datastore driver,
and handle the callback to update size or rollback quota on error.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Register the resize operation in the ImageAPI layer, XML-RPC binding
(ImageResizeXRPC), and gRPC binding (ImageResizeGRPC). The XML-RPC
method signature is "A:sis" (session, image_id, size_string). The
protobuf ResizeRequest message and Resize RPC are added to the image
service definition.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Replace the ImageResize gRPC stub with a real implementation that
calls the Resize RPC. Regenerate Go protobuf bindings from updated
image.proto to include ResizeRequest message and Resize service method.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add the RESIZE action to the datastore driver framework and implement
backend-specific resize scripts:
- dummy: echo requested size (for testing)
- fs: qemu-img resize (local or via SSH bridge)
- ceph: rbd resize
- dev: symlink to not_supported.sh (block devices)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add Image#resize(size) to the Ruby OCA library and the corresponding
"oneimage resize <id> <size>" CLI command. Size accepts suffixes
(T/G/M, default MiB) consistent with the existing disk-resize command.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
- Register resize scripts in install.sh for all 8 datastore backends
- Add not_supported.sh symlinks for iscsi_libvirt, restic, rsync, virtiofs
- Fix XML injection: use to_string(new_size) instead of raw user input
- Add resize to bash completion for oneimage
- Add iss.fail() validation for non-numeric size input
- Fix doc comment: @param error_str → @param error
- Consistent MiB units across docs and error messages

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>

int ds_id;

if ( auto img = ipool->get_ro(iid) )
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

We observed that you are using unintended operations or assignments that could lead to logical errors, vulnerabilities, or undefined behavior. To prevent these issues, ensure that you follow best practices, such as using explicit comparisons and clear operator usage, and using parentheses to clarify intent. Learn more - https://cvefeed.io/cwe/detail/cwe-480-use-of-incorrect-operator

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. This is standard C++ code — ipool->get_ro(iid) returns a smart pointer checked via implicit bool conversion, which is idiomatic C++. This code has since been refactored to use a single ipool->get(iid) call to eliminate the TOCTOU race.


string ds_data;

if (auto ds = dspool->get_ro(ds_id))
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

We observed that you are using unintended operations or assignments that could lead to logical errors, vulnerabilities, or undefined behavior. To prevent these issues, ensure that you follow best practices, such as using explicit comparisons and clear operator usage, and using parentheses to clarify intent. Learn more - https://cvefeed.io/cwe/detail/cwe-480-use-of-incorrect-operator

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as above — this pattern has been refactored in a subsequent commit. The get_ro() + get() pair was replaced with a single get() write lock.

if (new_size <= cur_size)
{
ostringstream oss;
oss << "New size (" << new_size
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

A cross-site scripting (XSS) vulnerability was detected. User-controlled data is being output without proper sanitization, allowing an attacker to inject malicious scripts. This could lead to session hijacking, malware installation, or phishing attacks. Always sanitize and encode user inputs before including them in the output, using context-appropriate encoding methods. Learn More - https://cwe.mitre.org/data/definitions/79.html.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. This is server-side C++ code building an error string returned via XML-RPC/gRPC — not rendered in a browser. new_size is a long long parsed from the input string via istringstream, so it is guaranteed to be a plain integer.


image_msg_t msg(ImageManagerMessages::RESIZE, "", iid, drv_msg);

imd->write(msg);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

A cross-site scripting (XSS) vulnerability was detected. User-controlled data is being output without proper sanitization, allowing an attacker to inject malicious scripts. This could lead to session hijacking, malware installation, or phishing attacks. Always sanitize and encode user inputs before including them in the output, using context-appropriate encoding methods. Learn More - https://cwe.mitre.org/data/definitions/79.html.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Valid concern — this was the XML injection vector where raw user input size was interpolated into <SIZE> XML. Already fixed in a subsequent commit: now uses to_string(new_size) (the parsed integer) instead of the raw string, and the value is also wrapped in <EXTRA_DATA>.

if (new_size <= cur_size)
{
ostringstream oss;
oss << "New size (" << new_size
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential log injection detected. Ensure all untrusted input is properly sanitized before logging. Use parameterized logging or validate input against an allow list to prevent log injection vulnerabilities. Consider using a dedicated logging library's built-in sanitization features when available. Learn more - https://cwe.mitre.org/data/definitions/117.html

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. The logged value new_size is a long long — a plain integer that cannot contain log control characters. The size string is validated via istringstream with iss.fail() || !iss.eof() checks before this point is reached.


image_msg_t msg(ImageManagerMessages::RESIZE, "", iid, drv_msg);

imd->write(msg);
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential log injection detected. Ensure all untrusted input is properly sanitized before logging. Use parameterized logging or validate input against an allow list to prevent log injection vulnerabilities. Consider using a dedicated logging library's built-in sanitization features when available. Learn more - https://cwe.mitre.org/data/definitions/117.html

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

Same as comment 4 — the raw size string is no longer used in XML construction. to_string(new_size) is used instead, which produces a sanitized integer string.


long long resize_delta = 0;
image->get_template_attribute("RESIZE_DELTA", resize_delta);
image->remove_template_attribute("RESIZE_DELTA");
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

User input is being used directly in C++ file system operations without proper validation. This can allow attackers to access files outside the intended directory through path traversal attacks, potentially exposing sensitive information or enabling system compromise. Use fs::canonical() to resolve paths and validate they start with an allowed base directory, implement allowlist validation using std::find() to check against permitted directories, or sanitize input by removing dangerous sequences like ".." using string operations. Learn more

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. get_template_attribute("RESIZE_DELTA") reads from an in-memory XML template object, not from the filesystem. There is no file path involved.

lexfrei and others added 2 commits February 27, 2026 00:02
- Eliminate TOCTOU race by using single get() write lock instead of
  separate get_ro() then get() calls in resize_image()
- Quote DISK_SRC in fs/resize to prevent shell injection with crafted paths
- Add SPARSE/preallocation handling in fs/resize for non-sparse raw images
- Remove unused FORMAT xpath extraction from fs/resize
- Add explicit M (MiB) suffix to rbd resize --size in ceph/resize
- Handle malformed driver payload in _resize callback: treat invalid
  size as error with quota rollback instead of silently succeeding
- Expand Go test coverage: invalid input, empty string, and positive
  resize with size verification

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Add !iss.eof() check to istringstream parsing so inputs like "100abc"
or "100GB" are rejected instead of silently truncating to 100. The CLI
path was already protected by regex validation in size_in_mb(), but
direct XML-RPC/gRPC API callers were not.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
{
monitor_datastore(ds_id);
}
else if (resize_delta > 0)
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

We observed that you are using unintended operations or assignments that could lead to logical errors, vulnerabilities, or undefined behavior. To prevent these issues, ensure that you follow best practices, such as using explicit comparisons and clear operator usage, and using parentheses to clarify intent. Learn more - https://cvefeed.io/cwe/detail/cwe-480-use-of-incorrect-operator

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. else if (resize_delta > 0) is an intentional comparison — quota rollback should only happen when a positive delta was reserved. The else branch (when resize_delta == 0) logs a warning about missing RESIZE_DELTA attribute.

lexfrei and others added 3 commits February 27, 2026 00:13
- Add iss.fail()/iss.eof() checks to _resize callback payload parsing,
  matching the strict validation already applied in resize_image() input
- Quote $RBD_SRC in ceph/resize to handle source paths with spaces

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
- Check returned size >= current size in _resize callback to reject
  drivers returning a smaller size than requested
- Quote CEPH_USER, CEPH_KEY, CEPH_CONF in ceph/resize for paths
  with spaces or special characters
- Quote ${SIZE}M in fs/resize for defense-in-depth

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
- Use relative DRIVER_PATH in dummy/resize instead of hardcoded path
- Remove dead RBD reassignment in ceph/resize heredoc (variable
  already expanded at heredoc creation time with unquoted EOF)
- Quote ${SIZE}M in ceph/resize heredoc for defense-in-depth
- Log warning when RESIZE_DELTA attribute is missing in error callback
  to aid debugging quota leaks
- Fix grammar in CLI resize help text

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>

if (new_size > 0 && new_size < cur_size)
{
oss << "Error resizing image: driver returned size ("
Copy link
Copy Markdown
Member

Choose a reason for hiding this comment

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

Recommendation generated by Amazon CodeGuru Reviewer. Leave feedback on this recommendation by replying to the comment or by reacting to the comment using emoji.

Potential log injection detected. Ensure all untrusted input is properly sanitized before logging. Use parameterized logging or validate input against an allow list to prevent log injection vulnerabilities. Consider using a dedicated logging library's built-in sanitization features when available. Learn more - https://cwe.mitre.org/data/definitions/117.html

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

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

False positive. The error message includes new_size and cur_size (both long long) — plain integers that cannot contain log control characters.

@lexfrei lexfrei marked this pull request as draft February 26, 2026 21:28
lexfrei and others added 6 commits February 27, 2026 00:29
- Revert single quotes around CEPH_USER/KEY/CONF in ceph/resize to
  match the unquoted pattern used by all other ceph datastore scripts
- Simplify dead branch 'else if (!success)' to plain 'else' since
  success is always false at that point
- Make TestResize fixture-independent by reading initial image size
  and computing target size dynamically

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
…fety

- Add comment to proto ResizeRequest.size: integer string in MiB
- Clarify XML-RPC method description with size format
- Document write lock guarantees and quota safety in resize_image

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
- Change callback size validation from >= to > (strictly greater than
  current), preventing quota leak when driver returns unchanged size
- Use case-insensitive SPARSE comparison in fs/resize, matching the
  pattern used by fs/cp (tr A-Z a-z)

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
- Use std::ws to skip trailing whitespace in _resize callback payload
  parsing, preventing false errors from driver output formatting
- Prepare driver message strings before quota reservation so that
  all allocations happen before quota is charged
- Add SIZE emptiness check in ceph/resize and fs/resize scripts
- Fix Ruby OCA doc: MB -> MiB for consistency with C++ and proto
- Add test cases for zero and negative size values

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
The driver scripts read SIZE via XPath
/DS_DRIVER_ACTION_DATA/EXTRA_DATA/SIZE but the extra_data string was
missing the <EXTRA_DATA> wrapper element. Without it, SIZE would be at
/DS_DRIVER_ACTION_DATA/SIZE and the xpath lookup would return empty,
causing all resize operations to fail at the driver level.

Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
Co-Authored-By: Claude <noreply@anthropic.com>
Signed-off-by: Aleksei Sviridkin <f@lex.la>
@lexfrei lexfrei marked this pull request as ready for review February 26, 2026 23:01
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.

2 participants