Comparison

tools/migration/prosody-migrator.lua @ 10003:4d702f0c6273

migrator: Rewrite to use storage modules This allows migrating to and from any storage module that supports the right methods. Based on experimental mod_migrate work.
author Kim Alvefur <zash@zash.se>
date Sun, 05 May 2019 21:32:34 +0200
parent 8062:739bb455cafd
child 10004:e057e8318130
comparison
equal deleted inserted replaced
10002:b6b5b9d7417d 10003:4d702f0c6273
1 #!/usr/bin/env lua 1 #!/usr/bin/env lua
2 2
3 CFG_SOURCEDIR=os.getenv("PROSODY_SRCDIR"); 3 CFG_SOURCEDIR=CFG_SOURCEDIR or os.getenv("PROSODY_SRCDIR");
4 CFG_CONFIGDIR=os.getenv("PROSODY_CFGDIR"); 4 CFG_CONFIGDIR=CFG_CONFIGDIR or os.getenv("PROSODY_CFGDIR");
5 CFG_PLUGINDIR=CFG_PLUGINDIR or os.getenv("PROSODY_PLUGINDIR");
6 CFG_DATADIR=CFG_DATADIR or os.getenv("PROSODY_DATADIR");
5 7
6 -- Substitute ~ with path to home directory in paths 8 -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- -- --
7 if CFG_CONFIGDIR then 9
8 CFG_CONFIGDIR = CFG_CONFIGDIR:gsub("^~", os.getenv("HOME")); 10 local function is_relative(path)
11 local path_sep = package.config:sub(1,1);
12 return ((path_sep == "/" and path:sub(1,1) ~= "/")
13 or (path_sep == "\\" and (path:sub(1,1) ~= "/" and path:sub(2,3) ~= ":\\")))
9 end 14 end
10 15
16 -- Tell Lua where to find our libraries
11 if CFG_SOURCEDIR then 17 if CFG_SOURCEDIR then
12 CFG_SOURCEDIR = CFG_SOURCEDIR:gsub("^~", os.getenv("HOME")); 18 local function filter_relative_paths(path)
19 if is_relative(path) then return ""; end
20 end
21 local function sanitise_paths(paths)
22 return (paths:gsub("[^;]+;?", filter_relative_paths):gsub(";;+", ";"));
23 end
24 package.path = sanitise_paths(CFG_SOURCEDIR.."/?.lua;"..package.path);
25 package.cpath = sanitise_paths(CFG_SOURCEDIR.."/?.so;"..package.cpath);
26 end
27
28 -- Substitute ~ with path to home directory in data path
29 if CFG_DATADIR then
30 if os.getenv("HOME") then
31 CFG_DATADIR = CFG_DATADIR:gsub("^~", os.getenv("HOME"));
32 end
13 end 33 end
14 34
15 local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua"; 35 local default_config = (CFG_CONFIGDIR or ".").."/migrator.cfg.lua";
36
37 local startup = require "util.startup";
38 startup.prosodyctl();
39 -- TODO startup.migrator ?
16 40
17 -- Command-line parsing 41 -- Command-line parsing
18 local options = {}; 42 local options = {};
19 local i = 1; 43 local i = 1;
20 while arg[i] do 44 while arg[i] do
27 else 51 else
28 i = i + 1; 52 i = i + 1;
29 end 53 end
30 end 54 end
31 55
32 if CFG_SOURCEDIR then
33 package.path = CFG_SOURCEDIR.."/?.lua;"..package.path;
34 package.cpath = CFG_SOURCEDIR.."/?.so;"..package.cpath;
35 else
36 package.path = "../../?.lua;"..package.path
37 package.cpath = "../../?.so;"..package.cpath
38 end
39 56
40 local envloadfile = require "util.envload".envloadfile; 57 local envloadfile = require "util.envload".envloadfile;
41 58
42 local config_file = options.config or default_config; 59 local config_file = options.config or default_config;
43 local from_store = arg[1] or "input"; 60 local from_store = arg[1] or "input";
67 if not config[to_store] then 84 if not config[to_store] then
68 have_err = true; 85 have_err = true;
69 print("Error: Output store '"..to_store.."' not found in the config file."); 86 print("Error: Output store '"..to_store.."' not found in the config file.");
70 end 87 end
71 88
72 function load_store_handler(name) 89 for store, conf in pairs(config) do -- COMPAT
73 local store_type = config[name].type; 90 if conf.type == "prosody_files" then
74 if not store_type then 91 conf.type = "internal";
75 print("Error: "..name.." store type not specified in the config file"); 92 elseif conf.type == "prosody_sql" then
76 return false; 93 conf.type = "sql";
77 else
78 local ok, err = pcall(require, "migrator."..store_type);
79 if not ok then
80 print(("Error: Failed to initialize '%s' store:\n\t%s")
81 :format(name, err));
82 return false;
83 end
84 end 94 end
85 return true;
86 end 95 end
87
88 have_err = have_err or not(load_store_handler(from_store, "input") and load_store_handler(to_store, "output"));
89 96
90 if have_err then 97 if have_err then
91 print(""); 98 print("");
92 print("Usage: "..arg[0].." FROM_STORE TO_STORE"); 99 print("Usage: "..arg[0].." FROM_STORE TO_STORE");
93 print("If no stores are specified, 'input' and 'output' are used."); 100 print("If no stores are specified, 'input' and 'output' are used.");
99 end 106 end
100 print(""); 107 print("");
101 os.exit(1); 108 os.exit(1);
102 end 109 end
103 110
104 local itype = config[from_store].type; 111 local async = require "util.async";
105 local otype = config[to_store].type; 112 local server = require "net.server";
106 local reader = require("migrator."..itype).reader(config[from_store]); 113 local watchers = {
107 local writer = require("migrator."..otype).writer(config[to_store]); 114 error = function (_, err)
115 error(err);
116 end;
117 waiting = function ()
118 server.loop();
119 end;
120 };
108 121
109 local json = require "util.json"; 122 local cm = require "core.configmanager";
123 local hm = require "core.hostmanager";
124 local sm = require "core.storagemanager";
125 local um = require "core.usermanager";
126
127 local function users(store, host)
128 if store.users then
129 return store:users();
130 else
131 return um.users(host);
132 end
133 end
134
135 local function prepare_config(host, conf)
136 if conf.type == "internal" then
137 sm.olddm.set_data_path(conf.path or prosody.paths.data);
138 elseif conf.type == "sql" then
139 cm.set(host, "sql", conf);
140 end
141 end
142
143 local function get_driver(host, conf)
144 prepare_config(host, conf);
145 return assert(sm.load_driver(host, conf.type));
146 end
147
148 local migration_runner = async.runner(function (job)
149 for host, stores in pairs(job.input.hosts) do
150 prosody.hosts[host] = startup.make_host(host);
151 sm.initialize_host(host);
152 um.initialize_host(host);
153
154 local input_driver = get_driver(host, job.input);
155
156 local output_driver = get_driver(host, job.output);
157
158 for _, store in ipairs(stores) do
159 local p, typ = store:match("()%-(%w+)$");
160 if typ then store = store:sub(1, p-1); else typ = "keyval"; end
161 log("info", "Migrating host %s store %s (%s)", host, store, typ);
162
163 local origin = assert(input_driver:open(store, typ));
164 local destination = assert(output_driver:open(store, typ));
165
166 if typ == "keyval" then -- host data
167 local data, err = origin:get(nil);
168 assert(not err, err);
169 assert(destination:set(nil, data));
170 end
171
172 for user in users(origin, host) do
173 if typ == "keyval" then
174 local data, err = origin:get(user);
175 assert(not err, err);
176 assert(destination:set(user, data));
177 else
178 error("Don't know how to migrate data of type '"..typ.."'.");
179 end
180 end
181 end
182 end
183 end, watchers);
110 184
111 io.stderr:write("Migrating...\n"); 185 io.stderr:write("Migrating...\n");
112 for x in reader do 186
113 --print(json.encode(x)) 187 migration_runner:run({ input = config[from_store], output = config[to_store] });
114 writer(x); 188
115 end
116 writer(nil); -- close
117 io.stderr:write("Done!\n"); 189 io.stderr:write("Done!\n");