Skip to content

HTTPS clone URL

Subversion checkout URL

You can clone with
or
.
Download ZIP
Branch: master
232 lines (204 sloc) 9.034 kB
%% -------------------------------------------------------------------
%%
%% riak_core: Core Riak Application
%%
%% Copyright (c) 2007-2010 Basho Technologies, Inc. All Rights Reserved.
%%
%% 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 Functions for manipulating bucket properties.
%% @type riak_core_bucketprops() = [{Propkey :: atom(), Propval :: term()}]
-module(riak_core_bucket).
-export([append_bucket_defaults/1,
set_bucket/2,
get_bucket/1,
get_bucket/2,
reset_bucket/1,
get_buckets/1,
bucket_nval_map/1,
default_object_nval/0,
all_n/1,
merge_props/2,
name/1,
n_val/1]).
-ifdef(TEST).
-include_lib("eunit/include/eunit.hrl").
-endif.
-define(METADATA_PREFIX, {core, buckets}).
%% @doc Add a list of defaults to global list of defaults for new
%% buckets. If any item is in Items is already set in the
%% current defaults list, the new setting is omitted, and the old
%% setting is kept. Omitting the new setting is intended
%% behavior, to allow settings from app.config to override any
%% hard-coded values.
append_bucket_defaults(Items) when is_list(Items) ->
riak_core_bucket_props:append_defaults(Items).
%% @doc Set the given BucketProps in Bucket or {BucketType, Bucket}. If BucketType does not
%% exist, or is not active, {error, no_type} is returned.
-spec set_bucket(binary() | {riak_core_bucket_type:bucket_type(), binary()}, [{atom(), any()}]) ->
ok | {error, no_type | [{atom(), atom()}]}.
set_bucket({<<"default">>, Name}, BucketProps) ->
set_bucket(Name, BucketProps);
set_bucket({Type, _Name}=Bucket, BucketProps0) ->
case riak_core_bucket_type:get(Type) of
undefined -> {error, no_type};
_ -> set_bucket(fun set_bucket_in_metadata/2, Bucket, BucketProps0)
end;
set_bucket(Name, BucketProps0) ->
set_bucket(fun set_bucket_in_ring/2, Name, BucketProps0).
set_bucket(StoreFun, Bucket, BucketProps0) ->
OldBucket = get_bucket(Bucket),
case riak_core_bucket_props:validate(update, Bucket, OldBucket, BucketProps0) of
{ok, BucketProps} ->
NewBucket = merge_props(BucketProps, OldBucket),
StoreFun(Bucket, NewBucket);
{error, Details} ->
lager:error("Bucket properties validation failed ~p~n", [Details]),
{error, Details}
end.
set_bucket_in_metadata(Bucket, BucketMeta) ->
riak_core_metadata:put(?METADATA_PREFIX, bucket_key(Bucket), BucketMeta).
set_bucket_in_ring(Bucket, BucketMeta) ->
F = fun(Ring, _Args) ->
{new_ring, riak_core_ring:update_meta(bucket_key(Bucket),
BucketMeta,
Ring)}
end,
{ok, _NewRing} = riak_core_ring_manager:ring_trans(F, undefined),
ok.
%% @spec merge_props(list(), list()) -> list()
%% @doc Merge two sets of bucket props. If duplicates exist, the
%% entries in Overriding are chosen before those in Other.
merge_props(Overriding, Other) ->
riak_core_bucket_props:merge(Overriding, Other).
%% @spec get_bucket(riak_object:bucket()) ->
%% {ok, BucketProps :: riak_core_bucketprops()} | {error, no_type}
%% @doc Return the complete current list of properties for Bucket.
%% Properties include but are not limited to:
%% <pre>
%% n_val: how many replicas of objects in this bucket (default: 3)
%% allow_mult: can objects in this bucket have siblings? (default: false)
%% linkfun: a function returning a m/r FunTerm for link extraction
%% </pre>
%%
get_bucket({<<"default">>, Name}) ->
get_bucket(Name);
get_bucket({Type, _Name}=Bucket) ->
TypeMeta = riak_core_bucket_type:get(Type),
BucketMeta = riak_core_metadata:get(?METADATA_PREFIX, bucket_key(Bucket),
[{resolver, fun riak_core_bucket_props:resolve/2}]),
case merge_type_props(TypeMeta, BucketMeta) of
{error, _}=Error -> Error;
Props -> [{name, Bucket} | Props]
end;
get_bucket(Name) ->
Meta = riak_core_ring_manager:get_bucket_meta(Name),
get_bucket_props(Name, Meta).
%% @spec get_bucket(Name, Ring::riak_core_ring:riak_core_ring()) ->
%% BucketProps :: riak_core_bucketprops()
%% @private
get_bucket({<<"default">>, Name}, Ring) ->
get_bucket(Name, Ring);
get_bucket({_Type, _Name}=Bucket, _Ring) ->
%% non-default type buckets are not stored in the ring, so just ignore it
get_bucket(Bucket);
get_bucket(Name, Ring) ->
Meta = riak_core_ring:get_meta(bucket_key(Name), Ring),
get_bucket_props(Name, Meta).
get_bucket_props(Name, undefined) ->
[{name, Name} | riak_core_bucket_props:defaults()];
get_bucket_props(_Name, {ok, Bucket}) ->
Bucket.
merge_type_props(undefined, _) ->
{error, no_type};
merge_type_props(TypeMeta, undefined) when is_list(TypeMeta) ->
TypeMeta;
merge_type_props(TypeMeta, BucketMeta) when is_list(TypeMeta) andalso
is_list(BucketMeta) ->
merge_props(BucketMeta, TypeMeta).
%% @spec reset_bucket(binary()) -> ok
%% @doc Reset the bucket properties for Bucket to the settings
%% inherited from its Bucket Type
reset_bucket({<<"default">>, Name}) ->
reset_bucket(Name);
reset_bucket({_Type, _Name}=Bucket) ->
riak_core_metadata:delete(?METADATA_PREFIX, bucket_key(Bucket));
reset_bucket(Bucket) ->
F = fun(Ring, _Args) ->
{new_ring, riak_core_ring:remove_meta(bucket_key(Bucket), Ring)}
end,
{ok, _NewRing} = riak_core_ring_manager:ring_trans(F, undefined),
ok.
%% @doc Get bucket properties `Props' for all the buckets in the given
%% `Ring' and stored in metadata
-spec get_buckets(riak_core_ring:riak_core_ring()) ->
Props::list().
get_buckets(Ring) ->
RingNames = riak_core_ring:get_buckets(Ring),
RingBuckets = [get_bucket(Name, Ring) || Name <- RingNames],
MetadataBuckets = riak_core_metadata:fold(fun({_, undefined}, Acc) ->
Acc;
({_Key, Props}, Acc) ->
[Props | Acc]
end,
[], ?METADATA_PREFIX,
[{resolver, fun riak_core_bucket_props:resolve/2},
{default, undefined}]),
RingBuckets ++ MetadataBuckets.
%% @doc returns a proplist containing all buckets and their respective N values
-spec bucket_nval_map(riak_core_ring:riak_core_ring()) -> [{binary(),integer()}].
bucket_nval_map(Ring) ->
[{riak_core_bucket:name(B), riak_core_bucket:n_val(B)} ||
B <- riak_core_bucket:get_buckets(Ring)].
%% @doc returns the default n value for buckets that have not explicitly set the property
-spec default_object_nval() -> integer().
default_object_nval() ->
riak_core_bucket:n_val(riak_core_bucket_props:defaults()).
all_n(Ring) ->
BucketNs = bucket_nval_map(Ring),
DefaultN = default_object_nval(),
AllN = lists:foldl(fun({_, N}, Acc) ->
ordsets:add_element(N, Acc)
end, [DefaultN], BucketNs),
AllN.
name(BProps) ->
proplists:get_value(name, BProps).
n_val(BProps) ->
proplists:get_value(n_val, BProps).
bucket_key({<<"default">>, Name}) ->
bucket_key(Name);
bucket_key({_Type, _Name}=Bucket) ->
Bucket;
bucket_key(Name) ->
{bucket, Name}.
%% ===================================================================
%% EUnit tests
%% ===================================================================
-ifdef(TEST).
simple_set_test() ->
application:load(riak_core),
%% appending an empty list of defaults makes up for the fact that
%% riak_core_app:start/2 is not called during eunit runs
%% (that's where the usual defaults are set at startup),
%% while also not adding any trash that might affect other tests
append_bucket_defaults([]),
riak_core_ring_events:start_link(),
riak_core_ring_manager:start_link(test),
ok = set_bucket(a_bucket,[{key,value}]),
Bucket = get_bucket(a_bucket),
riak_core_ring_manager:stop(),
?assertEqual(value, proplists:get_value(key, Bucket)).
-endif.
Jump to Line
Something went wrong with that request. Please try again.