Skip to content

Commit

Permalink
Add IS-14 bulkPropertiesManager object and endpoints (#7)
Browse files Browse the repository at this point in the history
* Add bulkPropertiesManager control class
* Add fixed role to bulkPropertiesManger
* Add fixed role and correct method_id in bulkPropertiesManager
* add Device Configuration method handlers
* Device Configuration method handlers in NcBulkPropertiesManager method
* Add bulkProperties endpoints
* Typedefs for bulk properties manager user defined methods
* Schema validation on bulkProperties endpoint
* Pass control protocol resources required for backup/restore
---------
Co-authored-by: Simon Lo <[email protected]>
  • Loading branch information
jonathan-r-thorpe authored Jun 19, 2024
1 parent 976d766 commit 2588cfb
Show file tree
Hide file tree
Showing 19 changed files with 414 additions and 36 deletions.
2 changes: 1 addition & 1 deletion Development/nmos-cpp-node/main.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -138,7 +138,7 @@ int main(int argc, char* argv[])
.on_request_authorization_code(nmos::experimental::make_request_authorization_code_handler(gate)); // may be omitted, only required for OAuth client which is using the Authorization Code Flow to obtain the access token
}

nmos::experimental::control_protocol_state control_protocol_state(node_implementation.control_protocol_property_changed);
nmos::experimental::control_protocol_state control_protocol_state(node_implementation.control_protocol_property_changed, node_implementation.get_properties_by_path, node_implementation.validate_set_properties_by_path, node_implementation.set_properties_by_path);
if (0 <= nmos::fields::control_protocol_ws_port(node_model.settings))
{
node_implementation
Expand Down
44 changes: 43 additions & 1 deletion Development/nmos-cpp-node/node_implementation.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -1206,6 +1206,9 @@ void node_implementation_init(nmos::node_model& model, nmos::experimental::contr
// example class manager
auto class_manager = nmos::make_class_manager(++oid, control_protocol_state);

// example bulk properties manager
auto bulk_properties_manager = nmos::make_bulk_properties_manager(++oid);

// example stereo gain
const auto stereo_gain_oid = ++oid;
auto stereo_gain = nmos::make_block(stereo_gain_oid, nmos::root_block_oid, U("stereo-gain"), U("Stereo gain"), U("Stereo gain block"));
Expand Down Expand Up @@ -1714,6 +1717,42 @@ nmos::control_protocol_property_changed_handler make_node_implementation_control
};
}

// Example Device Configuration callback for creating a back-up dataset
nmos::get_properties_by_path_handler make_node_implementation_get_properties_by_path_handler(const nmos::resources& resources, slog::base_gate& gate)
{
return [&resources, &gate](const nmos::resource& resource, bool recurse)
{
slog::log<slog::severities::info>(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do get_properties_by_path";

// Implement backup of device model here
return nmos::details::make_nc_method_result({ nmos::nc_method_status::ok });
};
}

// Example Device Configuration callback for validating a back-up dataset
nmos::validate_set_properties_by_path_handler make_node_implementation_validate_set_properties_by_path_handler(const nmos::resources& resources, slog::base_gate& gate)
{
return [&resources, &gate](const nmos::resource& resource, const web::json::value& backup_data_set, bool recurse)
{
slog::log<slog::severities::info>(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do validate_set_properties_by_path";

// Can this backup be restored?
return nmos::details::make_nc_method_result({ nmos::nc_method_status::ok });
};
}

// Example Device Configuration callback for restoring a back-up dataset
nmos::set_properties_by_path_handler make_node_implementation_set_properties_by_path_handler(nmos::resources& resources, slog::base_gate& gate)
{
return [&resources, &gate](const nmos::resource& resource, const web::json::value& data_set, bool recurse, bool allow_incomplete)
{
slog::log<slog::severities::info>(gate, SLOG_FLF) << nmos::stash_category(impl::categories::node_implementation) << "Do set_properties_by_path";

// Implement restore of device model here
return nmos::details::make_nc_method_result({ nmos::nc_method_status::ok });
};
}

namespace impl
{
nmos::interlace_mode get_interlace_mode(const nmos::settings& settings)
Expand Down Expand Up @@ -1868,5 +1907,8 @@ nmos::experimental::node_implementation make_node_implementation(nmos::node_mode
.on_connection_activated(make_node_implementation_connection_activation_handler(model, gate))
.on_validate_channelmapping_output_map(make_node_implementation_map_validator()) // may be omitted if not required
.on_channelmapping_activated(make_node_implementation_channelmapping_activation_handler(gate))
.on_control_protocol_property_changed(make_node_implementation_control_protocol_property_changed_handler(gate)); // may be omitted if IS-12 not required
.on_control_protocol_property_changed(make_node_implementation_control_protocol_property_changed_handler(gate)) // may be omitted if IS-12 not required
.on_get_properties_by_path(make_node_implementation_get_properties_by_path_handler(model.control_protocol_resources, gate)) // may be omitted if IS-14 not required
.on_validate_set_properties_by_path(make_node_implementation_validate_set_properties_by_path_handler(model.control_protocol_resources, gate)) // may be omitted if IS-14 not required
.on_set_properties_by_path(make_node_implementation_set_properties_by_path_handler(model.control_protocol_resources, gate)); // may be omitted if IS-14 not required
}
154 changes: 144 additions & 10 deletions Development/nmos/configuration_api.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,9 @@

namespace nmos
{
inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate);
inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate);

web::http::experimental::listener::api_router make_configuration_api(node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate)
web::http::experimental::listener::api_router make_configuration_api(node_model& model, web::http::experimental::listener::route_handler validate_authorization, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate)
{
using namespace web::http::experimental::listener::api_router_using_declarations;

Expand Down Expand Up @@ -51,7 +51,7 @@ namespace nmos
return pplx::task_from_result(true);
});

configuration_api.mount(U("/x-nmos/") + nmos::patterns::configuration_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_configuration_api(model, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, property_changed, gate));
configuration_api.mount(U("/x-nmos/") + nmos::patterns::configuration_api.pattern + U("/") + nmos::patterns::version.pattern, make_unmounted_configuration_api(model, get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor, get_properties_by_path, validate_set_properties_by_path, set_properties_by_path, property_changed, gate));

return configuration_api;
}
Expand Down Expand Up @@ -205,16 +205,29 @@ namespace nmos
static const web::json::experimental::json_validator validator
{
nmos::experimental::load_json_schema,
boost::copy_range<std::vector<web::uri>>(boost::range::join(
is14_versions::all | boost::adaptors::transformed(experimental::make_configrationapi_method_patch_request_schema_uri),
is14_versions::all | boost::adaptors::transformed(experimental::make_configrationapi_property_value_put_request_schema_uri)
))
boost::copy_range<std::vector<web::uri>>(boost::range::join(boost::range::join(boost::range::join(
is14_versions::all | boost::adaptors::transformed(experimental::make_configurationapi_method_patch_request_schema_uri),
is14_versions::all | boost::adaptors::transformed(experimental::make_configurationapi_property_value_put_request_schema_uri)),
is14_versions::all | boost::adaptors::transformed(experimental::make_configurationapi_bulkProperties_validate_request_schema_uri)),
is14_versions::all | boost::adaptors::transformed(experimental::make_configurationapi_bulkProperties_set_request_schema_uri)))
};
return validator;
}

bool parse_recurse_query_parameter(const utility::string_t& query)
{
web::json::value arguments = web::json::value_from_query(query);

if (arguments.has_boolean_field(fields::nc::recurse))
{
return fields::nc::recurse(arguments);
}

return true;
}
}

inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, control_protocol_property_changed_handler property_changed, slog::base_gate& gate_)
inline web::http::experimental::listener::api_router make_unmounted_configuration_api(node_model& model, get_control_protocol_class_descriptor_handler get_control_protocol_class_descriptor, get_control_protocol_datatype_descriptor_handler get_control_protocol_datatype_descriptor, get_control_protocol_method_descriptor_handler get_control_protocol_method_descriptor, get_properties_by_path_handler get_properties_by_path, validate_set_properties_by_path_handler validate_set_properties_by_path, set_properties_by_path_handler set_properties_by_path, control_protocol_property_changed_handler property_changed, slog::base_gate& gate_)
{
using namespace web::http::experimental::listener::api_router_using_declarations;

Expand Down Expand Up @@ -519,7 +532,7 @@ namespace nmos
const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name));

// Validate JSON syntax according to the schema
details::configurationapi_validator().validate(body, experimental::make_configrationapi_method_patch_request_schema_uri(version));
details::configurationapi_validator().validate(body, experimental::make_configurationapi_method_patch_request_schema_uri(version));

const auto role_path = parameters.at(nmos::patterns::rolePath.name);
const auto method_id = parameters.at(nmos::patterns::methodId.name);
Expand Down Expand Up @@ -594,7 +607,7 @@ namespace nmos
const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name));

// Validate JSON syntax according to the schema
details::configurationapi_validator().validate(body, experimental::make_configrationapi_property_value_put_request_schema_uri(version));
details::configurationapi_validator().validate(body, experimental::make_configurationapi_property_value_put_request_schema_uri(version));

const auto role_path = parameters.at(nmos::patterns::rolePath.name);
const auto property_id = parameters.at(nmos::patterns::propertyId.name);
Expand Down Expand Up @@ -634,6 +647,127 @@ namespace nmos
});
});

configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::GET, [&model, get_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters)
{
const auto role_path = parameters.at(nmos::patterns::rolePath.name);

auto lock = model.read_lock();

auto& resources = model.control_protocol_resources;

const auto& resource = details::find_resource(resources, role_path);
if (resources.end() != resource)
{
bool recurse = details::parse_recurse_query_parameter(req.request_uri().query());

auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("get_properties_by_path not provided"));
if (get_properties_by_path)
{
result = get_properties_by_path(*resource, recurse);
}

auto status = nmos::fields::nc::status(result);
auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError;
set_reply(res, code, result);
}
else
{
// resource not found for the role path
set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path);
}

return pplx::task_from_result(true);
});

configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PATCH, [&model, validate_set_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters)
{
const auto role_path = parameters.at(nmos::patterns::rolePath.name);

auto lock = model.read_lock();
const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name));
auto& resources = model.control_protocol_resources;

const auto& resource = details::find_resource(resources, role_path);
if (resources.end() != resource)
{
return details::extract_json(req, gate_).then([res, resources, resource, validate_set_properties_by_path, version, &gate_](value body) mutable
{
// Validate JSON syntax according to the schema
details::configurationapi_validator().validate(body, experimental::make_configurationapi_bulkProperties_validate_request_schema_uri(version));

bool recurse = nmos::fields::nc::recurse(body);
const auto& data_set = nmos::fields::nc::data_set(body);
if (!data_set.is_null())
{
auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("validate_set_properties_by_path not provided"));
if (validate_set_properties_by_path)
{
result = validate_set_properties_by_path(*resource, data_set, recurse);
}

auto status = nmos::fields::nc::status(result);
auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError;
set_reply(res, code, result);
}
else
{
set_reply(res, status_codes::BadRequest, nmos::details::make_nc_method_result_error({ nc_method_status::parameter_error }, U("Null dataSet parameter")));
}
return true;
});
}
else
{
// resource not found for the role path
set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path);
}

return pplx::task_from_result(true);
});

configuration_api.support(U("/rolePaths/") + nmos::patterns::rolePath.pattern + U("/bulkProperties/?"), methods::PUT, [&model, set_properties_by_path, &gate_](http_request req, http_response res, const string_t&, const route_parameters& parameters)
{
const auto role_path = parameters.at(nmos::patterns::rolePath.name);

auto lock = model.read_lock();
const nmos::api_version version = nmos::parse_api_version(parameters.at(nmos::patterns::version.name));
auto& resources = model.control_protocol_resources;

const auto& resource = details::find_resource(resources, role_path);
if (resources.end() != resource)
{
return details::extract_json(req, gate_).then([res, resources, resource, set_properties_by_path, version, &gate_](value body) mutable
{
// Validate JSON syntax according to the schema
details::configurationapi_validator().validate(body, experimental::make_configurationapi_bulkProperties_set_request_schema_uri(version));

const auto& arguments = nmos::fields::nc::arguments(body);
bool recurse = nmos::fields::nc::recurse(arguments);
bool allow_incomplete = nmos::fields::nc::allow_incomplete(arguments);
const auto& data_set = nmos::fields::nc::data_set(arguments);

auto result = details::make_nc_method_result_error({ nc_method_status::method_not_implemented }, U("set_properties_by_path not provided"));
if (set_properties_by_path)
{
result = set_properties_by_path(*resource, data_set, recurse, allow_incomplete);
}

auto status = nmos::fields::nc::status(result);
auto code = (nc_method_status::ok == status || nc_method_status::property_deprecated == status) ? status_codes::OK : status_codes::InternalError;
set_reply(res, code, result);

return true;
});
}
else
{
// resource not found for the role path
set_error_reply(res, status_codes::NotFound, U("Not Found; ") + role_path);
}

return pplx::task_from_result(true);
});

return configuration_api;
}
}
Loading

0 comments on commit 2588cfb

Please sign in to comment.