char *client_address,
uint32_t expire_seconds);
+/**
+ * Enable an application on a pool
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param force 0 if only single application per pool
+ * @returns 0 on success, negative error code on failure
+ */
+CEPH_RADOS_API int rados_application_enable(rados_ioctx_t io,
+ const char *app_name, int force);
+
+/**
+ * List all enabled applications
+ *
+ * If the provided buffer is too short, the required length is filled in and
+ * -ERANGE is returned. Otherwise, the buffers are filled with the application
+ * names, with a '\0' after each.
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param values buffer in which to store application names
+ * @param vals_len number of bytes in values buffer
+ * @returns 0 on success, negative error code on failure
+ * @returns -ERANGE if either buffer is too short
+ */
+CEPH_RADOS_API int rados_application_list(rados_ioctx_t io, char *values,
+ size_t *values_len);
+
+/**
+ * Get application metadata value from pool
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param key metadata key
+ * @param value result buffer
+ * @param value_len maximum len of value
+ * @returns 0 on success, negative error code on failure
+ */
+CEPH_RADOS_API int rados_application_metadata_get(rados_ioctx_t io,
+ const char *app_name,
+ const char *key, char *value,
+ size_t *value_len);
+
+/**
+ * Set application metadata on a pool
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param key metadata key
+ * @param value metadata key
+ * @returns 0 on success, negative error code on failure
+ */
+CEPH_RADOS_API int rados_application_metadata_set(rados_ioctx_t io,
+ const char *app_name,
+ const char *key,
+ const char *value);
+
+/**
+ * Remove application metadata from a pool
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param key metadata key
+ * @returns 0 on success, negative error code on failure
+ */
+CEPH_RADOS_API int rados_application_metadata_remove(rados_ioctx_t io,
+ const char *app_name,
+ const char *key);
+
+/**
+ * List all metadata key/value pairs associated with an application.
+ *
+ * This iterates over all metadata, key_len and val_len are filled in
+ * with the number of bytes put into the keys and values buffers.
+ *
+ * If the provided buffers are too short, the required lengths are filled
+ * in and -ERANGE is returned. Otherwise, the buffers are filled with
+ * the keys and values of the metadata, with a '\0' after each.
+ *
+ * @param ioctx pool ioctx
+ * @param app_name application name
+ * @param keys buffer in which to store key names
+ * @param keys_len number of bytes in keys buffer
+ * @param values buffer in which to store values
+ * @param vals_len number of bytes in values buffer
+ * @returns 0 on succcess, negative error code on failure
+ * @returns -ERANGE if either buffer is too short
+ */
+CEPH_RADOS_API int rados_application_metadata_list(rados_ioctx_t io,
+ const char *app_name,
+ char *keys, size_t *key_len,
+ char *values,
+ size_t *vals_len);
+
/**
* @name Mon/OSD/PG Commands
*
void set_osdmap_full_try();
void unset_osdmap_full_try();
+ int application_enable(const std::string& app_name, bool force);
+ int application_list(std::set<std::string> *app_names);
+ int application_metadata_get(const std::string& app_name,
+ const std::string &key,
+ std::string *value);
+ int application_metadata_set(const std::string& app_name,
+ const std::string &key,
+ const std::string& value);
+ int application_metadata_remove(const std::string& app_name,
+ const std::string &key);
+ int application_metadata_list(const std::string& app_name,
+ std::map<std::string, std::string> *values);
+
private:
/* You can only get IoCtx instances from Rados */
IoCtx(IoCtxImpl *io_ctx_impl_);
hobject_t::_reverse_bits(rev_finish), poolid, string());
}
+int librados::IoCtxImpl::application_enable(const std::string& app_name,
+ bool force)
+{
+ // pre-Luminous clusters will return -EINVAL and application won't be
+ // preserved until Luminous is configured as minimim version.
+ if (!client->get_required_monitor_features().contains_all(
+ ceph::features::mon::FEATURE_LUMINOUS)) {
+ return -EOPNOTSUPP;
+ }
+
+ std::stringstream cmd;
+ cmd << "{"
+ << "\"prefix\": \"osd pool application enable\","
+ << "\"pool\": \"" << get_cached_pool_name() << "\","
+ << "\"app\": \"" << app_name << "\"";
+ if (force) {
+ cmd << ",\"force\":\"--yes-i-really-mean-it\"";
+ }
+ cmd << "}";
+
+ std::vector<std::string> cmds;
+ cmds.push_back(cmd.str());
+ bufferlist inbl;
+ int r = client->mon_command(cmds, inbl, nullptr, nullptr);
+ if (r < 0) {
+ return r;
+ }
+
+ // ensure we have the latest osd map epoch before proceeding
+ return client->wait_for_latest_osdmap();
+}
+
+int librados::IoCtxImpl::application_list(std::set<std::string> *app_names)
+{
+ int r = 0;
+ app_names->clear();
+ objecter->with_osdmap([&](const OSDMap& o) {
+ auto pg_pool = o.get_pg_pool(poolid);
+ if (pg_pool == nullptr) {
+ r = -ENOENT;
+ return;
+ }
+
+ for (auto &pair : pg_pool->application_metadata) {
+ app_names->insert(pair.first);
+ }
+ });
+ return r;
+}
+
+int librados::IoCtxImpl::application_metadata_get(const std::string& app_name,
+ const std::string &key,
+ std::string* value)
+{
+ int r = 0;
+ objecter->with_osdmap([&](const OSDMap& o) {
+ auto pg_pool = o.get_pg_pool(poolid);
+ if (pg_pool == nullptr) {
+ r = -ENOENT;
+ return;
+ }
+
+ auto app_it = pg_pool->application_metadata.find(app_name);
+ if (app_it == pg_pool->application_metadata.end()) {
+ r = -ENOENT;
+ return;
+ }
+
+ auto it = app_it->second.find(key);
+ if (it == app_it->second.end()) {
+ r = -ENOENT;
+ return;
+ }
+
+ *value = it->second;
+ });
+ return r;
+}
+
+int librados::IoCtxImpl::application_metadata_set(const std::string& app_name,
+ const std::string &key,
+ const std::string& value)
+{
+ std::stringstream cmd;
+ cmd << "{"
+ << "\"prefix\":\"osd pool application set\","
+ << "\"pool\":\"" << get_cached_pool_name() << "\","
+ << "\"app\":\"" << app_name << "\","
+ << "\"key\":\"" << key << "\","
+ << "\"value\":\"" << value << "\""
+ << "}";
+
+ std::vector<std::string> cmds;
+ cmds.push_back(cmd.str());
+ bufferlist inbl;
+ int r = client->mon_command(cmds, inbl, nullptr, nullptr);
+ if (r < 0) {
+ return r;
+ }
+
+ // ensure we have the latest osd map epoch before proceeding
+ return client->wait_for_latest_osdmap();
+}
+
+int librados::IoCtxImpl::application_metadata_remove(const std::string& app_name,
+ const std::string &key)
+{
+ std::stringstream cmd;
+ cmd << "{"
+ << "\"prefix\":\"osd pool application rm\","
+ << "\"pool\":\"" << get_cached_pool_name() << "\","
+ << "\"app\":\"" << app_name << "\","
+ << "\"key\":\"" << key << "\""
+ << "}";
+
+ std::vector<std::string> cmds;
+ cmds.push_back(cmd.str());
+ bufferlist inbl;
+ int r = client->mon_command(cmds, inbl, nullptr, nullptr);
+ if (r < 0) {
+ return r;
+ }
+
+ // ensure we have the latest osd map epoch before proceeding
+ return client->wait_for_latest_osdmap();
+}
+
+int librados::IoCtxImpl::application_metadata_list(const std::string& app_name,
+ std::map<std::string, std::string> *values)
+{
+ int r = 0;
+ values->clear();
+ objecter->with_osdmap([&](const OSDMap& o) {
+ auto pg_pool = o.get_pg_pool(poolid);
+ if (pg_pool == nullptr) {
+ r = -ENOENT;
+ return;
+ }
+
+ auto it = pg_pool->application_metadata.find(app_name);
+ if (it == pg_pool->application_metadata.end()) {
+ r = -ENOENT;
+ return;
+ }
+
+ *values = it->second;
+ });
+ return r;
+}
+
int cache_pin(const object_t& oid);
int cache_unpin(const object_t& oid);
+ int application_enable(const std::string& app_name, bool force);
+ int application_list(std::set<std::string> *app_names);
+ int application_metadata_get(const std::string& app_name,
+ const std::string &key,
+ std::string* value);
+ int application_metadata_set(const std::string& app_name,
+ const std::string &key,
+ const std::string& value);
+ int application_metadata_remove(const std::string& app_name,
+ const std::string &key);
+ int application_metadata_list(const std::string& app_name,
+ std::map<std::string, std::string> *values);
+
};
#endif
}
return mgrclient.service_daemon_update_status(status);
}
+
+mon_feature_t librados::RadosClient::get_required_monitor_features() const
+{
+ return monclient.monmap.get_required_features();
+}
const std::map<std::string,std::string>& metadata); ///< static metadata about daemon
int service_daemon_update_status(
const std::map<std::string,std::string>& status);
+
+ mon_feature_t get_required_monitor_features() const;
};
#endif
return radosp->blacklist_add(client_address, expire_seconds);
}
+extern "C" int rados_application_enable(rados_ioctx_t io, const char *app_name,
+ int force)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ return ctx->application_enable(app_name, force != 0);
+}
+
+extern "C" int rados_application_list(rados_ioctx_t io, char *values,
+ size_t *values_len)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ std::set<std::string> app_names;
+ int r = ctx->application_list(&app_names);
+ if (r < 0) {
+ return r;
+ }
+
+ size_t total_len = 0;
+ for (auto app_name : app_names) {
+ total_len += app_name.size() + 1;
+ }
+
+ if (*values_len < total_len) {
+ *values_len = total_len;
+ return -ERANGE;
+ }
+
+ char *values_p = values;
+ for (auto app_name : app_names) {
+ size_t len = app_name.size() + 1;
+ strncpy(values_p, app_name.c_str(), len);
+ values_p += len;
+ }
+ *values_p = '\0';
+ *values_len = total_len;
+ return 0;
+}
+
+extern "C" int rados_application_metadata_get(rados_ioctx_t io,
+ const char *app_name,
+ const char *key, char *value,
+ size_t *value_len)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ std::string value_str;
+ int r = ctx->application_metadata_get(app_name, key, &value_str);
+ if (r < 0) {
+ return r;
+ }
+
+ size_t len = value_str.size() + 1;
+ if (*value_len < len) {
+ *value_len = len;
+ return -ERANGE;
+ }
+
+ strncpy(value, value_str.c_str(), len);
+ *value_len = len;
+ return 0;
+}
+
+extern "C" int rados_application_metadata_set(rados_ioctx_t io,
+ const char *app_name,
+ const char *key,
+ const char *value)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ return ctx->application_metadata_set(app_name, key, value);
+}
+
+extern "C" int rados_application_metadata_remove(rados_ioctx_t io,
+ const char *app_name,
+ const char *key)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ return ctx->application_metadata_remove(app_name, key);
+}
+
+extern "C" int rados_application_metadata_list(rados_ioctx_t io,
+ const char *app_name,
+ char *keys, size_t *keys_len,
+ char *values, size_t *vals_len)
+{
+ librados::IoCtxImpl *ctx = (librados::IoCtxImpl *)io;
+ std::map<std::string, std::string> metadata;
+ int r = ctx->application_metadata_list(app_name, &metadata);
+ if (r < 0) {
+ return r;
+ }
+
+ size_t total_key_len = 0;
+ size_t total_val_len = 0;
+ for (auto pair : metadata) {
+ total_key_len += pair.first.size() + 1;
+ total_val_len += pair.second.size() + 1;
+ }
+
+ if (*keys_len < total_key_len || *vals_len < total_val_len) {
+ *keys_len = total_key_len;
+ *vals_len = total_val_len;
+ return -ERANGE;
+ }
+
+ char *keys_p = keys;
+ char *vals_p = values;
+ for (auto pair : metadata) {
+ size_t key_len = pair.first.size() + 1;
+ strncpy(keys_p, pair.first.c_str(), key_len);
+ keys_p += key_len;
+
+ size_t val_len = pair.second.size() + 1;
+ strncpy(vals_p, pair.second.c_str(), val_len);
+ vals_p += val_len;
+ }
+ *keys_p = '\0';
+ *keys_len = total_key_len;
+
+ *vals_p = '\0';
+ *vals_len = total_val_len;
+ return 0;
+}
+
extern "C" int rados_pool_list(rados_t cluster, char *buf, size_t len)
{
tracepoint(librados, rados_pool_list_enter, cluster, len);
(hobject_t*)(split_finish->c_cursor));
}
+int librados::IoCtx::application_enable(const std::string& app_name,
+ bool force)
+{
+ return io_ctx_impl->application_enable(app_name, force);
+}
+
+int librados::IoCtx::application_list(std::set<std::string> *app_names)
+{
+ return io_ctx_impl->application_list(app_names);
+}
+
+int librados::IoCtx::application_metadata_get(const std::string& app_name,
+ const std::string &key,
+ std::string* value)
+{
+ return io_ctx_impl->application_metadata_get(app_name, key, value);
+}
+
+int librados::IoCtx::application_metadata_set(const std::string& app_name,
+ const std::string &key,
+ const std::string& value)
+{
+ return io_ctx_impl->application_metadata_set(app_name, key, value);
+}
+
+int librados::IoCtx::application_metadata_remove(const std::string& app_name,
+ const std::string &key)
+{
+ return io_ctx_impl->application_metadata_remove(app_name, key);
+}
+
+int librados::IoCtx::application_metadata_list(const std::string& app_name,
+ std::map<std::string, std::string> *values)
+{
+ return io_ctx_impl->application_metadata_list(app_name, values);
+}
+
+
int rados_cluster_stat(rados_t cluster, rados_cluster_stat_t *result)
int rados_cluster_fsid(rados_t cluster, char *buf, size_t len)
int rados_blacklist_add(rados_t cluster, char *client_address, uint32_t expire_seconds)
-
+ int rados_application_enable(rados_ioctx_t io, const char *app_name,
+ int force)
+ int rados_application_list(rados_ioctx_t io, char *values,
+ size_t *values_len)
+ int rados_application_metadata_get(rados_ioctx_t io, const char *app_name,
+ const char *key, char *value,
+ size_t *value_len)
+ int rados_application_metadata_set(rados_ioctx_t io, const char *app_name,
+ const char *key, const char *value)
+ int rados_application_metadata_remove(rados_ioctx_t io,
+ const char *app_name, const char *key)
+ int rados_application_metadata_list(rados_ioctx_t io,
+ const char *app_name, char *keys,
+ size_t *key_len, char *values,
+ size_t *value_len)
int rados_ping_monitor(rados_t cluster, const char *mon_id, char **outstr, size_t *outstrlen)
int rados_mon_command(rados_t cluster, const char **cmd, size_t cmdlen,
const char *inbuf, size_t inbuflen,
if ret < 0:
raise make_ex(ret, "Ioctx.rados_lock_exclusive(%s): failed to set lock %s on %s" % (self.name, name, key))
+ def application_enable(self, app_name, force=False):
+ """
+ Enable an application on an OSD pool
+
+ :param app_name: application name
+ :type app_name: str
+ :param force: False if only a single app should exist per pool
+ :type expire_seconds: boool
+
+ :raises: :class:`Error`
+ """
+ app_name = cstr(app_name, 'app_name')
+ cdef:
+ char *_app_name = app_name
+ int _force = (1 if force else 0)
+
+ with nogil:
+ ret = rados_application_enable(self.io, _app_name, _force)
+ if ret < 0:
+ raise make_ex(ret, "error enabling application")
+
+ def application_list(self):
+ """
+ Returns a list of enabled applications
+
+ :returns: list of app name string
+ """
+ cdef:
+ size_t length = 128
+ char *apps = NULL
+
+ try:
+ while True:
+ apps = <char *>realloc_chk(apps, length)
+ with nogil:
+ ret = rados_application_list(self.io, apps, &length)
+ if ret == 0:
+ return [decode_cstr(app) for app in
+ apps[:length].split(b'\0') if app]
+ elif ret == -errno.ENOENT:
+ return None
+ elif ret == -errno.ERANGE:
+ pass
+ else:
+ raise make_ex(ret, "error listing applications")
+ finally:
+ free(apps)
+
+ def application_metadata_set(self, app_name, key, value):
+ """
+ Sets application metadata on an OSD pool
+
+ :param app_name: application name
+ :type app_name: str
+ :param key: metadata key
+ :type key: str
+ :param value: metadata value
+ :type value: str
+
+ :raises: :class:`Error`
+ """
+ app_name = cstr(app_name, 'app_name')
+ key = cstr(key, 'key')
+ value = cstr(value, 'value')
+ cdef:
+ char *_app_name = app_name
+ char *_key = key
+ char *_value = value
+
+ with nogil:
+ ret = rados_application_metadata_set(self.io, _app_name, _key,
+ _value)
+ if ret < 0:
+ raise make_ex(ret, "error setting application metadata")
+
+ def application_metadata_remove(self, app_name, key):
+ """
+ Remove application metadata from an OSD pool
+
+ :param app_name: application name
+ :type app_name: str
+ :param key: metadata key
+ :type key: str
+
+ :raises: :class:`Error`
+ """
+ app_name = cstr(app_name, 'app_name')
+ key = cstr(key, 'key')
+ cdef:
+ char *_app_name = app_name
+ char *_key = key
+
+ with nogil:
+ ret = rados_application_metadata_remove(self.io, _app_name, _key)
+ if ret < 0:
+ raise make_ex(ret, "error removing application metadata")
+
+ def application_metadata_list(self, app_name):
+ """
+ Returns a list of enabled applications
+
+ :param app_name: application name
+ :type app_name: str
+ :returns: list of key/value tuples
+ """
+ app_name = cstr(app_name, 'app_name')
+ cdef:
+ char *_app_name = app_name
+ size_t key_length = 128
+ size_t val_length = 128
+ char *c_keys = NULL
+ char *c_vals = NULL
+
+ try:
+ while True:
+ c_keys = <char *>realloc_chk(c_keys, key_length)
+ c_vals = <char *>realloc_chk(c_vals, val_length)
+ with nogil:
+ ret = rados_application_metadata_list(self.io, _app_name,
+ c_keys, &key_length,
+ c_vals, &val_length)
+ if ret == 0:
+ keys = [decode_cstr(key) for key in
+ c_keys[:key_length].split(b'\0') if key]
+ vals = [decode_cstr(val) for val in
+ c_vals[:val_length].split(b'\0') if val]
+ return zip(keys, vals)
+ elif ret == -errno.ERANGE:
+ pass
+ else:
+ raise make_ex(ret, "error listing application metadata")
+ finally:
+ free(c_keys)
+ free(c_vals)
+
def set_object_locator(func):
def retfunc(self, *args, **kwargs):
ASSERT_EQ(-MAX_ERRNO - 5,
rados_cmpext(ioctx, "cmpextpp", mismatch_str, sizeof(mismatch_str), 0));
}
+
+TEST_F(LibRadosMisc, Applications) {
+ char apps[128];
+ size_t app_len;
+
+ app_len = sizeof(apps);
+ ASSERT_EQ(0, rados_application_list(ioctx, apps, &app_len));
+ ASSERT_EQ(0U, app_len);
+
+ ASSERT_EQ(0, rados_application_enable(ioctx, "app1", 0));
+ ASSERT_EQ(-EPERM, rados_application_enable(ioctx, "app2", 0));
+ ASSERT_EQ(0, rados_application_enable(ioctx, "app2", 1));
+
+ ASSERT_EQ(-ERANGE, rados_application_list(ioctx, apps, &app_len));
+ ASSERT_EQ(10U, app_len);
+ ASSERT_EQ(0, rados_application_list(ioctx, apps, &app_len));
+ ASSERT_EQ(10U, app_len);
+ ASSERT_EQ(0, memcmp("app1\0app2\0", apps, app_len));
+
+ char keys[128];
+ char vals[128];
+ size_t key_len;
+ size_t val_len;
+
+ key_len = sizeof(keys);
+ val_len = sizeof(vals);
+ ASSERT_EQ(-ENOENT, rados_application_metadata_list(ioctx, "dne", keys,
+ &key_len, vals, &val_len));
+ ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len,
+ vals, &val_len));
+ ASSERT_EQ(0U, key_len);
+ ASSERT_EQ(0U, val_len);
+
+ ASSERT_EQ(-ENOENT, rados_application_metadata_set(ioctx, "dne", "key",
+ "value"));
+ ASSERT_EQ(0, rados_application_metadata_set(ioctx, "app1", "key1", "value1"));
+ ASSERT_EQ(0, rados_application_metadata_set(ioctx, "app1", "key2", "value2"));
+
+ ASSERT_EQ(-ERANGE, rados_application_metadata_list(ioctx, "app1", keys,
+ &key_len, vals, &val_len));
+ ASSERT_EQ(10U, key_len);
+ ASSERT_EQ(14U, val_len);
+ ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len,
+ vals, &val_len));
+ ASSERT_EQ(10U, key_len);
+ ASSERT_EQ(14U, val_len);
+ ASSERT_EQ(0, memcmp("key1\0key2\0", keys, key_len));
+ ASSERT_EQ(0, memcmp("value1\0value2\0", vals, val_len));
+
+ ASSERT_EQ(0, rados_application_metadata_remove(ioctx, "app1", "key1"));
+ ASSERT_EQ(0, rados_application_metadata_list(ioctx, "app1", keys, &key_len,
+ vals, &val_len));
+ ASSERT_EQ(5U, key_len);
+ ASSERT_EQ(7U, val_len);
+ ASSERT_EQ(0, memcmp("key2\0", keys, key_len));
+ ASSERT_EQ(0, memcmp("value2\0", vals, val_len));
+}
+
+TEST_F(LibRadosMiscPP, Applications) {
+ std::set<std::string> expected_apps;
+ std::set<std::string> apps;
+ ASSERT_EQ(0, ioctx.application_list(&apps));
+ ASSERT_EQ(expected_apps, apps);
+
+ ASSERT_EQ(0, ioctx.application_enable("app1", false));
+ ASSERT_EQ(-EPERM, ioctx.application_enable("app2", false));
+ ASSERT_EQ(0, ioctx.application_enable("app2", true));
+
+ expected_apps = {"app1", "app2"};
+ ASSERT_EQ(0, ioctx.application_list(&apps));
+ ASSERT_EQ(expected_apps, apps);
+
+ std::map<std::string, std::string> expected_meta;
+ std::map<std::string, std::string> meta;
+ ASSERT_EQ(-ENOENT, ioctx.application_metadata_list("dne", &meta));
+ ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta));
+ ASSERT_EQ(expected_meta, meta);
+
+ ASSERT_EQ(-ENOENT, ioctx.application_metadata_set("dne", "key1", "value1"));
+ ASSERT_EQ(0, ioctx.application_metadata_set("app1", "key1", "value1"));
+ ASSERT_EQ(0, ioctx.application_metadata_set("app1", "key2", "value2"));
+
+ expected_meta = {{"key1", "value1"}, {"key2", "value2"}};
+ ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta));
+ ASSERT_EQ(expected_meta, meta);
+
+ ASSERT_EQ(0, ioctx.application_metadata_remove("app1", "key1"));
+
+ expected_meta = {{"key2", "value2"}};
+ ASSERT_EQ(0, ioctx.application_metadata_list("app1", &meta));
+ ASSERT_EQ(expected_meta, meta);
+}
[i.remove() for i in self.ioctx.list_objects()]
+ def test_applications(self):
+ eq([], self.ioctx.application_list())
+
+ self.ioctx.application_enable("app1")
+ assert_raises(Error, self.ioctx.application_enable, "app2")
+ self.ioctx.application_enable("app2", True)
+
+ assert_raises(Error, self.ioctx.application_metadata_list, "dne")
+ eq([], self.ioctx.application_metadata_list("app1"))
+
+ assert_raises(Error, self.ioctx.application_metadata_set, "dne", "key",
+ "key")
+ self.ioctx.application_metadata_set("app1", "key1", "val1")
+ self.ioctx.application_metadata_set("app1", "key2", "val2")
+ self.ioctx.application_metadata_set("app2", "key1", "val1")
+
+ eq([("key1", "val1"), ("key2", "val2")],
+ self.ioctx.application_metadata_list("app1"))
+
+ self.ioctx.application_metadata_remove("app1", "key1")
+ eq([("key2", "val2")], self.ioctx.application_metadata_list("app1"))
+
class TestObject(object):
def setUp(self):