PageRenderTime 48ms CodeModel.GetById 22ms RepoModel.GetById 0ms app.codeStats 0ms

/~mysql/mysql-utilities/trunk/mysql/utilities/common/topology_map.py

#
Python | 339 lines | 286 code | 14 blank | 39 comment | 12 complexity | 869fd87a758a61efff8138b745527ee3 MD5 | raw file
  1. #
  2. # Copyright (c) 2010, 2012 Oracle and/or its affiliates. All rights reserved.
  3. #
  4. # This program is free software; you can redistribute it and/or modify
  5. # it under the terms of the GNU General Public License as published by
  6. # the Free Software Foundation; version 2 of the License.
  7. #
  8. # This program is distributed in the hope that it will be useful,
  9. # but WITHOUT ANY WARRANTY; without even the implied warranty of
  10. # MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
  11. # GNU General Public License for more details.
  12. #
  13. # You should have received a copy of the GNU General Public License
  14. # along with this program; if not, write to the Free Software
  15. # Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA
  16. #
  17. """
  18. This module contains an abstraction of a topolgy map object used to discover
  19. slaves and down-stream replicants for mapping topologies.
  20. """
  21. import os
  22. from mysql.utilities.exception import UtilError
  23. class TopologyMap(object):
  24. """The TopologyMap class can be used to connect to a running MySQL server
  25. and discover its slaves. Setting the option "recurse" permits the
  26. class to discover a replication topology by finding the slaves for each
  27. slave for the first master requested.
  28. To generate a topology map, the caller must call the generate_topology_map()
  29. method to build the topology. This is left as a separate state because it
  30. can be a lengthy process thereby too long for a constructor method.
  31. The class also includes methods for printing a graph of the topology
  32. as well as returning a list of master, slave tuples reporting the
  33. host name and port for each.
  34. """
  35. def __init__(self, seed_server, options={}):
  36. """Constructor
  37. seed_server[in] Master (seed) server connection dictionary
  38. options[in] options for controlling behavior:
  39. recurse If True, check each slave found for add'l slaves
  40. Default = False
  41. prompt_user If True, prompt user if slave connection fails with
  42. master connection parameters
  43. Default = False
  44. quiet if True, print only the data
  45. Default = False
  46. width width of report
  47. Default = 75
  48. num_retries Number of times to retry a failed connection attempt
  49. Default = 0
  50. """
  51. from mysql.utilities.common.server import get_connection_dictionary
  52. self.recurse = options.get("recurse", False)
  53. self.quiet = options.get("quiet", False)
  54. self.prompt_user = options.get("prompt", False)
  55. self.num_retries = options.get("num_retries", 0)
  56. self.socket_path = options.get("socket_path", None)
  57. self.seed_server = seed_server
  58. self.topology = []
  59. def _connect(self, conn):
  60. """Find the attached slaves for a list of server connections.
  61. This method connects to each server in the list and retrieves its slaves.
  62. It can be called recursively if the recurse parameter is True.
  63. conn[in] Connection dictionary used to connect to server
  64. Returns tuple - master Server class instance, master:host string
  65. """
  66. import getpass
  67. from mysql.utilities.common.server import connect_servers
  68. conn_options = {
  69. 'quiet' : self.quiet,
  70. 'src_name' : "master",
  71. 'dest_name' : None,
  72. 'version' : "5.0.0",
  73. 'unique' : True,
  74. }
  75. master_info = "%s:%s" % (conn['host'],
  76. conn['port'])
  77. master = None
  78. # Clear socket if used with a local server
  79. if (conn['host'] == 'localhost' or \
  80. conn['host'] == "127.0.0.1"):
  81. conn['unix_socket'] = None
  82. # Increment num_retries if not set when --prompt is used
  83. if self.prompt_user and self.num_retries == 0:
  84. self.num_retries += 1
  85. # Attempt to connect to the server given the retry limit
  86. for i in range(0,self.num_retries+1):
  87. try:
  88. servers = connect_servers(conn, None, conn_options)
  89. master = servers[0]
  90. break
  91. except UtilError, e:
  92. print "FAILED.\n"
  93. if i < self.num_retries and self.prompt_user:
  94. print "Connection to %s has failed.\n" % master_info + \
  95. "Please enter the following information " + \
  96. "to connect to this server."
  97. conn['user'] = raw_input("User name: ")
  98. conn['passwd'] = getpass.getpass("Password: ")
  99. else:
  100. # retries expired - re-raise error if still failing
  101. raise UtilError(e.errmsg)
  102. return (master, master_info)
  103. def _check_permissions(self, server, priv):
  104. """Check to see if user has permissions to execute.
  105. server[in] Server class instance
  106. priv[in] privilege to check
  107. Returns True if permissions available, raises exception if not
  108. """
  109. from mysql.utilities.common.user import User
  110. # Check user permissions
  111. user_pass_host = server.user
  112. if server.passwd is not None and len(server.passwd) > 0:
  113. user_pass_host += ":" + server.passwd
  114. user_pass_host += "@" + server.host
  115. user = User(server, user_pass_host, False)
  116. if not user.has_privilege("*", "*", priv):
  117. raise UtilError("Not enough permissions. The user must have the "
  118. "%s privilege." % priv)
  119. def _get_slaves(self, max_depth, seed_conn=None, masters_found=[]):
  120. """Find the attached slaves for a list of server connections.
  121. This method connects to each server in the list and retrieves its slaves.
  122. It can be called recursively if the recurse option is True.
  123. max_depth[in] Maximum depth of recursive search
  124. seed_conn[in] Current master connection dictionary. Initially,
  125. this is the seed server (original master defined
  126. in constructor)
  127. masters_found[in] a list of all servers in master roles - used to
  128. detect a circular replication topology. Initially,
  129. this is an empty list as the master detection must
  130. occur as the topology is traversed.
  131. Returns list - list of slaves connected to each server in list
  132. """
  133. topology = []
  134. if seed_conn is None:
  135. seed_conn = self.seed_server
  136. master, master_info = self._connect(seed_conn)
  137. if master is None:
  138. return []
  139. # Check user permissions
  140. self._check_permissions(master, "REPLICATION SLAVE")
  141. # Save the master for circular replication identification
  142. masters_found.append(master_info)
  143. if not self.quiet:
  144. print "# Finding slaves for master: %s" % master_info
  145. # Get replication topology
  146. slaves = master.get_slaves()
  147. slave_list = []
  148. depth = 0
  149. if len(slaves) > 0:
  150. for slave in slaves:
  151. if slave.find(":") > 0:
  152. host, port = slave.split(":")
  153. else:
  154. host = slave
  155. port = START_PORT # Use the default
  156. slave_conn = self.seed_server.copy()
  157. slave_conn['host'] = host
  158. slave_conn['port'] = port
  159. # Now check for circular replication topology - do not recurse
  160. # if slave is also a master.
  161. if self.recurse and not slave in masters_found and \
  162. ((max_depth is None) or (depth < max_depth)):
  163. new_list = self._get_slaves(max_depth, slave_conn,
  164. masters_found)
  165. if new_list == []:
  166. slave_list.append((slave, []))
  167. else:
  168. slave_list.append(new_list)
  169. depth += 1
  170. else:
  171. slave_list.append((slave, []))
  172. topology.append((master_info, slave_list))
  173. return topology
  174. def generate_topology_map(self, max_depth):
  175. """Find the attached slaves for a list of server connections.
  176. This method generates the topology for the seed server specified at
  177. instantiation.
  178. max_depth[in] Maximum depth of recursive search
  179. """
  180. self.topology = self._get_slaves(max_depth)
  181. def depth(self):
  182. """Return depth of the topology tree.
  183. Returns int - depth of topology tree.
  184. """
  185. return len(self.topology)
  186. def slaves_found(self):
  187. """Check to see if any slaves were found.
  188. Returns bool - True if slaves found, False if no slaves.
  189. """
  190. return not (len(self.topology) and self.topology[0][1] == [])
  191. def print_graph(self, topology_list=[], masters_found=[],
  192. level=0, preamble=""):
  193. """Prints a graph of the topology map to standard output.
  194. This method traverses a list of the topology and prints a graph. The
  195. method is designed to be recursive traversing the list to print the
  196. slaves for each master in the topology. It will also detect a circular
  197. replication segment and indicate it on the graph.
  198. topology_list[in] a list in the form (master, slave) of server
  199. masters_found[in] a list of all servers in master roles - used to
  200. detect a circular replication topology. Initially,
  201. this is an empty list as the master detection must
  202. occur as the toplogy is traversed.
  203. level[in] the level of indentation - increases with each
  204. set of slaves found in topology
  205. preamble[in] prefix calculated during recursion to indent text
  206. """
  207. # if first iteration, use the topology list generated earlier
  208. if topology_list == []:
  209. if self.topology == []:
  210. # topology not generated yet
  211. raise UtilError("You must first generate the topology.")
  212. topology_list = self.topology
  213. # Detect if we are looking at a sublist or not. Get sublist.
  214. if len(topology_list) == 1:
  215. topology_list = topology_list[0]
  216. master = topology_list[0]
  217. # Save the master for circular replication identification
  218. masters_found.append(master)
  219. # For each slave, print the graph link
  220. slaves = topology_list[1]
  221. stop = len(slaves)
  222. if stop > 0:
  223. # Level 0 is always the first master in the topology.
  224. if level == 0:
  225. print "%s (MASTER)" % master
  226. for i in range(0,stop):
  227. if len(slaves[i]) == 1:
  228. slave = slaves[i][0]
  229. else:
  230. slave = slaves[i]
  231. new_preamble = preamble + " "
  232. print new_preamble+"|"
  233. role = "(SLAVE"
  234. if not slave[1] == [] or slave[0] in masters_found:
  235. role += " + MASTER"
  236. role += ")"
  237. print "%s+--- %s" % (new_preamble, slave[0]),
  238. if (slave[0] in masters_found):
  239. print "<-->",
  240. else:
  241. print "-",
  242. print "%s" % role
  243. if not slave[1] == []:
  244. if i < stop-1:
  245. new_preamble += "|"
  246. else:
  247. new_preamble += " "
  248. self.print_graph(slave, masters_found,
  249. level+1, new_preamble)
  250. def _get_row(self, topology_list):
  251. """Get a row (master, slave) for the topology map.
  252. topology_list[in] The topology list
  253. Returns tuple - a row (master, slave)
  254. """
  255. new_row = []
  256. if len(topology_list) == 1:
  257. topology_list = topology_list[0]
  258. master = topology_list[0]
  259. slaves = topology_list[1]
  260. for slave in slaves:
  261. if len(slave) == 1:
  262. new_slave = slave[0]
  263. else:
  264. new_slave = slave
  265. new_row.append((master, new_slave[0]))
  266. new_row.extend(self._get_row(new_slave))
  267. return new_row
  268. def get_topology_map(self):
  269. """Get a list of the topology map suitable for export
  270. Returns list - a list of masters and their slaves in two columns
  271. """
  272. # Get a row for the list
  273. # make a list from the topology
  274. master_slaves = [self._get_row(row) for row in self.topology]
  275. return master_slaves[0]