def create(): """ swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours> Creates <builder_file> with 2^<part_power> partitions and <replicas>. <min_part_hours> is number of hours to restrict moving a partition more than once. OR swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours> <policy_info_file> policy_info_file contains the information about the policy """ if len(argv) < 6: print Commands.create.__doc__.strip() exit(EXIT_ERROR) if len(argv) > 6: builder = RingBuilder(int(argv[3]), float(argv[4]), int(argv[5]), argv[6]) else: builder = RingBuilder(int(argv[3]), float(argv[4]), int(argv[5])) backup_dir = pathjoin(dirname(argv[1]), 'backups') try: mkdir(backup_dir) except OSError as err: if err.errno != EEXIST: raise builder.save(pathjoin(backup_dir, '%d.' % time() + basename(argv[1]))) builder.save(argv[1]) exit(EXIT_SUCCESS)
def test_composite_same_device_in_the_different_rings_error(self): builders = self.create_sample_ringbuilders(2) same_device = copy.deepcopy(builders[0].devs[0]) # create one more ring which duplicates a device in the first ring builder = RingBuilder(6, 3, 1) _, fname = tempfile.mkstemp(dir=self.tmpdir) # add info to feed to add_dev same_device.update({'region': 2, 'weight': 100}) builder.add_dev(same_device) # add rest of the devices, which are unique for _ in range(3): dev = self.pop_region_device(2) builder.add_dev(dev) builder.rebalance() builder.save(fname) # sanity self.assertTrue(os.path.exists(fname)) builders.append(builder) with self.assertRaises(ValueError) as cm: compose_rings(builders) self.assertIn( 'Duplicate ip/port/device combination %(ip)s/%(port)s/%(device)s ' 'found in builders at indexes 0 and 2' % same_device, cm.exception.message)
def test_different_part_power_error(self): # create a ring builder # (default, part power is 6 with create_sample_ringbuilders) builders = self.create_sample_ringbuilders(1) # prepare another ring which has different part power incorrect_builder = RingBuilder(4, 3, 1) _, fname = tempfile.mkstemp(dir=self.tmpdir) for _ in range(4): dev = self.pop_region_device(1) incorrect_builder.add_dev(dev) incorrect_builder.rebalance() incorrect_builder.save(fname) # sanity self.assertTrue(os.path.exists(fname)) # sanity correct_builder = builders[0] self.assertNotEqual(correct_builder.part_shift, incorrect_builder.part_shift) self.assertNotEqual(correct_builder.part_power, incorrect_builder.part_power) builders.append(incorrect_builder) with self.assertRaises(ValueError) as cm: compose_rings(builders) self.assertIn("All builders must have same value for 'part_power'", cm.exception.message)
def write_stub_builder(tmpdir, region=1, name=''): """ Pretty much just a three node, three replica, 8 part power builder... :param tmpdir: a place to write the builder, be sure to clean it up! :param region: an integer, fills in region and ip :param name: the name of the builder (i.e. <name>.builder) """ name = name or str(region) replicas = 3 builder = RingBuilder(8, replicas, 1) for i in range(replicas): dev = { 'weight': 100, 'region': '%d' % region, 'zone': '1', 'ip': '10.0.0.%d' % region, 'port': '3600', 'device': 'sdb%d' % i } builder.add_dev(dev) builder.rebalance() builder_file = os.path.join(tmpdir, '%s.builder' % name) builder.save(builder_file) return builder, builder_file
def test_compose_rings_float_replica_count_builder_error(self): builders = self.create_sample_ringbuilders(1) # prepare another ring which has float replica count incorrect_builder = RingBuilder(6, 1.5, 1) _, fname = tempfile.mkstemp(dir=self.tmpdir) for _ in range(4): dev = self.pop_region_device(1) incorrect_builder.add_dev(dev) incorrect_builder.rebalance() incorrect_builder.save(fname) # sanity self.assertTrue(os.path.exists(fname)) self.assertEqual(1.5, incorrect_builder.replicas) # the first replica has 2 ** 6 partitions self.assertEqual( 2 ** 6, len(incorrect_builder._replica2part2dev[0])) # but the second replica has the half of the first partitions self.assertEqual( 2 ** 5, len(incorrect_builder._replica2part2dev[1])) builders.append(incorrect_builder) with self.assertRaises(ValueError) as cm: compose_rings(builders) self.assertIn("Problem with builders", cm.exception.message) self.assertIn("Non integer replica count", cm.exception.message)
def create_sample_ring(self): """ Create a sample ring with two devices At least two devices are needed to test removing a device, since removing the last device of a ring is not allowed """ # Ensure there is no existing test builder file because # create_sample_ring() might be used more than once in a single test try: os.remove(self.tmpfile) except OSError: pass ring = RingBuilder(6, 3, 1) ring.add_dev({'weight': 100.0, 'region': 0, 'zone': 0, 'ip': '127.0.0.1', 'port': 6000, 'device': 'sda1', 'meta': 'some meta data', }) ring.add_dev({'weight': 100.0, 'region': 1, 'zone': 1, 'ip': '127.0.0.2', 'port': 6001, 'device': 'sda2' }) ring.save(self.tmpfile)
def gen_builder(self, balanced=False): builder = RingBuilder(18, 3, 1) for i in xrange(self.device_count): zone = i ipaddr = "1.1.1.1" port = 6010 device_name = "sd%s" % i weight = 100.0 meta = "meta for %s" % i next_dev_id = 0 if builder.devs: next_dev_id = max(d['id'] for d in builder.devs if d) + 1 builder.add_dev({ 'id': next_dev_id, 'zone': zone, 'ip': ipaddr, 'port': int(port), 'device': device_name, 'weight': weight, 'meta': meta }) # add an empty dev builder.devs.append(None) if balanced: builder.rebalance() return builder
def test_reconcile_remove_device(self): # given a ring builder with two devices b = RingBuilder(10, 1, 1) r = RingController(b, "object") ds = ["r1z1-192.168.0.2:6000/d3", "r1z2-192.168.2.2:5000/d1"] r.reconcile(ds) # when device is removed ds = ["r1z2-192.168.2.2:5000/d1"] r.reconcile(ds) # then only one device is present devs = b.search_devs([]) self.assertEqual(1, len(devs)) self.assertEqual(devs, [{ 'device': 'd1', 'id': 1, 'ip': '192.168.2.2', 'meta': '', 'parts': 1024, 'parts_wanted': 0, 'port': 5000, 'region': 1, 'replication_ip': None, 'replication_port': None, 'weight': 1.0, 'zone': 2 }])
def create_sample_ringbuilders(self, num_builders=2): """ Create sample rings with four devices :returns: a list of ring builder instances """ builders = [] for region in range(num_builders): fname = os.path.join(self.tmpdir, 'builder_%s.builder' % region) builder = RingBuilder(6, 3, 0) for _ in range(5): dev = self.pop_region_device(region) builder.add_dev(dev) # remove last dev to simulate a ring with some history builder.remove_dev(dev['id']) # add a dev that won't be assigned any parts new_dev = self.pop_region_device(region) new_dev['weight'] = 0 builder.add_dev(new_dev) builder.rebalance() builder.save(fname) self.assertTrue(os.path.exists(fname)) builders.append(builder) return builders
def create(): """ swift-ring-builder <builder_file> create <part_power> <replicas> <min_part_hours> Creates <builder_file> with 2^<part_power> partitions and <replicas>. <min_part_hours> is number of hours to restrict moving a partition more than once. """ if len(argv) < 6: print(Commands.create.__doc__.strip()) exit(EXIT_ERROR) # 1、生成RingBuilder对象实例 builder = RingBuilder(int(argv[3]), float(argv[4]), int(argv[5])) # 2、创建备份目录 backup_dir = pathjoin(dirname(builder_file), 'backups') try: mkdir(backup_dir) except OSError as err: if err.errno != EEXIST: raise # 3、保存原始数据到备份目录,以及/etc/swift目录中 builder.save( pathjoin(backup_dir, '%d.' % time() + basename(builder_file))) builder.save(builder_file) exit(EXIT_SUCCESS)
def _load_builder(path): # lifted straight from /usr/bin/swift-ring-builder from swift.common.ring import RingBuilder try: builder = pickle.load(open(path, 'rb')) if not hasattr(builder, 'devs'): builder_dict = builder builder = RingBuilder(1, 1, 1) builder.copy_from(builder_dict) except ImportError: # Happens with really old builder pickles builder = RingBuilder(1, 1, 1) builder.copy_from(pickle.load(open(path, 'rb'))) for dev in builder.devs: if dev and 'meta' not in dev: dev['meta'] = '' return builder
def storage_policies(request): """ Creates a storage policy to swift with an specific ring. Allows create replication storage policies and erasure code storage policies """ if request.method == "GET": try: r = get_redis_connection() except RedisError: return JSONResponse('Error connecting with DB', status=status.HTTP_500_INTERNAL_SERVER_ERROR) keys = r.keys("storage-policy:*") storage_policy_list = [] for key in keys: storage_policy = r.hgetall(key) to_json_bools(storage_policy, 'deprecated', 'default', 'deployed') storage_policy['id'] = str(key).split(':')[-1] storage_policy['devices'] = json.loads(storage_policy['devices']) storage_policy_list.append(storage_policy) return JSONResponse(storage_policy_list, status=status.HTTP_200_OK) if request.method == "POST": try: r = get_redis_connection() except RedisError: return JSONResponse('Error connecting with DB', status=status.HTTP_500_INTERNAL_SERVER_ERROR) data = JSONParser().parse(request) if data['policy_type'] == 'EC': data['replicas'] = int(data['ec_num_data_fragments']) + int( data['ec_num_parity_fragments']) try: sp_id = str(r.incr('storage-policies:id')) key = 'storage-policy:' + sp_id ring = RingBuilder(int(data['partition_power']), int(data['replicas']), int(data['time'])) ring.save(get_policy_file_path(settings.SWIFT_CFG_TMP_DIR, sp_id)) r.hmset(key, data) except: return JSONResponse('Error creating the Storage Policy', status=status.HTTP_500_INTERNAL_SERVER_ERROR) return JSONResponse('Account created successfully', status=status.HTTP_201_CREATED) return JSONResponse('Only HTTP POST requests allowed.', status=status.HTTP_405_METHOD_NOT_ALLOWED)
def _write_stub_builder(self, region): replicas = 3 builder = RingBuilder(8, replicas, 1) for i in range(replicas): dev = { 'weight': 100, 'region': '%d' % region, 'zone': '1', 'ip': '10.0.0.%d' % region, 'port': '3600', 'device': 'sdb%d' % i } builder.add_dev(dev) builder.rebalance() builder_file = os.path.join(self.tmpdir, '%d.builder' % region) builder.save(builder_file) return builder, builder_file
def write_ring(args, devices, builderfile): # Make an educated guess about the used port. These are the defaults for # TripleO-based deployments in Mitaka builder_fname = os.path.basename(builderfile) if 'account' in builder_fname: port = 6002 elif 'container' in builder_fname: port = 6001 elif 'object' in builder_fname: port = 6000 else: port = 6000 logging.debug('Set port for new devices to %d' % port) if not os.path.isfile(builderfile): logging.info('%s not found, creating new builder file', builderfile) rb = RingBuilder(args.part_power, args.replicas, args.min_part_hours) else: logging.info('Using existing builder file %s', builderfile) rb = RingBuilder.load(builderfile) # Add all missing devices for dev in devices: _dev = rb.search_devs({'meta': dev['meta']}) if not _dev: dev['weight'] = float(dev.get('size')) / 10**9 dev['region'] = 1 dev['zone'] = 1 dev['port'] = port dev['replication_ip'] = dev['ip'] dev['replication_port'] = dev['port'] rb.add_dev(dev) logging.info('Added device %s / %s', dev['ip'], dev['device']) else: logging.info('Ignoring existing device %s / %s', dev['ip'], dev['device']) try: rb.rebalance() except RingValidationError as exc: logging.error(exc) rb.save(builderfile) ring_file = os.path.splitext(builderfile)[0] + '.ring.gz' ring_data = rb.get_ring() ring_data.save(ring_file) return [builderfile, ring_file]
def write_builder(): """ swift-ring-builder <ring_file> write_builder [min_part_hours] Recreate a builder from a ring file (lossy) if you lost your builder backups. (Protip: don't lose your builder backups). [min_part_hours] is one of those numbers lost to the builder, you can change it with set_min_part_hours. """ if exists(builder_file): print 'Cowardly refusing to overwrite existing ' \ 'Ring Builder file: %s' % builder_file exit(EXIT_ERROR) if len(argv) > 3: min_part_hours = int(argv[3]) else: stderr.write("WARNING: default min_part_hours may not match " "the value in the lost builder.\n") min_part_hours = 24 ring = Ring(ring_file) for dev in ring.devs: dev.update({ 'parts': 0, 'parts_wanted': 0, }) builder_dict = { 'part_power': 32 - ring._part_shift, 'replicas': float(ring.replica_count), 'min_part_hours': min_part_hours, 'parts': ring.partition_count, 'devs': ring.devs, 'devs_changed': False, 'version': 0, '_replica2part2dev': ring._replica2part2dev_id, '_last_part_moves_epoch': None, '_last_part_moves': None, '_last_part_gather_start': 0, '_remove_devs': [], } builder = RingBuilder(1, 1, 1) builder.copy_from(builder_dict) for parts in builder._replica2part2dev: for dev_id in parts: builder.devs[dev_id]['parts'] += 1 builder._set_parts_wanted() builder.save(builder_file)
def remake_rings(partpower=16, weight=100, replicas=3, min_part_hours=1, drives=False): for service in ['object', 'container', 'account']: builder_file = '/etc/swift/%s.builder' % service ring_file = '/etc/swift/%s.ring.gz' % service try: os.remove(builder_file) os.remove(ring_file) except OSError: pass builder = RingBuilder(partpower, replicas, min_part_hours) builder.save(builder_file) devstrs = _get_builder_strings(service, drives) for devstr in devstrs: new_dev = _parse_add_values(devstr, str(weight)) builder.add_dev(new_dev[0]) builder.save(builder_file) builder.rebalance(builder_file) builder.get_ring().save(ring_file)
def test_reconcile_add_devices(self): # given a empty ring builder b = RingBuilder(10, 1, 1) r = RingController(b, "object") # when new devices are added ds = ["r1z1-192.168.0.2:6000/d3", "r1z1-192.168.2.2:5000/d1"] r.reconcile(ds) # then new devices are present devs = b.search_devs([]) self.assertEqual(2, len(devs)) self.assertEqual(devs, [{ 'device': 'd3', 'id': 0, 'ip': '192.168.0.2', 'meta': '', 'parts': 512, 'parts_wanted': 0, 'port': 6000, 'region': 1, 'replication_ip': None, 'replication_port': None, 'weight': 1.0, 'zone': 1 }, { 'device': 'd1', 'id': 1, 'ip': '192.168.2.2', 'meta': '', 'parts': 512, 'parts_wanted': 0, 'port': 5000, 'region': 1, 'replication_ip': None, 'replication_port': None, 'weight': 1.0, 'zone': 1 }])
def main(argv): setup_logging() args = read_args() logger.debug(args) load_config() v1 = client.CoreV1Api() config_map = load_config_map(v1, args.config_map_name) if config_map.binary_data != None and args.ring_type in config_map.binary_data: builder_ = base64.b64decode(config_map.binary_data[args.ring_type]) logger.info("loading existing ring builder") builder = RingBuilder.load("", open=lambda a, b: io.BytesIO(builder_)) else: logger.info("creating a new ring builder") builder = RingBuilder(10, 1, 1) r = RingController(builder, args.ring_type, logger=logger) logger.info("reconciling ring") r.reconcile(args.devices) ring_data = r.get_ring_data() patch_config_map(v1, config_map.metadata, ring_data)
def test_get_ring_data(self): # given a ring builder with two devices b = RingBuilder(10, 1, 1) r = RingController(b, "object") ds = ["r1z1-192.168.0.2:6000/d3", "r1z2-192.168.2.2:5000/d1"] r.reconcile(ds) # when ring is serialized ring_data = r.get_ring_data() # then ring and builder is returned self.assertIn("object.ring.gz", ring_data) self.assertIn("object", ring_data) # then correct compressed ring is returned object_ring = decompress(base64.b64decode(ring_data["object.ring.gz"])) expected_ring = b'{"byteorder": "little", "devs": [{"device": "d3", "id": 0, "ip": "192.168.0.2", "meta": "", "port": 6000, "region": 1, "replication_ip": null, "replication_port": null, "weight": 1.0, "zone": 1}, {"device": "d1", "id": 1, "ip": "192.168.2.2", "meta": "", "port": 5000, "region": 1, "replication_ip": null, "replication_port": null, "weight": 1.0, "zone": 2}], "part_shift": 22, "replica_count": 1, "version": 3}' self.assertIn(expected_ring, object_ring) # then correct serialized builder is returned builder_ = base64.b64decode(ring_data["object"]) loadedBuilder = RingBuilder.load( "", open=lambda a, b: io.BytesIO(builder_)) self.assertEqual(b.devs, loadedBuilder.devs)
def test_different_replica_count_works(self): # create a ring builder # (default, part power is 6 with create_sample_ringbuilders) builders = self.create_sample_ringbuilders(1) # prepare another ring which has different replica count builder = RingBuilder(6, 1, 1) _, fname = tempfile.mkstemp(dir=self.tmpdir) for _ in range(4): dev = self.pop_region_device(1) builder.add_dev(dev) builder.rebalance() builder.save(fname) # sanity self.assertTrue(os.path.exists(fname)) builders.append(builder) rd = compose_rings(builders) rd.save(self.output_ring) got_ring = Ring(self.output_ring) self.assertEqual(got_ring.partition_count, 2**6) self.assertEqual(got_ring.replica_count, 4) # 3 + 1 self.assertEqual(got_ring._part_shift, 26) self.assertDevices(got_ring, builders)
def initialize_ring(path, part_power, replicas, min_hours): """Initialize a new swift ring with given parameters.""" from swift.common.ring import RingBuilder ring = RingBuilder(part_power, replicas, min_hours) _write_ring(ring, path)
import math import pickle """ https://docs.openstack.org/swift/pike/admin/objectstorage-troubleshoot.html Using existing swift tools, there is no way to recover a builder file from a ring.gz file. However, if you have a knowledge of Python, it is possible to construct a builder file that is pretty close to the one you have lost. """ ring = RingData.load('/home/shahbazi/Desktop/rings/account.ring.gz') partitions = len(ring._replica2part2dev_id[0]) replicas = len(ring._replica2part2dev_id) builder = RingBuilder(int(math.log(partitions, 2)), replicas, 1) builder.devs = ring.devs builder._replica2part2dev = ring._replica2part2dev_id builder._last_part_moves_epoch = 0 builder._last_part_moves = array('B', (0 for _ in range(partitions))) builder.change_min_part_hours(24) # builder._set_parts_wanted() for d in builder._iter_devs(): d['parts'] = 0