mirror of
https://github.com/tuna/tunasync.git
synced 2025-04-20 20:22:46 +00:00
reload mirror jobs online
This commit is contained in:
parent
25fed222ff
commit
c80c35bba6
@ -5,5 +5,5 @@ tunasync
|
|||||||
|
|
||||||
- [ ] status file
|
- [ ] status file
|
||||||
- [x] btrfs backend (create snapshot before syncing)
|
- [x] btrfs backend (create snapshot before syncing)
|
||||||
- [ ] add mirror job online
|
- [x] add mirror job online
|
||||||
- [ ] debmirror provider
|
- [ ] debmirror provider
|
||||||
|
@ -2,12 +2,13 @@
|
|||||||
log_dir = /var/log/tunasync
|
log_dir = /var/log/tunasync
|
||||||
; mirror_root = /srv/mirror_disk
|
; mirror_root = /srv/mirror_disk
|
||||||
mirror_root = /mnt/sdb1/mirror
|
mirror_root = /mnt/sdb1/mirror
|
||||||
use_btrfs = yes
|
use_btrfs = no
|
||||||
local_dir = {mirror_root}/{mirror_name}/_working
|
local_dir = {mirror_root}/{mirror_name}/_working
|
||||||
; maximum numbers of running jobs
|
; maximum numbers of running jobs
|
||||||
concurrent = 3
|
concurrent = 2
|
||||||
; interval in minutes
|
; interval in minutes
|
||||||
interval = 120
|
interval = 1
|
||||||
|
max_retry = 2
|
||||||
|
|
||||||
[btrfs]
|
[btrfs]
|
||||||
service_dir = {mirror_root}/{mirror_name}/_current
|
service_dir = {mirror_root}/{mirror_name}/_current
|
||||||
@ -15,17 +16,17 @@ working_dir = {mirror_root}/{mirror_name}/_working
|
|||||||
tmp_dir = {mirror_root}/{mirror_name}/_tmp
|
tmp_dir = {mirror_root}/{mirror_name}/_tmp
|
||||||
|
|
||||||
|
|
||||||
[mirror:archlinux]
|
# rmirror:archlinux]
|
||||||
provider = rsync
|
# provider = rsync
|
||||||
upstream = rsync://mirror.us.leaseweb.net/archlinux/
|
# upstream = rsync://mirror.us.leaseweb.net/archlinux/
|
||||||
log_file = /tmp/archlinux-{date}.log
|
|
||||||
use_ipv6 = yes
|
|
||||||
|
|
||||||
# [mirror:archlinux]
|
|
||||||
# provider = shell
|
|
||||||
# command = sleep 10
|
|
||||||
# local_dir = /mnt/sdb1/mirror/archlinux/current/
|
|
||||||
# log_file = /tmp/archlinux-{date}.log
|
# log_file = /tmp/archlinux-{date}.log
|
||||||
|
# use_ipv6 = yes
|
||||||
|
|
||||||
|
[mirror:archlinux]
|
||||||
|
provider = shell
|
||||||
|
command = sleep 20
|
||||||
|
local_dir = /mnt/sdb1/mirror/archlinux/current/
|
||||||
|
log_file = /tmp/archlinux-{date}.log
|
||||||
|
|
||||||
|
|
||||||
[mirror:arch2]
|
[mirror:arch2]
|
||||||
@ -40,3 +41,9 @@ provider = shell
|
|||||||
command = ./shell_provider.sh
|
command = ./shell_provider.sh
|
||||||
log_file = /tmp/arch3-{date}.log
|
log_file = /tmp/arch3-{date}.log
|
||||||
use_btrfs = no
|
use_btrfs = no
|
||||||
|
|
||||||
|
[mirror:arch4]
|
||||||
|
provider = shell
|
||||||
|
command = ./shell_provider.sh
|
||||||
|
log_file = /tmp/arch4-{date}.log
|
||||||
|
use_btrfs = no
|
||||||
|
@ -2,13 +2,14 @@
|
|||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
import sh
|
import sh
|
||||||
import os
|
import os
|
||||||
|
from .hook import JobHook
|
||||||
|
|
||||||
|
|
||||||
class BtrfsVolumeError(Exception):
|
class BtrfsVolumeError(Exception):
|
||||||
pass
|
pass
|
||||||
|
|
||||||
|
|
||||||
class BtrfsHook(object):
|
class BtrfsHook(JobHook):
|
||||||
|
|
||||||
def __init__(self, service_dir, working_dir, tmp_dir):
|
def __init__(self, service_dir, working_dir, tmp_dir):
|
||||||
self.service_dir = service_dir
|
self.service_dir = service_dir
|
||||||
|
13
tunasync/hook.py
Normal file
13
tunasync/hook.py
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
#!/usr/bin/env python2
|
||||||
|
# -*- coding:utf-8 -*-
|
||||||
|
|
||||||
|
|
||||||
|
class JobHook(object):
|
||||||
|
|
||||||
|
def before_job(self):
|
||||||
|
raise NotImplementedError("")
|
||||||
|
|
||||||
|
def after_job(self):
|
||||||
|
raise NotImplementedError("")
|
||||||
|
|
||||||
|
# vim: ts=4 sw=4 sts=4 expandtab
|
@ -1,22 +1,49 @@
|
|||||||
#!/usr/bin/env python2
|
#!/usr/bin/env python2
|
||||||
# -*- coding:utf-8 -*-
|
# -*- coding:utf-8 -*-
|
||||||
|
import sys
|
||||||
import time
|
import time
|
||||||
|
import signal
|
||||||
|
|
||||||
|
|
||||||
def run_job(sema, provider):
|
def run_job(sema, child_q, manager_q, provider):
|
||||||
|
aquired = False
|
||||||
|
|
||||||
|
def before_quit(*args):
|
||||||
|
provider.terminate()
|
||||||
|
if aquired:
|
||||||
|
print("{} release semaphore".format(provider.name))
|
||||||
|
sema.release()
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
|
signal.signal(signal.SIGTERM, before_quit)
|
||||||
|
|
||||||
while 1:
|
while 1:
|
||||||
sema.acquire(True)
|
try:
|
||||||
|
sema.acquire(True)
|
||||||
|
except:
|
||||||
|
break
|
||||||
|
aquired = True
|
||||||
print("start syncing {}".format(provider.name))
|
print("start syncing {}".format(provider.name))
|
||||||
|
|
||||||
for hook in provider.hooks:
|
for hook in provider.hooks:
|
||||||
hook.before_job()
|
hook.before_job()
|
||||||
|
|
||||||
provider.run()
|
provider.run()
|
||||||
|
provider.wait()
|
||||||
|
|
||||||
for hook in provider.hooks[::-1]:
|
for hook in provider.hooks[::-1]:
|
||||||
hook.after_job()
|
hook.after_job()
|
||||||
|
|
||||||
sema.release()
|
sema.release()
|
||||||
|
aquired = False
|
||||||
|
try:
|
||||||
|
msg = child_q.get(timeout=1)
|
||||||
|
if msg == "terminate":
|
||||||
|
manager_q.put((provider.name, "QUIT"))
|
||||||
|
break
|
||||||
|
except:
|
||||||
|
pass
|
||||||
|
|
||||||
print("syncing {} finished, sleep {} minutes for the next turn".format(
|
print("syncing {} finished, sleep {} minutes for the next turn".format(
|
||||||
provider.name, provider.interval
|
provider.name, provider.interval
|
||||||
))
|
))
|
||||||
|
@ -17,10 +17,22 @@ class MirrorProvider(object):
|
|||||||
self.log_file = log_file
|
self.log_file = log_file
|
||||||
self.interval = interval
|
self.interval = interval
|
||||||
self.hooks = hooks
|
self.hooks = hooks
|
||||||
|
self.p = None
|
||||||
|
|
||||||
def run(self):
|
def run(self):
|
||||||
raise NotImplementedError("run method should be implemented")
|
raise NotImplementedError("run method should be implemented")
|
||||||
|
|
||||||
|
def terminate(self):
|
||||||
|
if self.p is not None:
|
||||||
|
self.p.process.terminate()
|
||||||
|
print("{} terminated".format(self.name))
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
def wait(self):
|
||||||
|
if self.p is not None:
|
||||||
|
self.p.wait()
|
||||||
|
self.p = None
|
||||||
|
|
||||||
|
|
||||||
class RsyncProvider(MirrorProvider):
|
class RsyncProvider(MirrorProvider):
|
||||||
|
|
||||||
@ -60,7 +72,8 @@ class RsyncProvider(MirrorProvider):
|
|||||||
now = datetime.now().strftime("%Y-%m-%d_%H")
|
now = datetime.now().strftime("%Y-%m-%d_%H")
|
||||||
log_file = self.log_file.format(date=now)
|
log_file = self.log_file.format(date=now)
|
||||||
|
|
||||||
sh.rsync(*_args, _out=log_file, _err=log_file, _out_bufsize=1)
|
self.p = sh.rsync(*_args, _out=log_file, _err=log_file,
|
||||||
|
_out_bufsize=1, _bg=True)
|
||||||
|
|
||||||
|
|
||||||
class ShellProvider(MirrorProvider):
|
class ShellProvider(MirrorProvider):
|
||||||
@ -78,6 +91,7 @@ class ShellProvider(MirrorProvider):
|
|||||||
log_file = self.log_file.format(date=now)
|
log_file = self.log_file.format(date=now)
|
||||||
|
|
||||||
new_env = os.environ.copy()
|
new_env = os.environ.copy()
|
||||||
|
new_env["TUNASYNC_MIRROR_NAME"] = self.name
|
||||||
new_env["TUNASYNC_LOCAL_DIR"] = self.local_dir
|
new_env["TUNASYNC_LOCAL_DIR"] = self.local_dir
|
||||||
new_env["TUNASYNC_LOG_FILE"] = log_file
|
new_env["TUNASYNC_LOG_FILE"] = log_file
|
||||||
|
|
||||||
@ -85,7 +99,8 @@ class ShellProvider(MirrorProvider):
|
|||||||
_args = [] if len(self.command) == 1 else self.command[1:]
|
_args = [] if len(self.command) == 1 else self.command[1:]
|
||||||
|
|
||||||
cmd = sh.Command(_cmd)
|
cmd = sh.Command(_cmd)
|
||||||
cmd(*_args, _env=new_env, _out=log_file, _err=log_file, _out_bufsize=1)
|
self.p = cmd(*_args, _env=new_env, _out=log_file,
|
||||||
|
_err=log_file, _out_bufsize=1, _bg=True)
|
||||||
|
|
||||||
|
|
||||||
# vim: ts=4 sw=4 sts=4 expandtab
|
# vim: ts=4 sw=4 sts=4 expandtab
|
||||||
|
@ -3,8 +3,9 @@
|
|||||||
import ConfigParser
|
import ConfigParser
|
||||||
import os.path
|
import os.path
|
||||||
import signal
|
import signal
|
||||||
|
import sys
|
||||||
|
|
||||||
from multiprocessing import Process, Semaphore
|
from multiprocessing import Process, Semaphore, Queue
|
||||||
from . import jobs
|
from . import jobs
|
||||||
from .mirror_provider import RsyncProvider, ShellProvider
|
from .mirror_provider import RsyncProvider, ShellProvider
|
||||||
from .btrfs_snapshot import BtrfsHook
|
from .btrfs_snapshot import BtrfsHook
|
||||||
@ -61,6 +62,65 @@ class MirrorConfig(object):
|
|||||||
except ConfigParser.NoOptionError:
|
except ConfigParser.NoOptionError:
|
||||||
self.options["use_btrfs"] = self._parent.use_btrfs
|
self.options["use_btrfs"] = self._parent.use_btrfs
|
||||||
|
|
||||||
|
def __getattr__(self, key):
|
||||||
|
if key in self.__dict__:
|
||||||
|
return self.__dict__[key]
|
||||||
|
else:
|
||||||
|
return self.__dict__["options"].get(key, None)
|
||||||
|
|
||||||
|
def to_provider(self, hooks=[]):
|
||||||
|
if self.provider == "rsync":
|
||||||
|
provider = RsyncProvider(
|
||||||
|
self.name,
|
||||||
|
self.upstream,
|
||||||
|
self.local_dir,
|
||||||
|
self.use_ipv6,
|
||||||
|
self.exclude_file,
|
||||||
|
self.log_file,
|
||||||
|
self.interval,
|
||||||
|
hooks,
|
||||||
|
)
|
||||||
|
elif self.options["provider"] == "shell":
|
||||||
|
provider = ShellProvider(
|
||||||
|
self.name,
|
||||||
|
self.command,
|
||||||
|
self.local_dir,
|
||||||
|
self.log_file,
|
||||||
|
self.interval,
|
||||||
|
hooks
|
||||||
|
)
|
||||||
|
|
||||||
|
return provider
|
||||||
|
|
||||||
|
def compare(self, other):
|
||||||
|
assert self.name == other.name
|
||||||
|
|
||||||
|
for key, val in self.options.iteritems():
|
||||||
|
if other.options.get(key, None) != val:
|
||||||
|
return False
|
||||||
|
|
||||||
|
return True
|
||||||
|
|
||||||
|
def hooks(self):
|
||||||
|
hooks = []
|
||||||
|
parent = self._parent
|
||||||
|
if self.options["use_btrfs"]:
|
||||||
|
working_dir = parent.btrfs_working_dir_tmpl.format(
|
||||||
|
mirror_root=parent.mirror_root,
|
||||||
|
mirror_name=self.name
|
||||||
|
)
|
||||||
|
service_dir = parent.btrfs_service_dir_tmpl.format(
|
||||||
|
mirror_root=parent.mirror_root,
|
||||||
|
mirror_name=self.name
|
||||||
|
)
|
||||||
|
tmp_dir = parent.btrfs_tmp_dir_tmpl.format(
|
||||||
|
mirror_root=parent.mirror_root,
|
||||||
|
mirror_name=self.name
|
||||||
|
)
|
||||||
|
hooks.append(BtrfsHook(service_dir, working_dir, tmp_dir))
|
||||||
|
|
||||||
|
return hooks
|
||||||
|
|
||||||
|
|
||||||
class TUNASync(object):
|
class TUNASync(object):
|
||||||
|
|
||||||
@ -75,14 +135,16 @@ class TUNASync(object):
|
|||||||
return cls._instance
|
return cls._instance
|
||||||
|
|
||||||
def read_config(self, config_file):
|
def read_config(self, config_file):
|
||||||
|
self._config_file = config_file
|
||||||
self._settings = ConfigParser.ConfigParser()
|
self._settings = ConfigParser.ConfigParser()
|
||||||
self._settings.read(config_file)
|
self._settings.read(config_file)
|
||||||
|
|
||||||
self._inited = True
|
self._inited = True
|
||||||
self._mirrors = []
|
self._mirrors = {}
|
||||||
self._providers = []
|
self._providers = {}
|
||||||
self.processes = []
|
self.processes = {}
|
||||||
self.semaphore = Semaphore(self._settings.getint("global", "concurrent"))
|
self.semaphore = Semaphore(self._settings.getint("global", "concurrent"))
|
||||||
|
self.channel = Queue()
|
||||||
|
|
||||||
self.mirror_root = self._settings.get("global", "mirror_root")
|
self.mirror_root = self._settings.get("global", "mirror_root")
|
||||||
self.use_btrfs = self._settings.getboolean("global", "use_btrfs")
|
self.use_btrfs = self._settings.getboolean("global", "use_btrfs")
|
||||||
@ -93,6 +155,9 @@ class TUNASync(object):
|
|||||||
self.btrfs_tmp_dir_tmpl = self._settings.get(
|
self.btrfs_tmp_dir_tmpl = self._settings.get(
|
||||||
"btrfs", "tmp_dir")
|
"btrfs", "tmp_dir")
|
||||||
|
|
||||||
|
def hooks(self):
|
||||||
|
return []
|
||||||
|
|
||||||
@property
|
@property
|
||||||
def mirrors(self):
|
def mirrors(self):
|
||||||
if self._mirrors:
|
if self._mirrors:
|
||||||
@ -102,8 +167,8 @@ class TUNASync(object):
|
|||||||
self._settings.sections()):
|
self._settings.sections()):
|
||||||
|
|
||||||
_, name = section.split(":")
|
_, name = section.split(":")
|
||||||
self._mirrors.append(
|
self._mirrors[name] = \
|
||||||
MirrorConfig(self, name, self._settings, section))
|
MirrorConfig(self, name, self._settings, section)
|
||||||
return self._mirrors
|
return self._mirrors
|
||||||
|
|
||||||
@property
|
@property
|
||||||
@ -111,64 +176,95 @@ class TUNASync(object):
|
|||||||
if self._providers:
|
if self._providers:
|
||||||
return self._providers
|
return self._providers
|
||||||
|
|
||||||
for mirror in self.mirrors:
|
for name, mirror in self.mirrors.iteritems():
|
||||||
hooks = []
|
hooks = mirror.hooks() + self.hooks()
|
||||||
if mirror.options["use_btrfs"]:
|
provider = mirror.to_provider(hooks)
|
||||||
working_dir = self.btrfs_working_dir_tmpl.format(
|
self._providers[name] = provider
|
||||||
mirror_root=self.mirror_root,
|
|
||||||
mirror_name=mirror.name
|
|
||||||
)
|
|
||||||
service_dir = self.btrfs_service_dir_tmpl.format(
|
|
||||||
mirror_root=self.mirror_root,
|
|
||||||
mirror_name=mirror.name
|
|
||||||
)
|
|
||||||
tmp_dir = self.btrfs_tmp_dir_tmpl.format(
|
|
||||||
mirror_root=self.mirror_root,
|
|
||||||
mirror_name=mirror.name
|
|
||||||
)
|
|
||||||
hooks.append(BtrfsHook(service_dir, working_dir, tmp_dir))
|
|
||||||
|
|
||||||
if mirror.options["provider"] == "rsync":
|
|
||||||
self._providers.append(
|
|
||||||
RsyncProvider(
|
|
||||||
mirror.name,
|
|
||||||
mirror.options["upstream"],
|
|
||||||
mirror.options["local_dir"],
|
|
||||||
mirror.options["use_ipv6"],
|
|
||||||
mirror.options.get("exclude_file", None),
|
|
||||||
mirror.options["log_file"],
|
|
||||||
mirror.options["interval"],
|
|
||||||
hooks,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
elif mirror.options["provider"] == "shell":
|
|
||||||
self._providers.append(
|
|
||||||
ShellProvider(
|
|
||||||
mirror.name,
|
|
||||||
mirror.options["command"],
|
|
||||||
mirror.options["local_dir"],
|
|
||||||
mirror.options["log_file"],
|
|
||||||
mirror.options["interval"],
|
|
||||||
hooks,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
|
|
||||||
return self._providers
|
return self._providers
|
||||||
|
|
||||||
def run_jobs(self):
|
def run_jobs(self):
|
||||||
for provider in self.providers:
|
for name in self.providers:
|
||||||
p = Process(target=jobs.run_job, args=(self.semaphore, provider, ))
|
self.run_provider(name)
|
||||||
p.start()
|
|
||||||
self.processes.append(p)
|
|
||||||
|
|
||||||
def sig_handler(*args):
|
def sig_handler(*args):
|
||||||
print("terminate subprocesses")
|
print("terminate subprocesses")
|
||||||
for p in self.processes:
|
for _, np in self.processes.iteritems():
|
||||||
|
_, p = np
|
||||||
p.terminate()
|
p.terminate()
|
||||||
print("Good Bye")
|
print("Good Bye")
|
||||||
|
sys.exit(0)
|
||||||
|
|
||||||
signal.signal(signal.SIGINT, sig_handler)
|
signal.signal(signal.SIGINT, sig_handler)
|
||||||
signal.signal(signal.SIGTERM, sig_handler)
|
signal.signal(signal.SIGTERM, sig_handler)
|
||||||
|
signal.signal(signal.SIGUSR1, self.reload_mirrors)
|
||||||
|
signal.signal(signal.SIGUSR2, self.reload_mirrors_force)
|
||||||
|
|
||||||
|
while 1:
|
||||||
|
name, status = self.channel.get()
|
||||||
|
if status == "QUIT":
|
||||||
|
print("New configuration applied to {}".format(name))
|
||||||
|
self.run_provider(name)
|
||||||
|
|
||||||
|
def run_provider(self, name):
|
||||||
|
if name not in self.providers:
|
||||||
|
print("{} doesnot exist".format(name))
|
||||||
|
return
|
||||||
|
|
||||||
|
provider = self.providers[name]
|
||||||
|
child_queue = Queue()
|
||||||
|
p = Process(
|
||||||
|
target=jobs.run_job,
|
||||||
|
args=(self.semaphore, child_queue, self.channel, provider, )
|
||||||
|
)
|
||||||
|
p.start()
|
||||||
|
self.processes[name] = (child_queue, p)
|
||||||
|
|
||||||
|
def reload_mirrors(self, signum, frame):
|
||||||
|
try:
|
||||||
|
return self._reload_mirrors(signum, frame, force=False)
|
||||||
|
except Exception, e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def reload_mirrors_force(self, signum, frame):
|
||||||
|
try:
|
||||||
|
return self._reload_mirrors(signum, frame, force=True)
|
||||||
|
except Exception, e:
|
||||||
|
print(e)
|
||||||
|
|
||||||
|
def _reload_mirrors(self, signum, frame, force=False):
|
||||||
|
print("reload mirror configs, force restart: {}".format(force))
|
||||||
|
self._settings.read(self._config_file)
|
||||||
|
|
||||||
|
for section in filter(lambda s: s.startswith("mirror:"),
|
||||||
|
self._settings.sections()):
|
||||||
|
|
||||||
|
_, name = section.split(":")
|
||||||
|
newMirCfg = MirrorConfig(self, name, self._settings, section)
|
||||||
|
|
||||||
|
if name in self._mirrors:
|
||||||
|
if newMirCfg.compare(self._mirrors[name]):
|
||||||
|
continue
|
||||||
|
|
||||||
|
self._mirrors[name] = newMirCfg
|
||||||
|
|
||||||
|
hooks = newMirCfg.hooks() + self.hooks()
|
||||||
|
newProvider = newMirCfg.to_provider(hooks)
|
||||||
|
self._providers[name] = newProvider
|
||||||
|
|
||||||
|
if name in self.processes:
|
||||||
|
q, p = self.processes[name]
|
||||||
|
|
||||||
|
if force:
|
||||||
|
p.terminate()
|
||||||
|
print("Terminated Job: {}".format(name))
|
||||||
|
self.run_provider(name)
|
||||||
|
else:
|
||||||
|
q.put("terminate")
|
||||||
|
print("New configuration queued to {}".format(name))
|
||||||
|
else:
|
||||||
|
print("New mirror: {}".format(name))
|
||||||
|
self.run_provider(name)
|
||||||
|
|
||||||
# def config(self, option):
|
# def config(self, option):
|
||||||
# if self._settings is None:
|
# if self._settings is None:
|
||||||
|
Loading…
x
Reference in New Issue
Block a user