PageRenderTime 53ms CodeModel.GetById 26ms RepoModel.GetById 0ms app.codeStats 0ms

/pycassa/system_manager.py

http://github.com/pycassa/pycassa
Python | 468 lines | 439 code | 7 blank | 22 comment | 2 complexity | 77949436c0faecc4ca81b0df56043e10 MD5 | raw file
Possible License(s): Apache-2.0
  1. import time
  2. from pycassa.connection import (Connection, default_socket_factory,
  3. default_transport_factory)
  4. from pycassa.cassandra.ttypes import IndexType, KsDef, CfDef, ColumnDef,\
  5. SchemaDisagreementException
  6. import pycassa.marshal as marshal
  7. import pycassa.types as types
  8. _DEFAULT_TIMEOUT = 30
  9. _SAMPLE_PERIOD = 0.25
  10. SIMPLE_STRATEGY = 'SimpleStrategy'
  11. """ Replication strategy that simply chooses consecutive nodes in the ring for replicas """
  12. NETWORK_TOPOLOGY_STRATEGY = 'NetworkTopologyStrategy'
  13. """ Replication strategy that puts a number of replicas in each datacenter """
  14. OLD_NETWORK_TOPOLOGY_STRATEGY = 'OldNetworkTopologyStrategy'
  15. """
  16. Original replication strategy for putting a number of replicas in each datacenter.
  17. This was originally called 'RackAwareStrategy'.
  18. """
  19. KEYS_INDEX = IndexType.KEYS
  20. """ A secondary index type where each indexed value receives its own row """
  21. BYTES_TYPE = types.BytesType()
  22. LONG_TYPE = types.LongType()
  23. INT_TYPE = types.IntegerType()
  24. ASCII_TYPE = types.AsciiType()
  25. UTF8_TYPE = types.UTF8Type()
  26. TIME_UUID_TYPE = types.TimeUUIDType()
  27. LEXICAL_UUID_TYPE = types.LexicalUUIDType()
  28. COUNTER_COLUMN_TYPE = types.CounterColumnType()
  29. DOUBLE_TYPE = types.DoubleType()
  30. FLOAT_TYPE = types.FloatType()
  31. DECIMAL_TYPE = types.DecimalType()
  32. BOOLEAN_TYPE = types.BooleanType()
  33. DATE_TYPE = types.DateType()
  34. class SystemManager(object):
  35. """
  36. Lets you examine and modify schema definitions as well as get basic
  37. information about the cluster.
  38. This class is mainly designed to be used manually in a python shell,
  39. not as part of a program, although it can be used that way.
  40. All operations which modify a keyspace or column family definition
  41. will block until the cluster reports that all nodes have accepted
  42. the modification.
  43. Example Usage:
  44. .. code-block:: python
  45. >>> from pycassa.system_manager import *
  46. >>> sys = SystemManager('192.168.10.2:9160')
  47. >>> sys.create_keyspace('TestKeyspace', SIMPLE_STRATEGY, {'replication_factor': '1'})
  48. >>> sys.create_column_family('TestKeyspace', 'TestCF', super=False,
  49. ... comparator_type=LONG_TYPE)
  50. >>> sys.alter_column_family('TestKeyspace', 'TestCF', key_cache_size=42, gc_grace_seconds=1000)
  51. >>> sys.drop_keyspace('TestKeyspace')
  52. >>> sys.close()
  53. """
  54. def __init__(self, server='localhost:9160', credentials=None, framed_transport=True,
  55. timeout=_DEFAULT_TIMEOUT, socket_factory=default_socket_factory,
  56. transport_factory=default_transport_factory):
  57. self._conn = Connection(None, server, framed_transport, timeout,
  58. credentials, socket_factory, transport_factory)
  59. def close(self):
  60. """ Closes the underlying connection """
  61. self._conn.close()
  62. def get_keyspace_column_families(self, keyspace, use_dict_for_col_metadata=False):
  63. """
  64. Returns a raw description of the keyspace, which is more useful for use
  65. in programs than :meth:`describe_keyspace()`.
  66. If `use_dict_for_col_metadata` is ``True``, the CfDef's column_metadata will
  67. be stored as a dictionary where the keys are column names instead of a list.
  68. Returns a dictionary of the form ``{column_family_name: CfDef}``
  69. """
  70. if keyspace is None:
  71. keyspace = self._keyspace
  72. ks_def = self._conn.describe_keyspace(keyspace)
  73. cf_defs = dict()
  74. for cf_def in ks_def.cf_defs:
  75. cf_defs[cf_def.name] = cf_def
  76. if use_dict_for_col_metadata:
  77. old_metadata = cf_def.column_metadata
  78. new_metadata = dict()
  79. for datum in old_metadata:
  80. new_metadata[datum.name] = datum
  81. cf_def.column_metadata = new_metadata
  82. return cf_defs
  83. def get_keyspace_properties(self, keyspace):
  84. """
  85. Gets a keyspace's properties.
  86. Returns a :class:`dict` with 'strategy_class' and
  87. 'strategy_options' as keys.
  88. """
  89. if keyspace is None:
  90. keyspace = self._keyspace
  91. ks_def = self._conn.describe_keyspace(keyspace)
  92. return {'replication_strategy': ks_def.strategy_class,
  93. 'strategy_options': ks_def.strategy_options}
  94. def list_keyspaces(self):
  95. """ Returns a list of all keyspace names. """
  96. return [ks.name for ks in self._conn.describe_keyspaces()]
  97. def describe_ring(self, keyspace):
  98. """ Describes the Cassandra cluster """
  99. return self._conn.describe_ring(keyspace)
  100. def describe_token_map(self):
  101. """ List tokens and their node assignments. """
  102. return self._conn.describe_token_map()
  103. def describe_cluster_name(self):
  104. """ Gives the cluster name """
  105. return self._conn.describe_cluster_name()
  106. def describe_version(self):
  107. """ Gives the server's API version """
  108. return self._conn.describe_version()
  109. def describe_schema_versions(self):
  110. """ Lists what schema version each node has """
  111. return self._conn.describe_schema_versions()
  112. def describe_partitioner(self):
  113. """ Gives the partitioner that the cluster is using """
  114. part = self._conn.describe_partitioner()
  115. return part[part.rfind('.') + 1:]
  116. def describe_snitch(self):
  117. """ Gives the snitch that the cluster is using """
  118. snitch = self._conn.describe_snitch()
  119. return snitch[snitch.rfind('.') + 1:]
  120. def _system_add_keyspace(self, ksdef):
  121. return self._schema_update(self._conn.system_add_keyspace, ksdef)
  122. def _system_update_keyspace(self, ksdef):
  123. return self._schema_update(self._conn.system_update_keyspace, ksdef)
  124. def create_keyspace(self, name,
  125. replication_strategy=SIMPLE_STRATEGY,
  126. strategy_options=None, durable_writes=True, **ks_kwargs):
  127. """
  128. Creates a new keyspace. Column families may be added to this keyspace
  129. after it is created using :meth:`create_column_family()`.
  130. `replication_strategy` determines how replicas are chosen for this keyspace.
  131. The strategies that Cassandra provides by default
  132. are available as :const:`SIMPLE_STRATEGY`, :const:`NETWORK_TOPOLOGY_STRATEGY`,
  133. and :const:`OLD_NETWORK_TOPOLOGY_STRATEGY`.
  134. `strategy_options` is a dictionary of strategy options. For
  135. NetworkTopologyStrategy, the dictionary should look like
  136. ``{'Datacenter1': '2', 'Datacenter2': '1'}``. This maps each
  137. datacenter (as defined in a Cassandra property file) to a replica count.
  138. For SimpleStrategy, you can specify the replication factor as follows:
  139. ``{'replication_factor': '1'}``.
  140. Example Usage:
  141. .. code-block:: python
  142. >>> from pycassa.system_manager import *
  143. >>> sys = SystemManager('192.168.10.2:9160')
  144. >>> # Create a SimpleStrategy keyspace
  145. >>> sys.create_keyspace('SimpleKS', SIMPLE_STRATEGY, {'replication_factor': '1'})
  146. >>> # Create a NetworkTopologyStrategy keyspace
  147. >>> sys.create_keyspace('NTS_KS', NETWORK_TOPOLOGY_STRATEGY, {'DC1': '2', 'DC2': '1'})
  148. >>> sys.close()
  149. """
  150. if replication_strategy.find('.') == -1:
  151. strategy_class = 'org.apache.cassandra.locator.%s' % replication_strategy
  152. else:
  153. strategy_class = replication_strategy
  154. ksdef = KsDef(name, strategy_class=strategy_class,
  155. strategy_options=strategy_options,
  156. cf_defs=[],
  157. durable_writes=durable_writes)
  158. for k, v in ks_kwargs.iteritems():
  159. setattr(ksdef, k, v)
  160. self._system_add_keyspace(ksdef)
  161. def alter_keyspace(self, keyspace, replication_strategy=None,
  162. strategy_options=None, durable_writes=None, **ks_kwargs):
  163. """
  164. Alters an existing keyspace.
  165. .. warning:: Don't use this unless you know what you are doing.
  166. Parameters are the same as for :meth:`create_keyspace()`.
  167. """
  168. old_ksdef = self._conn.describe_keyspace(keyspace)
  169. old_durable = getattr(old_ksdef, 'durable_writes', True)
  170. ksdef = KsDef(name=old_ksdef.name,
  171. strategy_class=old_ksdef.strategy_class,
  172. strategy_options=old_ksdef.strategy_options,
  173. cf_defs=[],
  174. durable_writes=old_durable)
  175. if replication_strategy is not None:
  176. if replication_strategy.find('.') == -1:
  177. ksdef.strategy_class = 'org.apache.cassandra.locator.%s' % replication_strategy
  178. else:
  179. ksdef.strategy_class = replication_strategy
  180. if strategy_options is not None:
  181. ksdef.strategy_options = strategy_options
  182. if durable_writes is not None:
  183. ksdef.durable_writes = durable_writes
  184. for k, v in ks_kwargs.iteritems():
  185. setattr(ksdef, k, v)
  186. self._system_update_keyspace(ksdef)
  187. def drop_keyspace(self, keyspace):
  188. """
  189. Drops a keyspace from the cluster.
  190. """
  191. self._schema_update(self._conn.system_drop_keyspace, keyspace)
  192. def _system_add_column_family(self, cfdef):
  193. self._conn.set_keyspace(cfdef.keyspace)
  194. return self._schema_update(self._conn.system_add_column_family, cfdef)
  195. def create_column_family(self, keyspace, name, column_validation_classes=None, **cf_kwargs):
  196. """
  197. Creates a new column family in a given keyspace. If a value is not
  198. supplied for any of optional parameters, Cassandra will use a reasonable
  199. default value.
  200. `keyspace` should be the name of the keyspace the column family will
  201. be created in. `name` gives the name of the column family.
  202. """
  203. self._conn.set_keyspace(keyspace)
  204. cfdef = CfDef()
  205. cfdef.keyspace = keyspace
  206. cfdef.name = name
  207. if cf_kwargs.pop('super', False):
  208. cf_kwargs.setdefault('column_type', 'Super')
  209. for k, v in cf_kwargs.iteritems():
  210. v = self._convert_class_attrs(k, v)
  211. setattr(cfdef, k, v)
  212. if column_validation_classes:
  213. for (colname, value_type) in column_validation_classes.items():
  214. cfdef = self._alter_column_cfdef(cfdef, colname, value_type)
  215. self._system_add_column_family(cfdef)
  216. def _system_update_column_family(self, cfdef):
  217. return self._schema_update(self._conn.system_update_column_family, cfdef)
  218. def alter_column_family(self, keyspace, column_family, column_validation_classes=None, **cf_kwargs):
  219. """
  220. Alters an existing column family.
  221. Parameter meanings are the same as for :meth:`create_column_family`.
  222. """
  223. self._conn.set_keyspace(keyspace)
  224. cfdef = self.get_keyspace_column_families(keyspace)[column_family]
  225. for k, v in cf_kwargs.iteritems():
  226. v = self._convert_class_attrs(k, v)
  227. setattr(cfdef, k, v)
  228. if column_validation_classes:
  229. for (colname, value_type) in column_validation_classes.items():
  230. cfdef = self._alter_column_cfdef(cfdef, colname, value_type)
  231. self._system_update_column_family(cfdef)
  232. def drop_column_family(self, keyspace, column_family):
  233. """
  234. Drops a column family from the keyspace.
  235. """
  236. self._conn.set_keyspace(keyspace)
  237. self._schema_update(self._conn.system_drop_column_family, column_family)
  238. def _convert_class_attrs(self, attr, value):
  239. if attr in ('comparator_type', 'subcomparator_type',
  240. 'key_validation_class', 'default_validation_class'):
  241. return self._qualify_type_class(value)
  242. else:
  243. return value
  244. def _qualify_type_class(self, classname):
  245. if classname:
  246. if isinstance(classname, types.CassandraType):
  247. s = str(classname)
  248. elif isinstance(classname, basestring):
  249. s = classname
  250. else:
  251. raise TypeError(
  252. "Column family validators and comparators " \
  253. "must be specified as instances of " \
  254. "pycassa.types.CassandraType subclasses or strings.")
  255. if s.find('.') == -1:
  256. return 'org.apache.cassandra.db.marshal.%s' % s
  257. else:
  258. return s
  259. else:
  260. return None
  261. def _alter_column_cfdef(self, cfdef, column, value_type):
  262. if cfdef.column_type == 'Super':
  263. packer = marshal.packer_for(cfdef.subcomparator_type)
  264. else:
  265. packer = marshal.packer_for(cfdef.comparator_type)
  266. packed_column = packer(column)
  267. value_type = self._qualify_type_class(value_type)
  268. cfdef.column_metadata = cfdef.column_metadata or []
  269. matched = False
  270. for c in cfdef.column_metadata:
  271. if c.name == packed_column:
  272. c.validation_class = value_type
  273. matched = True
  274. break
  275. if not matched:
  276. cfdef.column_metadata.append(ColumnDef(packed_column, value_type, None, None))
  277. return cfdef
  278. def alter_column(self, keyspace, column_family, column, value_type):
  279. """
  280. Sets a data type for the value of a specific column.
  281. `value_type` is a string that determines what type the column value will be.
  282. By default, :const:`LONG_TYPE`, :const:`INT_TYPE`,
  283. :const:`ASCII_TYPE`, :const:`UTF8_TYPE`, :const:`TIME_UUID_TYPE`,
  284. :const:`LEXICAL_UUID_TYPE` and :const:`BYTES_TYPE` are provided. Custom
  285. types may be used as well by providing the class name; if the custom
  286. comparator class is not in ``org.apache.cassandra.db.marshal``, the fully
  287. qualified class name must be given.
  288. For super column families, this sets the subcolumn value type for
  289. any subcolumn named `column`, regardless of the super column name.
  290. """
  291. self._conn.set_keyspace(keyspace)
  292. cfdef = self.get_keyspace_column_families(keyspace)[column_family]
  293. self._system_update_column_family(self._alter_column_cfdef(cfdef, column, value_type))
  294. def create_index(self, keyspace, column_family, column, value_type,
  295. index_type=KEYS_INDEX, index_name=None):
  296. """
  297. Creates an index on a column.
  298. This allows efficient for index usage via
  299. :meth:`~pycassa.columnfamily.ColumnFamily.get_indexed_slices()`
  300. `column` specifies what column to index, and `value_type` is a string
  301. that describes that column's value's data type; see
  302. :meth:`alter_column()` for a full description of `value_type`.
  303. `index_type` determines how the index will be stored internally. Currently,
  304. :const:`KEYS_INDEX` is the only option. `index_name` is an optional name
  305. for the index.
  306. Example Usage:
  307. .. code-block:: python
  308. >>> from pycassa.system_manager import *
  309. >>> sys = SystemManager('192.168.2.10:9160')
  310. >>> sys.create_index('Keyspace1', 'Standard1', 'birthdate', LONG_TYPE, index_name='bday_index')
  311. >>> sys.close
  312. """
  313. self._conn.set_keyspace(keyspace)
  314. cfdef = self.get_keyspace_column_families(keyspace)[column_family]
  315. packer = marshal.packer_for(cfdef.comparator_type)
  316. packed_column = packer(column)
  317. value_type = self._qualify_type_class(value_type)
  318. coldef = ColumnDef(packed_column, value_type, index_type, index_name)
  319. for c in cfdef.column_metadata:
  320. if c.name == packed_column:
  321. cfdef.column_metadata.remove(c)
  322. break
  323. cfdef.column_metadata.append(coldef)
  324. self._system_update_column_family(cfdef)
  325. def drop_index(self, keyspace, column_family, column):
  326. """
  327. Drops an index on a column.
  328. """
  329. self._conn.set_keyspace(keyspace)
  330. cfdef = self.get_keyspace_column_families(keyspace)[column_family]
  331. matched = False
  332. for c in cfdef.column_metadata:
  333. if c.name == column:
  334. c.index_type = None
  335. c.index_name = None
  336. matched = True
  337. break
  338. if matched:
  339. self._system_update_column_family(cfdef)
  340. def _wait_for_agreement(self):
  341. while True:
  342. versions = self._conn.describe_schema_versions()
  343. # ignore unreachable nodes
  344. live_versions = [key for key in versions.keys() if key != 'UNREACHABLE']
  345. if len(live_versions) == 1:
  346. break
  347. else:
  348. time.sleep(_SAMPLE_PERIOD)
  349. def _schema_update(self, schema_func, *args):
  350. """
  351. Call schema updates functions and properly
  352. waits for agreement if needed.
  353. """
  354. while True:
  355. try:
  356. schema_version = schema_func(*args)
  357. except SchemaDisagreementException:
  358. self._wait_for_agreement()
  359. else:
  360. break
  361. return schema_version