How to convert between replicas and original datasets in SynetoOS 4

Written By Christian Castagna (Administrator)

Updated at December 9th, 2024

→ Applies to: SynetoOS 4.x

 

Step 1. Connect to SynetoOS appliance via SSH as admin

ssh admin@<your_ip_address_or_hostname>

 

Step 2. Remove bash_profile

rm /var/storage/admin/.bash_profile

 

Step 3. Create file

touch /tmp/trans

 

Step 4. Give permissions to file

chmod +x trans

 

Step 5. Add this script to the file just created at step 3

#!/usr/bin/env python3

import sys
from hashlib import md5
from socket import gethostname
from argparse import ArgumentParser
from collections import defaultdict
from json import loads, dumps
from subprocess import Popen, PIPE
from logging import basicConfig, debug, error, DEBUG  # , info, warning

basicConfig(filename='/var/log/trans.log', level=DEBUG)

parser = ArgumentParser()
parser.add_argument("--list", help="List replicas", action="store_true")
parser.add_argument("--serial", help="This host Serial Number", action="store_true")
parser.add_argument("--to-local", type=str, nargs='+',
                    help="Transform a replica into a local dataset", action="store")
parser.add_argument("--to-replica", type=str, nargs='+',
                    help="Transform a local dataset into a replica", action="store")
if len(sys.argv) == 1:
    parser.print_help(sys.stderr)
    sys.exit(1)
args = parser.parse_args()


def _run(cmd):
    result = Popen(cmd, shell=True, stdout=PIPE, stderr=PIPE, close_fds=True)
    stdout, stderr = result.communicate()
    if len(stderr.decode()) != 0:
        error('{0} failed with {1}'.format(cmd, stderr))
        sys.exit(0)
    else:
        return stdout.decode()


def ser2id(s, newline=True):
    if newline:
        s = '{}\n'.format(s)
    return md5(s.encode()).hexdigest()[:8]


def _replicas():
    """Return a dict with metadata of replica datasets"""
    datasets_cmd = 'zfs list -Hro name'
    dataset_list = [i for i in _run(datasets_cmd).split('\n') if i]
    replicas = defaultdict(dict)
    for dataset in dataset_list:
        metadata_cmd = 'zfs-meta get {}'.format(dataset)
        metadata = loads(_run(metadata_cmd))
        if metadata:
            if 'createdOn' in metadata.keys():
                serial_number = '{}\n'.format(metadata['createdOn']['serialNumber'])
                replicas[metadata['name']] = metadata
                if 'trans' not in metadata.keys():
                    replicas[metadata['name']]['trans'] = {
                        'zfspath': dataset,
                        'machineid': ser2id(serial_number),
                        'createdOn': metadata['createdOn']
                    }

    return replicas


def to_local(dataset_name):
    metadata = _replicas()[dataset_name]
    if len(metadata['hosts']) != 0:
        print('{} is mounted. Unmount and try again.\n'.format(dataset_name))
        sys.exit(0)

    metadata['createdOn'] = {
        'domain': metadata['createdOn']['domain'],
        'poolName': metadata['createdOn']['poolName'],
        'hostname': gethostname(),
        'serialNumber': LOCAL
    }
    zfspath = metadata['trans']['zfspath']
    if zfspath.split('/').count('backups') > 1:
        pool = zfspath.split('/')[-3]
    else:
        pool = zfspath.split('/')[0]
    metadata_set_cmd = "echo '{0}' | zfs-meta set {1}".format(dumps(metadata), zfspath)
    out = _run(metadata_set_cmd)
    debug('writing metadata on {}: {}'.format(dataset_name, out))

    rename_cmd = 'sudo zfs rename -f {0} {1}/datastores/{2}'.format(zfspath, pool, dataset_name)
    out = _run(rename_cmd)
    debug('renaming dataset {} like so: {}'.format(dataset_name, rename_cmd))


def to_replica(dataset_name):
    metadata = _replicas()[dataset_name]
    # check if the dataset is mounted
    if len(metadata['hosts']) != 0:
        print('{} is mounted. Unmount and try again.\n'.format(dataset_name))
        sys.exit(0)

    if 'trans' in metadata.keys():
        zfspath = metadata['trans']['zfspath']
        pool = zfspath.split('/')[0]
        metadata['createdOn'] = metadata['trans']['createdOn']
    else:
        zfspath = None  # build the zfspath

    rename_cmd = 'sudo zfs rename {0}/datasores/{1} {2}'.format(pool, dataset_name, zfspath)
    out = _run(rename_cmd)
    debug('renaming dataset {} like so: {}'.format(dataset_name, rename_cmd))
    metadata_set_cmd = "echo '{0}' | zfs-meta set {1}".format(dumps(metadata), zfspath)
    out = _run(metadata_set_cmd)
    debug('writing metadata on {}: {}'.format(dataset_name, out))


if __name__ == "__main__":
    datasets = _replicas()
    LOCAL = _run('smbios -t 1|grep Serial').split(':')[1].strip()
    if args.serial:
        print(LOCAL)
    if args.list:
        for k, v in datasets.items():
            name = k
            sn = v['createdOn']['serialNumber']
            hostname = v['createdOn']['hostname']
            mounted = len(v['hosts']) != 0
            
            if sn == LOCAL:
                kind = 'Local dataset'
            else:
                kind = 'Replica'
            text = '{}: {} Mounted: {} From: {} ({})'.format(kind, name, mounted, hostname, sn)
            print(text)

    if args.to_local:
        for ds in args.to_local:
            # TODO: check the kind of dataset and prevent transforming a replica into a replica 
            if ds in datasets.keys() and datasets[ds]['createdOn']['serialNumber'] != LOCAL:
                print('convert replica into local dataset: ', ds)
                to_local(ds)
            else:
                debug('{} is not a valid dataset. Ignoring.'.format(ds))
    if args.to_replica:
        for ds in args.to_replica:
            if ds in datasets.keys() and datasets[ds]['createdOn']['serialNumber'] == LOCAL:
                print('convert local dataset to replica: ', ds)
                to_replica(ds)
            else:
                debug('{} is not a valid dataset. Ignoring.'.format(ds))

 

Step 6. List replicas

./tmp/trans --list

EXAMPLE OUTPUT

# ./trans --list
Replica: ad     From: hyper-a-demo2 (VMware-56 4d c8 3f f2 83 ee e2-5e 88 eb 98 e8 c9 bb c5)
Replica: victim From: hyper-a-demo2 (VMware-56 4d c8 3f f2 83 ee e2-5e 88 eb 98 e8 c9 bb c5)

 

Step 7. Run the script (replace <datastore_name> with the correct information)

./trans --to-local <datastore_name>