From: Adam Crume Date: Tue, 22 Jul 2014 23:06:52 +0000 (-0700) Subject: rbd-replay: Add --map-image option for image name mapping X-Git-Tag: v0.86~231^2~40 X-Git-Url: http://git-server-git.apps.pok.os.sepia.ceph.com/?a=commitdiff_plain;h=2eb6ea3369814447822017887b30a0a1806cf604;p=ceph.git rbd-replay: Add --map-image option for image name mapping Signed-off-by: Adam Crume --- diff --git a/src/rbd_replay/ImageNameMap.cc b/src/rbd_replay/ImageNameMap.cc new file mode 100644 index 000000000000..f17c750f4a6c --- /dev/null +++ b/src/rbd_replay/ImageNameMap.cc @@ -0,0 +1,83 @@ +// -*- 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 + * + * 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 +#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; +} diff --git a/src/rbd_replay/ImageNameMap.hpp b/src/rbd_replay/ImageNameMap.hpp new file mode 100644 index 000000000000..5e9fa8cc93c7 --- /dev/null +++ b/src/rbd_replay/ImageNameMap.hpp @@ -0,0 +1,74 @@ +// -*- 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 + * + * 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 +#include +#include +#include +#include +#include "NameMap.hpp" + +namespace rbd_replay { + +class ImageNameMap { +public: + typedef std::pair Mapping; + + typedef std::pair Name; + + typedef boost::function 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 diff --git a/src/rbd_replay/Makefile.am b/src/rbd_replay/Makefile.am index 5425229e417f..aff2e9208c32 100644 --- a/src/rbd_replay/Makefile.am +++ b/src/rbd_replay/Makefile.am @@ -1,12 +1,19 @@ 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 diff --git a/src/rbd_replay/NameMap.cc b/src/rbd_replay/NameMap.cc new file mode 100644 index 000000000000..6496878a1818 --- /dev/null +++ b/src/rbd_replay/NameMap.cc @@ -0,0 +1,52 @@ +// -*- 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 + * + * 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 +#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; +} diff --git a/src/rbd_replay/NameMap.hpp b/src/rbd_replay/NameMap.hpp new file mode 100644 index 000000000000..68b0b6522bbd --- /dev/null +++ b/src/rbd_replay/NameMap.hpp @@ -0,0 +1,53 @@ +// -*- 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 + * + * 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 +#include +#include +#include + +namespace rbd_replay { + +class NameMap { +public: + typedef std::pair 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 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 diff --git a/src/rbd_replay/Replayer.cc b/src/rbd_replay/Replayer.cc index c7de2062b1e1..54b61e1ef749 100644 --- a/src/rbd_replay/Replayer.cc +++ b/src/rbd_replay/Replayer.cc @@ -128,6 +128,10 @@ bool Worker::readonly() const { return m_replayer.readonly(); } +pair Worker::map_image_name(string image_name, string snap_name) const { + return m_replayer.image_name_map().map(pair(image_name, snap_name)); +} + Replayer::Replayer(int num_action_trackers) : m_pool_name("rbd"), diff --git a/src/rbd_replay/Replayer.hpp b/src/rbd_replay/Replayer.hpp index 870fabf2ecc8..55654e08c513 100644 --- a/src/rbd_replay/Replayer.hpp +++ b/src/rbd_replay/Replayer.hpp @@ -18,6 +18,7 @@ #include #include #include "BoundedBuffer.hpp" +#include "ImageNameMap.hpp" #include "PendingIO.hpp" namespace rbd_replay { @@ -55,6 +56,8 @@ public: bool readonly() const; + std::pair map_image_name(std::string image_name, std::string snap_name) const; + private: void run(); @@ -106,6 +109,14 @@ public: 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 @@ -127,6 +138,7 @@ private: std::string m_pool_name; float m_latency_multiplier; bool m_readonly; + ImageNameMap m_image_name_map; std::map m_images; boost::shared_mutex m_images_mutex; diff --git a/src/rbd_replay/actions.cc b/src/rbd_replay/actions.cc index e328e22b9754..0ac7801365c0 100644 --- a/src/rbd_replay/actions.cc +++ b/src/rbd_replay/actions.cc @@ -316,14 +316,20 @@ void OpenImageAction::perform(ActionCtx &worker) { worker.add_pending(io); librbd::Image *image = new librbd::Image(); librbd::RBD *rbd = worker.rbd(); + pair 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); diff --git a/src/rbd_replay/actions.hpp b/src/rbd_replay/actions.hpp index 14bfeeeed1e3..c384f62ad92c 100644 --- a/src/rbd_replay/actions.hpp +++ b/src/rbd_replay/actions.hpp @@ -67,6 +67,8 @@ public: virtual void set_action_complete(action_id_t id) = 0; virtual void stop() = 0; + + virtual std::pair map_image_name(std::string image_name, std::string snap_name) const = 0; }; diff --git a/src/rbd_replay/rbd-replay.cc b/src/rbd_replay/rbd-replay.cc index bb95787fa4af..a90317e0e694 100644 --- a/src/rbd_replay/rbd-replay.cc +++ b/src/rbd_replay/rbd-replay.cc @@ -18,6 +18,7 @@ #include "global/global_init.h" #include "Replayer.hpp" #include "rbd_replay_debug.hpp" +#include "ImageNameMap.hpp" using namespace std; @@ -39,6 +40,19 @@ static void usage(const char* program) { cout << " -p, --pool-name Name of the pool to use. Default: rbd" << std::endl; cout << " --latency-multiplier Multiplies inter-request latencies. Default: 1" << std::endl; cout << " --read-only Only perform non-destructive operations." << std::endl; + cout << " --map-image 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) { @@ -52,6 +66,8 @@ 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(); ) { @@ -67,6 +83,14 @@ int main(int argc, const char **argv) { } } 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; @@ -95,5 +119,6 @@ int main(int argc, const char **argv) { 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); }