blob: aa6149c3f08abcf0d58e9408f40fcab931b823b3 [file] [log] [blame]
#!/usr/bin/env python
# -*- coding: utf-8 -*-
# Copyright (c) 2015-2016, Mirantis, Inc.
#
# Licensed under the Apache License, Version 2.0 (the "License"); you may
# not use this file except in compliance with the License. You may obtain
# a copy of the License at
#
# http://www.apache.org/licenses/LICENSE-2.0
#
# Unless required by applicable law or agreed to in writing, software
# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
# License for the specific language governing permissions and limitations
# under the License.
import os
from trsync.objects import RemoteLock
from trsync.objects import RsyncOps
from trsync.objects import RsyncUrl
from trsync.objects import TRsync
class TRSyncApi(object):
def _success(self, server, message=None, force=False):
if force:
server['success'] = True
server['messages'].append(message)
def _fail(self, server, message=None):
server['success'] = False
server['errors'].append(message)
def _init_serverdata(self, dests):
"""Make a serverdata object for each destiantion"""
return [{'dest': dest,
'remote': None,
'lock': None,
'success': True,
'messages': [],
'errors': []}
for dest in dests]
def _extract_stats(self, servers):
return dict((s['dest'], {'success': s['success'],
'messages': s['messages'],
'errors': s['errors']})
for s in servers)
def _connect_and_lock(self, servers, repo_name, **kwargs):
"""Connect and lock each destination"""
for s in servers:
try:
remote = TRsync(s['dest'], **kwargs)
path = remote.get_repo_path(repo_name)
lock = RemoteLock(remote, path=path, force=False)
lock.acquire()
s['lock'] = lock
s['remote'] = remote
except Exception as e:
self._fail(s, 'Locking %s on %s: FAILED' % (path, s['dest']) +
"\n" + str(e))
def _do_push(self, servers, source_url, repo_name, symlinks=[]):
"""Rsync stuff to all servers"""
for s in servers:
if s['remote'] is not None:
try:
s['remote'].push(source_url, repo_name,
symlinks=symlinks)
self._success(s, 'Push %s to %s: SUCCESS'
% (source_url, s))
except Exception as e:
self._fail(s, 'Push %s to %s: FAILED'
% (source_url, s['dest']) +
"\n" + str(e))
def _release_locks(self, servers):
"""Release locks on all destinations"""
for s in servers:
if s['lock'] is not None:
s['lock'].release()
def _check_status(self, servers):
"""Check if all went well"""
for s in servers:
if s['success'] is False:
return False
return True
def _rollback(self, servers, repo_name):
"""Undo the damage"""
for s in servers:
if s['remote'] is not None:
s['remote'].undo_push(repo_name)
def _commit(self, servers, repo_name, symlinks=[]):
"""Symlink the synced dir to where it belongs"""
for s in servers:
try:
s['remote'].symlink(repo_name, symlinks=symlinks)
self._success(s, "Symlinking SUCCESS")
except Exception as e:
self._fail(s, "Symlinking FAILED\n" + str(e))
def _delete_old_snapshots(self, servers, repo_name):
for s in servers:
s['remote'].remove_old_snapshots(repo_name)
def push(self, source_url, dests, **kwargs):
symlinks = kwargs.pop('symlinks', [])
snapshot_name = kwargs.pop('snapshot_name', '')
source = RsyncOps(source_url)
source_url = source.url.url_dir()
snapshot_name = snapshot_name.strip(' /') or \
os.path.basename(source.url.path)
if not snapshot_name:
raise RuntimeError("Could not infer snapshot name from source url"
" and snapshot_name was not provided.")
servers = self._init_serverdata(dests)
self._connect_and_lock(servers, snapshot_name, **kwargs)
self._do_push(servers, source_url, snapshot_name, symlinks)
all_ok = self._check_status(servers)
if all_ok:
self._commit(servers, snapshot_name, symlinks)
self._delete_old_snapshots(servers, snapshot_name)
else:
self._rollback(servers, snapshot_name)
self._release_locks(servers)
return self._extract_stats(servers)
def symlink(self, dests, symlinks, target, update=False,
rsync_extra_params=""):
for symlink in symlinks:
if symlink.startswith('/') or symlink.startswith('../'):
raise RuntimeError('Symlink points outside the root url: {}'
.format(symlink))
servers = self._init_serverdata(dests)
for s in servers:
try:
s['remote'] = RsyncOps(s['dest'],
rsync_extra_params=rsync_extra_params)
for symlink in symlinks:
s['remote'].symlink(symlink, target, update=update)
self._success(s, 'Creating symlinks %s targeted to %s on %s: '
'SUCCESS' %
(str(symlinks), target, s['dest']))
except Exception as e:
self._fail(s, 'Creating symlinks %s targeted to %s on %s: '
'FAILED' % (str(symlinks), target, s['dest']) +
"\n" + str(e))
return self._extract_stats(servers)
def remove(self, dests, path, rsync_extra_params=""):
servers = self._init_serverdata(dests)
for s in servers:
try:
s['remote'] = RsyncOps(s['dest'],
rsync_extra_params=rsync_extra_params)
s['remote'].rm_all(path)
self._success(s, 'Remove %s: SUCCESS' % (path))
except Exception as e:
self._fail(s, 'Remove %s: FAILED' % (path) +
"\n" + str(e))
return self._extract_stats(servers)
def get_target(self, symlink_url, recursive=False):
url = RsyncUrl(symlink_url)
remote = RsyncOps(url.root)
return remote.symlink_target(url.path, recursive=recursive)