--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2014 Adam Crume <adamcrume@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "ImageNameMap.hpp"
+#include <boost/foreach.hpp>
+#include "include/assert.h"
+
+
+using namespace std;
+using namespace rbd_replay;
+
+
+static ImageNameMap::Name bad_mapping_abort(ImageNameMap::Name input, string output) {
+ assertf(false, "Bad value returned from mapping. Image: '%s', snap: '%s', output: '%s'", input.first.c_str(), input.second.c_str(), output.c_str());
+}
+
+ImageNameMap::ImageNameMap()
+ : m_name("((?:[^/\\\\]|\\\\.)*)/((?:[^/\\\\]|\\\\.)*)"),
+ m_escaped_slash("\\\\/"),
+ m_escaped_backslash("\\\\\\\\"),
+ m_slash("/"),
+ m_backslash("\\\\"),
+ m_bad_mapping_fallback(bad_mapping_abort) {
+}
+
+bool ImageNameMap::parse_mapping(string mapping_string, Mapping *mapping) const {
+ return m_map.parse_mapping(mapping_string, mapping);
+}
+
+void ImageNameMap::add_mapping(Mapping mapping) {
+ m_map.add_mapping(mapping);
+}
+
+string ImageNameMap::unescape_slash(string s) const {
+ s = boost::regex_replace(s, m_escaped_slash, "/");
+ return boost::regex_replace(s, m_escaped_backslash, "\\\\");
+}
+
+string ImageNameMap::escape_slash(string s) const {
+ string result = boost::regex_replace(s, m_backslash, "\\\\");
+ return boost::regex_replace(result, m_slash, "\\\\/");
+}
+
+bool ImageNameMap::parse_name(string name_string, Name *name) const {
+ boost::smatch what;
+ if (boost::regex_match(name_string, what, m_name)) {
+ string image(unescape_slash(what[1]));
+ string snap(unescape_slash(what[2]));
+ *name = Name(image, snap);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+string ImageNameMap::format_name(ImageNameMap::Name name) const {
+ return escape_slash(name.first) + "/" + escape_slash(name.second);
+}
+
+ImageNameMap::Name ImageNameMap::map(ImageNameMap::Name name) const {
+ string in_string(format_name(name));
+ string out_string(m_map.map(in_string));
+ Name out;
+ if (!parse_name(out_string, &out)) {
+ return m_bad_mapping_fallback(name, out_string);
+ }
+ return out;
+}
+
+void ImageNameMap::set_bad_mapping_fallback(BadMappingFallback bad_mapping_fallback) {
+ m_bad_mapping_fallback = bad_mapping_fallback;
+}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2014 Adam Crume <adamcrume@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef _INCLUDED_RBD_REPLAY_IMAGENAMEMAP_HPP
+#define _INCLUDED_RBD_REPLAY_IMAGENAMEMAP_HPP
+
+#include <map>
+#include <string>
+#include <vector>
+#include <boost/function.hpp>
+#include <boost/regex.hpp>
+#include "NameMap.hpp"
+
+namespace rbd_replay {
+
+class ImageNameMap {
+public:
+ typedef std::pair<boost::regex, std::string> Mapping;
+
+ typedef std::pair<std::string, std::string> Name;
+
+ typedef boost::function<Name (Name input, std::string output)> BadMappingFallback;
+
+ ImageNameMap();
+
+ bool parse_mapping(std::string mapping_string, Mapping *mapping) const;
+
+ void add_mapping(Mapping mapping);
+
+ bool parse_name(std::string name_string, Name *name) const;
+
+ std::string format_name(Name name) const;
+
+ Name map(Name name) const;
+
+ void set_bad_mapping_fallback(BadMappingFallback bad_mapping_fallback);
+
+private:
+ std::string escape_slash(std::string s) const;
+
+ std::string unescape_slash(std::string s) const;
+
+ NameMap m_map;
+
+ // Split "a/b" into "a" and "b", allowing for escaped slashes
+ boost::regex m_name;
+
+ // We don't have to worry about an even number of backslahes followed by a slash,
+ // because that slash would have already been matched and removed.
+ boost::regex m_escaped_slash;
+
+ boost::regex m_escaped_backslash;
+
+ boost::regex m_slash;
+
+ boost::regex m_backslash;
+
+ BadMappingFallback m_bad_mapping_fallback;
+};
+
+}
+
+#endif
rbd_replay_SOURCES = rbd_replay/rbd-replay.cc \
rbd_replay/actions.cc \
rbd_replay/Deser.cc \
+ rbd_replay/ImageNameMap.cc \
+ rbd_replay/NameMap.cc \
rbd_replay/PendingIO.cc \
rbd_replay/Replayer.cc
-rbd_replay_LDADD = $(LIBRBD) $(LIBRADOS) $(CEPH_GLOBAL)
+rbd_replay_LDADD = $(LIBRBD) \
+ $(LIBRADOS) \
+ $(CEPH_GLOBAL) \
+ -lboost_regex
noinst_HEADERS += rbd_replay/BoundedBuffer.hpp \
rbd_replay/actions.hpp \
rbd_replay/Deser.hpp \
+ rbd_replay/ImageNameMap.hpp \
+ rbd_replay/NameMap.hpp \
rbd_replay/PendingIO.hpp \
rbd_replay/rbd_replay_debug.hpp \
rbd_replay/Replayer.hpp
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2014 Adam Crume <adamcrume@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#include "NameMap.hpp"
+#include <boost/foreach.hpp>
+#include "include/assert.h"
+
+
+using namespace std;
+using namespace rbd_replay;
+
+
+bool NameMap::parse_mapping(string mapping_string, Mapping *mapping) const {
+ boost::smatch what;
+ if (boost::regex_match(mapping_string, what, m_keyval)) {
+ string pattern(what[1]);
+ string replacement(what[2]);
+
+ pattern = boost::regex_replace(pattern, m_escaped_equals, "=");
+ replacement = boost::regex_replace(replacement, m_escaped_equals, "=");
+
+ *mapping = Mapping(boost::regex(pattern), replacement);
+ return true;
+ } else {
+ return false;
+ }
+}
+
+void NameMap::add_mapping(Mapping mapping) {
+ m_mappings.push_back(mapping);
+}
+
+string NameMap::map(string name) const {
+ BOOST_FOREACH(const Mapping &m, m_mappings) {
+ boost::smatch what;
+ if (boost::regex_match(name, what, m.first)) {
+ return what.format(m.second);
+ }
+ }
+ return name;
+}
--- /dev/null
+// -*- mode:C++; tab-width:8; c-basic-offset:2; indent-tabs-mode:t -*-
+// vim: ts=8 sw=2 smarttab
+/*
+ * Ceph - scalable distributed file system
+ *
+ * Copyright (C) 2014 Adam Crume <adamcrume@gmail.com>
+ *
+ * This is free software; you can redistribute it and/or
+ * modify it under the terms of the GNU Lesser General Public
+ * License version 2.1, as published by the Free Software
+ * Foundation. See file COPYING.
+ *
+ */
+
+#ifndef _INCLUDED_RBD_REPLAY_NAMEMAP_HPP
+#define _INCLUDED_RBD_REPLAY_NAMEMAP_HPP
+
+#include <map>
+#include <string>
+#include <vector>
+#include <boost/regex.hpp>
+
+namespace rbd_replay {
+
+class NameMap {
+public:
+ typedef std::pair<boost::regex, std::string> Mapping;
+
+ NameMap()
+ : m_keyval("((?:[^=\\\\]|\\\\.)*)=((?:[^=\\\\]|\\\\.)*)"),
+ m_escaped_equals("\\\\=") {
+ }
+
+ bool parse_mapping(std::string mapping_string, Mapping *mapping) const;
+
+ void add_mapping(Mapping mapping);
+
+ std::string map(std::string name) const;
+
+private:
+ std::vector<Mapping> m_mappings;
+
+ // Split "a=b" into "a" and "b", allowing for escaped equal signs
+ boost::regex m_keyval;
+
+ // We don't have to worry about an even number of backslahes followed by an equal sign,
+ // because that equal sign would have already been matched and removed.
+ boost::regex m_escaped_equals;
+};
+
+}
+
+#endif
return m_replayer.readonly();
}
+pair<string, string> Worker::map_image_name(string image_name, string snap_name) const {
+ return m_replayer.image_name_map().map(pair<string,string>(image_name, snap_name));
+}
+
Replayer::Replayer(int num_action_trackers)
: m_pool_name("rbd"),
#include <boost/thread/mutex.hpp>
#include <boost/thread/shared_mutex.hpp>
#include "BoundedBuffer.hpp"
+#include "ImageNameMap.hpp"
#include "PendingIO.hpp"
namespace rbd_replay {
bool readonly() const;
+ std::pair<std::string, std::string> map_image_name(std::string image_name, std::string snap_name) const;
+
private:
void run();
void set_readonly(bool readonly);
+ void set_image_name_map(const ImageNameMap &map) {
+ m_image_name_map = map;
+ }
+
+ const ImageNameMap &image_name_map() const {
+ return m_image_name_map;
+ }
+
private:
struct action_tracker_d {
// Maps an action ID to the time the action completed
std::string m_pool_name;
float m_latency_multiplier;
bool m_readonly;
+ ImageNameMap m_image_name_map;
std::map<imagectx_id_t, librbd::Image*> m_images;
boost::shared_mutex m_images_mutex;
worker.add_pending(io);
librbd::Image *image = new librbd::Image();
librbd::RBD *rbd = worker.rbd();
+ pair<string, string> name(worker.map_image_name(m_name, m_snap_name));
int r;
if (m_readonly || worker.readonly()) {
- r = rbd->open_read_only(*worker.ioctx(), *image, m_name.c_str(), m_snap_name.c_str());
+ r = rbd->open_read_only(*worker.ioctx(), *image, name.first.c_str(), name.second.c_str());
} else {
- r = rbd->open(*worker.ioctx(), *image, m_name.c_str(), m_snap_name.c_str());
+ r = rbd->open(*worker.ioctx(), *image, name.first.c_str(), name.second.c_str());
}
if (r) {
- cerr << "Unable to open image '" << m_name << "' with snap '" << m_snap_name << "' and readonly " << m_readonly << ": " << strerror(-r) << std::endl;
+ cerr << "Unable to open image '" << m_name
+ << "' with snap '" << m_snap_name
+ << "' (mapped to '" << name.first
+ << "' and '" << name.second
+ << "') and readonly " << m_readonly
+ << ": (" << -r << ") " << strerror(-r) << std::endl;
exit(1);
}
worker.put_image(m_imagectx_id, image);
virtual void set_action_complete(action_id_t id) = 0;
virtual void stop() = 0;
+
+ virtual std::pair<std::string, std::string> map_image_name(std::string image_name, std::string snap_name) const = 0;
};
#include "global/global_init.h"
#include "Replayer.hpp"
#include "rbd_replay_debug.hpp"
+#include "ImageNameMap.hpp"
using namespace std;
cout << " -p, --pool-name <pool> Name of the pool to use. Default: rbd" << std::endl;
cout << " --latency-multiplier <float> Multiplies inter-request latencies. Default: 1" << std::endl;
cout << " --read-only Only perform non-destructive operations." << std::endl;
+ cout << " --map-image <rule> Add a rule to map image names in the trace to" << std::endl;
+ cout << " image names in the replay cluster." << std::endl;
+ cout << std::endl;
+ cout << "Image mapping rules:" << std::endl;
+ cout << "A rule of image1/snap1=image2/snap2 would map snap1 of image1 to snap2 of" << std::endl;
+ cout << "image2. Regular expressions are used, so image/snap_(.*)=image_$1/ would map" << std::endl;
+ cout << "image/snap_1 to image_1/. (Note that an un-snapshotted image has the empty" << std::endl;
+ cout << "string for the snapshot name.)" << std::endl;
+}
+
+static ImageNameMap::Name bad_mapping(ImageNameMap::Name input, string output) {
+ dout(0) << "Bad value returned from mapping. Image: '" << input.first << "', snap '" << input.second << "', output: '" << output << "'. Using image: '" << output << ", snap ''." << dendl;
+ return ImageNameMap::Name(output, "");
}
int main(int argc, const char **argv) {
string pool_name = "rbd";
float latency_multiplier = 1;
bool readonly = false;
+ ImageNameMap image_name_map;
+ image_name_map.set_bad_mapping_fallback(bad_mapping);
std::string val;
std::ostringstream err;
for (i = args.begin(); i != args.end(); ) {
}
} else if (ceph_argparse_flag(args, i, "--read-only", (char*)NULL)) {
readonly = true;
+ } else if (ceph_argparse_witharg(args, i, &val, "--map-image", (char*)NULL)) {
+ ImageNameMap::Mapping mapping;
+ if (image_name_map.parse_mapping(val, &mapping)) {
+ image_name_map.add_mapping(mapping);
+ } else {
+ cerr << "Unable to parse mapping string: '" << val << "'" << std::endl;
+ return 1;
+ }
} else if (ceph_argparse_flag(args, i, "-h", "--help", (char*)NULL)) {
usage(argv[0]);
return 0;
replayer.set_latency_multiplier(latency_multiplier);
replayer.set_pool_name(pool_name);
replayer.set_readonly(readonly);
+ replayer.set_image_name_map(image_name_map);
replayer.run(replay_file);
}