Skip to content

Commit

Permalink
add wm module for GETing (and encoding) active preflist
Browse files Browse the repository at this point in the history
  • Loading branch information
zeeshanlakhani committed Feb 21, 2015
1 parent bf7a325 commit bef6121
Show file tree
Hide file tree
Showing 2 changed files with 229 additions and 0 deletions.
3 changes: 3 additions & 0 deletions src/riak_kv_web.erl
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ raw_dispatch(Name) ->
{Prefix ++ ["buckets", bucket, "keys", key],
riak_kv_wm_object, Props},

{Prefix ++ ["buckets", bucket, "keys", key, "preflist"],
riak_kv_wm_preflist, Props},

{Prefix ++ ["buckets", bucket, "keys", key, '*'],
riak_kv_wm_link_walker, Props},

Expand Down
226 changes: 226 additions & 0 deletions src/riak_kv_wm_preflist.erl
Original file line number Diff line number Diff line change
@@ -0,0 +1,226 @@
%% --------------------------------------------------------------------------
%%
%% riak_kv_wm_preflist - Webmachine resource for getting bucket/key active
%% preflist.
%%
%%
%% Copyright (c) 2015 Basho Technologies, Inc.
%%
%% This file is provided to you under the Apache License,
%% Version 2.0 (the "License"); you may not use this file
%% except in compliance with the License. You may obtain
%% a copy of the License at
%%
%% http://www.apache.org/licenses/LICENSE-2.0
%%
%% Unless required by applicable law or agreed to in writing,
%% software distributed under the License is distributed on an
%% "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
%% KIND, either express or implied. See the License for the
%% specific language governing permissions and limitations
%% under the License.
%%
%% --------------------------------------------------------------------------

%% @doc Resource for getting Riak bucket/key active preflist over HTTP.
%%
%% Available operations:
%%
%% GET /types/Type/buckets/Bucket/keys/Key/preflist
%% GET /buckets/Bucket/keys/Key/preflist
%% Get information about the active preflist for a particular buckey/key
%% combo.

-module(riak_kv_wm_preflist).

%% webmachine resource exports
-export([
init/1,
service_available/2,
is_authorized/2,
forbidden/2,
allowed_methods/2,
resource_exists/2,
content_types_provided/2,
encodings_provided/2,
produce_preflist_body/2,
malformed_request/2
]).

-record(ctx, {
api_version :: integer(), %% version of the API to use.
bucket_type :: binary(), %% bucket type (from uri)
bucket :: binary(), %% Bucket name (from uri)
key :: binary(), %% Key (from uri)
client :: riak_client:riak_client(), %% the store client
prefix :: string(), %% prefix for resource uris
riak :: local | {node(), atom()}, %% params for riak client
method :: atom(), %% HTTP method for the request
security :: riak_core_security:context() %% security context
}).

-type context() :: #ctx{}.

-include_lib("webmachine/include/webmachine.hrl").
-include("riak_kv_wm_raw.hrl").

-spec init(proplists:proplist()) -> {ok, context()}.
%% @doc Initialize this resource. This function extracts the
%% 'prefix' and 'riak' properties from the dispatch args.
init(Props) ->
{ok, #ctx{
api_version=proplists:get_value(api_version, Props),
prefix=proplists:get_value(prefix, Props),
riak=proplists:get_value(riak, Props),
bucket_type=proplists:get_value(bucket_type, Props)}}.

-spec service_available(#wm_reqdata{}, context()) ->
{boolean(), #wm_reqdata{}, context()}.
%% @doc Determine whether or not a connection to Riak
%% can be established. This function also takes this
%% opportunity to extract the 'bucket' and 'key' path
%% bindings from the dispatch, as well as any vtag
%% query parameter.
service_available(RD, Ctx0=#ctx{riak=RiakProps}) ->
Ctx = riak_kv_wm_utils:ensure_bucket_type(RD, Ctx0, #ctx.bucket_type),
case riak_kv_wm_utils:get_riak_client(RiakProps, riak_kv_wm_utils:get_client_id(RD)) of
{ok, C} ->
{true,
RD,
Ctx#ctx{
method=wrq:method(RD),
client=C,
bucket=case wrq:path_info(bucket, RD) of
undefined -> undefined;
B -> list_to_binary(riak_kv_wm_utils:maybe_decode_uri(RD, B))
end,
key=case wrq:path_info(key, RD) of
undefined -> undefined;
K -> list_to_binary(riak_kv_wm_utils:maybe_decode_uri(RD, K))
end
}};
Error ->
{false,
wrq:set_resp_body(
io_lib:format("Unable to connect to Riak: ~p~n", [Error]),
wrq:set_resp_header(?HEAD_CTYPE, "text/plain", RD)),
Ctx}
end.

is_authorized(ReqData, Ctx) ->
case riak_api_web_security:is_authorized(ReqData) of
false ->
{"Basic realm=\"Riak\"", ReqData, Ctx};
{true, SecContext} ->
{true, ReqData, Ctx#ctx{security=SecContext}};
insecure ->
%% XXX 301 may be more appropriate here, but since the http and
%% https port are different and configurable, it is hard to figure
%% out the redirect URL to serve.
{{halt, 426}, wrq:append_to_resp_body(<<"Security is enabled and "
"Riak does not accept credentials over HTTP. Try HTTPS "
"instead.">>, ReqData), Ctx}
end.

forbidden(RD, Ctx = #ctx{security=undefined}) ->
{riak_kv_wm_utils:is_forbidden(RD), RD, Ctx};
forbidden(RD, Ctx) ->
case riak_kv_wm_utils:is_forbidden(RD) of
true ->
{true, RD, Ctx};
false ->
Res = riak_core_security:check_permission({"riak_kv.get_preflist",
Ctx#ctx.bucket_type},
Ctx#ctx.security),
case Res of
{false, Error, _} ->
RD1 = wrq:set_resp_header("Content-Type", "text/plain", RD),
{true, wrq:append_to_resp_body(
unicode:characters_to_binary(
Error, utf8, utf8), RD1), Ctx};
{true, _} ->
{false, RD, Ctx}
end
end.

-spec allowed_methods(#wm_reqdata{}, context()) ->
{[atom()], #wm_reqdata{}, context()}.
%% @doc Get the list of methods this resource supports.
%% Properties allows, GET.
allowed_methods(RD, Ctx) ->
{['GET'], RD, Ctx}.

-spec content_types_provided(#wm_reqdata{}, context()) ->
{[{ContentType::string(), Producer::atom()}], #wm_reqdata{}, context()}.
%% @doc List the content types available for representing this resource.
%% "application/json" is the content-type for listing keys.
content_types_provided(RD, Ctx) ->
%% bucket-level: JSON description only
{[{"application/json", produce_preflist_body}], RD, Ctx}.

-spec encodings_provided(#wm_reqdata{}, context()) ->
{[{Encoding::string(), Producer::function()}], #wm_reqdata{}, context()}.
%% @doc List the encodings available for representing this resource.
%% "identity" and "gzip" are available for a preflist request.
encodings_provided(RD, Ctx) ->
%% identity and gzip for top-level and bucket-level requests
{riak_kv_wm_utils:default_encodings(), RD, Ctx}.

resource_exists(RD, #ctx{bucket_type=BType}=Ctx) ->
{riak_kv_wm_utils:bucket_type_exists(BType), RD, Ctx}.

-spec malformed_request(#wm_reqdata{}, context()) ->
{boolean(), #wm_reqdata{}, context()}.
malformed_request(RD, Ctx) ->
{false, RD, Ctx}.

-spec produce_preflist_body(#wm_reqdata{}, context()) ->
{mochijson2:json_object()|{halt, 404},
#wm_reqdata{},
context()}.
%% @doc Produce bucket/key annotated preflist as JSON.
produce_preflist_body(RD, #ctx{bucket=Bucket0,
bucket_type=BType,
key=Key}=Ctx) ->
Bucket = riak_kv_wm_utils:maybe_bucket_type(BType, Bucket0),
Preflist = riak_core_apl:get_apl_ann_with_pnum({Bucket, Key}),
case Preflist of
[] ->
{{halt, 404},
wrq:set_resp_header("Content-Type", "text/plain",
wrq:append_to_response_body(
io_lib:format(
"No preflist for bucket '~s' and key '~s'~n",
[Bucket, Key]),
RD)),
Ctx};
P ->
%% Encode
Json = mochijson2:encode({struct,
[{<<"preflist">>,
lists:flatten(jsonify_results(P))}]}),
{Json, RD, Ctx}
end.

%% Private

-spec jsonify_results(riak_core_apl:preflist_with_pnum_ann()) -> list().
%% @doc Jsonify active preflist to json.
jsonify_results(Preflist) ->
[jsonify_results({PartitionNumber, Node}, T) ||
{{PartitionNumber, Node}, T} <- Preflist].

-spec jsonify_results({non_neg_integer(), node()}, primary|fallback) ->
[{struct, list()}].

jsonify_results({PartitionNumber, Node}, primary) ->
[{struct,
[{<<"partition">>, PartitionNumber},
{<<"node">>, atom_to_binary(Node, utf8)},
{<<"primary">>, true}]}]
;
jsonify_results({PartitionNumber, Node}, fallback) ->
[{struct,
[{<<"partition">>, PartitionNumber},
{<<"node">>, atom_to_binary(Node, utf8)},
{<<"primary">>, false}]}].

0 comments on commit bef6121

Please sign in to comment.