Hacked By AnonymousFox
"""
This program is free software: you can redistribute it and/or modify it under
the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License,
or (at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
See the GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
Copyright © 2019 Cloud Linux Software Inc.
This software is also available under ImunifyAV commercial license,
see <https://www.imunify360.com/legal/eula>
"""
import asyncio
import logging
import time
from defence360agent.contracts.config import BackupConfig
from defence360agent.contracts.license import LicenseCLN
from defence360agent.internals.cln import CLNError
from defence360agent.rpc_tools import ValidationError
from defence360agent.rpc_tools.lookup import CommonEndpoints, bind, wraps
from defence360agent.subsys.backup_systems import (
Acronis,
BackupException,
CloudLinux,
CloudLinuxBase,
CloudLinuxOnPremise,
R1Soft,
get_available_backends_names,
get_backend,
)
from defence360agent.utils import Scope, Singleton
logger = logging.getLogger(__name__)
CLN_RESPONSE_ERROR = """
Error with one of the next reasons:
* Not found linked backup for the server
* Not found linked cloudlinux user for the server
* IP of server is different in last time
"""
PLAIN_AUTH_FIELDS = {
"username": "Username should be provided",
"password": "Password should be provided",
}
REQUIRED_FIELDS = {
Acronis: PLAIN_AUTH_FIELDS,
R1Soft: {
"encryption_key": "Encryption key should be provided",
"ip": "IP should be provided",
**PLAIN_AUTH_FIELDS,
},
CloudLinuxOnPremise: PLAIN_AUTH_FIELDS,
}
def validate_backend_args(f):
@wraps(f)
async def wrapper(*args, **kwargs):
required_options = REQUIRED_FIELDS.get(kwargs["backend"], {})
errors = [
error_msg
for field, error_msg in required_options.items()
if not kwargs.get(field)
]
if errors:
raise ValidationError("\n".join(errors))
return await f(*args, **kwargs)
return wrapper
class BackupEndpoints(CommonEndpoints, metaclass=Singleton):
SCOPE = Scope.IM360
NOT_RUNNING, INIT, BACKUP, DONE = "not_running", "init", "backup", "done"
def __init__(self, sink):
super().__init__(sink)
self._status = self.NOT_RUNNING
self._current_backend = self._error = None
self._init_task = self._backup_task = None
self._backup_started_time = 0
@property
def _backup_pending(self):
return time.time() - self._backup_started_time < 60 * 5
@validate_backend_args
@bind("backup-systems", "init")
async def init(self, backend, **kwargs):
if LicenseCLN.is_demo():
raise ValidationError("This action is not allowed in demo version")
loop = asyncio.get_event_loop()
if self._current_backend and not self._error:
if self._status == self.INIT:
raise ValidationError(
"Backup initialization is already in progress"
)
if self._status == self.BACKUP:
raise ValidationError("Backup process is already in progress")
logger.info("Starting init task")
# flush error from previous sessions
self._error = None
self._task = loop.create_task(backend.init(**kwargs))
self._task.add_done_callback(self._init_task_done)
self._status = self.INIT
self._current_backend = backend
return "Backup initialization process is in progress"
async def _set_current_backend(self, conf):
if not self._current_backend and conf["backup_system"]:
self._current_backend = get_backend(conf["backup_system"])
if self._status == self.NOT_RUNNING:
# if backup exists, assuming all done
if await self._current_backend.check_state():
self._status = self.DONE
else:
self._error = "No backups found!"
async def _include_init_stages_info(self, status):
if status["state"] != self.INIT:
advanced_data = await self._current_backend.show()
status.update(advanced_data)
if (
isinstance(self._current_backend, CloudLinuxBase)
and self._status == self.BACKUP
):
if self._error is not None:
if await self._current_backend.check_state():
# error should already be fixed,
# because there is some backups
self._error = None
status["state"] = self._status = self.DONE
else:
progress = await self._current_backend.get_backup_progress()
if progress is None:
if self._backup_pending:
status["progress"] = 0
else:
# if there is no progress after a while
# let's admit backup is completed
status["state"] = self._status = self.DONE
else:
status["progress"] = progress
return status
async def _get_advanced_status(self, status):
await self._set_current_backend(status)
status["state"] = self._status
status["error"] = self._error
status["log_path"] = getattr(self._current_backend, "log_path", None)
status["backup_system"] = getattr(self._current_backend, "name", None)
if self._error is not None and self._status == self.INIT:
self._error = self._current_backend = None
self._status = self.NOT_RUNNING
if self._current_backend:
try:
status = await self._include_init_stages_info(status)
except asyncio.CancelledError:
raise
except Exception as e:
# Ignoring "No backup for host" errors for 5 minutes
if "No backup for host" in str(e) and self._backup_pending:
logger.info(
"Error %s will be ignored for 5 minutes "
"after init state has been finished",
e,
)
# want to show human friendly error if backup is unpaid
elif self._current_backend == CloudLinux:
resp = await self._current_backend.check()
if resp.get("status") == CloudLinux.UNPAID:
status["error"] = (
"Backup is unpaid! Please, check "
"it out in your CLN account."
)
else:
raise e
return status
@bind("backup-systems", "extended-status")
async def extended_status(self):
status = BackupConfig().config_to_dict().get("BACKUP_SYSTEM", {})
status = await self._get_advanced_status(status)
return {"items": status}
@bind("backup-systems", "status")
async def status(self, user=None):
status = BackupConfig().config_to_dict().get("BACKUP_SYSTEM", {})
return {"items": status}
@bind("backup-systems", "list")
async def list(self):
return get_available_backends_names()
@bind("backup-systems", "disable")
async def disable(self, backend, **kwargs):
self._current_backend = self._error = None
self._status = self.NOT_RUNNING
await backend.disable(**kwargs)
@bind("backup-systems", "check")
async def check(self, backend):
try:
return {"items": await backend.check()}
except CLNError:
raise ValidationError(CLN_RESPONSE_ERROR)
except (LookupError, RuntimeError) as e:
raise ValidationError(str(e)) from e
def _init_task_done(self, future):
logger.info("In init done callback")
e = future.exception()
if e is not None:
if isinstance(e, CLNError):
self._error = CLN_RESPONSE_ERROR
else:
logger.exception("Backup init task failed", exc_info=e)
self._error = str(e)
else:
logger.info("Starting initial backup task")
self._status = self.BACKUP
self._backup_started_time = time.time()
self._backup_task = asyncio.get_event_loop().create_task(
self._current_backend.make_backup()
)
self._backup_task.add_done_callback(self._backup_task_done)
def _backup_task_done(self, future):
logger.info("In backup done callback")
e = future.exception()
if e is not None:
logger.exception("Initial backup task failed", exc_info=e)
if isinstance(e, BackupException):
self._error = str(e)
else:
self._error = "Unknown error: " + str(e)
else:
self._status = self.DONE
Hacked By AnonymousFox1.0, Coded By AnonymousFox