Compare commits
7 Commits
| Author | SHA1 | Date | |
|---|---|---|---|
| 30057d4e59 | |||
| 75fd21e454 | |||
| 0d2f73d728 | |||
| 9b803aad2d | |||
| fb74addc1e | |||
| 7f89372cfa | |||
| 26b436463d |
Generated
+1
-1
@@ -83,7 +83,7 @@
|
|||||||
branch = "master"
|
branch = "master"
|
||||||
name = "github.com/problame/go-netssh"
|
name = "github.com/problame/go-netssh"
|
||||||
packages = ["."]
|
packages = ["."]
|
||||||
revision = "ffa145d2506e222977205e7666a9722d6b9959ac"
|
revision = "53a2e445f8ace7ec678f2d8cdd9c1428dfef4562"
|
||||||
|
|
||||||
[[projects]]
|
[[projects]]
|
||||||
branch = "master"
|
branch = "master"
|
||||||
|
|||||||
@@ -11,6 +11,7 @@ RUN /tmp/lazy.sh devsetup
|
|||||||
# prepare volume mount of git checkout to /zrepl
|
# prepare volume mount of git checkout to /zrepl
|
||||||
RUN mkdir -p /go/src/github.com/zrepl/zrepl
|
RUN mkdir -p /go/src/github.com/zrepl/zrepl
|
||||||
RUN chmod -R 0777 /go
|
RUN chmod -R 0777 /go
|
||||||
|
RUN mkdir -p /.cache && chmod -R 0777 /.cache
|
||||||
WORKDIR /go/src/github.com/zrepl/zrepl
|
WORKDIR /go/src/github.com/zrepl/zrepl
|
||||||
|
|
||||||
|
|
||||||
|
|||||||
+16
-30
@@ -5,7 +5,6 @@ import (
|
|||||||
"fmt"
|
"fmt"
|
||||||
"github.com/zrepl/zrepl/zfs"
|
"github.com/zrepl/zrepl/zfs"
|
||||||
"sort"
|
"sort"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -166,46 +165,33 @@ func (a *IntervalAutosnap) doSnapshots(didSnaps chan struct{}) {
|
|||||||
|
|
||||||
// don't cache the result from previous run in case the user added
|
// don't cache the result from previous run in case the user added
|
||||||
// a new dataset in the meantime
|
// a new dataset in the meantime
|
||||||
fss, stop := a.filterFilesystems()
|
ds, stop := a.filterFilesystems()
|
||||||
if stop {
|
if stop {
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
a.task.Log().Info("beginning parallel snapshots")
|
|
||||||
|
|
||||||
// TODO channel programs -> allow a little jitter?
|
// TODO channel programs -> allow a little jitter?
|
||||||
var wg sync.WaitGroup
|
for _, d := range ds {
|
||||||
for fsi := range fss {
|
suffix := time.Now().In(time.UTC).Format("20060102_150405_000")
|
||||||
wg.Add(1)
|
snapname := fmt.Sprintf("%s%s", a.Prefix, suffix)
|
||||||
go func(fs *zfs.DatasetPath) {
|
|
||||||
defer wg.Done()
|
|
||||||
|
|
||||||
suffix := time.Now().In(time.UTC).Format("20060102_150405_000")
|
l := a.task.Log().WithField(logFSField, d.ToString()).
|
||||||
snapname := fmt.Sprintf("%s%s", a.Prefix, suffix)
|
WithField("snapname", snapname)
|
||||||
|
|
||||||
l := a.task.Log().WithField(logFSField, fs.ToString()).
|
l.Info("create snapshot")
|
||||||
WithField("snapname", snapname)
|
err := zfs.ZFSSnapshot(d, snapname, false)
|
||||||
|
if err != nil {
|
||||||
|
a.task.Log().WithError(err).Error("cannot create snapshot")
|
||||||
|
}
|
||||||
|
|
||||||
l.Info("create snapshot")
|
l.Info("create corresponding bookmark")
|
||||||
err := zfs.ZFSSnapshot(fs, snapname, false)
|
err = zfs.ZFSBookmark(d, snapname, snapname)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
l.WithError(err).Error("cannot create snapshot")
|
a.task.Log().WithError(err).Error("cannot create bookmark")
|
||||||
return
|
}
|
||||||
}
|
|
||||||
|
|
||||||
l.Info("create corresponding bookmark")
|
|
||||||
err = zfs.ZFSBookmark(fs, snapname, snapname)
|
|
||||||
if err != nil {
|
|
||||||
l.WithError(err).Error("cannot create bookmark")
|
|
||||||
}
|
|
||||||
|
|
||||||
}(fss[fsi])
|
|
||||||
}
|
}
|
||||||
|
|
||||||
a.task.Log().Info("waiting for parallel snapshots to finish")
|
|
||||||
wg.Wait()
|
|
||||||
a.task.Log().Info("snapshots finished")
|
|
||||||
|
|
||||||
select {
|
select {
|
||||||
case didSnaps <- struct{}{}:
|
case didSnaps <- struct{}{}:
|
||||||
default:
|
default:
|
||||||
|
|||||||
+20
-2
@@ -9,6 +9,7 @@ import (
|
|||||||
"github.com/mitchellh/mapstructure"
|
"github.com/mitchellh/mapstructure"
|
||||||
"github.com/pkg/errors"
|
"github.com/pkg/errors"
|
||||||
"github.com/problame/go-netssh"
|
"github.com/problame/go-netssh"
|
||||||
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
type SSHStdinserverConnecter struct {
|
type SSHStdinserverConnecter struct {
|
||||||
@@ -19,6 +20,8 @@ type SSHStdinserverConnecter struct {
|
|||||||
TransportOpenCommand []string `mapstructure:"transport_open_command"`
|
TransportOpenCommand []string `mapstructure:"transport_open_command"`
|
||||||
SSHCommand string `mapstructure:"ssh_command"`
|
SSHCommand string `mapstructure:"ssh_command"`
|
||||||
Options []string
|
Options []string
|
||||||
|
DialTimeout string `mapstructure:"dial_timeout"`
|
||||||
|
dialTimeout time.Duration
|
||||||
}
|
}
|
||||||
|
|
||||||
func parseSSHStdinserverConnecter(i map[string]interface{}) (c *SSHStdinserverConnecter, err error) {
|
func parseSSHStdinserverConnecter(i map[string]interface{}) (c *SSHStdinserverConnecter, err error) {
|
||||||
@@ -29,6 +32,15 @@ func parseSSHStdinserverConnecter(i map[string]interface{}) (c *SSHStdinserverCo
|
|||||||
return nil, err
|
return nil, err
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if c.DialTimeout != "" {
|
||||||
|
c.dialTimeout, err = time.ParseDuration(c.DialTimeout)
|
||||||
|
if err != nil {
|
||||||
|
return nil, errors.Wrap(err, "cannot parse dial_timeout")
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
c.dialTimeout = 10 * time.Second
|
||||||
|
}
|
||||||
|
|
||||||
// TODO assert fields are filled
|
// TODO assert fields are filled
|
||||||
return
|
return
|
||||||
|
|
||||||
@@ -38,9 +50,15 @@ func (c *SSHStdinserverConnecter) Connect() (rwc io.ReadWriteCloser, err error)
|
|||||||
|
|
||||||
var endpoint netssh.Endpoint
|
var endpoint netssh.Endpoint
|
||||||
if err = copier.Copy(&endpoint, c); err != nil {
|
if err = copier.Copy(&endpoint, c); err != nil {
|
||||||
return
|
return nil, errors.WithStack(err)
|
||||||
}
|
}
|
||||||
if rwc, err = netssh.Dial(context.TODO(), endpoint); err != nil {
|
var dialCtx context.Context
|
||||||
|
dialCtx, dialCancel := context.WithTimeout(context.TODO(), c.dialTimeout) // context.TODO tied to error handling below
|
||||||
|
defer dialCancel()
|
||||||
|
if rwc, err = netssh.Dial(dialCtx, endpoint); err != nil {
|
||||||
|
if err == context.DeadlineExceeded {
|
||||||
|
err = errors.Errorf("dial_timeout of %s exceeded", c.dialTimeout)
|
||||||
|
}
|
||||||
err = errors.WithStack(err)
|
err = errors.WithStack(err)
|
||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|||||||
+40
-59
@@ -3,9 +3,7 @@ package cmd
|
|||||||
import (
|
import (
|
||||||
"context"
|
"context"
|
||||||
"fmt"
|
"fmt"
|
||||||
"github.com/zrepl/zrepl/util"
|
|
||||||
"github.com/zrepl/zrepl/zfs"
|
"github.com/zrepl/zrepl/zfs"
|
||||||
"sync"
|
|
||||||
"time"
|
"time"
|
||||||
)
|
)
|
||||||
|
|
||||||
@@ -25,8 +23,9 @@ type PruneResult struct {
|
|||||||
Remove []zfs.FilesystemVersion
|
Remove []zfs.FilesystemVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME must not call p.task.Enter because it runs in parallel
|
|
||||||
func (p *Pruner) filterFilesystems() (filesystems []*zfs.DatasetPath, stop bool) {
|
func (p *Pruner) filterFilesystems() (filesystems []*zfs.DatasetPath, stop bool) {
|
||||||
|
p.task.Enter("filter_fs")
|
||||||
|
defer p.task.Finish()
|
||||||
filesystems, err := zfs.ZFSListMapping(p.DatasetFilter)
|
filesystems, err := zfs.ZFSListMapping(p.DatasetFilter)
|
||||||
if err != nil {
|
if err != nil {
|
||||||
p.task.Log().WithError(err).Error("error applying filesystem filter")
|
p.task.Log().WithError(err).Error("error applying filesystem filter")
|
||||||
@@ -39,8 +38,9 @@ func (p *Pruner) filterFilesystems() (filesystems []*zfs.DatasetPath, stop bool)
|
|||||||
return filesystems, false
|
return filesystems, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME must not call p.task.Enter because it runs in parallel
|
|
||||||
func (p *Pruner) filterVersions(fs *zfs.DatasetPath) (fsversions []zfs.FilesystemVersion, stop bool) {
|
func (p *Pruner) filterVersions(fs *zfs.DatasetPath) (fsversions []zfs.FilesystemVersion, stop bool) {
|
||||||
|
p.task.Enter("filter_versions")
|
||||||
|
defer p.task.Finish()
|
||||||
log := p.task.Log().WithField(logFSField, fs.ToString())
|
log := p.task.Log().WithField(logFSField, fs.ToString())
|
||||||
|
|
||||||
filter := NewPrefixFilter(p.SnapshotPrefix)
|
filter := NewPrefixFilter(p.SnapshotPrefix)
|
||||||
@@ -56,8 +56,9 @@ func (p *Pruner) filterVersions(fs *zfs.DatasetPath) (fsversions []zfs.Filesyste
|
|||||||
return fsversions, false
|
return fsversions, false
|
||||||
}
|
}
|
||||||
|
|
||||||
// FIXME must not call p.task.Enter because it runs in parallel
|
func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath) (r PruneResult, valid bool) {
|
||||||
func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath, destroySemaphore util.Semaphore) (r PruneResult, valid bool) {
|
p.task.Enter("prune_fs")
|
||||||
|
defer p.task.Finish()
|
||||||
log := p.task.Log().WithField(logFSField, fs.ToString())
|
log := p.task.Log().WithField(logFSField, fs.ToString())
|
||||||
|
|
||||||
fsversions, stop := p.filterVersions(fs)
|
fsversions, stop := p.filterVersions(fs)
|
||||||
@@ -65,7 +66,9 @@ func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath, destroySemaphore util.Sema
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
|
p.task.Enter("prune_policy")
|
||||||
keep, remove, err := p.PrunePolicy.Prune(fs, fsversions)
|
keep, remove, err := p.PrunePolicy.Prune(fs, fsversions)
|
||||||
|
p.task.Finish()
|
||||||
if err != nil {
|
if err != nil {
|
||||||
log.WithError(err).Error("error evaluating prune policy")
|
log.WithError(err).Error("error evaluating prune policy")
|
||||||
return
|
return
|
||||||
@@ -78,37 +81,33 @@ func (p *Pruner) pruneFilesystem(fs *zfs.DatasetPath, destroySemaphore util.Sema
|
|||||||
|
|
||||||
r = PruneResult{fs, fsversions, keep, remove}
|
r = PruneResult{fs, fsversions, keep, remove}
|
||||||
|
|
||||||
var wg sync.WaitGroup
|
makeFields := func(v zfs.FilesystemVersion) (fields map[string]interface{}) {
|
||||||
for v := range remove {
|
fields = make(map[string]interface{})
|
||||||
wg.Add(1)
|
fields["version"] = v.ToAbsPath(fs)
|
||||||
go func(v zfs.FilesystemVersion) {
|
timeSince := v.Creation.Sub(p.Now)
|
||||||
defer wg.Done()
|
fields["age_ns"] = timeSince
|
||||||
// log fields
|
const day time.Duration = 24 * time.Hour
|
||||||
fields := make(map[string]interface{})
|
days := timeSince / day
|
||||||
fields["version"] = v.ToAbsPath(fs)
|
remainder := timeSince % day
|
||||||
timeSince := v.Creation.Sub(p.Now)
|
fields["age_str"] = fmt.Sprintf("%dd%s", days, remainder)
|
||||||
fields["age_ns"] = timeSince
|
return
|
||||||
const day time.Duration = 24 * time.Hour
|
|
||||||
days := timeSince / day
|
|
||||||
remainder := timeSince % day
|
|
||||||
fields["age_str"] = fmt.Sprintf("%dd%s", days, remainder)
|
|
||||||
|
|
||||||
log.WithFields(fields).Info("destroying version")
|
|
||||||
// echo what we'll do and exec zfs destroy if not dry run
|
|
||||||
// TODO special handling for EBUSY (zfs hold)
|
|
||||||
// TODO error handling for clones? just echo to cli, skip over, and exit with non-zero status code (we're idempotent)
|
|
||||||
if !p.DryRun {
|
|
||||||
destroySemaphore.Down()
|
|
||||||
err := zfs.ZFSDestroyFilesystemVersion(fs, v)
|
|
||||||
destroySemaphore.Up()
|
|
||||||
if err != nil {
|
|
||||||
log.WithFields(fields).WithError(err).Error("error destroying version")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}(remove[v])
|
|
||||||
}
|
}
|
||||||
wg.Wait()
|
|
||||||
|
|
||||||
|
for _, v := range remove {
|
||||||
|
fields := makeFields(v)
|
||||||
|
log.WithFields(fields).Info("destroying version")
|
||||||
|
// echo what we'll do and exec zfs destroy if not dry run
|
||||||
|
// TODO special handling for EBUSY (zfs hold)
|
||||||
|
// TODO error handling for clones? just echo to cli, skip over, and exit with non-zero status code (we're idempotent)
|
||||||
|
if !p.DryRun {
|
||||||
|
p.task.Enter("destroy")
|
||||||
|
err := zfs.ZFSDestroyFilesystemVersion(fs, v)
|
||||||
|
p.task.Finish()
|
||||||
|
if err != nil {
|
||||||
|
log.WithFields(fields).WithError(err).Error("error destroying version")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
return r, true
|
return r, true
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -125,31 +124,13 @@ func (p *Pruner) Run(ctx context.Context) (r []PruneResult, err error) {
|
|||||||
return
|
return
|
||||||
}
|
}
|
||||||
|
|
||||||
maxConcurrentDestroy := len(filesystems)
|
|
||||||
p.task.Log().WithField("max_concurrent_destroy", maxConcurrentDestroy).Info("begin concurrent destroy")
|
|
||||||
destroySem := util.NewSemaphore(maxConcurrentDestroy)
|
|
||||||
|
|
||||||
resChan := make(chan PruneResult, len(filesystems))
|
|
||||||
var wg sync.WaitGroup
|
|
||||||
for _, fs := range filesystems {
|
|
||||||
wg.Add(1)
|
|
||||||
go func(fs *zfs.DatasetPath) {
|
|
||||||
defer wg.Done()
|
|
||||||
res, ok := p.pruneFilesystem(fs, destroySem)
|
|
||||||
if ok {
|
|
||||||
resChan <- res
|
|
||||||
}
|
|
||||||
|
|
||||||
}(fs)
|
|
||||||
}
|
|
||||||
wg.Wait()
|
|
||||||
close(resChan)
|
|
||||||
|
|
||||||
p.task.Log().Info("destroys done")
|
|
||||||
|
|
||||||
r = make([]PruneResult, 0, len(filesystems))
|
r = make([]PruneResult, 0, len(filesystems))
|
||||||
for res := range resChan {
|
|
||||||
r = append(r, res)
|
for _, fs := range filesystems {
|
||||||
|
res, ok := p.pruneFilesystem(fs)
|
||||||
|
if ok {
|
||||||
|
r = append(r, res)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return
|
return
|
||||||
|
|||||||
+9
-1
@@ -1,6 +1,7 @@
|
|||||||
.. |break_config| replace:: **[BREAK]**
|
.. |break_config| replace:: **[BREAK]**
|
||||||
.. |break| replace:: **[BREAK]**
|
.. |break| replace:: **[BREAK]**
|
||||||
.. |bugfix| replace:: [BUG]
|
.. |bugfix| replace:: [BUG]
|
||||||
|
.. |docs| replace:: [DOCS]
|
||||||
.. |feature| replace:: [FEATURE]
|
.. |feature| replace:: [FEATURE]
|
||||||
|
|
||||||
Changelog
|
Changelog
|
||||||
@@ -19,7 +20,13 @@ Developers should consult the git commit log or GitHub issue tracker.
|
|||||||
* Make sure to understand the meaning bookmarks have for :ref:`maximum replication downtime <replication-downtime>`.
|
* Make sure to understand the meaning bookmarks have for :ref:`maximum replication downtime <replication-downtime>`.
|
||||||
* Example: :sampleconf:`pullbackup/productionhost.yml`
|
* Example: :sampleconf:`pullbackup/productionhost.yml`
|
||||||
|
|
||||||
* |break| :commit:`ccd062e`: both sides of a replication setup must be updated and restarted. Otherwise the connecting side will hang and not time out.
|
* |break| :commit:`ccd062e`: ``ssh+stdinserver`` transport: changed protocol requires daemon restart on both sides
|
||||||
|
|
||||||
|
* The delicate procedure of talking to the serving-side zrepl daemon via the stdinserver proxy command now has better error handling.
|
||||||
|
* This includes handshakes between client+proxy and client + remote daemo, which is not implemented in previous versions of zrepl.
|
||||||
|
* The connecting side will therefore time out, with the message ``dial_timeout of 10s exceeded``.
|
||||||
|
* Both sides of a replication setup must be updated and restarted. Otherwise the connecting side will hang and not time out.
|
||||||
|
|
||||||
* |break_config| :commit:`2bfcfa5`: first outlet in ``global.logging`` is now used for logging meta-errors, for example problems encountered when writing to other outlets.
|
* |break_config| :commit:`2bfcfa5`: first outlet in ``global.logging`` is now used for logging meta-errors, for example problems encountered when writing to other outlets.
|
||||||
* |feature| :issue:`10`: ``zrepl control status`` subcommand
|
* |feature| :issue:`10`: ``zrepl control status`` subcommand
|
||||||
|
|
||||||
@@ -34,6 +41,7 @@ Developers should consult the git commit log or GitHub issue tracker.
|
|||||||
* |bugfix| :issue:`8` and :issue:`56`: ``ssh+stdinserver`` transport properly reaps SSH child processes
|
* |bugfix| :issue:`8` and :issue:`56`: ``ssh+stdinserver`` transport properly reaps SSH child processes
|
||||||
* |bugfix| :commit:`cef63ac`: ``human`` format now prints non-string values correctly
|
* |bugfix| :commit:`cef63ac`: ``human`` format now prints non-string values correctly
|
||||||
* |bugfix| :issue:`26`: slow TCP outlets no longer block the daemon
|
* |bugfix| :issue:`26`: slow TCP outlets no longer block the daemon
|
||||||
|
* |docs| :issue:`64`: tutorial: document ``known_host`` file entry
|
||||||
|
|
||||||
0.0.2
|
0.0.2
|
||||||
-----
|
-----
|
||||||
|
|||||||
@@ -85,8 +85,9 @@ Connect Mode
|
|||||||
user: root
|
user: root
|
||||||
port: 22
|
port: 22
|
||||||
identity_file: /etc/zrepl/ssh/identity
|
identity_file: /etc/zrepl/ssh/identity
|
||||||
options: # optional
|
options: # optional, default [], `-o` arguments passed to ssh
|
||||||
- "Compression=on"
|
- "Compression=on"
|
||||||
|
dial_timeout: # optional, default 10s, max time.Duration until initial handshake is completed
|
||||||
|
|
||||||
The connecting zrepl daemon
|
The connecting zrepl daemon
|
||||||
|
|
||||||
@@ -101,10 +102,13 @@ The connecting zrepl daemon
|
|||||||
#. The remote user, host and port correspond to those configured.
|
#. The remote user, host and port correspond to those configured.
|
||||||
#. Further options can be specified using the ``options`` field, which appends each entry in the list to the command line using ``-o $entry``.
|
#. Further options can be specified using the ``options`` field, which appends each entry in the list to the command line using ``-o $entry``.
|
||||||
|
|
||||||
1. Wraps the pipe ends in an ``io.ReadWriteCloser`` and uses it for RPC.
|
#. Wraps the pipe ends in an ``io.ReadWriteCloser`` and uses it for RPC.
|
||||||
|
|
||||||
|
|
||||||
As discussed in the section above, the connecting zrepl daemon expects that ``zrepl stdinserver $client_identity`` is executed automatically via an ``authorized_keys`` file entry.
|
As discussed in the section above, the connecting zrepl daemon expects that ``zrepl stdinserver $client_identity`` is executed automatically via an ``authorized_keys`` file entry.
|
||||||
|
|
||||||
|
The ``known_hosts`` file used by the ssh command must contain an entry for the serving host, e.g., ``app-srv.example.com`` in the example above.
|
||||||
|
|
||||||
.. NOTE::
|
.. NOTE::
|
||||||
|
|
||||||
The environment variables of the underlying SSH process are cleared. ``$SSH_AUTH_SOCK`` will not be available.
|
The environment variables of the underlying SSH process are cleared. ``$SSH_AUTH_SOCK`` will not be available.
|
||||||
|
|||||||
+1
-1
@@ -16,7 +16,7 @@ zrepl - ZFS replication
|
|||||||
Getting started
|
Getting started
|
||||||
~~~~~~~~~~~~~~~
|
~~~~~~~~~~~~~~~
|
||||||
|
|
||||||
The :ref:`5 minute tutorial setup <tutorial>` gives you a first impression.
|
The :ref:`10 minutes tutorial setup <tutorial>` gives you a first impression.
|
||||||
|
|
||||||
Main Features
|
Main Features
|
||||||
~~~~~~~~~~~~~
|
~~~~~~~~~~~~~
|
||||||
|
|||||||
+9
-2
@@ -60,7 +60,7 @@ Follow the :ref:`OS-specific installation instructions <installation>` and come
|
|||||||
Configure ``backup-srv``
|
Configure ``backup-srv``
|
||||||
------------------------
|
------------------------
|
||||||
|
|
||||||
We define a **pull job** named ``pull_app-srv`` in the |mainconfig|: ::
|
We define a **pull job** named ``pull_app-srv`` in the |mainconfig| on host ``backup-srv``: ::
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- name: pull_app-srv
|
- name: pull_app-srv
|
||||||
@@ -94,6 +94,13 @@ It uses the private key specified at ``connect.identity_file`` which we still ne
|
|||||||
Note that most use cases do not benefit from separate keypairs per remote endpoint.
|
Note that most use cases do not benefit from separate keypairs per remote endpoint.
|
||||||
Thus, it is sufficient to create one keypair and use it for all ``connect`` directives on one host.
|
Thus, it is sufficient to create one keypair and use it for all ``connect`` directives on one host.
|
||||||
|
|
||||||
|
zrepl uses ssh's default ``known_hosts`` file, which must contain a host identification entry for ``app-srv.example.com``.
|
||||||
|
If that entry does not already exist, we need to generate it.
|
||||||
|
Run the following command, compare the host fingerprints, and confirm with yes if they match.
|
||||||
|
You will not be able to get a shell with the identity file we just generated, which is fine. ::
|
||||||
|
|
||||||
|
ssh -i /etc/zrepl/ssh/identity root@app-srv.example.com
|
||||||
|
|
||||||
Learn more about :ref:`transport-ssh+stdinserver` transport and the :ref:`pull job <job-pull>` format.
|
Learn more about :ref:`transport-ssh+stdinserver` transport and the :ref:`pull job <job-pull>` format.
|
||||||
|
|
||||||
.. _tutorial-configure-app-srv:
|
.. _tutorial-configure-app-srv:
|
||||||
@@ -101,7 +108,7 @@ Learn more about :ref:`transport-ssh+stdinserver` transport and the :ref:`pull j
|
|||||||
Configure ``app-srv``
|
Configure ``app-srv``
|
||||||
---------------------
|
---------------------
|
||||||
|
|
||||||
We define a corresponding **source job** named ``pull_backup`` in the |mainconfig|: ::
|
We define a corresponding **source job** named ``pull_backup`` in the |mainconfig| on host ``app-srv``: ::
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
- name: pull_backup
|
- name: pull_backup
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import "fmt"
|
import "strconv"
|
||||||
|
|
||||||
const _DataType_name = "DataTypeNoneDataTypeControlDataTypeMarshaledJSONDataTypeOctets"
|
const _DataType_name = "DataTypeNoneDataTypeControlDataTypeMarshaledJSONDataTypeOctets"
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ var _DataType_index = [...]uint8{0, 12, 27, 48, 62}
|
|||||||
func (i DataType) String() string {
|
func (i DataType) String() string {
|
||||||
i -= 1
|
i -= 1
|
||||||
if i >= DataType(len(_DataType_index)-1) {
|
if i >= DataType(len(_DataType_index)-1) {
|
||||||
return fmt.Sprintf("DataType(%d)", i+1)
|
return "DataType(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||||
}
|
}
|
||||||
return _DataType_name[_DataType_index[i]:_DataType_index[i+1]]
|
return _DataType_name[_DataType_index[i]:_DataType_index[i+1]]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import "fmt"
|
import "strconv"
|
||||||
|
|
||||||
const (
|
const (
|
||||||
_FrameType_name_0 = "FrameTypeHeaderFrameTypeDataFrameTypeTrailer"
|
_FrameType_name_0 = "FrameTypeHeaderFrameTypeDataFrameTypeTrailer"
|
||||||
@@ -11,7 +11,6 @@ const (
|
|||||||
|
|
||||||
var (
|
var (
|
||||||
_FrameType_index_0 = [...]uint8{0, 15, 28, 44}
|
_FrameType_index_0 = [...]uint8{0, 15, 28, 44}
|
||||||
_FrameType_index_1 = [...]uint8{0, 12}
|
|
||||||
)
|
)
|
||||||
|
|
||||||
func (i FrameType) String() string {
|
func (i FrameType) String() string {
|
||||||
@@ -22,6 +21,6 @@ func (i FrameType) String() string {
|
|||||||
case i == 255:
|
case i == 255:
|
||||||
return _FrameType_name_1
|
return _FrameType_name_1
|
||||||
default:
|
default:
|
||||||
return fmt.Sprintf("FrameType(%d)", i)
|
return "FrameType(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package rpc
|
package rpc
|
||||||
|
|
||||||
import "fmt"
|
import "strconv"
|
||||||
|
|
||||||
const _Status_name = "StatusOKStatusRequestErrorStatusServerErrorStatusError"
|
const _Status_name = "StatusOKStatusRequestErrorStatusServerErrorStatusError"
|
||||||
|
|
||||||
@@ -11,7 +11,7 @@ var _Status_index = [...]uint8{0, 8, 26, 43, 54}
|
|||||||
func (i Status) String() string {
|
func (i Status) String() string {
|
||||||
i -= 1
|
i -= 1
|
||||||
if i >= Status(len(_Status_index)-1) {
|
if i >= Status(len(_Status_index)-1) {
|
||||||
return fmt.Sprintf("Status(%d)", i+1)
|
return "Status(" + strconv.FormatInt(int64(i+1), 10) + ")"
|
||||||
}
|
}
|
||||||
return _Status_name[_Status_index[i]:_Status_index[i+1]]
|
return _Status_name[_Status_index[i]:_Status_index[i+1]]
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -1,17 +0,0 @@
|
|||||||
package util
|
|
||||||
|
|
||||||
type Semaphore struct {
|
|
||||||
c chan struct{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func NewSemaphore(cap int) Semaphore {
|
|
||||||
return Semaphore{make(chan struct{}, cap)}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Semaphore) Down() {
|
|
||||||
s.c <- struct{}{}
|
|
||||||
}
|
|
||||||
|
|
||||||
func (s Semaphore) Up() {
|
|
||||||
<-s.c
|
|
||||||
}
|
|
||||||
@@ -2,7 +2,7 @@
|
|||||||
|
|
||||||
package zfs
|
package zfs
|
||||||
|
|
||||||
import "fmt"
|
import "strconv"
|
||||||
|
|
||||||
const _Conflict_name = "ConflictIncrementalConflictAllRightConflictNoCommonAncestorConflictDiverged"
|
const _Conflict_name = "ConflictIncrementalConflictAllRightConflictNoCommonAncestorConflictDiverged"
|
||||||
|
|
||||||
@@ -10,7 +10,7 @@ var _Conflict_index = [...]uint8{0, 19, 35, 59, 75}
|
|||||||
|
|
||||||
func (i Conflict) String() string {
|
func (i Conflict) String() string {
|
||||||
if i < 0 || i >= Conflict(len(_Conflict_index)-1) {
|
if i < 0 || i >= Conflict(len(_Conflict_index)-1) {
|
||||||
return fmt.Sprintf("Conflict(%d)", i)
|
return "Conflict(" + strconv.FormatInt(int64(i), 10) + ")"
|
||||||
}
|
}
|
||||||
return _Conflict_name[_Conflict_index[i]:_Conflict_index[i+1]]
|
return _Conflict_name[_Conflict_index[i]:_Conflict_index[i+1]]
|
||||||
}
|
}
|
||||||
|
|||||||
Reference in New Issue
Block a user