feat(redfish): add UpdateService upload APIs#97
Conversation
5334ae9 to
518e8b8
Compare
| /// Common Redfish UpdateService multipart upload URI. | ||
| #[cfg(feature = "bmc-http")] | ||
| pub const UPDATE_MULTIPART_URI: &str = "/redfish/v1/UpdateService/update-multipart"; | ||
|
|
||
| /// Common Redfish UpdateService raw file upload URI. | ||
| #[cfg(feature = "bmc-http")] | ||
| pub const UPDATE_URI: &str = "/redfish/v1/UpdateService/update"; |
There was a problem hiding this comment.
Different platforms may define these URIs differently. Redfish provides properties with value of these properties:
UPDATE_MULTIPART_URI value must be taken from MultipartHttpPushUri property (See:
https://docs.rs/nv-redfish/latest/nv_redfish/schema/update_service/struct.UpdateService.html#structfield.multipart_http_push_uri )
UPDATE_URI value from HttpPushUri (https://docs.rs/nv-redfish/latest/nv_redfish/schema/update_service/struct.UpdateService.html#structfield.http_push_uri)
These values can be accessed using data property of UpdateService struct.
There was a problem hiding this comment.
Thanks for catching this. Agreed, using UpdateService's data properties now.
| #[cfg(feature = "bmc-http")] | ||
| const FORCE_UPDATE_PARAMETER: &str = "ForceUpdate"; | ||
| #[cfg(feature = "bmc-http")] | ||
| const TARGETS_PARAMETER: &str = "Targets"; | ||
|
|
||
| /// Typed `UpdateParameters` part for Redfish UpdateService multipart uploads. | ||
| #[cfg(feature = "bmc-http")] | ||
| #[derive(Debug, Clone, serde::Serialize, PartialEq, Eq)] | ||
| pub struct MultipartUpdateParameters { | ||
| /// Force the update even when the service would otherwise reject it by policy. | ||
| #[serde(rename = "ForceUpdate")] | ||
| pub force_update: bool, | ||
| /// Redfish target resource URIs to update. | ||
| #[serde(rename = "Targets")] | ||
| pub targets: Vec<String>, | ||
| /// Additional Redfish or vendor-specific update parameters. | ||
| /// | ||
| /// Use [`Self::with_parameter`] to add entries without colliding with typed | ||
| /// `ForceUpdate` or `Targets` fields. | ||
| #[serde(flatten)] | ||
| additional_parameters: serde_json::Map<String, JsonValue>, | ||
| } |
There was a problem hiding this comment.
It seems that this is UpdateParameters complex type that is defined by schema here:
https://github.com/DMTF/Redfish-Publications/blob/26c949654e3e510800b05fa8d39cf336f10fa687/csdl/UpdateService_v1.xml#L1016
In nv-redfish we avoid manually implement data structures that are defined by standard. However, CSDL compiler today doesn't have way to bring this to you because it is defined as ComplexType and this type is not attached to the Redfish tree. We had similar approach to this for EntityType (root_patterns). I guess that we should extend it to complex types as well. I'll do it as separate PR.
After my PR of extending root_patterns you can avoid defining these parameters.
There was a problem hiding this comment.
Generated type:
#[derive(Deserialize, Debug)]
pub struct UpdateParameters {
/// An array of URIs that indicate where to apply the update image.
///
/// This property shall contain zero or more URIs that indicate where to apply the update image when
/// using the URI specified by the `MultipartHttpPushUri` property to push a software image. These
/// targets should correspond to software inventory instances or their related items. If this property
/// is not present or contains no targets, the service shall apply the software image to all applicable
/// targets, as determined by the service. If the target specifies a device resource, the software
/// image file shall be applied to the specified device. If the target specifies a resource
/// collection, the software image shall be applied to each applicable member of the specified
/// collection. If the target resource specifies an `Aggregate` resource, the software image file
/// shall be applied to each applicable element of the specified aggregate. If the target resource
/// specifies a `ComputerSystem` resource, the software image file shall be applied to the applicable
/// components within the specified computer system.
#[serde(rename = "Targets", default, deserialize_with = "de_optional_nullable")]
pub targets: Option<Option<Vec<redfish::edm::String>>>,
/// The OEM extension property.
///
/// This property shall contain the OEM extensions. All values for properties contained in this object
/// shall conform to the Redfish Specification-described requirements.
#[serde(rename = "Oem", default)]
pub oem: Option<redfish::resource::Oem>,
/// An indication of whether the service should bypass update policies when applying the provided
/// image. The default is `false`.
///
/// This property shall indicate whether the service should bypass update policies when applying the
/// provided image, such as allowing a component to be downgraded. Services may contain update
/// policies that are never bypassed, such as minimum version enforcement. If the client does not
/// provide this parameter, the service shall default this value to `false`.
#[serde(rename = "ForceUpdate", default)]
pub force_update: Option<redfish::edm::Boolean>,
/// An indication of whether the service stages the image on target devices for a client to activate at
/// a later time.
///
/// This property shall indicate whether the service stages the image on target devices for a client to
/// activate at a later time with the `Activate` action on the `SoftwareInventory` resource. If the
/// client does not provide this parameter, the service shall default this value to `false`.
#[serde(rename = "Stage", default)]
pub stage: Option<redfish::edm::Boolean>,
/// An indication of whether the service adds the image to the local image store.
///
/// This property shall indicate whether the service adds the image to the resource collection
/// referenced by the `LocalImageStore` property. If the client does not provide this parameter, the
/// service shall default this value to `false`.
#[serde(rename = "LocalImage", default)]
pub local_image: Option<redfish::edm::Boolean>,
/// An array of URIs that indicate where not to apply the update image.
///
/// This property shall contain zero or more URIs that indicate where not to apply the update image.
/// This property shall be ignored if the `Targets` property is provided and contains at least one
/// member. These excluded targets should correspond to software inventory instances or their related
/// items. If this parameter is not present or contains no targets, the service shall apply the
/// software image to all applicable targets, as determined by the service. If an excluded target
/// specifies a device resource, the software image file shall not be applied to that specified device.
/// If an excluded target specifies a resource collection, the software image shall not be applied to
/// each applicable member of the specified collection. If an excluded target resource specifies an
/// `Aggregate` resource, the software image file shall not be applied to each applicable element of
/// the specified aggregate. If an excluded target resource specifies a `ComputerSystem` resource, the
/// software image file shall not be applied to the applicable components within the specified computer
/// system.
#[serde(rename = "ExcludeTargets", default)]
pub exclude_targets: Option<Vec<redfish::edm::String>>,
}
There was a problem hiding this comment.
I'll use the build-time generated code after your PR as reference to reduce manual definitions of structs.
| #[cfg(feature = "bmc-http")] | ||
| impl UpdateService<HttpBmc<ReqwestClient>> { |
There was a problem hiding this comment.
nv-redfish consistently implements everything through BMC trait. This approach opens possibility to write testable code without need to bring up mock HTTP server. So, we need to keep it this way.
I don't know full upload service business so far but if we need to extend BMC trait to accommodate requirements we need to do so.
There was a problem hiding this comment.
Thanks for the comment! Multipart update is used for firmware uploads through Redfish. We can:
- extend the Bmc trait -> breaking change, or
- use Bmc trait as a supertrait for this -> testable without HTTP server and no breaking change
Which way would suite this crate better?
There was a problem hiding this comment.
Added a trait UpdateServiceUploadBmc to extend Bmc supertrait. Code is testable without a mock HTTP server.
660cf86 to
fd45b7a
Compare
## Description Adds stream-based Redfish UpdateService multipart upload support. - Extends the transport-agnostic `Bmc` trait with `multipart_update`. - Extends `bmc-http` `HttpClient` with `post_multipart_update`. - Implements reqwest multipart POST with `UpdateParameters` JSON and a named `UpdateFile` stream. - Uses the BMC-provided `MultipartHttpPushUri` from the UpdateService resource. - Supports generated `MultipartUpdateParameters` and arbitrary serializable UpdateParameters payloads, including OEM fields. - Adds mock BMC support so product code can test multipart uploads without an HTTP server. ## Type of Change - [x] **Add** - New feature or capability - [ ] **Change** - Changes in existing functionality - [ ] **Fix** - Bug fixes - [ ] **Remove** - Removed features or deprecated functionality - [ ] **Internal** - Internal changes (refactoring, tests, docs, etc.) ## Related Issues (Optional) ## Breaking Changes - [x] This PR contains breaking changes Adds required methods to `Bmc` and `HttpClient`. Implementations must implement multipart update support. ## Testing - [x] Unit tests added/updated - [x] Integration tests added/updated - [ ] Manual testing performed - [ ] No testing required (docs, internal refactor, etc.) ## Additional Notes Multipart upload is reader/stream-based so callers can provide file-backed streams or other data sources. Signed-off-by: Jay Zhu <jayzhu@nvidia.com>
fd45b7a to
f5f95b3
Compare
| impl<T> UploadReader for T where T: AsyncRead + Send + 'static {} | ||
|
|
||
| /// Named data stream accepted by upload methods. | ||
| pub struct DataStream<R> { |
There was a problem hiding this comment.
i'm not sure if this is right place for it, maybe different file? bmc.rs holds just trait and adding structs here specific only for one of the functions looks out of place
There was a problem hiding this comment.
I thought about it too. Let me try to find a better place for it.
| ) -> impl Future<Output = Result<ModificationResponse<R>, Self::Error>> + Send; | ||
|
|
||
| /// POST a Redfish `UpdateService` multipart upload using a named stream. | ||
| fn multipart_update<U, V, R>( |
There was a problem hiding this comment.
Can you please make sure this signature does conforms Redfish spec about multipart https://www.dmtf.org/sites/default/files/standards/documents/DSP0266_1.23.0.html
There was a problem hiding this comment.
Other than the upload_timeout, we are not supporting the OEM parts that the spec allows yet. Otherwise it conforms.
| OEM specific parts | Content-Disposition | form-data; name="OemXXXX" |
|---|
There was a problem hiding this comment.
Yea, i would add them here, as optional.
| See `examples/session-token` for Redfish SessionService authentication using | ||
| `X-Auth-Token`. | ||
|
|
||
| ## Update Uploads |
There was a problem hiding this comment.
Maybe name it Multipart Uploads? Looks closer to spec, also not sure this part of Readme is where it should ist. But i have no strong opinion here.
There was a problem hiding this comment.
Agreed, could be a left-over from my previous version, removing the whole section from README.
|
|
||
| let update_parameters_part = update_parameters_part(update_parameters)?; | ||
| let form = Form::new() | ||
| .part("UpdateParameters", update_parameters_part) |
There was a problem hiding this comment.
I think we need to support optional parts of form, like Oem data
There was a problem hiding this comment.
Makes sense, will address this together with signature conformation to the spec, we will likely need the OEM parts in use cases.
Signed-off-by: Jay Zhu <jayzhu@nvidia.com>
| /// | ||
| /// Accepts a named stream so callers can upload firmware from any source | ||
| /// and still send Redfish's required `UpdateFile` multipart part. | ||
| fn post_multipart_update<U, V, T>( |
There was a problem hiding this comment.
This feels bloated in terms of amount of params, maybe we can reuse post (wrap body), or at least move multipart related data into separate struct.
There was a problem hiding this comment.
Yes, I used DataStream to wrap some but it does still look borderline too many. Let me think about it.
Description
Adds stream-based Redfish UpdateService multipart upload support.
Bmctrait withmultipart_update.bmc-httpHttpClientwithpost_multipart_update.UpdateParametersJSON and a namedUpdateFilestream.MultipartHttpPushUrifrom the UpdateService resource.MultipartUpdateParametersand arbitrary serializable UpdateParameters payloads, including OEM fields.Type of Change
Related Issues (Optional)
Breaking Changes
Adds required methods to
BmcandHttpClient. Implementations must implement multipart update support.Testing
Additional Notes
Multipart upload is reader/stream-based so callers can provide file-backed streams or other data sources.
Signed-off-by: Jay Zhu jayzhu@nvidia.com