Skip to content

Commit

Permalink
Merge pull request #81 from tgockel/issue/79/system-error
Browse files Browse the repository at this point in the history
Issue/79/system error
  • Loading branch information
tgockel authored Feb 8, 2018
2 parents 9c9b035 + 023e066 commit e8b81f3
Show file tree
Hide file tree
Showing 11 changed files with 600 additions and 472 deletions.
18 changes: 17 additions & 1 deletion src/zk/acl.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -49,6 +49,8 @@ constexpr permission operator~(permission a)
/** Check that \a self allows it \a perform all operations. For example,
* `allows(permission::read | permission::write, permission::read)` will be \c true, as `read|write` is allowed to
* \c read.
*
* \relates permission
**/
constexpr bool allows(permission self, permission perform)
{
Expand All @@ -62,7 +64,8 @@ std::string to_string(const permission&);
/** An individual rule in an \c acl. It consists of a \c scheme and \c id pair to identify the \e who and a
* \c permission set to determine what they are allowed to do.
*
* \see https://zookeeper.apache.org/doc/r3.1.2/zookeeperProgrammers.html#sc_ACLPermissions
* See <a href="https://zookeeper.apache.org/doc/r3.4.10/zookeeperProgrammers.html#sc_ACLPermissions">"Builtin ACL
* Schemes"</a> in the ZooKeeper Programmer's Guide for more information.
**/
class acl_rule final
{
Expand All @@ -74,6 +77,19 @@ class acl_rule final

/** The authentication scheme this list is used for. The most common scheme is `"auth"`, which allows any
* authenticated user to perform actions (see \c acls::creator_all).
*
* ZooKeeper's authentication system is extensible, but the majority of use cases are covered by the built-in
* schemes:
*
* - \c "world" -- This has a single ID \c "anyone" that represents any user of the system. The ACLs
* \ref acls::open_unsafe and \ref acls::read_unsafe use the \c "world" scheme.
* - \c "auth" -- This represents any authenticated user. The \c id field is unused. The ACL \ref acls::creator_all
* uses the \c "auth" scheme.
* - \c "digest" -- This uses a \c "${username}:${password}" string to generate MD5 hash which is then used as an
* identity. Authentication is done by sending the string in clear text. When used in the ACL, the expression
* will be the \c "${username}:${digest}", where \c digest is the base 64 encoded SHA1 digest of \c password.
* - \c "ip" -- This uses the client host IP as an identity. The \c id expression is an IP address or CIDR netmask,
* which will be matched against the client identity.
**/
const std::string& scheme() const
{
Expand Down
104 changes: 52 additions & 52 deletions src/zk/client.hpp
Original file line number Diff line number Diff line change
Expand Up @@ -56,63 +56,63 @@ class client final

void close();

/** Return the data and the \c stat of the node of the given \a path.
/** Return the data and the \c stat of the entry of the given \a path.
*
* \throws no_node If no node with the given path exists, the future will be deliever with \c no_node.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
**/
future<get_result> get(string_view path) const;

/** Similar to \c get, but if the call is successful (no error is returned), a watch will be left on the node with
* the given \a path. The watch will be triggered by a successful operation that sets data on the node or erases
* the node.
/** Similar to \c get, but if the call is successful (no error is returned), a watch will be left on the entry with
* the given \a path. The watch will be triggered by a successful operation that sets data or erases the entry.
*
* \throws no_node If no node with the given path exists, the future will be deliever with \c no_node. To watch for
* the creation of a node, use \c watch_exists.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry. To
* watch for the creation of an entry, use \c watch_exists.
**/
future<watch_result> watch(string_view path) const;

/** Return the list of the children of the node of the given \a path. The returned values are not prefixed with the
/** Return the list of the children of the entry of the given \a path. The returned values are not prefixed with the
* provided \a path; i.e. if the database contains \c "/path/a" and \c "/path/b", the result of \c get_children for
* \c "/path" will be `["a", "b"]`. The list of children returned is not sorted and no guarantee is provided as to
* its natural or lexical order.
*
* \throws no_node If no node with the given path exists, the future will be delivered with \c no_node.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
**/
future<get_children_result> get_children(string_view path) const;

/** Similar to \c get_children, but if the call is successful (no error is returned), a watch will be left on the
* node with the given \a path. The watch will be triggered by a successful operation that erases the node of the
* given \e path or creates or erases a child under the node.
* entry with the given \a path. The watch will be triggered by a successful operation that erases the entry at the
* given \e path or creates or erases a child immediately under the path (it is not recursive).
**/
future<watch_children_result> watch_children(string_view path) const;

/** Return the \c stat of the node of the given \a path or \c nullopt if no such node exists. **/
/** Return the \c stat of the entry of the given \a path or \c nullopt if it does not exist. **/
future<exists_result> exists(string_view path) const;

/** Similar to \c watch, but if the call is successful (no error is returned), a watch will be left on the node with
* the given \a path. The watch will be triggered by a successful operation that creates the node, erases the node,
* or sets the data on the node.
/** Similar to \c watch, but if the call is successful (no error is returned), a watch will be left on the entry
* with the given \a path. The watch will be triggered by a successful operation that creates the entry, erases the
* entry, or sets the data on the entry.
**/
future<watch_exists_result> watch_exists(string_view path) const;

/** Create a node with the given \a path.
/** Create an entry at the given \a path.
*
* This operation, if successful, will trigger all the watches left on the node of the given path by \c watch API
* calls, and the watches left on the parent node by \c watch_children API calls.
* This operation, if successful, will trigger all the watches left on the entry of the given path by \c watch API
* calls, and the watches left on the parent entry by \c watch_children API calls.
*
* \param path The path or path pattern (if using \c create_mode::sequential) to create.
* \param data The data to create inside the node.
* \param mode Specifies the behavior of the created node (see \c create_mode for more information).
* \param rules The ACL for the created znode. If unspecified, it is equivalent to providing \c acls::open_unsafe.
* \returns A future which will be filled with the name of the created znode and its \c stat.
*
* \throws node_exists If a node with the same actual path already exists in the ZooKeeper, the future will be
* delivered with \c node_exists. Note that since a different actual path is used for each invocation of creating
* sequential node with the same path argument, the call should never error in this manner.
* \throws no_node If the parent node does not exist in the ZooKeeper, the future will be delivered with
* \c no_node.
* \throws no_children_for_ephemerals An ephemeral node cannot have children. If the parent node of the given path
* is ephemeral, the future will be delivered with \c no_children_for_ephemerals.
* \param data The data to create for the entry.
* \param mode Specifies the behavior of the created entry (see \c create_mode for more information).
* \param rules The ACL for the created entry. If unspecified, it is equivalent to providing
* \ref acls::open_unsafe.
* \returns A future which will be filled with the name of the created entry and its \ref stat.
*
* \throws entry_exists If an entry with the same actual \a path already exists in the ZooKeeper, the future will
* be delivered with \c entry_exists. Note that since a different actual path is used for each invocation of
* creating sequential entry with the same \a path argument, the call should never error in this manner.
* \throws no_entry If the parent of the given \a path does not exist, the future will be delievered with
* \c no_entry.
* \throws no_children_for_ephemerals An ephemeral entry cannot have children. If the parent entry of the given
* path is ephemeral, the future will be delivered with \c no_children_for_ephemerals.
* \throws invalid_acl If the \a acl is invalid or empty, the future will be delivered with \c invalid_acl.
* \throws invalid_arguments The maximum allowable size of the data array is 1 MiB (1,048,576 bytes). If \a data
* is larger than this the future will be delivered with \c invalid_arguments.
Expand All @@ -127,46 +127,46 @@ class client final
create_mode mode = create_mode::normal
);

/** Set the data for the node of the given \a path if such a node exists and the given version matches the version
* of the node (if the given version is \c version::any, there is no version check). This operation, if successful,
* will trigger all the watches on the node of the given \c path left by \c watch calls.
/** Set the data for the entry of the given \a path if such an entry exists and the given version matches the
* version of the entry (if the given version is \c version::any, there is no version check). This operation, if
* successful, will trigger all the watches on the entry of the given \c path left by \c watch calls.
*
* \throws no_node If no node with the given \a path exists, the future will be delivered with \c no_node.
* \throws bad_version If the given version \a check does not match the node's version, the future will be
* delivered with \c bad_version.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
* \throws version_mismatch If the given version \a check does not match the entry's version, the future will be
* delivered with \c version_mismatch.
* \throws invalid_arguments The maximum allowable size of the data array is 1 MiB (1,048,576 bytes). If \a data
* is larger than this the future will be delivered with \c invalid_arguments.
**/
future<set_result> set(string_view path, const buffer& data, version check = version::any());

/** Return the ACL and \c stat of the node of the given path.
/** Return the ACL and \c stat of the entry of the given path.
*
* \throws no_node If no node with the given path exists, the future will be deliever with \c no_node.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
**/
future<get_acl_result> get_acl(string_view path) const;

/** Set the ACL for the node of the given \a path if such a node exists and the given version \a check matches the
* version of the node.
/** Set the ACL for the entry of the given \a path if such an entry exists and the given version \a check matches
* the version of the entry.
*
* \param check If specified, check that the ACL matches. Keep in mind this is the \c acl_version, not the data
* \c version -- there is no way to have this operation fail on changes to \c stat::data_version.
*
* \throws no_node If no node with the given \a path exists, the future will be delivered with \c no_node.
* \throws bad_version If the given version \a check does not match the node's version, the future will be
* delivered with \c bad_version.
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
* \throws version_mismatch If the given version \a check does not match the entry's version, the future will be
* delivered with \c version_mismatch.
**/
future<void> set_acl(string_view path, const acl& rules, acl_version check = acl_version::any());

/** Erase the node with the given \a path. The call will succeed if such a node exists, and the given version
* \a check matches the node's version (if the given version is \c version::any, it matches any node's versions).
* This operation, if successful, will trigger all the watches on the node of the given path left by \c watch API
* calls, watches left by \c watch_exists API calls, and the watches on the parent node left by \c watch_children
* API calls.
/** Erase the entry at the given \a path. The call will succeed if such an entry exists, and the given version
* \a check matches the entry's version (if the given version is \c version::any, it matches any entry's versions).
* This operation, if successful, will trigger all the watches on the entry of the given \a path left by \c watch
* API calls, watches left by \c watch_exists API calls, and the watches on the parent entry left by
* \c watch_children API calls.
*
* \throws no_node If no node with the given \a path exists, the future will be delivered with \c no_node.
* \throws bad_version If the given version \a check does not match the node's version, the future will be
* delivered with \c bad_version.
* \throws not_empty You are only allowed to erase nodes with no children. If the node has children, the future
* \throws no_entry If no entry exists at the given \a path, the future will be delievered with \c no_entry.
* \throws version_mismatch If the given version \a check does not match the entry's version, the future will be
* delivered with \c version_mismatch.
* \throws not_empty You are only allowed to erase entries with no children. If the entry has children, the future
* will be delievered with \c not_empty.
**/
future<void> erase(string_view path, version check = version::any());
Expand Down
2 changes: 1 addition & 1 deletion src/zk/client_tests.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -69,7 +69,7 @@ GTEST_TEST_F(client_tests, create_seq_and_erase)
auto name = f_create.get().name();
auto orig_stat = c.get(name).get().stat();
c.erase(name, orig_stat.data_version).get();
CHECK_THROWS(no_node)
CHECK_THROWS(no_entry)
{
c.get(name).get();
};
Expand Down
52 changes: 44 additions & 8 deletions src/zk/connection_zk.cpp
Original file line number Diff line number Diff line change
Expand Up @@ -67,6 +67,21 @@ auto with_acl(const acl& rules, FAction&& action)

static error_code error_code_from_raw(int raw)
{
switch (raw)
{
case ZOPERATIONTIMEOUT:
raw = ZCONNECTIONLOSS;
break;
case ZINVALIDCALLBACK:
case ZINVALIDACL:
raw = ZBADARGUMENTS;
break;
case ZSESSIONMOVED:
raw = ZCONNECTIONLOSS;
break;
default:
break;
}
return static_cast<error_code>(raw);
}

Expand All @@ -75,8 +90,33 @@ static event_type event_from_raw(int raw)
return static_cast<event_type>(raw);
}

// ZooKeeper does not have this concept pre-3.5
#if ZOO_MAJOR_VERSION <= 3 && ZOO_MINOR_VERSION <= 4
static const int ZOO_NOTCONNECTED_STATE = 999;
#endif

static state state_from_raw(int raw)
{
// The C client will put us into `ZOO_NOTCONNECTED_STATE` for two reasons:
//
// 1. This is the state of the initial connection (zookeeper_init_internal), which is then replaced when the adaptor
// threads first call the interest function.
// 2. During a reconfiguration, the client disconnects and transitions to this state (update_addrs), which is then
// updated the next time the I/O thread touches interest.
//
// In both cases, the state is still "connecting" from the point of view of a client.
if (raw == ZOO_NOTCONNECTED_STATE)
{
raw = ZOO_CONNECTING_STATE;
}
// `ZOO_ASSOCIATING_STATE` means we have connected to a server, but have not yet authenticated and created the
// session. We still can't perform any operations, so treat it as connecting -- the client does not care about the
// difference between establishing a TCP connection and negotiating credentials.
else if (raw == ZOO_ASSOCIATING_STATE)
{
raw = ZOO_CONNECTING_STATE;
}

return static_cast<state>(raw);
}

Expand Down Expand Up @@ -208,7 +248,7 @@ class connection_zk::basic_watcher :
{
if (!_data_delivered.load(std::memory_order_relaxed))
{
deliver_data(nullopt, get_exception_ptr_of(error_code::closing));
deliver_data(nullopt, get_exception_ptr_of(error_code::closed));
}

watcher::deliver_event(std::move(ev));
Expand Down Expand Up @@ -485,7 +525,7 @@ future<exists_result> connection_zk::exists(string_view path)
auto rc = error_code_from_raw(rc_in);
if (rc == error_code::ok)
prom->set_value(exists_result(stat_from_raw(*stat_in)));
else if (rc == error_code::no_node)
else if (rc == error_code::no_entry)
prom->set_value(exists_result(nullopt));
else
prom->set_exception(get_exception_ptr_of(rc));
Expand Down Expand Up @@ -524,7 +564,7 @@ class connection_zk::exists_watcher :
std::exception_ptr()
);
}
else if (rc == error_code::no_node)
else if (rc == error_code::no_entry)
{
self.deliver_data(watch_exists_result(exists_result(nullopt), self.get_event_future()),
std::exception_ptr()
Expand Down Expand Up @@ -805,18 +845,14 @@ struct connection_zk_commit_completer

prom.set_value(std::move(out));
}
else if (is_api_error(rc))
else
{
// All results until the failure are 0, equal to rc where we care, and runtime_inconsistency after that.
auto iter = std::partition_point(raw_results.begin(), raw_results.end(),
[] (auto res) { return res.err == 0; }
);
throw transaction_failed(rc, std::size_t(std::distance(raw_results.begin(), iter)));
}
else
{
throw_error(rc);
}
}
catch (...)
{
Expand Down
Loading

0 comments on commit e8b81f3

Please sign in to comment.