def _undo_remove_shard(shard_id, update_only=False): """Recreate the shard metadata. :param shard_id: The ID of the shard that was being removed. :param update_only: Only update the state store and skip adding range checks. """ if update_only: return group_id = Shards.fetch(shard_id).group_id try: SHARD_METADATA.create_shard_meta_data(group_id) except _errors.DatabaseError as error: #If the create fails, do not insert the metadata. return spec_handler = SHARDING_SPECIFICATION_HANDLER[shard_type] upper_bound = spec_handler.get_upper_bound( shard.lower_bound, shard_mapping_id, shard_type ) SHARD_METADATA.insert_shard_meta_data(shard.shard_id, shard.lower_bound, upper_bound, group_id)
def _remove_shard(shard_id, update_only=False): """Remove the RANGE specification mapping represented by the current RANGE shard specification object. :param shard_id: The shard ID of the shard that needs to be removed. :param update_only: Only update the state store and skip adding range checks. :return: True if the remove succeeded False if the query failed :raises: ShardingError if the shard id is not found, : ShardingError if the shard is not disabled. """ range_sharding_specification, shard, _, _ = \ verify_and_fetch_shard(shard_id) if shard.state == "ENABLED": raise _errors.ShardingError(SHARD_NOT_DISABLED) #Stop the replication of the shard group with the global #group. Also clear the references of the master and the #slave group from the current group. #NOTE: When we do the stopping of the shard group #replication in shard remove we are actually just clearing #the references, since a shard cannot be removed unless #it is disabled and when it is disabled the replication is #stopped but the references are not cleared. if not update_only: #The Group ID needs to be fetched before the sharding #information is removed. group_id = Shards.fetch(shard.shard_id).group_id _stop_shard_group_replication(shard_id, True) range_sharding_specification.remove() shard.remove() if not update_only: SHARD_METADATA.drop_shard_meta_data(group_id) _LOGGER.debug("Removed Shard (%s).", shard_id)
def _add_shard_range_check(shard_mapping_id, shard_type): """Add the shard ranges in the metadata tables of the shards :param shard_mapping_id: The shard mapping ID of the shards for which the metadata needs to be added. :param shard_type: The datatype of the sharding definition. """ shard_list = SHARDING_SPECIFICATION_HANDLER[shard_type].list(shard_mapping_id) for shard in shard_list: group_id = Shards.fetch(shard.shard_id).group_id #The metadata has to be created before adding the ranges because #the global group might have still not replicated the schema to the #shard group. If the replication has still not happened, adding #the bounds will throw an error. SHARD_METADATA.create_shard_meta_data(group_id) spec_handler = SHARDING_SPECIFICATION_HANDLER[shard_type] upper_bound = spec_handler.get_upper_bound( shard.lower_bound, shard_mapping_id, shard_type ) SHARD_METADATA.insert_shard_meta_data(shard.shard_id, shard.lower_bound, upper_bound, group_id)
def _lookup(lookup_arg, key, hint): """Given a table name and a key return the servers of the Group where the shard of this table can be found :param lookup_arg: table name for "LOCAL" lookups Shard Mapping ID for "GLOBAL" lookups. :param key: The key value that needs to be looked up :param hint: A hint indicates if the query is LOCAL or GLOBAL :return: The servers of the Group that contains the range in which the key belongs. """ VALID_HINTS = ('LOCAL', 'GLOBAL') hint = hint.upper() if hint not in VALID_HINTS: raise _errors.ShardingError(INVALID_SHARDING_HINT) group = None #Perform the lookup for the group contaning the lookup data. if hint == "GLOBAL": #Fetch the shard mapping object. In the case of GLOBAL lookups #the shard mapping ID is passed directly. In the case of "LOCAL" #lookups it is the table name that is passed. shard_mapping = ShardMapping.fetch_by_id(lookup_arg) if shard_mapping is None: raise _errors.ShardingError( SHARD_MAPPING_NOT_FOUND % (lookup_arg, ) ) #GLOBAL lookups. There can be only one global group, hence using #shard_mapping[0] is safe. group = Group.fetch(shard_mapping[0].global_group) else: shard_mapping = ShardMapping.fetch(lookup_arg) if shard_mapping is None: raise _errors.ShardingError(TABLE_NAME_NOT_FOUND % (lookup_arg, )) sharding_specification =\ SHARDING_SPECIFICATION_HANDLER[shard_mapping.type_name].\ lookup(key, shard_mapping.shard_mapping_id, shard_mapping.type_name) if sharding_specification is None: raise _errors.ShardingError(INVALID_SHARDING_KEY % (key, )) shard = Shards.fetch(str(sharding_specification.shard_id)) if shard.state == "DISABLED": raise _errors.ShardingError(SHARD_NOT_ENABLED) #group cannot be None since there is a foreign key on the group_id. #An exception will be thrown nevertheless. group = Group.fetch(shard.group_id) if group is None: raise _errors.ShardingError(SHARD_LOCATION_NOT_FOUND) ret = [] #An empty list will be returned if the registered group has not #servers. for server in group.servers(): ret.append([str(server.uuid), server.address, group.master == server.uuid]) return ret
def verify_and_fetch_shard(shard_id): """Find out if the shard_id exists and return the sharding specification for it. If it does not exist throw an exception. :param shard_id: The ID for the shard whose specification needs to be fetched. :return: The sharding specification class representing the shard ID. :raises: ShardingError if the shard ID is not found. """ #Here the underlying sharding specification might be a RANGE #or a HASH. The type of sharding specification is obtained from the #shard mapping. range_sharding_spec = RangeShardingSpecification.fetch(shard_id) if range_sharding_spec is None: raise _errors.ShardingError(SHARD_NOT_FOUND % (shard_id, )) #Fetch the shard mappings and use them to find the type of sharding #scheme. shard_mappings = ShardMapping.fetch_by_id( range_sharding_spec.shard_mapping_id ) if shard_mappings is None: raise _errors.ShardingError( SHARD_MAPPING_NOT_FOUND % ( range_sharding_spec.shard_mapping_id, ) ) #Fetch the shard mapping definition. There is only one shard mapping #definition associated with all of the shard mappings. shard_mapping_defn = ShardMapping.fetch_shard_mapping_defn( range_sharding_spec.shard_mapping_id ) if shard_mapping_defn is None: raise _errors.ShardingError( SHARD_MAPPING_DEFN_NOT_FOUND % ( range_sharding_spec.shard_mapping_id, ) ) shard = Shards.fetch(shard_id) if shard is None: raise _errors.ShardingError(SHARD_NOT_FOUND % (shard_id, )) #Both of the shard_mappings retrieved will be of the same sharding #type. Hence it is safe to use one of them to retireve the sharding type. if shard_mappings[0].type_name == "HASH": return HashShardingSpecification.fetch(shard_id), \ shard, shard_mappings, shard_mapping_defn else: return range_sharding_spec, shard, shard_mappings, shard_mapping_defn
def _undo_add_shard_range_check(shard_mapping_id, shard_type): """Remove the shard ranges in the metadata tables of the shards :param shard_mapping_id: The shard mapping ID of the shards for which the metadata needs to be added. :param shard_type: The datatype of the sharding definition. """ shard_list = SHARDING_SPECIFICATION_HANDLER[shard_type].list(shard_mapping_id) for shard in shard_list: group_id = Shards.fetch(shard.shard_id).group_id SHARD_METADATA.delete_shard_meta_data(group_id, shard.shard_id) SHARD_METADATA.drop_shard_meta_data(group_id)
def _undo_add_shard_range_check(shard_mapping_id, shard_type): """Remove the shard ranges in the metadata tables of the shards :param shard_mapping_id: The shard mapping ID of the shards for which the metadata needs to be added. :param shard_type: The datatype of the sharding definition. """ shard_list = SHARDING_SPECIFICATION_HANDLER[shard_type].list( shard_mapping_id) for shard in shard_list: group_id = Shards.fetch(shard.shard_id).group_id SHARD_METADATA.delete_shard_meta_data(group_id, shard.shard_id) SHARD_METADATA.drop_shard_meta_data(group_id)
def _lookup(lookup_arg, key, hint): """Given a table name and a key return the servers of the Group where the shard of this table can be found :param lookup_arg: table name for "LOCAL" lookups Shard Mapping ID for "GLOBAL" lookups. :param key: The key value that needs to be looked up :param hint: A hint indicates if the query is LOCAL or GLOBAL :return: The servers of the Group that contains the range in which the key belongs. """ VALID_HINTS = ('LOCAL', 'GLOBAL') hint = hint.upper() if hint not in VALID_HINTS: raise _errors.ShardingError(INVALID_SHARDING_HINT) group = None #Perform the lookup for the group contaning the lookup data. if hint == "GLOBAL": #Fetch the shard mapping object. In the case of GLOBAL lookups #the shard mapping ID is passed directly. In the case of "LOCAL" #lookups it is the table name that is passed. shard_mapping = ShardMapping.fetch_by_id(lookup_arg) if shard_mapping is None: raise _errors.ShardingError(SHARD_MAPPING_NOT_FOUND % (lookup_arg, )) #GLOBAL lookups. There can be only one global group, hence using #shard_mapping[0] is safe. group_id = shard_mapping[0].global_group else: shard_mapping = ShardMapping.fetch(lookup_arg) if shard_mapping is None: raise _errors.ShardingError(TABLE_NAME_NOT_FOUND % (lookup_arg, )) sharding_specification = \ SHARDING_SPECIFICATION_HANDLER[shard_mapping.type_name].\ lookup(key, shard_mapping.shard_mapping_id, shard_mapping.type_name) if sharding_specification is None: raise _errors.ShardingError(INVALID_SHARDING_KEY % (key, )) shard = Shards.fetch(str(sharding_specification.shard_id)) if shard.state == "DISABLED": raise _errors.ShardingError(SHARD_NOT_ENABLED) #group cannot be None since there is a foreign key on the group_id. #An exception will be thrown nevertheless. group_id = shard.group_id return ServerLookups().execute(group_id=group_id)
def verify_and_fetch_shard(shard_id): """Find out if the shard_id exists and return the sharding specification for it. If it does not exist throw an exception. :param shard_id: The ID for the shard whose specification needs to be fetched. :return: The sharding specification class representing the shard ID. :raises: ShardingError if the shard ID is not found. """ #Here the underlying sharding specification might be a RANGE #or a HASH. The type of sharding specification is obtained from the #shard mapping. range_sharding_spec = RangeShardingSpecification.fetch(shard_id) if range_sharding_spec is None: raise _errors.ShardingError(SHARD_NOT_FOUND % (shard_id, )) #Fetch the shard mappings and use them to find the type of sharding #scheme. shard_mappings = ShardMapping.fetch_by_id( range_sharding_spec.shard_mapping_id) if shard_mappings is None: raise _errors.ShardingError(SHARD_MAPPING_NOT_FOUND % (range_sharding_spec.shard_mapping_id, )) #Fetch the shard mapping definition. There is only one shard mapping #definition associated with all of the shard mappings. shard_mapping_defn = ShardMapping.fetch_shard_mapping_defn( range_sharding_spec.shard_mapping_id) if shard_mapping_defn is None: raise _errors.ShardingError(SHARD_MAPPING_DEFN_NOT_FOUND % (range_sharding_spec.shard_mapping_id, )) shard = Shards.fetch(shard_id) if shard is None: raise _errors.ShardingError(SHARD_NOT_FOUND % (shard_id, )) #Both of the shard_mappings retrieved will be of the same sharding #type. Hence it is safe to use one of them to retireve the sharding type. if shard_mappings[0].type_name == "HASH": return HashShardingSpecification.fetch(shard_id), \ shard, shard_mappings, shard_mapping_defn else: return range_sharding_spec, shard, shard_mappings, shard_mapping_defn
def _undo_remove_shard(shard_id, update_only=False): """Recreate the shard metadata. :param shard_id: The ID of the shard that was being removed. :param update_only: Only update the state store and skip adding range checks. """ if update_only: return group_id = Shards.fetch(shard_id).group_id try: SHARD_METADATA.create_shard_meta_data(group_id) except _errors.DatabaseError as error: #If the create fails, do not insert the metadata. return spec_handler = SHARDING_SPECIFICATION_HANDLER[shard_type] upper_bound = spec_handler.get_upper_bound(shard.lower_bound, shard_mapping_id, shard_type) SHARD_METADATA.insert_shard_meta_data(shard.shard_id, shard.lower_bound, upper_bound, group_id)
def _add_shard_range_check(shard_mapping_id, shard_type): """Add the shard ranges in the metadata tables of the shards :param shard_mapping_id: The shard mapping ID of the shards for which the metadata needs to be added. :param shard_type: The datatype of the sharding definition. """ shard_list = SHARDING_SPECIFICATION_HANDLER[shard_type].list( shard_mapping_id) for shard in shard_list: group_id = Shards.fetch(shard.shard_id).group_id #The metadata has to be created before adding the ranges because #the global group might have still not replicated the schema to the #shard group. If the replication has still not happened, adding #the bounds will throw an error. SHARD_METADATA.create_shard_meta_data(group_id) spec_handler = SHARDING_SPECIFICATION_HANDLER[shard_type] upper_bound = spec_handler.get_upper_bound(shard.lower_bound, shard_mapping_id, shard_type) SHARD_METADATA.insert_shard_meta_data(shard.shard_id, shard.lower_bound, upper_bound, group_id)
def _check_shard_information(shard_id, destn_group_id, mysqldump_binary, mysqlclient_binary, split_value, config_file, prune_limit, cmd, update_only): """Verify the sharding information before starting a re-sharding operation. :param shard_id: The destination shard ID. :param destn_group_id: The Destination group ID. :param mysqldump_binary: The path to the mysqldump binary. :param mysqlclient_binary: The path to the mysqlclient binary. :param split_value: The point at which the sharding definition should be split. :param config_file: The complete path to the fabric configuration file. :param prune_limit: The number of DELETEs that should be done in one batch. :param cmd: Indicates if it is a split or a move being executed. :param update_only: If the operation is a update only operation. """ if not _services_utils.is_valid_binary(mysqldump_binary): raise _errors.ShardingError( _services_sharding.MYSQLDUMP_NOT_FOUND % mysqldump_binary) if not _services_utils.is_valid_binary(mysqlclient_binary): raise _errors.ShardingError( _services_sharding.MYSQLCLIENT_NOT_FOUND % mysqlclient_binary) if cmd == "SPLIT": range_sharding_spec, _, shard_mappings, _ = \ _services_sharding.verify_and_fetch_shard(shard_id) upper_bound = \ SHARDING_SPECIFICATION_HANDLER[shard_mappings[0].type_name].\ get_upper_bound( range_sharding_spec.lower_bound, range_sharding_spec.shard_mapping_id, shard_mappings[0].type_name ) #If the underlying sharding scheme is a HASH. When a shard is split, #all the tables that are part of the shard, have the same sharding #scheme. All the shard mappings associated with this shard_id will be #of the same sharding type. Hence it is safe to use one of the shard #mappings. if shard_mappings[0].type_name == "HASH": if split_value is not None: raise _errors.ShardingError( _services_sharding.NO_LOWER_BOUND_FOR_HASH_SHARDING ) if upper_bound is None: #While splitting a range, retrieve the next upper bound and #find the mid-point, in the case where the next upper_bound #is unavailable pick the maximum value in the set of values in #the shard. upper_bound = HashShardingSpecification.fetch_max_key(shard_id) #Calculate the split value. split_value = \ SHARDING_DATATYPE_HANDLER[shard_mappings[0].type_name].\ split_value( range_sharding_spec.lower_bound, upper_bound ) elif split_value is not None: if not (SHARDING_DATATYPE_HANDLER[shard_mappings[0].type_name].\ is_valid_split_value( split_value, range_sharding_spec.lower_bound, upper_bound ) ): raise _errors.ShardingError( _services_sharding.INVALID_LOWER_BOUND_VALUE % (split_value, ) ) elif split_value is None: raise _errors.ShardingError( _services_sharding.SPLIT_VALUE_NOT_DEFINED ) #Ensure that the group does not already contain a shard. if Shards.lookup_shard_id(destn_group_id) is not None: raise _errors.ShardingError( _services_sharding.SHARD_MOVE_DESTINATION_NOT_EMPTY % (destn_group_id, ) ) #Fetch the group information for the source shard that #needs to be moved. source_shard = Shards.fetch(shard_id) if source_shard is None: raise _errors.ShardingError( _services_sharding.SHARD_NOT_FOUND % (shard_id, )) #Fetch the group_id and the group that hosts the source shard. source_group_id = source_shard.group_id destn_group = Group.fetch(destn_group_id) if destn_group is None: raise _errors.ShardingError( _services_sharding.SHARD_GROUP_NOT_FOUND % (destn_group_id, )) if not update_only: _events.trigger_within_procedure( BACKUP_SOURCE_SHARD, shard_id, source_group_id, destn_group_id, mysqldump_binary, mysqlclient_binary, split_value, config_file, prune_limit, cmd, update_only ) else: _events.trigger_within_procedure( SETUP_RESHARDING_SWITCH, shard_id, source_group_id, destn_group_id, split_value, prune_limit, cmd, update_only )
def _check_shard_information(shard_id, destn_group_id, mysqldump_binary, mysqlclient_binary, split_value, config_file, prune_limit, cmd, update_only): """Verify the sharding information before starting a re-sharding operation. :param shard_id: The destination shard ID. :param destn_group_id: The Destination group ID. :param mysqldump_binary: The path to the mysqldump binary. :param mysqlclient_binary: The path to the mysqlclient binary. :param split_value: The point at which the sharding definition should be split. :param config_file: The complete path to the fabric configuration file. :param prune_limit: The number of DELETEs that should be done in one batch. :param cmd: Indicates if it is a split or a move being executed. :param update_only: If the operation is a update only operation. """ if not _services_utils.is_valid_binary(mysqldump_binary): raise _errors.ShardingError(_services_sharding.MYSQLDUMP_NOT_FOUND % mysqldump_binary) if not _services_utils.is_valid_binary(mysqlclient_binary): raise _errors.ShardingError(_services_sharding.MYSQLCLIENT_NOT_FOUND % mysqlclient_binary) if cmd == "SPLIT": range_sharding_spec, _, shard_mappings, _ = \ _services_sharding.verify_and_fetch_shard(shard_id) upper_bound = \ SHARDING_SPECIFICATION_HANDLER[shard_mappings[0].type_name].\ get_upper_bound( range_sharding_spec.lower_bound, range_sharding_spec.shard_mapping_id, shard_mappings[0].type_name ) #If the underlying sharding scheme is a HASH. When a shard is split, #all the tables that are part of the shard, have the same sharding #scheme. All the shard mappings associated with this shard_id will be #of the same sharding type. Hence it is safe to use one of the shard #mappings. if shard_mappings[0].type_name == "HASH": if split_value is not None: raise _errors.ShardingError( _services_sharding.NO_LOWER_BOUND_FOR_HASH_SHARDING) if upper_bound is None: #While splitting a range, retrieve the next upper bound and #find the mid-point, in the case where the next upper_bound #is unavailable pick the maximum value in the set of values in #the shard. upper_bound = HashShardingSpecification.fetch_max_key(shard_id) #Calculate the split value. split_value = \ SHARDING_DATATYPE_HANDLER[shard_mappings[0].type_name].\ split_value( range_sharding_spec.lower_bound, upper_bound ) elif split_value is not None: if not (SHARDING_DATATYPE_HANDLER[shard_mappings[0].type_name].\ is_valid_split_value( split_value, range_sharding_spec.lower_bound, upper_bound ) ): raise _errors.ShardingError( _services_sharding.INVALID_LOWER_BOUND_VALUE % (split_value, )) elif split_value is None: raise _errors.ShardingError( _services_sharding.SPLIT_VALUE_NOT_DEFINED) #Ensure that the group does not already contain a shard. if Shards.lookup_shard_id(destn_group_id) is not None: raise _errors.ShardingError( _services_sharding.SHARD_MOVE_DESTINATION_NOT_EMPTY % (destn_group_id, )) #Fetch the group information for the source shard that #needs to be moved. source_shard = Shards.fetch(shard_id) if source_shard is None: raise _errors.ShardingError(_services_sharding.SHARD_NOT_FOUND % (shard_id, )) #Fetch the group_id and the group that hosts the source shard. source_group_id = source_shard.group_id destn_group = Group.fetch(destn_group_id) if destn_group is None: raise _errors.ShardingError(_services_sharding.SHARD_GROUP_NOT_FOUND % (destn_group_id, )) if not update_only: _events.trigger_within_procedure(BACKUP_SOURCE_SHARD, shard_id, source_group_id, destn_group_id, mysqldump_binary, mysqlclient_binary, split_value, config_file, prune_limit, cmd, update_only) else: _events.trigger_within_procedure(SETUP_RESHARDING_SWITCH, shard_id, source_group_id, destn_group_id, split_value, prune_limit, cmd, update_only)