class Backup(): col_name = "backup" def __init__(self, id=None, job=None, source=None, target=None): self.filename = "" log.clearBuffer() if(job != None): self.setJob(job) if(source != None): self.setSource(source) if(target != None): self.setTarget(target) self.start_time = self.getTimestamp() if(id != None): self.get(id) def getID(self): if hasattr(self, "id"): return self.id else: return False def getAll(self, filter={}, type="object"): log.write("get all backups", "debug") docs = self.getDB().getCollection(query=filter) if(type == "JSON"): return docs else: ret = [] for d in docs: r = Backup(str(d["_id"])) ret.append(r) return ret def getTarget(self): if not hasattr(self, "target"): self.target = Target(self.target_id) return self.target def getHost(self): if not hasattr(self, "source"): self.source = Target(self.host_id) return self.target def get(self, id): log.write("load backup by id: " + id, "debug") ret = self.getDB().get(id) if ret: for k, v in ret.items(): setattr(self, k, v) self.id = str(ret["_id"]) del(self._id) self.exist = True else: return False def getDB(self): return DB(self.col_name) def toJSON(self): r = self return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) def setJob(self, job): self.job = job def getJob(self): if not hasattr(self, "job"): self.job = Job(self.job) return self.job def setTarget(self, target): self.target = target def getTarget(self): if not hasattr(self, "target"): self.target = Target(self.target_id) return self.target def setSource(self, source): self.source = source def getSource(self): if not hasattr(self, "source"): self.source = Host(self.host_id) return self.source def getTimestamp(self): now = datetime.datetime.now() ts = now.strftime('%Y-%m-%d_%H-%M-%S') return ts def getNewBackupPath(self): return self.getBackupRoot() + "/" + self.start_time + "-" + self.task.getName() + ".tar.gz" def getContainerBackupPath(self, name): return self.getBackupRoot() + "/" + self.start_time + "-" + name + ".tar.gz" def getStackBackupPath(self, stack, name): return self.getBackupRootShort() + "/" + stack + "/" + self.start_time + "-" + name + ".tar.gz" def run(self, task): self.task = task log.write("run task: " + str(self.task.getID())) self.prepare(task) if(self.task.getType() == "file"): log.write("init file backup", "notice") self.filename = self.getNewBackupPath() self.backupFile() return True elif(self.task.getType() == "docker"): log.write("init docker backup", "notice") c_ids = [] if self.task.getData("container"): for container in self.task.getData("container"): log.write("fetch ids for container name: " + container, "debug") c_ids = self.source.getContainersByName(container) if len(c_ids) == 0: log.write("no matching containers found", "debug") else: for id in c_ids: self.filename = self.getContainerBackupPath(container) self.backupDockerContainer(id, container) if self.task.getData("stacks"): for stack in self.task.getData("stacks"): log.write("fetch containers for stack name: " + stack, "debug") c_names = self.source.getContainersByStack(stack) if len(c_names) == 0: log.write("no matching containers found", "debug") else: for container in c_names: self.filename = self.getStackBackupPath(stack, container) for id in self.source.getContainersByName(container): self.backupDockerContainer(id, container, stack) self.purgeOld() log.write("finish docker backup", "debug") # self.target.openFile(filename) else: log.write("error unsupported task type: " + task["type"]) def backupFile(self): if(not self.target.fileExists(self.getBackupRoot())): log.write("backup root does not exist. creating: " + self.getBackupRoot()) self.target.createDirectoryRecursive(self.getBackupRoot()) self.target.openFile(self.filename) self.source.createArchiveFromPaths(self.task.getData()) while True: data = self.source.readBinary() if not data: break self.target.writeFile(data) log.write("finish backup") self.addBackupEntry() self.target.closeFile() def backupDockerContainer(self, id, container, stack=None): log.write("start backup of container %s to %s" % (id, self.filename)) if not self.target.fileExists(os.path.dirname(self.getFilename())): log.write("backup root does not exist. creating: " + os.path.dirname(self.getFilename())) self.target.createDirectoryRecursive(os.path.dirname(self.getFilename())) self.target.openFile(self.getFilename()) self.source.createArchiveFromContainerId(id) while True: data = self.source.readBinary() if not data: break self.target.writeFile(data) self.target.closeFile() log.write("finish backup of container %s" % (id)) logs = {"container": container} if not stack == None: logs["stack"] = stack self.addBackupEntry(logs) return True def exists(self): if hasattr(self, "exist"): return self.exist else: return False def getFilename(self): if hasattr(self, "filename"): return self.filename return False def getSnapshots(self): log.write("get snapshots") filter = { "task_id": self.getTask().getID(), "host_id": self.getHost().conf.getID(), "target_id": self.getTarget().conf.getID() } ret = self.getDB().find(filter=filter).sort("start_time", 1) log.write("found %i snapshots" % (ret.count())) return ret def deleteOldestSnapshot(self): id = self.getSnapshots()[0]["_id"] return Backup(id).delete() def purgeOld(self): log.write("purge old backups", "info") while self.getSnapshots().count() > self.getJob().getMaxSnapshotCount(): self.deleteOldestSnapshot() return True def addBackupEntry(self, data={}): log.write("mark backup as compeleted", "debug") self.end_time = self.getTimestamp() doc = {"job_id": str(self.job.getID()), "filename": self.filename, "task_id": self.task.getID(), "host_id": self.source.conf.getID(), "target_id": self.target.conf.getID(), "type": self.task.getType(), "hostname": self.source.conf.getHostname(), "log": log.getBuffer(),"start_time": self.start_time, "end_time": self.end_time} self.getDB().addDoc(doc) def restore(self, overwrite=True, replace=False): if(self.task.getType() == "file"): log.write("restore backup " + self.getID(), "notice") self.getTarget().prepare() self.getTarget().getConnection().openFile(self.getFilename(), "rb") self.getSource().prepare() self.getSource().getConnection().restoreArchive() while True: data = self.getTarget().getConnection().readBinary() if not data: break self.getSource().getConnection().writeBinary(data) self.getSource().getConnection().closeFile() self.getSource().getConnection().syncDirectory() log.write("restored backup " + self.getID()) else: log.write("error: restore not supported for this type", "error") return False def getBackupRootShort(self): return "/backup/" + str(self.job.getID()) + "/" + self.task.getID() + "/" def getBackupRootStack(self, stack): return "/backup/" + str(self.job.getID()) + "/" + self.task.getID() + "/" + stack + "/" def getBackupRoot(self): return "/backup/" + str(self.job.getID()) + "/" + self.task.getID() + "/" + self.source.conf.getHostname() + "/" def getTask(self): if hasattr(self, "task"): return self.task return False def prepare(self, task): self.task = task def delete(self): if(not self.exists()): return True log.write("delete backup " + str(self.getID()), "debug") self.getTarget().prepare() self.getTarget().getConnection().deleteFile(self.getFilename()) return self.getDB().deleteById(self.getID())
class Dump(): col_name = "dump" cmd = { "mongodb": { "dump": "mongodump -o /dumps", "cred_format": "--uri='%s'", "db_format": "--db=" }, "mysql": { "dump": "mysqldump --comments --routines ", "dump_all": 'mkdir /dumps; cd /dumps; mysql -N -e "show databases" | grep -vE "^(mysql|performance_schema|information_schema)$" | while read dbname; do mysqldump --complete-insert --routines --triggers --single-transaction "$dbname" | gzip > "$dbname".sql.gz; done', "db_format": "%s" } } def __init__(self, id=None, job=None, source=None, target=None): self.filename = "" log.clearBuffer() if (job != None): self.setJob(job) if (source != None): self.setSource(source) if (target != None): self.setTarget(target) self.start_time = self.getTimestamp() if (id != None): self.get(id) def get(self, id): log.write("load dump by id: " + id, "debug") ret = self.getDB().get(id) if ret: for k, v in ret.items(): setattr(self, k, v) self.id = str(ret["_id"]) del (self._id) self.exist = True else: return False def getAll(self, filter={}, type="object"): log.write("get all dumps", "debug") docs = self.getDB().getCollection(query=filter) if (type == "JSON"): return docs else: ret = [] for d in docs: r = Dump(str(d["_id"])) ret.append(r) return ret def getCmd(self): type = self.task.getType() if type: return self.cmd[type] return False def getTimestamp(self): now = datetime.datetime.now() ts = now.strftime('%Y-%m-%d_%H-%M-%S') return ts def getDB(self): return DB(self.col_name) def exists(self): if hasattr(self, "exist"): return self.exist else: return False def getFilename(self): if hasattr(self, "filename"): return self.filename return False def getSnapshots(self): log.write("get snapshots") filter = { "task_id": self.getTask().getID(), "host_id": self.getHost().getID(), "target_id": self.getTarget().getID() } ret = self.getDB.find(filter=filter).sort("start_time", 1) log.write("found %i snapshots" % (ret.count())) return ret def deleteOldestSnapshot(self): id = self.getSnapshots()[0]["_id"] return Dump(id).delete() def purgeOld(self): log.write("purge old dumps", "info") while self.getSnapshots().count() > self.getJob().getMaxSnapshotCount( ): self.deleteOldestSnapshot() return True def toJSON(self): r = self return json.dumps(self, default=lambda o: o.__dict__, sort_keys=True, indent=4) def getID(self): if hasattr(self, "id"): return self.id else: return False def setJob(self, job): self.job = job def getJob(self): if not hasattr(self, "job"): self.job = Job(self.job) return self.job def setTarget(self, target): self.target = target def getTarget(self): if not hasattr(self, "target"): self.target = Target(self.target_id) return self.target def setSource(self, source): self.source = source def getSource(self): if not hasattr(self, "source"): self.source = Host(self.host_id) return self.source def getTarget(self): if not hasattr(self, "target"): self.target = Target(self.target_id) return self.target def getHost(self): if not hasattr(self, "host"): self.host = Target(self.host_id) return self.target return ts def getType(self): if not hasattr(self, "type"): return self.type return False def getTask(self): if hasattr(self, "task"): return self.task return False def prepare(self, task): self.task = task def delete(self): if (not self.exists()): return True log.write("delete dump " + str(self.getID()), "debug") self.getTarget().prepare() self.getTarget().getConnection().deleteFile(self.getFilename()) return self.getDB().deleteById(self.getID()) def run(self, task): self.task = task log.write("run task: " + str(self.task.getID())) self.prepare(task) if self.task.getData("container"): for container in self.task.getData("container"): self.c = self.task.getData("container")[container] log.write("fetch ids for container name: " + container, "debug") c_ids = self.source.getContainersByName(container) if len(c_ids) == 0: log.write("no matching containers found", "debug") else: for c_id in c_ids: self.container_id = c_id if "db" in self.c: if type(self.c["db"]) is str: self.c["db"] = [self.c["db"]] for db in self.c["db"]: self.container = container self.filename = self.getDumpFilename( db=db, container=self.container) self.backupDB(db=db, container_id=c_id) else: self.container = container self.filename = self.getDumpFilename( container=self.container) self.backupDB(container_id=c_id) if self.task.getData("stacks"): for stack in self.task.getData("stacks"): self.c = self.task.getData("stacks")[stack] log.write("fetch containers for stack name: " + stack, "debug") c_ids = self.source.getContainersByStack(stack) if len(c_ids) == 0: log.write("no matching containers found", "debug") else: for c_id in c_ids: if "db" in self.c: if type(self.c["db"]) is str: self.c["db"] = [self.c["db"]] for db in self.c["db"]: self.filename = self.getDumpFilename( db=db, stack=stack) self.backupDB(db=db, container_id=c_id) else: self.filename = self.getDumpFilename(stack=stack) self.backupDB(container_id=c_id) def getDumpFilename(self, db=None, container=None, stack=None): base = "/dumps/" + str( self.job.getID()) + "/" + self.task.getID() + "/" if (stack != None): base += stack + "/" if (container != None): base += container + "/" if (db != None): filename = self.getTimestamp() + "_" + db else: filename = self.getTimestamp() + "_all_databases" if self.task.getType() == "mongodb": filename += ".tar" elif self.task.getType() == "mysql": filename += ".tar" return base + filename def getFilename(self): if hasattr(self, "filename"): return self.filename return False def backupDB(self, db=None, container_id=None): state = False backup_root = os.path.dirname(self.getFilename()) if (not self.target.fileExists(backup_root)): log.write("dump root does not exist. creating: " + backup_root) self.target.createDirectoryRecursive(backup_root) cmd = self.getCmd()["dump"] if "port" in self.c: cmd += " --port " + str(self.c["port"]) if (db != None): cmd += " " + self.getCmd()["db_format"] + db elif ("dump_all" in self.getCmd()): cmd = self.getCmd()["dump_all"] if "gzip" in self.c: cmd += " | gzip" self.target.openFile(self.filename) if (container_id != None): self.source.execCommandDocker(container_id, cmd, wait=True) else: self.source.execCommand(cmd, wait=True) if (self.task.getType() in ["mongodb", "mysql"]): data = self.source.read() if (container_id != None): self.source.execCommandDocker( container_id, "tar -Oc /dumps 2>/dev/null | cat") else: self.source.execCommand("tar -Oc /dumps 2>/dev/null | cat") while True: data = self.source.readBinary() state = True if not data: break self.target.writeFile(data) if (self.task.getType() in ["mongodb", "mysql"]): if (container_id != None): self.source.execCommandDocker(container_id, "rm -rf /dumps") else: self.source.execCommand("rm -rf /dumps") self.target.closeFile() if (state == False): log.write("error: no data received", "error") self.getTarget().getConnection().deleteFile() else: self.addDumpEntry() log.write("finish dump") def addDumpEntry(self, data={}): log.write("mark dump as compeleted", "debug") self.end_time = self.getTimestamp() doc = { "job_id": str(self.job.getID()), "filename": self.filename, "task_id": self.task.getID(), "host_id": self.source.conf.getID(), "target_id": self.target.conf.getID(), "type": self.task.getType(), "hostname": self.source.conf.getHostname(), "log": log.getBuffer(), "start_time": self.start_time, "end_time": self.end_time } self.getDB().addDoc(doc)