Skip to content
Open
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
46 changes: 38 additions & 8 deletions rbd2qcow2/main.py
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@

import rados
import rbd
import sys

from rbd2qcow2.libc_wrappers import fallocate, FALLOC_FL_KEEP_SIZE
from rbd2qcow2.nbd_client import open_image
Expand Down Expand Up @@ -130,7 +131,7 @@ async def do_transfer(
os.fsync(xxx.fileno())
log.debug('Fsyncing complete.')

os.chmod(tmp_filename, 0o400)
#os.chmod(tmp_filename, 0o400)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Но зачем? все генерируемые файлы должны быть ридонли. это защита от случайных правок (не по друтом которые)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Он же делает ребейс. Можно конечно покрасивше сделать - скидывать атрибут непосредственно перед ребейсом и после ребейса обратно ставить.

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

может лутьше отдельную тулзу написать ? (т.е. не городить в этом питоне) -- бекапилка-бекапилкой, а удалялка тершака - отдельно. запускать их можно независимо в теории.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну, тут сложна-сложна. По идее, это один процесс - хлопнуть один лишний бекап - добавить один новый бекап. Одна строка в кроне. Культурненько.

os.rename(tmp_filename, qcow2_name)
# Safe rename
fd = os.open(qcow2_directory, os.O_DIRECTORY | os.O_RDONLY)
Expand All @@ -147,20 +148,18 @@ async def do_transfer(
raise


def get_latest_backup(xxx: str, image_name: str) -> int:
def get_latest_backup(xxx: str, image_name: str)->dict:
Copy link
Copy Markdown
Owner

@socketpair socketpair Sep 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

  1. отинденти код как минимум (Pycharm).
  2. -> Dict[int, str]

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

я не уметь, питона в первый раз в руки взял

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

поэтому я и подсказываю

log.debug('Searching for previous images in backup dir.')
ts = 0
ret = dict()
for filename in os.listdir(xxx):
mtch = re.fullmatch(r'([^@]+)@([0-9]+)\.qcow2', filename)
if mtch is None:
continue
if mtch.group(1) != image_name:
log.warning('Unexpected filename %r in dir %r.', filename, xxx)
continue
timestamp = int(mtch.group(2))
if timestamp > ts:
ts = timestamp
return ts
ret[int(mtch.group(2))] = filename
return ret


def check_skip_backup(rbd_image) -> bool:
Expand Down Expand Up @@ -196,7 +195,26 @@ async def do_backup(rbd_image_name: str, loop, ioctx):
if not os.path.isdir(xxx):
log.debug('Creating directory %s.', xxx)
os.makedirs(xxx)
latest_ts = get_latest_backup(xxx, rbd_image_name)
itms = get_latest_backup(xxx, rbd_image_name)
cnt=len(itms)
srt=sorted(itms)
if cnt>0:
latest_ts=srt[-1]
else:
latest_ts=0
if cnt >= options.bk_count:
removingCount = 1 + cnt - options.bk_count;
args = ['qemu-img',
'rebase',
'-b',
os.path.join(xxx, itms[srt[1]]),
os.path.join(xxx, itms[srt[removingCount+2]])
];
log.info('Rebasing image %s.', args)
subprocess.check_call(args)
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

если упадёт, то надо рековериться. ну и защита от параллельного бекапенья же в этом время

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Вот тут не знаю. Если ребейз упал, по идее, всей цепочке бекапов звездык

for idx in range(2, removingCount+2):
log.info('Removing old image %s.',os.path.join(xxx,itms[srt[idx]]))
os.remove(os.path.join(xxx,itms[srt[idx]]))
if latest_ts == 0:
log.info('Did not found previous backup for image %s.', rbd_image_name)
empty_image_path = os.path.join(xxx, 'empty.qcow2')
Expand All @@ -220,6 +238,7 @@ async def do_backup(rbd_image_name: str, loop, ioctx):
qcow2_name,
backing_store_filename,
)

except Exception as e:
log.info('Removing RBD snapshot %s@%s due to error %r.', rbd_image_name, rbd_new_snapshot_name,
e)
Expand Down Expand Up @@ -327,6 +346,14 @@ def main():
help='Path to a directory where backups should be placed.'
)

parser.add_argument(
'--bk_count',
metavar='BK_COUNT',
type=int,
help='Count of backups to store.',
Copy link
Copy Markdown
Owner

@socketpair socketpair Sep 17, 2018

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

to store on Ceph (!) нет ?

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

нит, то сторе он винт!

default=sys.maxsize
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

упоролся чтоли. по дефолту 1 же логичнее :)

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну, тогда он будет держать ровно 1 бекап, остальные грохать

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

бля, я вначале думал -- сколько снапов в цефе будет оставаться.

)

parser.add_argument(
'images',
metavar='IMAGE_NAME',
Expand All @@ -339,6 +366,9 @@ def main():
if not (1 <= options.parallel <= 100):
raise ValueError('Wrong parallel count.')

if not (options.bk_count >= 4):
raise ValueError('Wrong backups count. Minimum is 4.')
Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

С чего это ради.

Copy link
Copy Markdown
Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Ну, если меньше четырех, то ребейсить некуда. Пушо получается, что самый первый бекап в цепочке он самый большой, поэтому на него ребейсить будет долго (наверное)


logging.basicConfig(level=logging.DEBUG if options.verbose else logging.INFO)

log.info('Starting backup process.')
Expand Down