anchor.diff:  %%% Step functions
   %%%===================================================================
   
  -%%--------------------------------------------------------------------
  -%% @doc Checks if onezone is available at given address
  -%% @end
  -%%--------------------------------------------------------------------
  --spec check_oz_availability(Ctx :: service:step_ctx()) -> ok | no_return().
  -check_oz_availability(Ctx) ->
  -    Protocol = onepanel_env:get(oz_worker_nagios_protocol),
  -    Port = onepanel_env:get(oz_worker_nagios_port),
  -    OzDomain = maps:get(onezone_domain, Ctx),
  -    Url = onepanel_utils:join([Protocol, "://", OzDomain, ":", Port, "/nagios"]),
  -    Opts = case Protocol of
  -        "https" ->
  -            CaCerts = cert_utils:load_ders_in_dir(oz_plugin:get_cacerts_dir()),
  -            [{ssl_options, [{secure, only_verify_peercert}, {cacerts, CaCerts}]}];
  -        _ ->
  -            []
  -    end,
  -
  -    case http_client:get(Url, #{}, <<>>, Opts) of
  -        {ok, ?HTTP_200_OK, _Headers, Body} ->
  -            {Xml, _} = xmerl_scan:string(onepanel_utils:convert(Body, list)),
  -            [Status] = [X#xmlAttribute.value || X <- Xml#xmlElement.attributes,
  -                X#xmlAttribute.name == status],
  -            case Status of
  -                "ok" ->
  -                    ok;
  -                _ ->
  -                    ?debug("Onezone availability check failed - nagios status was ~p", [Status]),
  -                    throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
  -            end;
  -        Other ->
  -            ?debug("Onezone availability check failed - ~p", [Other]),
  -            throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Checks if Oneprovider is registered and connected to onezone
  -%% @end
  -%%--------------------------------------------------------------------
  --spec check_oz_connection() -> ok | no_return().
  -check_oz_connection() ->
  -    case service_op_worker:is_connected_to_oz() of
  -        true -> ok;
  -        false -> throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Registers provider in the zone.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec register(Ctx :: service:step_ctx()) ->
  -    {ok, ProviderId :: id()} | no_return().
  -register(Ctx) ->
  -    {ok, OpwNode} = nodes:any(?SERVICE_OPW),
  -
  -    OnezoneDomain = maps:get(onezone_domain, Ctx),
  -    DomainParams = case onepanel_utils:get_converted(oneprovider_subdomain_delegation, Ctx, boolean, false) of
  -        true ->
  -            Subdomain = onepanel_utils:get_converted(oneprovider_subdomain, Ctx, binary),
  -            #{
  -                <<"subdomainDelegation">> => true,
  -                <<"subdomain">> => string:lowercase(Subdomain),
  -                <<"ipList">> => [] % IPs will be updated in the step set_cluster_ips
  -            };
  -        false ->
  -            Domain = onepanel_utils:get_converted(oneprovider_domain, Ctx, binary),
  -            #{
  -                <<"subdomainDelegation">> => false,
  -                <<"domain">> => string:lowercase(Domain)
  -            }
  -
  -    end,
  -
  -    Params = DomainParams#{
  -        <<"token">> => onepanel_utils:get_converted(oneprovider_token, Ctx, binary),
  -        <<"name">> => onepanel_utils:get_converted(oneprovider_name, Ctx, binary),
  -        <<"adminEmail">> => onepanel_utils:get_converted(oneprovider_admin_email, Ctx, binary),
  -        <<"latitude">> => onepanel_utils:get_converted(oneprovider_geo_latitude, Ctx, float, 0.0),
  -        <<"longitude">> => onepanel_utils:get_converted(oneprovider_geo_longitude, Ctx, float, 0.0)
  -    },
  -
  -    case onezone_client:register_provider(OnezoneDomain, Params) of
  -        {ok, #{provider_id := ProviderId, root_token := RootToken}} ->
  -            on_registered(OpwNode, ProviderId, RootToken, OnezoneDomain),
  -            {ok, ProviderId};
  -        {error, _} = Error ->
  -            throw(Error)
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Unregisters provider in the zone.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec unregister() -> ok | no_return().
  -unregister() ->
  -    oz_providers:unregister(provider),
  -
  -    op_worker_rpc:on_deregister(),
  -    onepanel_deployment:unset_marker(?PROGRESS_LETSENCRYPT_CONFIG),
  -    {ok, _} = service:update_ctx(name(), fun(ServiceCtx) ->
  -        maps:without([cluster, onezone_domain, ?DETAILS_PERSISTENCE],
  -            ServiceCtx#{registered => false})
  -    end),
  -    ok.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Modifies configuration details of the provider.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec modify_details(Ctx :: service:step_ctx()) -> ok | no_return().
  -modify_details(Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    ok = modify_domain_details(Node, Ctx),
  -
  -    Params = kv_utils:copy_found([
  -        {oneprovider_name, <<"name">>},
  -        {oneprovider_geo_latitude, <<"latitude">>},
  -        {oneprovider_geo_longitude, <<"longitude">>},
  -        {oneprovider_admin_email, <<"adminEmail">>}
  -    ], Ctx, #{}),
  -
  -    case maps:size(Params) of
  -        0 -> ok;
  -        _ -> ok = op_worker_rpc:provider_logic_update(Params)
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Returns configuration details of the provider.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec get_details() -> #{atom() := term()} | no_return().
  -get_details() ->
  -    try
  -        case service_op_worker:is_connected_to_oz() of
  -            true -> get_details_by_graph_sync();
  -            false -> get_details_by_rest()
  -        end
  -    catch
  -        Type:?ERROR_UNREGISTERED_ONEPROVIDER = Error ->
  -            erlang:raise(Type, Error, erlang:get_stacktrace());
  -        Type:Error ->
  -            case service:get_ctx(name()) of
  -                #{?DETAILS_PERSISTENCE := Cached} -> Cached;
  -                _ -> erlang:raise(Type, Error, erlang:get_stacktrace())
  -            end
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Returns IPs of hosts with op_worker instances.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec format_cluster_ips(service:step_ctx()) ->
  -    #{isConfigured := boolean(), hosts := #{binary() => binary()}}.
  -format_cluster_ips(Ctx) ->
  -    service_cluster_worker:get_cluster_ips(Ctx#{name => ?SERVICE_OPW}).
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Supports space with selected storage.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec support_space(service:step_ctx()) -> SpaceId :: binary().
  -support_space(#{storage_id := StorageId} = Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    assert_storage_exists(Node, StorageId),
  -    SupportSize = onepanel_utils:get_converted(size, Ctx, binary),
  -    Token = onepanel_utils:get_converted(token, Ctx, binary),
  -
  -    case op_worker_rpc:support_space(StorageId, Token, SupportSize) of
  -        {ok, SpaceId} -> configure_space(Node, SpaceId, StorageId, Ctx);
  -        Error -> throw(Error)
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Revokes support for the space given by ID.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec revoke_space_support(Ctx :: service:step_ctx()) -> ok.
  -revoke_space_support(#{id := SpaceId}) ->
  -    ok = op_worker_rpc:revoke_space_support(SpaceId).
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Returns list of spaces supported by the provider.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec get_spaces() -> [binary()].
  -get_spaces() ->
  -    {ok, SpaceIds} = op_worker_rpc:get_spaces(),
  -    SpaceIds.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Calls op_worker to check if a space is supported.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec is_space_supported(Ctx :: service:step_ctx()) -> boolean().
  -is_space_supported(#{space_id := Id}) ->
  -    op_worker_rpc:supports_space(Id).
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Returns details of the space given by ID.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec get_space_details(Ctx :: service:step_ctx()) -> json_utils:json_term().
  -get_space_details(#{id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    {Name, Providers} = case op_worker_rpc:get_space_details(Node, SpaceId) of
  -        {ok, #{name := Name0, providers := Providers0}} -> {Name0, Providers0};
  -        {error, _} = Error -> throw(Error)
  -    end,
  -    {ok, StorageIds} = op_worker_storage:get_supporting_storages(Node, SpaceId),
  -    StorageId = hd(StorageIds),
  -    ImportedStorage = op_worker_storage:is_imported_storage(Node, StorageId),
  -    StorageImportDetails = op_worker_storage_import:get_storage_import_details(Node, SpaceId),
  -    CurrentSize = op_worker_rpc:space_quota_current_size(Node, SpaceId),
  -    maps_utils:remove_undefined(#{
  -        id => SpaceId,
  -        importedStorage => ImportedStorage,
  -        localStorages => StorageIds,
  -        name => Name,
  -        spaceOccupancy => CurrentSize,
  -        storageId => StorageId,
  -        storageImport => StorageImportDetails,
  -        supportingProviders => Providers
  -    }).
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc Modifies space details.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec modify_space(Ctx :: service:step_ctx()) -> #{id => op_worker_rpc:od_space_id()}.
  -modify_space(#{space_id := SpaceId} = Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    AutoStorageImportConfig = maps:get(auto_storage_import_config, Ctx, #{}),
  -    ok = maybe_update_support_size(Node, SpaceId, Ctx),
  -    op_worker_storage_import:maybe_reconfigure_storage_import(Node, SpaceId, AutoStorageImportConfig),
  -    #{id => SpaceId}.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @private
  -%% @doc If new size for a space is specified, enacts the change.
  -%% @end
  -%%--------------------------------------------------------------------
  -maybe_update_support_size(OpNode, SpaceId, #{size := SupportSize}) ->
  -    case op_worker_rpc:update_space_support_size(OpNode, SpaceId, SupportSize) of
  -        ok -> ok;
  -        Error -> throw(Error)
  -    end;
  -
  -maybe_update_support_size(_OpNode, _SpaceId, _Ctx) -> ok.
  -
  -
  --spec get_auto_storage_import_stats(Ctx :: service:step_ctx()) -> json_utils:json_term().
  -get_auto_storage_import_stats(#{space_id := SpaceId} = Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    Period = onepanel_utils:get_converted(period, Ctx, binary),
  -    MetricsJoined = onepanel_utils:get_converted(metrics, Ctx, binary),
  -    Metrics = binary:split(MetricsJoined, <<",">>, [global, trim]),
  -    op_worker_storage_import:get_stats(Node, SpaceId, Period, Metrics).
  -
  -
  --spec get_auto_storage_import_info(Ctx :: service:step_ctx()) -> json_utils:json_term().
  -get_auto_storage_import_info(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    op_worker_storage_import:get_info(Node, SpaceId).
  -
  -
  --spec get_manual_storage_import_example(Ctx :: service:step_ctx()) -> json_utils:json_term().
  -get_manual_storage_import_example(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    op_worker_storage_import:get_manual_example(Node, SpaceId).
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc
  -%% Notify onezone about IPs change.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec update_provider_ips() -> ok.
  -update_provider_ips() ->
  -    case is_registered() of
  -        true ->
  -            % no check of results - if the provider is not connected to onezone
  -            % the IPs will be sent automatically after connection is acquired
  -            op_worker_rpc:update_subdomain_delegation_ips(),
  -            ok;
  -        false -> ok
  -    end.
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Returns list of auto-cleaning runs reports started since Since date.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec get_auto_cleaning_reports(Ctx :: service:step_ctx()) -> [ReportId :: binary()].
  -get_auto_cleaning_reports(Ctx = #{space_id := SpaceId}) ->
  -    Offset = onepanel_utils:get_converted(offset, Ctx, integer, 0),
  -    Limit = onepanel_utils:get_converted(limit, Ctx, integer, all),
  -    Index = onepanel_utils:get_converted(index, Ctx, binary, undefined),
  -    {ok, Ids} = op_worker_rpc:autocleaning_list_reports(
  -        SpaceId, Index, Offset, Limit),
  -    Ids.
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Returns auto-cleaning run report.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec get_auto_cleaning_report(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  -get_auto_cleaning_report(#{report_id := ReportId}) ->
  -    case op_worker_rpc:autocleaning_get_run_report(ReportId) of
  -        {ok, Report} ->
  -            maps_utils:undefined_to_null(
  -                kv_utils:copy_found([
  -                    {id, id},
  -                    {index, index},
  -                    {started_at, startedAt},
  -                    {stopped_at, stoppedAt},
  -                    {released_bytes, releasedBytes},
  -                    {bytes_to_release, bytesToRelease},
  -                    {files_number, filesNumber},
  -                    {status, status}
  -                ], Report));
  -        {error, _} = Error ->
  -            throw(Error)
  -    end.
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Returns status of current working auto-cleaning process for given space.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec get_auto_cleaning_status(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  -get_auto_cleaning_status(#{space_id := SpaceId}) ->
  -    op_worker_rpc:autocleaning_status(SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Returns configuration of auto-cleaning mechanism in given space.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec get_auto_cleaning_configuration(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  -get_auto_cleaning_configuration(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    op_worker_storage:get_auto_cleaning_configuration(Node, SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Returns configuration of file-popularity mechanism in given space.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec get_file_popularity_configuration(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  -get_file_popularity_configuration(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    op_worker_storage:get_file_popularity_configuration(Node, SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Manually starts auto-cleaning of given space.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec start_auto_cleaning(Ctx :: service:step_ctx()) ->
  -    {ok, ReportId :: binary()} | no_need.
  -start_auto_cleaning(#{space_id := SpaceId}) ->
  -    case op_worker_rpc:autocleaning_force_run(SpaceId) of
  -        {ok, ReportId} -> {ok, ReportId};
  -        {error, {already_started, _}} -> no_need;
  -        {error, nothing_to_clean} -> no_need;
  -        {error, _} = Error -> throw(Error)
  -    end.
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Manually cancels auto-cleaning of given space.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec cancel_auto_cleaning(Ctx :: service:step_ctx()) -> ok.
  -cancel_auto_cleaning(#{space_id := SpaceId}) ->
  -    ok = op_worker_rpc:autocleaning_cancel_run(SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Manually starts scan of storage import mechanism.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec force_start_auto_storage_import_scan(Ctx :: service:step_ctx()) -> ok.
  -force_start_auto_storage_import_scan(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    ok = op_worker_storage_import:start_scan(Node, SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Manually stops scan of storage import mechanism.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec force_stop_auto_storage_import_scan(Ctx :: service:step_ctx()) -> ok.
  -force_stop_auto_storage_import_scan(#{space_id := SpaceId}) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    ok = op_worker_storage_import:stop_scan(Node, SpaceId).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Marks all configuration steps as already performed.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec mark_configured() -> ok.
  -mark_configured() ->
  -    onepanel_deployment:set_marker([
  -        ?PROGRESS_LETSENCRYPT_CONFIG,
  -        ?PROGRESS_CLUSTER_IPS,
  -        ?DNS_CHECK_ACKNOWLEDGED
  -    ]).
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Removes legacy way of storing letsencrypt configuration and
  -%% returns its value.
  -%% @end
  -%%-------------------------------------------------------------------
  --spec pop_legacy_letsencrypt_config() -> boolean().
  -pop_legacy_letsencrypt_config() ->
  -    Result = case service:get(name()) of
  -        {ok, #service{ctx = #{has_letsencrypt_cert := Enabled}}} ->
  -            % upgrade from versions 18.02.0-beta5 and older
  -            service:update(name(), fun(#service{ctx = C} = S) ->
  -                S#service{ctx = maps:remove(has_letsencrypt_cert, C)}
  -            end),
  -            Enabled;
  -        {ok, #service{ctx = #{configured := GbSet}}} ->
  -            case gb_sets:is_set(GbSet) of
  -                true ->
  -                    % upgrade from versions 18.02.0-beta6..18.02.0-rc1
  -                    service:update(name(), fun(#service{ctx = C} = S) ->
  -                        S#service{
  -                            ctx = C#{configured => gb_sets:del_element(letsencrypt, GbSet)}
  -                        }
  -                    end),
  -                    gb_sets:is_member(letsencrypt, GbSet);
  -                false -> false
  -            end;
  -        _ -> false
  -    end,
  -    case Result of
  -        true ->
  -            onepanel_deployment:set_marker(?PROGRESS_LETSENCRYPT_CONFIG),
  -            Result;
  -        _ -> Result
  -    end.
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Configures file-popularity mechanism
  -%% @end
  -%%-------------------------------------------------------------------
  --spec configure_file_popularity(Ctx :: service:step_ctx()) -> list().
  -configure_file_popularity(#{space_id := SpaceId} = Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    Config = maps:without([space_id, hosts], Ctx),
  -    ok = op_worker_storage:maybe_update_file_popularity(Node, SpaceId, Config),
  -    [{id, SpaceId}].
  -
  -
  -%%-------------------------------------------------------------------
  -%% @doc
  -%% Configures auto-cleaning mechanism
  -%% @end
  -%%-------------------------------------------------------------------
  --spec configure_auto_cleaning(Ctx :: service:step_ctx()) -> list().
  -configure_auto_cleaning(#{space_id := SpaceId} = Ctx) ->
  -    {ok, Node} = nodes:any(?SERVICE_OPW),
  -    Config = maps:without([space_id, hosts], Ctx),
  -    ok = op_worker_storage:maybe_update_auto_cleaning(Node, SpaceId, Config),
  -    [{id, SpaceId}].
  -
  -
  --spec connect_and_set_up_in_onezone(no_fallback | fallback_to_async) -> ok | no_return().
  -connect_and_set_up_in_onezone(FallbackPolicy) ->
  -    ?notice("Trying to connect to Onezone (~ts)...", [get_oz_domain()]),
  -    case {try_to_establish_onezone_connection(), FallbackPolicy} of
  -        {true, _} ->
  -            set_up_in_onezone();
  -        {false, no_fallback} ->
  -            ?error("Failed to establish Onezone connection"),
  -            error(failed_to_establish_onezone_connection);
  -        {false, fallback_to_async} ->
  -            schedule_periodic_onezone_connection_check()
  -    end.
  -
  -
  -%%--------------------------------------------------------------------
  -%% @doc
  -%% Stores absolute path to oneprovider token file in Onepanel config.
  -%% This operation requires op-worker to be up.
  -%% This function should be executed after cluster deployment, and after
  -%% upgrading to version 20.02 when the path variable name and contents
  -%% have changed.
  -%%
  -%% The absolute path is mostly relevant to one-env source deployments,
  -%% since in production the default in app.config is already absolute.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec store_absolute_auth_file_path() -> ok.
  -store_absolute_auth_file_path() ->
  -    PanelNodes = nodes:all(?SERVICE_PANEL),
  -    RootTokenPath = op_worker_rpc:get_root_token_file_path(),
  -    onepanel_env:write(PanelNodes,
  -        [?SERVICE_PANEL, op_worker_root_token_path], RootTokenPath, ?SERVICE_PANEL),
  -    onepanel_env:set(PanelNodes,
  -        op_worker_root_token_path, RootTokenPath, ?APP_NAME),
  -    ok.
  -
  -
  -%%%===================================================================
  -%%% Internal RPC functions
  -%%%===================================================================
  -
  -%%--------------------------------------------------------------------
  -%% @private
  -%% @doc Reads provider root token stored in a file
  -%% and returns it with a time caveat added.
  -%% @end
  -%%--------------------------------------------------------------------
  --spec root_token_from_file() -> tokens:serialized().
  -root_token_from_file() ->
  -    Self = node(),
  -    % ensure correct node for reading op_worker configuration
  -    case nodes:onepanel_with(?SERVICE_OPW) of
  -        {_, Self} ->
  -            RootTokenBin = maps:get(root_token, read_auth_file()),
  -            {ok, TTL} = onepanel_env:read_effective(
  -                [?SERVICE_OPW, provider_token_ttl_sec], ?SERVICE_OPW),
  -            Now = global_clock:timestamp_seconds(),
  -            tokens:confine(RootTokenBin, #cv_time{valid_until = Now + TTL});
  -        {ok, Other} ->
  -            <<_/binary>> = rpc:call(Other, ?MODULE, ?FUNCTION_NAME, [])
  -    end.
  -
  -
  -%% @private
  --spec await_registration_token_from_file(file:filename_all()) -> tokens:serialized() | no_return().
  -await_registration_token_from_file(FilePath) ->
  -    try
  -        {ok, FileBody} = file:read_file(FilePath),
  -        Token = sanitize_registration_token(FileBody),
  -        ?info("Successfully retrieved registration token from file ~s", [FilePath]),
  -        Token
  -    catch Class:Reason ->
  -        utils:throttle(60, fun() ->
  -            ?notice(
  -                "No suitable Oneprovider registration token found in file '~s', retrying...~n"
  -                "Last error was: ~w:~w", [FilePath, Class, Reason]
  -            )
  -        end),
  -        ?debug_stacktrace("Reading registration token from file failed due to ~w:~p", [Class, Reason]),
  -        throw(attempt_failed)
  -    end.
  -
  -%%%===================================================================
  -%%% Internal functions
  -%%%===================================================================
  -
  -%% @private
  --spec resolve_registration_token(service:step_ctx()) -> tokens:serialized() | no_return().
  +-spec resolve_registration_token(service:step_ctx()) -> ok | no_return().
   resolve_registration_token(Ctx) ->
  -    case kv_utils:get(oneprovider_token_provision_method, Ctx, <<"inline">>) of
  +    RegistrationToken = case kv_utils:get(oneprovider_token_provision_method, Ctx, <<"inline">>) of
           <<"inline">> ->
               case kv_utils:find(oneprovider_token, Ctx) of
                   {ok, Token} ->
  
positive.code:         #steps{service = ?SERVICE_OPW, action = status}
      ];
  
  get_steps(register, #{hosts := _, onezone_domain := _, oneprovider_token := _} = Ctx) ->
      [
          #step{ctx = Ctx, function = check_oz_availability,
              attempts = onepanel_env:get(connect_to_onezone_attempts)},
          #step{ctx = Ctx, function = register, hosts = [hosts:self()]},
          #step{ctx = Ctx, function = connect_and_set_up_in_onezone, args = [no_fallback], selection = any},
          % explicitly fail on connection problems before executing further steps
          #step{ctx = Ctx, function = check_oz_connection, args = [],
              attempts = onepanel_env:get(connect_to_onezone_attempts)},
          #steps{ctx = Ctx, action = set_cluster_ips}
      ];
  get_steps(register, Ctx0) ->
      Ctx1 = case maps:is_key(hosts, Ctx0) of
          false -> Ctx0#{hosts => hosts:all(?SERVICE_OPW)};
          true -> Ctx0
      end,
      Ctx2 = case maps:is_key(oneprovider_token, Ctx1) of
          false -> Ctx1#{oneprovider_token => resolve_registration_token(Ctx1)};
          true -> Ctx1
      end,
      % the domain is read from the token, which can be given either directly or
      % from a file that may appear with a delay
      Ctx3 = case maps:is_key(onezone_domain, Ctx2) of
          false -> Ctx2#{onezone_domain => onezone_tokens:read_domain(maps:get(oneprovider_token, Ctx2))};
          true -> Ctx2
      end,
      get_steps(register, Ctx3);
  
  get_steps(unregister, _Ctx) ->
      [#step{function = unregister, selection = any, args = []}];
  
negative.code:                 error ->
                      throw(?ERROR_MISSING_REQUIRED_VALUE(<<"tokenFile">>))
              end
      end,
  
      {ok, _} = service:update_ctx(name(), fun(ServiceCtx) ->
          ServiceCtx#{
              oneprovider_token => RegistrationToken,
              onezone_domain => onezone_tokens:read_domain(RegistrationToken)
          }
      end),
      ok.
  
  
  %%--------------------------------------------------------------------
  %% @doc Checks if onezone is available at given address
  %% @end
  %%--------------------------------------------------------------------
  -spec check_oz_availability(Ctx :: service:step_ctx()) -> ok | no_return().
  check_oz_availability(_Ctx) ->
      Protocol = onepanel_env:get(oz_worker_nagios_protocol),
      Port = onepanel_env:get(oz_worker_nagios_port),
  
      % The domain is not stored in the step ctx, which is established at the beginning
      % of the deployment process when it's not known yet. Instead, read from the
      % context stored in db which has been updated in resolve_registration_token/1.
      OzDomain = onepanel_utils:get_converted(onezone_domain, service:get_ctx(name()), binary),
  
      Url = onepanel_utils:join([Protocol, "://", OzDomain, ":", Port, "/nagios"]),
      Opts = case Protocol of
          "https" ->
              CaCerts = cert_utils:load_ders_in_dir(oz_plugin:get_cacerts_dir()),
              [{ssl_options, [{secure, only_verify_peercert}, {cacerts, CaCerts}]}];
          _ ->
              []
      end,
  
      case http_client:get(Url, #{}, <<>>, Opts) of
          {ok, ?HTTP_200_OK, _Headers, Body} ->
              {Xml, _} = xmerl_scan:string(onepanel_utils:convert(Body, list)),
              [Status] = [X#xmlAttribute.value || X <- Xml#xmlElement.attributes,
                  X#xmlAttribute.name == status],
              case Status of
                  "ok" ->
                      ok;
                  _ ->
                      ?debug("Onezone availability check failed - nagios status was ~p", [Status]),
                      throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
              end;
          Other ->
              ?debug("Onezone availability check failed - ~p", [Other]),
              throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Checks if Oneprovider is registered and connected to onezone
  %% @end
  %%--------------------------------------------------------------------
  -spec check_oz_connection() -> ok | no_return().
  check_oz_connection() ->
      case service_op_worker:is_connected_to_oz() of
          true -> ok;
          false -> throw(?ERROR_NO_CONNECTION_TO_ONEZONE)
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Registers provider in the zone.
  %% @end
  %%--------------------------------------------------------------------
  -spec register(Ctx :: service:step_ctx()) ->
      {ok, ProviderId :: id()} | no_return().
  register(Ctx) ->
      {ok, OpwNode} = nodes:any(?SERVICE_OPW),
  
      % The token and domain are not stored in the step ctx, which is established at the beginning
      % of the deployment process when they are not known yet. Instead, read from the
      % context stored in db which has been updated in resolve_registration_token/1.
      OnezoneDomain = onepanel_utils:get_converted(onezone_domain, service:get_ctx(name()), binary),
      RegistrationToken = onepanel_utils:get_converted(oneprovider_token, service:get_ctx(name()), binary),
  
      DomainParams = case onepanel_utils:get_converted(oneprovider_subdomain_delegation, Ctx, boolean, false) of
          true ->
              Subdomain = onepanel_utils:get_converted(oneprovider_subdomain, Ctx, binary),
              #{
                  <<"subdomainDelegation">> => true,
                  <<"subdomain">> => string:lowercase(Subdomain),
                  <<"ipList">> => [] % IPs will be updated in the step set_cluster_ips
              };
          false ->
              Domain = onepanel_utils:get_converted(oneprovider_domain, Ctx, binary),
              #{
                  <<"subdomainDelegation">> => false,
                  <<"domain">> => string:lowercase(Domain)
              }
  
      end,
  
      Params = DomainParams#{
          <<"token">> => RegistrationToken,
          <<"name">> => onepanel_utils:get_converted(oneprovider_name, Ctx, binary),
          <<"adminEmail">> => onepanel_utils:get_converted(oneprovider_admin_email, Ctx, binary),
          <<"latitude">> => onepanel_utils:get_converted(oneprovider_geo_latitude, Ctx, float, 0.0),
          <<"longitude">> => onepanel_utils:get_converted(oneprovider_geo_longitude, Ctx, float, 0.0)
      },
  
      case onezone_client:register_provider(OnezoneDomain, Params) of
          {ok, #{provider_id := ProviderId, root_token := RootToken}} ->
              on_registered(OpwNode, ProviderId, RootToken, OnezoneDomain),
              {ok, ProviderId};
          {error, _} = Error ->
              throw(Error)
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Unregisters provider in the zone.
  %% @end
  %%--------------------------------------------------------------------
  -spec unregister() -> ok | no_return().
  unregister() ->
      oz_providers:unregister(provider),
  
      op_worker_rpc:on_deregister(),
      onepanel_deployment:unset_marker(?PROGRESS_LETSENCRYPT_CONFIG),
      {ok, _} = service:update_ctx(name(), fun(ServiceCtx) ->
          maps:without([cluster, onezone_domain, oneprovider_token, ?DETAILS_PERSISTENCE],
              ServiceCtx#{registered => false})
      end),
      ok.
  
  
  %%--------------------------------------------------------------------
  %% @doc Modifies configuration details of the provider.
  %% @end
  %%--------------------------------------------------------------------
  -spec modify_details(Ctx :: service:step_ctx()) -> ok | no_return().
  modify_details(Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      ok = modify_domain_details(Node, Ctx),
  
      Params = kv_utils:copy_found([
          {oneprovider_name, <<"name">>},
          {oneprovider_geo_latitude, <<"latitude">>},
          {oneprovider_geo_longitude, <<"longitude">>},
          {oneprovider_admin_email, <<"adminEmail">>}
      ], Ctx, #{}),
  
      case maps:size(Params) of
          0 -> ok;
          _ -> ok = op_worker_rpc:provider_logic_update(Params)
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Returns configuration details of the provider.
  %% @end
  %%--------------------------------------------------------------------
  -spec get_details() -> #{atom() := term()} | no_return().
  get_details() ->
      try
          case service_op_worker:is_connected_to_oz() of
              true -> get_details_by_graph_sync();
              false -> get_details_by_rest()
          end
      catch
          Type:?ERROR_UNREGISTERED_ONEPROVIDER = Error ->
              erlang:raise(Type, Error, erlang:get_stacktrace());
          Type:Error ->
              case service:get_ctx(name()) of
                  #{?DETAILS_PERSISTENCE := Cached} -> Cached;
                  _ -> erlang:raise(Type, Error, erlang:get_stacktrace())
              end
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Returns IPs of hosts with op_worker instances.
  %% @end
  %%--------------------------------------------------------------------
  -spec format_cluster_ips(service:step_ctx()) ->
      #{isConfigured := boolean(), hosts := #{binary() => binary()}}.
  format_cluster_ips(Ctx) ->
      service_cluster_worker:get_cluster_ips(Ctx#{name => ?SERVICE_OPW}).
  
  
  %%--------------------------------------------------------------------
  %% @doc Supports space with selected storage.
  %% @end
  %%--------------------------------------------------------------------
  -spec support_space(service:step_ctx()) -> SpaceId :: binary().
  support_space(#{storage_id := StorageId} = Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      assert_storage_exists(Node, StorageId),
      SupportSize = onepanel_utils:get_converted(size, Ctx, binary),
      Token = onepanel_utils:get_converted(token, Ctx, binary),
  
      case op_worker_rpc:support_space(StorageId, Token, SupportSize) of
          {ok, SpaceId} -> configure_space(Node, SpaceId, StorageId, Ctx);
          Error -> throw(Error)
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc Revokes support for the space given by ID.
  %% @end
  %%--------------------------------------------------------------------
  -spec revoke_space_support(Ctx :: service:step_ctx()) -> ok.
  revoke_space_support(#{id := SpaceId}) ->
      ok = op_worker_rpc:revoke_space_support(SpaceId).
  
  
  %%--------------------------------------------------------------------
  %% @doc Returns list of spaces supported by the provider.
  %% @end
  %%--------------------------------------------------------------------
  -spec get_spaces() -> [binary()].
  get_spaces() ->
      {ok, SpaceIds} = op_worker_rpc:get_spaces(),
      SpaceIds.
  
  
  %%--------------------------------------------------------------------
  %% @doc Calls op_worker to check if a space is supported.
  %% @end
  %%--------------------------------------------------------------------
  -spec is_space_supported(Ctx :: service:step_ctx()) -> boolean().
  is_space_supported(#{space_id := Id}) ->
      op_worker_rpc:supports_space(Id).
  
  
  %%--------------------------------------------------------------------
  %% @doc Returns details of the space given by ID.
  %% @end
  %%--------------------------------------------------------------------
  -spec get_space_details(Ctx :: service:step_ctx()) -> json_utils:json_term().
  get_space_details(#{id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      {Name, Providers} = case op_worker_rpc:get_space_details(Node, SpaceId) of
          {ok, #{name := Name0, providers := Providers0}} -> {Name0, Providers0};
          {error, _} = Error -> throw(Error)
      end,
      {ok, StorageIds} = op_worker_storage:get_supporting_storages(Node, SpaceId),
      StorageId = hd(StorageIds),
      ImportedStorage = op_worker_storage:is_imported_storage(Node, StorageId),
      StorageImportDetails = op_worker_storage_import:get_storage_import_details(Node, SpaceId),
      CurrentSize = op_worker_rpc:space_quota_current_size(Node, SpaceId),
      maps_utils:remove_undefined(#{
          id => SpaceId,
          importedStorage => ImportedStorage,
          localStorages => StorageIds,
          name => Name,
          spaceOccupancy => CurrentSize,
          storageId => StorageId,
          storageImport => StorageImportDetails,
          supportingProviders => Providers
      }).
  
  
  %%--------------------------------------------------------------------
  %% @doc Modifies space details.
  %% @end
  %%--------------------------------------------------------------------
  -spec modify_space(Ctx :: service:step_ctx()) -> #{id => op_worker_rpc:od_space_id()}.
  modify_space(#{space_id := SpaceId} = Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      AutoStorageImportConfig = maps:get(auto_storage_import_config, Ctx, #{}),
      ok = maybe_update_support_size(Node, SpaceId, Ctx),
      op_worker_storage_import:maybe_reconfigure_storage_import(Node, SpaceId, AutoStorageImportConfig),
      #{id => SpaceId}.
  
  
  %%--------------------------------------------------------------------
  %% @private
  %% @doc If new size for a space is specified, enacts the change.
  %% @end
  %%--------------------------------------------------------------------
  maybe_update_support_size(OpNode, SpaceId, #{size := SupportSize}) ->
      case op_worker_rpc:update_space_support_size(OpNode, SpaceId, SupportSize) of
          ok -> ok;
          Error -> throw(Error)
      end;
  
  maybe_update_support_size(_OpNode, _SpaceId, _Ctx) -> ok.
  
  
  -spec get_auto_storage_import_stats(Ctx :: service:step_ctx()) -> json_utils:json_term().
  get_auto_storage_import_stats(#{space_id := SpaceId} = Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      Period = onepanel_utils:get_converted(period, Ctx, binary),
      MetricsJoined = onepanel_utils:get_converted(metrics, Ctx, binary),
      Metrics = binary:split(MetricsJoined, <<",">>, [global, trim]),
      op_worker_storage_import:get_stats(Node, SpaceId, Period, Metrics).
  
  
  -spec get_auto_storage_import_info(Ctx :: service:step_ctx()) -> json_utils:json_term().
  get_auto_storage_import_info(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      op_worker_storage_import:get_info(Node, SpaceId).
  
  
  -spec get_manual_storage_import_example(Ctx :: service:step_ctx()) -> json_utils:json_term().
  get_manual_storage_import_example(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      op_worker_storage_import:get_manual_example(Node, SpaceId).
  
  
  %%--------------------------------------------------------------------
  %% @doc
  %% Notify onezone about IPs change.
  %% @end
  %%--------------------------------------------------------------------
  -spec update_provider_ips() -> ok.
  update_provider_ips() ->
      case is_registered() of
          true ->
              % no check of results - if the provider is not connected to onezone
              % the IPs will be sent automatically after connection is acquired
              op_worker_rpc:update_subdomain_delegation_ips(),
              ok;
          false -> ok
      end.
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Returns list of auto-cleaning runs reports started since Since date.
  %% @end
  %%-------------------------------------------------------------------
  -spec get_auto_cleaning_reports(Ctx :: service:step_ctx()) -> [ReportId :: binary()].
  get_auto_cleaning_reports(Ctx = #{space_id := SpaceId}) ->
      Offset = onepanel_utils:get_converted(offset, Ctx, integer, 0),
      Limit = onepanel_utils:get_converted(limit, Ctx, integer, all),
      Index = onepanel_utils:get_converted(index, Ctx, binary, undefined),
      {ok, Ids} = op_worker_rpc:autocleaning_list_reports(
          SpaceId, Index, Offset, Limit),
      Ids.
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Returns auto-cleaning run report.
  %% @end
  %%-------------------------------------------------------------------
  -spec get_auto_cleaning_report(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  get_auto_cleaning_report(#{report_id := ReportId}) ->
      case op_worker_rpc:autocleaning_get_run_report(ReportId) of
          {ok, Report} ->
              maps_utils:undefined_to_null(
                  kv_utils:copy_found([
                      {id, id},
                      {index, index},
                      {started_at, startedAt},
                      {stopped_at, stoppedAt},
                      {released_bytes, releasedBytes},
                      {bytes_to_release, bytesToRelease},
                      {files_number, filesNumber},
                      {status, status}
                  ], Report));
          {error, _} = Error ->
              throw(Error)
      end.
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Returns status of current working auto-cleaning process for given space.
  %% @end
  %%-------------------------------------------------------------------
  -spec get_auto_cleaning_status(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  get_auto_cleaning_status(#{space_id := SpaceId}) ->
      op_worker_rpc:autocleaning_status(SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Returns configuration of auto-cleaning mechanism in given space.
  %% @end
  %%-------------------------------------------------------------------
  -spec get_auto_cleaning_configuration(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  get_auto_cleaning_configuration(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      op_worker_storage:get_auto_cleaning_configuration(Node, SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Returns configuration of file-popularity mechanism in given space.
  %% @end
  %%-------------------------------------------------------------------
  -spec get_file_popularity_configuration(Ctx :: service:step_ctx()) -> #{atom() => term()}.
  get_file_popularity_configuration(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      op_worker_storage:get_file_popularity_configuration(Node, SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Manually starts auto-cleaning of given space.
  %% @end
  %%-------------------------------------------------------------------
  -spec start_auto_cleaning(Ctx :: service:step_ctx()) ->
      {ok, ReportId :: binary()} | no_need.
  start_auto_cleaning(#{space_id := SpaceId}) ->
      case op_worker_rpc:autocleaning_force_run(SpaceId) of
          {ok, ReportId} -> {ok, ReportId};
          {error, {already_started, _}} -> no_need;
          {error, nothing_to_clean} -> no_need;
          {error, _} = Error -> throw(Error)
      end.
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Manually cancels auto-cleaning of given space.
  %% @end
  %%-------------------------------------------------------------------
  -spec cancel_auto_cleaning(Ctx :: service:step_ctx()) -> ok.
  cancel_auto_cleaning(#{space_id := SpaceId}) ->
      ok = op_worker_rpc:autocleaning_cancel_run(SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Manually starts scan of storage import mechanism.
  %% @end
  %%-------------------------------------------------------------------
  -spec force_start_auto_storage_import_scan(Ctx :: service:step_ctx()) -> ok.
  force_start_auto_storage_import_scan(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      ok = op_worker_storage_import:start_scan(Node, SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Manually stops scan of storage import mechanism.
  %% @end
  %%-------------------------------------------------------------------
  -spec force_stop_auto_storage_import_scan(Ctx :: service:step_ctx()) -> ok.
  force_stop_auto_storage_import_scan(#{space_id := SpaceId}) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      ok = op_worker_storage_import:stop_scan(Node, SpaceId).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Marks all configuration steps as already performed.
  %% @end
  %%-------------------------------------------------------------------
  -spec mark_configured() -> ok.
  mark_configured() ->
      onepanel_deployment:set_marker([
          ?PROGRESS_LETSENCRYPT_CONFIG,
          ?PROGRESS_CLUSTER_IPS,
          ?DNS_CHECK_ACKNOWLEDGED
      ]).
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Removes legacy way of storing letsencrypt configuration and
  %% returns its value.
  %% @end
  %%-------------------------------------------------------------------
  -spec pop_legacy_letsencrypt_config() -> boolean().
  pop_legacy_letsencrypt_config() ->
      Result = case service:get(name()) of
          {ok, #service{ctx = #{has_letsencrypt_cert := Enabled}}} ->
              % upgrade from versions 18.02.0-beta5 and older
              service:update(name(), fun(#service{ctx = C} = S) ->
                  S#service{ctx = maps:remove(has_letsencrypt_cert, C)}
              end),
              Enabled;
          {ok, #service{ctx = #{configured := GbSet}}} ->
              case gb_sets:is_set(GbSet) of
                  true ->
                      % upgrade from versions 18.02.0-beta6..18.02.0-rc1
                      service:update(name(), fun(#service{ctx = C} = S) ->
                          S#service{
                              ctx = C#{configured => gb_sets:del_element(letsencrypt, GbSet)}
                          }
                      end),
                      gb_sets:is_member(letsencrypt, GbSet);
                  false -> false
              end;
          _ -> false
      end,
      case Result of
          true ->
              onepanel_deployment:set_marker(?PROGRESS_LETSENCRYPT_CONFIG),
              Result;
          _ -> Result
      end.
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Configures file-popularity mechanism
  %% @end
  %%-------------------------------------------------------------------
  -spec configure_file_popularity(Ctx :: service:step_ctx()) -> list().
  configure_file_popularity(#{space_id := SpaceId} = Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      Config = maps:without([space_id, hosts], Ctx),
      ok = op_worker_storage:maybe_update_file_popularity(Node, SpaceId, Config),
      [{id, SpaceId}].
  
  
  %%-------------------------------------------------------------------
  %% @doc
  %% Configures auto-cleaning mechanism
  %% @end
  %%-------------------------------------------------------------------
  -spec configure_auto_cleaning(Ctx :: service:step_ctx()) -> list().
  configure_auto_cleaning(#{space_id := SpaceId} = Ctx) ->
      {ok, Node} = nodes:any(?SERVICE_OPW),
      Config = maps:without([space_id, hosts], Ctx),
      ok = op_worker_storage:maybe_update_auto_cleaning(Node, SpaceId, Config),
      [{id, SpaceId}].
  
  
  -spec connect_and_set_up_in_onezone(no_fallback | fallback_to_async) -> ok | no_return().
  connect_and_set_up_in_onezone(FallbackPolicy) ->
      ?notice("Trying to connect to Onezone (~ts)...", [get_oz_domain()]),
      case {try_to_establish_onezone_connection(), FallbackPolicy} of
          {true, _} ->
              set_up_in_onezone();
          {false, no_fallback} ->
              ?error("Failed to establish Onezone connection"),
              error(failed_to_establish_onezone_connection);
          {false, fallback_to_async} ->
              schedule_periodic_onezone_connection_check()
      end.
  
  
  %%--------------------------------------------------------------------
  %% @doc
  %% Stores absolute path to oneprovider token file in Onepanel config.
  %% This operation requires op-worker to be up.
  %% This function should be executed after cluster deployment, and after
  %% upgrading to version 20.02 when the path variable name and contents
  %% have changed.
  %%
  %% The absolute path is mostly relevant to one-env source deployments,
  %% since in production the default in app.config is already absolute.
  %% @end
  %%--------------------------------------------------------------------
  -spec store_absolute_auth_file_path() -> ok.
  store_absolute_auth_file_path() ->
      PanelNodes = nodes:all(?SERVICE_PANEL),
      RootTokenPath = op_worker_rpc:get_root_token_file_path(),
      onepanel_env:write(PanelNodes,
          [?SERVICE_PANEL, op_worker_root_token_path], RootTokenPath, ?SERVICE_PANEL),
      onepanel_env:set(PanelNodes,
          op_worker_root_token_path, RootTokenPath, ?APP_NAME),
      ok.
  
  
  %%%===================================================================
  %%% Internal RPC functions
  %%%===================================================================
  
  %%--------------------------------------------------------------------
  %% @private
  %% @doc Reads provider root token stored in a file
  %% and returns it with a time caveat added.
  %% @end
  %%--------------------------------------------------------------------
  -spec root_token_from_file() -> tokens:serialized().
  root_token_from_file() ->
      Self = node(),
      % ensure correct node for reading op_worker configuration
      case nodes:onepanel_with(?SERVICE_OPW) of
          {_, Self} ->
              RootTokenBin = maps:get(root_token, read_auth_file()),
              {ok, TTL} = onepanel_env:read_effective(
                  [?SERVICE_OPW, provider_token_ttl_sec], ?SERVICE_OPW),
              Now = global_clock:timestamp_seconds(),
              tokens:confine(RootTokenBin, #cv_time{valid_until = Now + TTL});
          {ok, Other} ->
              <<_/binary>> = rpc:call(Other, ?MODULE, ?FUNCTION_NAME, [])
      end.
  
  
  %% @private
  -spec await_registration_token_from_file(file:filename_all()) -> tokens:serialized() | no_return().
  await_registration_token_from_file(FilePath) ->
      try
          {ok, FileBody} = file:read_file(FilePath),
          Token = sanitize_registration_token(FileBody),
          ?info("Successfully retrieved registration token from file ~s", [FilePath]),
          Token
      catch Class:Reason ->
          utils:throttle(60, fun() ->
              ?notice(
                  "No suitable Oneprovider registration token found in file '~s', retrying...~n"
                  "Last error was: ~w:~w", [FilePath, Class, Reason]
              )
          end),
          ?debug_stacktrace("Reading registration token from file failed due to ~w:~p", [Class, Reason]),
          throw(attempt_failed)
      end.
  
  %%%===================================================================
  %%% Internal functions
  %%%===================================================================
  
  %% @private
  -spec sanitize_registration_token(binary()) -> tokens:serialized() | no_return().
  