From 10ab91179be6fa462ebe27d58f447353640110a7 Mon Sep 17 00:00:00 2001 From: Bill Thiede Date: Mon, 9 Oct 2017 20:29:01 -0700 Subject: [PATCH] Use SSH to list most recent snapshot age. --- zfs_replication_exporter.go | 146 ++++++++++++++++++++---------------- 1 file changed, 83 insertions(+), 63 deletions(-) diff --git a/zfs_replication_exporter.go b/zfs_replication_exporter.go index f535463..325d0fa 100644 --- a/zfs_replication_exporter.go +++ b/zfs_replication_exporter.go @@ -1,18 +1,19 @@ package main import ( + "bufio" + "bytes" "flag" "fmt" - "io" - "log" + "io/ioutil" "os" - "strings" + "path/filepath" + "regexp" "time" - "golang.org/x/crypto/ssh" + "github.com/golang/glog" - "xinu.tv/thumbnailer/auth" - "xinu.tv/thumbnailer/scp" + "golang.org/x/crypto/ssh" ) var ( @@ -20,51 +21,54 @@ var ( user = flag.String("user", os.Getenv("USER"), "ssh user") ) -func checkFatal(err error) { - if err != nil { - log.Fatal(err) - } -} +// Example: @auto-20171001.1400-2w +var ( + snapshotPattern = regexp.MustCompile(`^([^@]+)@auto-(\d{8}\.\d{4})-\d+[mwy]$`) + snapshotFormat = "20060102.1504" +) -func progress(w io.Writer, r io.Reader, size int64) error { - const ( - chunkSize = 1 << 10 - width = 50 - ) - var err error - total, bLast := int64(0), int64(0) - tLast := time.Now() - defer fmt.Println() - for { - var n int64 - n, err = io.CopyN(w, r, chunkSize) - total += n - bLast += n - if time.Since(tLast) > (300 * time.Millisecond) { - kbps := float64(bLast) / time.Since(tLast).Seconds() / 1024 - frac := int(total * width / size) - fmt.Printf("\rDownloaded: %s>%s| %.2f Kb/s", strings.Repeat("=", frac), - strings.Repeat(" ", width-frac), kbps) - tLast = time.Now() - bLast = 0 - } +func newPublicKey() ([]ssh.AuthMethod, error) { + var signers []ssh.AuthMethod + for _, path := range []string{ + filepath.Join(os.Getenv("HOME"), ".ssh", "id_dsa"), + filepath.Join(os.Getenv("HOME"), ".ssh", "id_rsa"), + } { + + // A public key may be used to authenticate against the remote + // server by using an unencrypted PEM-encoded private key file. + // + // If you have an encrypted private key, the crypto/x509 package + // can be used to decrypt it. + key, err := ioutil.ReadFile(path) if err != nil { - break + if os.IsNotExist(err) { + continue + } + return nil, fmt.Errorf("unable to read private key %q: %v", path, err) } - } - if err == io.EOF { - return nil - } - return err -} -var sink, _ = os.Create(os.DevNull) + // Create the Signer for this private key. + signer, err := ssh.ParsePrivateKey(key) + if err != nil { + return nil, fmt.Errorf("unable to parse private key %q: %v", path, err) + } + signers = append(signers, ssh.PublicKeys(signer)) + } + if len(signers) == 0 { + return nil, fmt.Errorf("no public keys configured") + } + return signers, nil +} func main() { flag.Parse() - ams, err := auth.NewPublicKey() - checkFatal(err) + defer glog.Flush() + + ams, err := newPublicKey() + if err != nil { + glog.Exitf("Error fetching public keys: %v", err) + } // An SSH client is represented with a ClientConn. // @@ -77,27 +81,43 @@ func main() { HostKeyCallback: ssh.InsecureIgnoreHostKey(), } client, err := ssh.Dial("tcp", *host, config) - checkFatal(err) + if err != nil { + glog.Exitf("Error dialing %q: %v", *host, err) + } - for _, fn := range flag.Args() { - log.Println("Fetching", fn) - // Each ClientConn can support multiple interactive sessions, - // represented by a Session. - session, err := client.NewSession() - checkFatal(err) - - f, err := scp.ScpFrom(session, fn) - if err != nil { - log.Println(err) - continue + // Each ClientConn can support multiple interactive sessions, + // represented by a Session. + s, err := client.NewSession() + if err != nil { + glog.Exitf("Error creating new session: %v", err) + } + cmd := "/sbin/zfs list -t snapshot -H -o name -s name" + b, err := s.CombinedOutput(cmd) + if err != nil { + glog.Exitf("Error running %q: %v", cmd, err) + } + scanner := bufio.NewScanner(bytes.NewReader(b)) + snapshotAges := map[string]time.Time{} + for scanner.Scan() { + l := scanner.Text() + m := snapshotPattern.FindStringSubmatch(l) + if len(m) == 3 { + snapshot := m[1] + t, err := time.Parse(snapshotFormat, m[2]) + if err != nil { + glog.Errorf("Malformed time in snapshot %q: %v", m[2], err) + continue + } + snapshotTime := snapshotAges[snapshot] + if snapshotTime.Before(t) { + snapshotAges[snapshot] = t + } } - - log.Println(f.Name, f.Perm, f.Size) - - err = progress(sink, f, f.Size) - checkFatal(err) - - checkFatal(f.Close()) - checkFatal(session.Close()) + } + if err := scanner.Err(); err != nil { + glog.Exitf("Failed to scan response: %v", err) + } + for snapshot, snapshotTime := range snapshotAges { + fmt.Println(snapshot, snapshotTime, time.Since(snapshotTime)) } }