Commit 90402347 authored by David's avatar David

Getin thigns done I think

parent b84e7c0d
package is.kow.backupcoordinator
case class HostBackup(
hostname: String,
repoName: String,
backupPaths: List[String],
preBackupCommand: Option[String] = None,
postBackupCommand: Option[String] = None,
keepDaily: Int = 30,
keepMonthly: Int = 3
)
......@@ -8,8 +8,6 @@ object Main extends App {
Security.addProvider(new BouncyCastleProvider)
//Set up an actor system to do the work
println("HELLO WORLD")
}
package is.kow.backupcoordinator.actors
import akka.actor.{Actor, ActorLogging, ActorRef, Props, ReceiveTimeout}
import akka.actor.{Actor, ActorLogging, ActorRef, Props, ReceiveTimeout, Terminated}
import is.kow.backupcoordinator.HostBackup
import is.kow.backupcoordinator.actors.HostBackupActor.EstablishSSHSession
import is.kow.backupcoordinator.actors.SSHCommandActor.{ExecuteCommand, ExecutionCompleted}
import is.kow.backupcoordinator.actors.SSHConnectionActor.Connect
import net.schmizz.sshj.SSHClient
import scala.concurrent.duration.Duration
object HostBackupActor {
def props(hostname: String, statusActor: ActorRef): Props = Props(new HostBackupActor(hostname, statusActor))
def props(hostBackup: HostBackup, statusActor: ActorRef): Props = Props(new HostBackupActor(hostBackup, statusActor))
case object EstablishSSHSession
......@@ -15,7 +16,10 @@ object HostBackupActor {
}
class HostBackupActor(hostname: String, statusActor: ActorRef) extends Actor with ActorLogging {
class HostBackupActor(hostBackup: HostBackup, statusActor: ActorRef) extends Actor with ActorLogging {
import scala.concurrent.duration._
/*
stages of backing up a host
1. establish SSH connection
......@@ -39,20 +43,135 @@ class HostBackupActor(hostname: String, statusActor: ActorRef) extends Actor wit
case EstablishSSHSession =>
//Start a tiny actor to do the actual establishment, so it can fail, and we can retry
val connectionActor = context.actorOf(SSHConnectionActor.props)
import scala.concurrent.duration._
connectionActor ! Connect(hostBackup.hostname) //Assuming default port
context.setReceiveTimeout(3.seconds)
context.become(awaitingConnection)
}
def awaitingConnection: Receive = {
case ReceiveTimeout =>
log.error("Did not receive connection in time! Retrying")
log.error("Did not receive connection in time! OH NOES")
throw new Exception("Did not receive connection in time!")
case client:SSHClient =>
case client: SSHClient =>
context.setReceiveTimeout(Duration.Undefined)
//Have an ssh connection, time to send commands like make backup directory
val mkdirActor = context.actorOf(SSHCommandActor.props(client))
context.watch(mkdirActor)
mkdirActor ! ExecuteCommand("mkdir /mnt/auto-backup")
context.become(awaitingMkdirComplete(client, mkdirActor))
context.setReceiveTimeout(1.second)
}
def awaitingMkdirComplete(client: SSHClient, mkdirActor: ActorRef): Receive = {
case ReceiveTimeout =>
log.error("Unable to create directory in time! OH NOES")
throw new Exception("Did not create directory in time!")
//TODO: need to clean up!
case t@Terminated(`client`) =>
log.error(s"there was an exception and the actor died")
//TODO: need to clean up behind myself
case ExecutionCompleted(cmd, output, status) =>
context.setReceiveTimeout(Duration.Undefined)
log.info(s"Command Processed ($status): $cmd -- $output")
//Can move onto step three
val mountActor = context.actorOf(SSHCommandActor.props(client))
context.watch(mountActor)
mountActor ! ExecuteCommand("mount 10.10.220.92:/mnt/OldTank/backupTesting /mnt/auto-backup")
context.become(awaitingMountComplete(client, mountActor))
context.setReceiveTimeout(3.seconds)
}
def awaitingMountComplete(client: SSHClient, mountActor: ActorRef): Receive = {
case ReceiveTimeout =>
log.error("Unable to mount nfs in time! OH NOES")
throw new Exception("Did mount nfs in time!")
//TODO: need to clean up behind me
case t@Terminated(`client`) =>
log.error(s"there was an exception and mounting command died")
case ExecutionCompleted(cmd, output, status) =>
context.setReceiveTimeout(Duration.Undefined)
//Completed the call to mount the nfs directory
log.info(s"Command Processed ($status): $cmd -- $output")
//Step four!
doBackup(client, hostBackup.preBackupCommand, hostBackup.backupPaths, hostBackup.postBackupCommand)
}
def awaitingPreBackup(client: SSHClient, cmdActor: ActorRef, backupPaths: List[String], postBackup: Option[String]): Receive = {
case t@Terminated(`cmdActor`) =>
log.error("Pre-backup failed!")
//TODO: need to clean up hard
case ExecutionCompleted(cmd, output, status) =>
//Completed the call to do pre-backup commands
log.info(s"Command Processed ($status): $cmd -- $output")
//Can now execute the actual backup command
doBackup(client, None, backupPaths, postBackup)
}
def awaitingBackupDirectory(client: SSHClient, cmdActor: ActorRef, backupPaths: List[String], postBackup: Option[String]): Receive = {
case t@Terminated(`cmdActor`) =>
log.error("Directory backup failed!")
//TODO: need to clean up hard
case ExecutionCompleted(cmd, output, status) =>
//A backup directory is completed!
log.info(s"Completed a directory backup!")
doBackup(client, None, backupPaths.tail, postBackup)
}
def awaitingUnmount(client: SSHClient, cmdActor: ActorRef): Receive = {
case t@Terminated(`cmdActor`) =>
log.error("unmount failed!")
//TODO: need to clean up hard
case ExecutionCompleted(cmd, output, status) =>
log.info("ALL DONE, closing down")
//Done with this, now we can close our ssh connection
client.close() //Boom!
context.stop(self)
}
def doBackup(client: SSHClient, preBackup: Option[String], backupPaths: List[String], postBackup: Option[String]): Unit = {
if (preBackup.nonEmpty) {
//Do the pre-backup commands, and become awaitingPreBackup
val preBackupCmd = context.actorOf(SSHCommandActor.props(client))
preBackupCmd ! ExecuteCommand(preBackup.get)
//TODO: what's a reasonable time for stopping? Should have some kind of tick to indicate processing
context.become(awaitingPreBackup(client, preBackupCmd, backupPaths, postBackup))
} else if (backupPaths.nonEmpty) {
//TODO: this assumes already initialized -- Need to add an initializer actor to handle that for me
//TODO: can check initialization with `borg info`
//have to build the backup command and issue one for each backup path
val path = backupPaths.head
val repoPath = s"/mnt/auto-backup/${hostBackup.repoName}"
val backupName = path.split("/").last
val backupCmd =
s"""
| borg create \
| --info \
| --stats \
| --one-file-system \
| --compression zlib,7 \
| ${repoPath}::${backupName}-{now:%Y-%m-%d_%H:%M:%S} \
| ${path}
|
""".stripMargin
//backup command created
val backupDirectoryActor = context.actorOf(SSHCommandActor.props(client))
backupDirectoryActor ! ExecuteCommand(backupCmd)
context.become(awaitingBackupDirectory(client, backupDirectoryActor, backupPaths, postBackup))
} else if (postBackup.nonEmpty) {
log.error("TODO Post backup stuff isn't actually ever run")
//Backup paths should be empty at this stage
doBackup(client, None, backupPaths, None)
} else {
//All done doing the backup, step five!
val cmd = "umount /mnt/auto-backup"
val actor = context.actorOf(SSHCommandActor.props(client))
actor ! ExecuteCommand(cmd)
context.become(awaitingUnmount(client, actor))
}
}
}
......
Markdown is supported
0% or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment