"""
This script test if a Swift cluster ( or a swiftsore server) is under replication
"""


import os, sys, socket
import eventlet
from swift.cli import recon
from swift.common.ring import RingBuilder
from urllib.parse import urlparse

# Initialize Swift connection
swiftrecon = recon.SwiftRecon()
pool_size = 30
pool = eventlet.GreenPool(pool_size)

try:
    builder = RingBuilder.load('/etc/swift/object.builder')
except exceptions.UnPicklingError as e:
    sys.exit(e)

def analyze_ring():
    """
    Analyze the ring for retrieve informations about host or cluster
    If script running in a switstore, he check only this server,
    otherwise, he check all cluster
    return: list off tuble with ip and port, total partitions on this hosts
            parttion to be emptied ans filled
    """
    hosts = {}
    parts = 0
    filling = {}
    emptying = {}
    devs = [ d for d in builder.devs if d]
    # If server have disk, use it, else use all swiftstore
    ip = socket.gethostbyname(socket.gethostname())
    devs_ip = [d for d in devs if d['ip'] == ip]
    if devs_ip:
        devs = devs_ip
    for h in devs:
        hosts[h['ip']] = h['port']
        parts += h['parts']
        if h['parts_wanted'] < -builder.parts:
            # If emtying swiftstore, parts-wanted is 
            # (builder.parts * builder.replicas) - 
            # total.parts to rebalance + device parts to rebalance
            # So is always < -builder.parts
            emptying.setdefault(h['ip'], 0)
            emptying[h['ip']] += h['parts']
        elif h['parts_wanted'] < 0:
            # If rebalance after add swifstore, this juste parts number
            # in swiftstore that they are emptied
            emptying.setdefault(h['ip'], 0)
            emptying[h['ip']] -= h['parts_wanted']
        elif  h['parts_wanted'] > 0:
            # In all case this number of part of device that theyr are filled
            filling.setdefault(h['ip'], 0)
            filling[h['ip']] += h['parts_wanted']
    return set(hosts.items()),parts,filling,emptying

def get_attempted(hosts):
    """
    Analyze all hosts
    return: tuple with number of replication in progress
    """
    scout = recon.Scout(recon_type="replication/object", suppress_errors=True)
    attempted = {}
    for url, response, status, _ts_start, _ts_end in pool.imap(scout.scout, hosts):
        if status == 200:
            ip = urlparse(url).hostname
            attempted[ip] = response['replication_stats']['attempted']
    return sum(attempted.values())

def main():
    """
    Entry point for the script
    """
    hosts,parts,filling,emptying = analyze_ring()
    total_emptying = sum(emptying.values())
    total_filling = sum(filling.values())
    percent_emptying = total_emptying /parts
    percent_filling = total_filling / parts
    if total_emptying or total_filling:
        print("Dispersion for the cluster is not optimal !")
        print(f"{total_filling} parts remains to be filled ({percent_filling:.2%}) on {len(filling)} hosts")
        print(f"{total_emptying} parts remains to be emptied ({percent_emptying:.2%}) on {len(emptying)} hosts")
        print("You can re-run 'swift-ring-builder object.builder rebalance' AFTER the replication is OK !")
        print("=" * 80)

    attempted = get_attempted(hosts)
    inProgress = abs(attempted - parts)
    perCent = 1 - inProgress / parts
    if inProgress:
        print(f"Replication in progress: {inProgress} parts to sync ({perCent:.2%} Ok) ")
    else:
        print("Replication OK")

if __name__ == "__main__":
    sys.exit(main())
