Compare commits
27 Commits
Author | SHA1 | Date | |
---|---|---|---|
97cd0a2a6d | |||
49a92084a9 | |||
9bdeecaee4 | |||
843880f008 | |||
a6ed5e1273 | |||
74f94d0678 | |||
946c3e8a81 | |||
7b212c1f79 | |||
3b2046d263 | |||
1ffe030123 | |||
5255e641fa | |||
c86b6f40d7 | |||
5a718dce17 | |||
1b32750644 | |||
5aa103c3c3 | |||
fd3f690104 | |||
24b638bd9f | |||
9624c5eecb | |||
503dd339a8 | |||
36ea5df444 | |||
dce9dd6f70 | |||
88e28e15e4 | |||
399e48a1ed | |||
7ae571e7cb | |||
4264c5023b | |||
82b7adf90b | |||
71c4a3138f |
@ -1,6 +1,6 @@
|
||||
[package]
|
||||
name = "proxmox-backup"
|
||||
version = "0.8.11"
|
||||
version = "0.8.13"
|
||||
authors = ["Dietmar Maurer <dietmar@proxmox.com>"]
|
||||
edition = "2018"
|
||||
license = "AGPL-3"
|
||||
@ -43,7 +43,7 @@ proxmox = { version = "0.3.3", features = [ "sortable-macro", "api-macro", "webs
|
||||
#proxmox = { git = "ssh://gitolite3@proxdev.maurer-it.com/rust/proxmox", version = "0.1.2", features = [ "sortable-macro", "api-macro" ] }
|
||||
#proxmox = { path = "../proxmox/proxmox", features = [ "sortable-macro", "api-macro", "websocket" ] }
|
||||
proxmox-fuse = "0.1.0"
|
||||
pxar = { version = "0.3.0", features = [ "tokio-io", "futures-io" ] }
|
||||
pxar = { version = "0.6.0", features = [ "tokio-io", "futures-io" ] }
|
||||
#pxar = { path = "../pxar", features = [ "tokio-io", "futures-io" ] }
|
||||
regex = "1.2"
|
||||
rustyline = "6"
|
||||
|
20
debian/changelog
vendored
20
debian/changelog
vendored
@ -1,3 +1,23 @@
|
||||
rust-proxmox-backup (0.8.13-1) unstable; urgency=medium
|
||||
|
||||
* improve and add to documentation
|
||||
|
||||
* save last verify result in snapshot manifest and show it in the GUI
|
||||
|
||||
* gc: use human readable units for summary in task log
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Thu, 27 Aug 2020 16:12:07 +0200
|
||||
|
||||
rust-proxmox-backup (0.8.12-1) unstable; urgency=medium
|
||||
|
||||
* verify: speedup - only verify chunks once
|
||||
|
||||
* verify: sort backup groups
|
||||
|
||||
* bump pxar dep to 0.4.0
|
||||
|
||||
-- Proxmox Support Team <support@proxmox.com> Tue, 25 Aug 2020 08:55:52 +0200
|
||||
|
||||
rust-proxmox-backup (0.8.11-1) unstable; urgency=medium
|
||||
|
||||
* improve sync jobs, allow to stop them and better logging
|
||||
|
6
debian/control
vendored
6
debian/control
vendored
@ -39,9 +39,9 @@ Build-Depends: debhelper (>= 11),
|
||||
librust-proxmox-0.3+sortable-macro-dev (>= 0.3.3-~~),
|
||||
librust-proxmox-0.3+websocket-dev (>= 0.3.3-~~),
|
||||
librust-proxmox-fuse-0.1+default-dev,
|
||||
librust-pxar-0.3+default-dev,
|
||||
librust-pxar-0.3+futures-io-dev,
|
||||
librust-pxar-0.3+tokio-io-dev,
|
||||
librust-pxar-0.6+default-dev,
|
||||
librust-pxar-0.6+futures-io-dev,
|
||||
librust-pxar-0.6+tokio-io-dev,
|
||||
librust-regex-1+default-dev (>= 1.2-~~),
|
||||
librust-rustyline-6+default-dev,
|
||||
librust-serde-1+default-dev,
|
||||
|
@ -146,6 +146,74 @@ when setting up the backup server.
|
||||
filesystem configuration from being supported for a datastore. For example,
|
||||
``ext3`` as a whole or ``ext4`` with the ``dir_nlink`` feature manually disabled.
|
||||
|
||||
Disk Management
|
||||
~~~~~~~~~~~~~~~
|
||||
Proxmox Backup Server comes with a set of disk utilities, which are
|
||||
accessed using the ``disk`` subcommand. This subcommand allows you to initialize
|
||||
disks, create various filesystems, and get information about the disks.
|
||||
|
||||
To view the disks connected to the system, use the ``list`` subcommand of
|
||||
``disk``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager disk list
|
||||
┌──────┬────────┬─────┬───────────┬─────────────┬───────────────┬─────────┬────────┐
|
||||
│ name │ used │ gpt │ disk-type │ size │ model │ wearout │ status │
|
||||
╞══════╪════════╪═════╪═══════════╪═════════════╪═══════════════╪═════════╪════════╡
|
||||
│ sda │ lvm │ 1 │ hdd │ 34359738368 │ QEMU_HARDDISK │ - │ passed │
|
||||
├──────┼────────┼─────┼───────────┼─────────────┼───────────────┼─────────┼────────┤
|
||||
│ sdb │ unused │ 1 │ hdd │ 68719476736 │ QEMU_HARDDISK │ - │ passed │
|
||||
├──────┼────────┼─────┼───────────┼─────────────┼───────────────┼─────────┼────────┤
|
||||
│ sdc │ unused │ 1 │ hdd │ 68719476736 │ QEMU_HARDDISK │ - │ passed │
|
||||
└──────┴────────┴─────┴───────────┴─────────────┴───────────────┴─────────┴────────┘
|
||||
|
||||
To initialize a disk with a new GPT, use the ``initialize`` subcommand:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager disk initialize sdX
|
||||
|
||||
You can create an ``ext4`` or ``xfs`` filesystem on a disk, using ``fs
|
||||
create``. The following command creates an ``ext4`` filesystem and passes the
|
||||
``--add-datastore`` parameter, in order to automatically create a datastore on
|
||||
the disk (in this case ``sdd``). This will create a datastore at the location
|
||||
``/mnt/datastore/store1``:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager disk fs create store1 --disk sdd --filesystem ext4 --add-datastore true
|
||||
create datastore 'store1' on disk sdd
|
||||
Percentage done: 1
|
||||
...
|
||||
Percentage done: 99
|
||||
TASK OK
|
||||
|
||||
You can also create a ``zpool`` with various raid levels. The command below
|
||||
creates a mirrored ``zpool`` using two disks (``sdb`` & ``sdc``) and mounts it
|
||||
on the root directory (default):
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager disk zpool create zpool1 --devices sdb,sdc --raidlevel mirror
|
||||
create Mirror zpool 'zpool1' on devices 'sdb,sdc'
|
||||
# "zpool" "create" "-o" "ashift=12" "zpool1" "mirror" "sdb" "sdc"
|
||||
|
||||
TASK OK
|
||||
|
||||
.. note::
|
||||
You can also pass the ``--add-datastore`` parameter here, to automatically
|
||||
create a datastore from the disk.
|
||||
|
||||
You can use ``disk fs list`` and ``disk zpool list`` to keep track of your
|
||||
filesystems and zpools respectively.
|
||||
|
||||
If a disk supports S.M.A.R.T. capability, and you have this enabled, you can
|
||||
display S.M.A.R.T. attributes using the command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager disk smart-attributes sdX
|
||||
|
||||
Datastore Configuration
|
||||
~~~~~~~~~~~~~~~~~~~~~~~
|
||||
@ -404,6 +472,72 @@ A single user can be assigned multiple permission sets for different data stores
|
||||
remote (see `Remote` below) and ``{storename}`` is the name of the data store on
|
||||
the remote.
|
||||
|
||||
Network Management
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
Proxmox Backup Server provides an interface for network configuration, through the
|
||||
``network`` subcommand. This allows you to carry out some basic network
|
||||
management tasks such as adding, configuring and removing network interfaces.
|
||||
|
||||
To get a list of available interfaces, use the following command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network list
|
||||
┌───────┬────────┬───────────┬────────┬─────────┬───────────────────┬──────────────┬──────────────┐
|
||||
│ name │ type │ autostart │ method │ method6 │ address │ gateway │ ports/slaves │
|
||||
╞═══════╪════════╪═══════════╪════════╪═════════╪═══════════════════╪══════════════╪══════════════╡
|
||||
│ bond0 │ bond │ 1 │ manual │ │ │ │ ens18 ens19 │
|
||||
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||
│ ens18 │ eth │ 1 │ manual │ │ │ │ │
|
||||
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||
│ ens19 │ eth │ 1 │ manual │ │ │ │ │
|
||||
├───────┼────────┼───────────┼────────┼─────────┼───────────────────┼──────────────┼──────────────┤
|
||||
│ vmbr0 │ bridge │ 1 │ static │ │ x.x.x.x/x │ x.x.x.x │ bond0 │
|
||||
└───────┴────────┴───────────┴────────┴─────────┴───────────────────┴──────────────┴──────────────┘
|
||||
|
||||
To add a new network interface, use the ``create`` subcommand with the relevant
|
||||
parameters. The following command shows a template for creating a new bridge:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network create vmbr1 --autostart true --cidr x.x.x.x/x --gateway x.x.x.x --bridge_ports iface_name --type bridge
|
||||
|
||||
You can make changes to the configuration of a network interface with the
|
||||
``update`` subcommand:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network update vmbr1 --cidr y.y.y.y/y
|
||||
|
||||
You can also remove a network interface:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network remove vmbr1
|
||||
|
||||
To view the changes made to the network configuration file, before committing
|
||||
them, use the command:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network changes
|
||||
|
||||
If you would like to cancel all changes at this point, you can do this using:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network revert
|
||||
|
||||
If you are happy with the changes and would like to write them into the
|
||||
configuration file, the command is:
|
||||
|
||||
.. code-block:: console
|
||||
|
||||
# proxmox-backup-manager network reload
|
||||
|
||||
You can also configure DNS settings using the ``dns`` subcommand of
|
||||
``proxmox-backup-manager``.
|
||||
|
||||
:term:`Remote`
|
||||
~~~~~~~~~~~~~~
|
||||
|
||||
@ -461,6 +595,14 @@ provide it with a :term:`schedule` to run regularly. The
|
||||
└────────────┴───────┴────────┴──────────────┴───────────┴─────────┘
|
||||
# proxmox-backup-manager sync-job remove pbs2-local
|
||||
|
||||
Garbage Collection
|
||||
~~~~~~~~~~~~~~~~~~
|
||||
You can monitor and run :ref:`garbage collection <garbage-collection>` on the
|
||||
Proxmox Backup Server using the ``garbage-collection`` subcommand of
|
||||
``proxmox-backup-manager``. You can use the ``start`` subcommand to manually start garbage
|
||||
collection on an entire data store and the ``status`` subcommand to see
|
||||
attributes relating to the :ref:`garbage collection <garbage-collection>`.
|
||||
|
||||
|
||||
Backup Client usage
|
||||
-------------------
|
||||
@ -1143,6 +1285,10 @@ benchmark using the ``benchmark`` subcommand of ``proxmox-backup-client``:
|
||||
│ AES256 GCM encryption speed │ 3974.03 MB/s (104%) │
|
||||
└───────────────────────────────────┴─────────────────────┘
|
||||
|
||||
.. note:: The percentages given in the output table correspond to a
|
||||
comparison against a Ryzen 7 2700X. The TLS test connects to the
|
||||
local host, so there is no network involved.
|
||||
|
||||
You can also pass the ``--output-format`` parameter to output stats in ``json``,
|
||||
rather than the default table format.
|
||||
|
||||
|
@ -13,7 +13,8 @@
|
||||
.. _Proxmox: https://www.proxmox.com
|
||||
.. _Proxmox Community Forum: https://forum.proxmox.com
|
||||
.. _Proxmox Virtual Environment: https://www.proxmox.com/proxmox-ve
|
||||
.. _Proxmox Backup: https://pbs.proxmox.com/wiki/index.php/Main_Page // FIXME
|
||||
// FIXME
|
||||
.. _Proxmox Backup: https://pbs.proxmox.com/wiki/index.php/Main_Page
|
||||
.. _PBS Development List: https://lists.proxmox.com/cgi-bin/mailman/listinfo/pbs-devel
|
||||
.. _reStructuredText: https://www.sphinx-doc.org/en/master/usage/restructuredtext/index.html
|
||||
.. _Rust: https://www.rust-lang.org/
|
||||
|
@ -361,7 +361,7 @@ pub fn list_snapshots (
|
||||
|
||||
let mut size = None;
|
||||
|
||||
let (comment, files) = match get_all_snapshot_files(&datastore, &info) {
|
||||
let (comment, verification, files) = match get_all_snapshot_files(&datastore, &info) {
|
||||
Ok((manifest, files)) => {
|
||||
size = Some(files.iter().map(|x| x.size.unwrap_or(0)).sum());
|
||||
// extract the first line from notes
|
||||
@ -370,11 +370,21 @@ pub fn list_snapshots (
|
||||
.and_then(|notes| notes.lines().next())
|
||||
.map(String::from);
|
||||
|
||||
(comment, files)
|
||||
let verify = manifest.unprotected["verify_state"].clone();
|
||||
let verify: Option<SnapshotVerifyState> = match serde_json::from_value(verify) {
|
||||
Ok(verify) => verify,
|
||||
Err(err) => {
|
||||
eprintln!("error parsing verification state : '{}'", err);
|
||||
None
|
||||
}
|
||||
};
|
||||
|
||||
(comment, verify, files)
|
||||
},
|
||||
Err(err) => {
|
||||
eprintln!("error during snapshot file listing: '{}'", err);
|
||||
(
|
||||
None,
|
||||
None,
|
||||
info
|
||||
.files
|
||||
@ -394,6 +404,7 @@ pub fn list_snapshots (
|
||||
backup_id: group.backup_id().to_string(),
|
||||
backup_time: info.backup_dir.backup_time().timestamp(),
|
||||
comment,
|
||||
verification,
|
||||
files,
|
||||
size,
|
||||
owner: Some(owner),
|
||||
@ -489,7 +500,7 @@ pub fn verify(
|
||||
(None, None, None) => {
|
||||
worker_id = store.clone();
|
||||
}
|
||||
_ => bail!("parameters do not spefify a backup group or snapshot"),
|
||||
_ => bail!("parameters do not specify a backup group or snapshot"),
|
||||
}
|
||||
|
||||
let userid: Userid = rpcenv.get_user().unwrap().parse()?;
|
||||
@ -519,7 +530,7 @@ pub fn verify(
|
||||
for dir in failed_dirs {
|
||||
worker.log(format!("\t{}", dir));
|
||||
}
|
||||
bail!("verfication failed - please check the log for details");
|
||||
bail!("verification failed - please check the log for details");
|
||||
}
|
||||
Ok(())
|
||||
},
|
||||
@ -1218,7 +1229,7 @@ fn catalog(
|
||||
pub const API_METHOD_PXAR_FILE_DOWNLOAD: ApiMethod = ApiMethod::new(
|
||||
&ApiHandler::AsyncHttp(&pxar_file_download),
|
||||
&ObjectSchema::new(
|
||||
"Download single file from pxar file of a bacup snapshot. Only works if it's not encrypted.",
|
||||
"Download single file from pxar file of a backup snapshot. Only works if it's not encrypted.",
|
||||
&sorted!([
|
||||
("store", false, &DATASTORE_SCHEMA),
|
||||
("backup-type", false, &BACKUP_TYPE_SCHEMA),
|
||||
|
@ -6,6 +6,7 @@ use proxmox::const_regex;
|
||||
use proxmox::{IPRE, IPV4RE, IPV6RE, IPV4OCTET, IPV6H16, IPV6LS32};
|
||||
|
||||
use crate::backup::CryptMode;
|
||||
use crate::server::UPID;
|
||||
|
||||
#[macro_use]
|
||||
mod macros;
|
||||
@ -379,6 +380,25 @@ pub struct GroupListItem {
|
||||
pub owner: Option<Userid>,
|
||||
}
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
upid: {
|
||||
schema: UPID_SCHEMA
|
||||
},
|
||||
state: {
|
||||
type: String
|
||||
},
|
||||
},
|
||||
)]
|
||||
#[derive(Serialize, Deserialize)]
|
||||
/// Task properties.
|
||||
pub struct SnapshotVerifyState {
|
||||
/// UPID of the verify task
|
||||
pub upid: UPID,
|
||||
/// State of the verification. "failed" or "ok"
|
||||
pub state: String,
|
||||
}
|
||||
|
||||
#[api(
|
||||
properties: {
|
||||
"backup-type": {
|
||||
@ -390,6 +410,14 @@ pub struct GroupListItem {
|
||||
"backup-time": {
|
||||
schema: BACKUP_TIME_SCHEMA,
|
||||
},
|
||||
comment: {
|
||||
schema: SINGLE_LINE_COMMENT_SCHEMA,
|
||||
optional: true,
|
||||
},
|
||||
verification: {
|
||||
type: SnapshotVerifyState,
|
||||
optional: true,
|
||||
},
|
||||
files: {
|
||||
items: {
|
||||
schema: BACKUP_ARCHIVE_NAME_SCHEMA
|
||||
@ -411,6 +439,9 @@ pub struct SnapshotListItem {
|
||||
/// The first line from manifest "notes"
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub comment: Option<String>,
|
||||
/// The result of the last run verify task
|
||||
#[serde(skip_serializing_if="Option::is_none")]
|
||||
pub verification: Option<SnapshotVerifyState>,
|
||||
/// List of contained archive files.
|
||||
pub files: Vec<BackupContent>,
|
||||
/// Overall snapshot size (sum of all archive sizes).
|
||||
|
@ -9,7 +9,7 @@
|
||||
//! with `String`, meaning you can only make references to it.
|
||||
//! * [`Realm`]: an owned realm (`String` equivalent).
|
||||
//! * [`RealmRef`]: a borrowed realm (`str` equivalent).
|
||||
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separte
|
||||
//! * [`Userid`]: an owned user id (`"user@realm"`). Note that this does not have a separate
|
||||
//! borrowed type.
|
||||
//!
|
||||
//! Note that `Username`s are not unique, therefore they do not implement `Eq` and cannot be
|
||||
|
@ -120,6 +120,8 @@ macro_rules! PROXMOX_BACKUP_READER_PROTOCOL_ID_V1 {
|
||||
|
||||
/// Unix system user used by proxmox-backup-proxy
|
||||
pub const BACKUP_USER_NAME: &str = "backup";
|
||||
/// Unix system group used by proxmox-backup-proxy
|
||||
pub const BACKUP_GROUP_NAME: &str = "backup";
|
||||
|
||||
/// Return User info for the 'backup' user (``getpwnam_r(3)``)
|
||||
pub fn backup_user() -> Result<nix::unistd::User, Error> {
|
||||
@ -129,6 +131,14 @@ pub fn backup_user() -> Result<nix::unistd::User, Error> {
|
||||
}
|
||||
}
|
||||
|
||||
/// Return Group info for the 'backup' group (``getgrnam(3)``)
|
||||
pub fn backup_group() -> Result<nix::unistd::Group, Error> {
|
||||
match nix::unistd::Group::from_name(BACKUP_GROUP_NAME)? {
|
||||
Some(group) => Ok(group),
|
||||
None => bail!("Unable to lookup backup user."),
|
||||
}
|
||||
}
|
||||
|
||||
mod file_formats;
|
||||
pub use file_formats::*;
|
||||
|
||||
|
@ -45,6 +45,31 @@ pub struct BackupGroup {
|
||||
backup_id: String,
|
||||
}
|
||||
|
||||
impl std::cmp::Ord for BackupGroup {
|
||||
|
||||
fn cmp(&self, other: &Self) -> std::cmp::Ordering {
|
||||
let type_order = self.backup_type.cmp(&other.backup_type);
|
||||
if type_order != std::cmp::Ordering::Equal {
|
||||
return type_order;
|
||||
}
|
||||
// try to compare IDs numerically
|
||||
let id_self = self.backup_id.parse::<u64>();
|
||||
let id_other = other.backup_id.parse::<u64>();
|
||||
match (id_self, id_other) {
|
||||
(Ok(id_self), Ok(id_other)) => id_self.cmp(&id_other),
|
||||
(Ok(_), Err(_)) => std::cmp::Ordering::Less,
|
||||
(Err(_), Ok(_)) => std::cmp::Ordering::Greater,
|
||||
_ => self.backup_id.cmp(&other.backup_id),
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl std::cmp::PartialOrd for BackupGroup {
|
||||
fn partial_cmp(&self, other: &Self) -> Option<std::cmp::Ordering> {
|
||||
Some(self.cmp(other))
|
||||
}
|
||||
}
|
||||
|
||||
impl BackupGroup {
|
||||
|
||||
pub fn new<T: Into<String>, U: Into<String>>(backup_type: T, backup_id: U) -> Self {
|
||||
|
@ -104,7 +104,7 @@ impl ChunkStore {
|
||||
}
|
||||
let percentage = (i*100)/(64*1024);
|
||||
if percentage != last_percentage {
|
||||
eprintln!("Percentage done: {}", percentage);
|
||||
eprintln!("{}%", percentage);
|
||||
last_percentage = percentage;
|
||||
}
|
||||
}
|
||||
@ -295,7 +295,7 @@ impl ChunkStore {
|
||||
for (entry, percentage) in self.get_chunk_iterator()? {
|
||||
if last_percentage != percentage {
|
||||
last_percentage = percentage;
|
||||
worker.log(format!("percentage done: {}, chunk count: {}", percentage, chunk_count));
|
||||
worker.log(format!("{}%, processed {} chunks", percentage, chunk_count));
|
||||
}
|
||||
|
||||
worker.fail_on_abort()?;
|
||||
|
@ -21,6 +21,7 @@ use super::{DataBlob, ArchiveType, archive_type};
|
||||
use crate::config::datastore;
|
||||
use crate::server::WorkerTask;
|
||||
use crate::tools;
|
||||
use crate::tools::format::HumanByte;
|
||||
use crate::tools::fs::{lock_dir_noblock, DirLockGuard};
|
||||
use crate::api2::types::{GarbageCollectionStatus, Userid};
|
||||
|
||||
@ -299,7 +300,7 @@ impl DataStore {
|
||||
/// And set the owner to 'userid'. If the group already exists, it returns the
|
||||
/// current owner (instead of setting the owner).
|
||||
///
|
||||
/// This also aquires an exclusive lock on the directory and returns the lock guard.
|
||||
/// This also acquires an exclusive lock on the directory and returns the lock guard.
|
||||
pub fn create_locked_backup_group(
|
||||
&self,
|
||||
backup_group: &BackupGroup,
|
||||
@ -462,9 +463,8 @@ impl DataStore {
|
||||
|
||||
let _exclusive_lock = self.chunk_store.try_exclusive_lock()?;
|
||||
|
||||
let now = unsafe { libc::time(std::ptr::null_mut()) };
|
||||
|
||||
let oldest_writer = self.chunk_store.oldest_writer().unwrap_or(now);
|
||||
let phase1_start_time = unsafe { libc::time(std::ptr::null_mut()) };
|
||||
let oldest_writer = self.chunk_store.oldest_writer().unwrap_or(phase1_start_time);
|
||||
|
||||
let mut gc_status = GarbageCollectionStatus::default();
|
||||
gc_status.upid = Some(worker.to_string());
|
||||
@ -474,26 +474,26 @@ impl DataStore {
|
||||
self.mark_used_chunks(&mut gc_status, &worker)?;
|
||||
|
||||
worker.log("Start GC phase2 (sweep unused chunks)");
|
||||
self.chunk_store.sweep_unused_chunks(oldest_writer, now, &mut gc_status, &worker)?;
|
||||
self.chunk_store.sweep_unused_chunks(oldest_writer, phase1_start_time, &mut gc_status, &worker)?;
|
||||
|
||||
worker.log(&format!("Removed bytes: {}", gc_status.removed_bytes));
|
||||
worker.log(&format!("Removed garbage: {}", HumanByte::from(gc_status.removed_bytes)));
|
||||
worker.log(&format!("Removed chunks: {}", gc_status.removed_chunks));
|
||||
if gc_status.pending_bytes > 0 {
|
||||
worker.log(&format!("Pending removals: {} bytes ({} chunks)", gc_status.pending_bytes, gc_status.pending_chunks));
|
||||
worker.log(&format!("Pending removals: {} (in {} chunks)", HumanByte::from(gc_status.pending_bytes), gc_status.pending_chunks));
|
||||
}
|
||||
|
||||
worker.log(&format!("Original data bytes: {}", gc_status.index_data_bytes));
|
||||
worker.log(&format!("Original data usage: {}", HumanByte::from(gc_status.index_data_bytes)));
|
||||
|
||||
if gc_status.index_data_bytes > 0 {
|
||||
let comp_per = (gc_status.disk_bytes*100)/gc_status.index_data_bytes;
|
||||
worker.log(&format!("Disk bytes: {} ({} %)", gc_status.disk_bytes, comp_per));
|
||||
let comp_per = (gc_status.disk_bytes as f64 * 100.)/gc_status.index_data_bytes as f64;
|
||||
worker.log(&format!("On-Disk usage: {} ({:.2}%)", HumanByte::from(gc_status.disk_bytes), comp_per));
|
||||
}
|
||||
|
||||
worker.log(&format!("Disk chunks: {}", gc_status.disk_chunks));
|
||||
worker.log(&format!("On-Disk chunks: {}", gc_status.disk_chunks));
|
||||
|
||||
if gc_status.disk_chunks > 0 {
|
||||
let avg_chunk = gc_status.disk_bytes/(gc_status.disk_chunks as u64);
|
||||
worker.log(&format!("Average chunk size: {}", avg_chunk));
|
||||
worker.log(&format!("Average chunk size: {}", HumanByte::from(avg_chunk)));
|
||||
}
|
||||
|
||||
*self.last_gc_status.lock().unwrap() = gc_status;
|
||||
|
@ -145,7 +145,7 @@ impl BackupManifest {
|
||||
Ok(())
|
||||
}
|
||||
|
||||
// Generate cannonical json
|
||||
// Generate canonical json
|
||||
fn to_canonical_json(value: &Value) -> Result<Vec<u8>, Error> {
|
||||
let mut data = Vec::new();
|
||||
Self::write_canonical_json(value, &mut data)?;
|
||||
|
@ -1,8 +1,9 @@
|
||||
use std::collections::HashSet;
|
||||
|
||||
use anyhow::{bail, Error};
|
||||
use anyhow::{bail, format_err, Error};
|
||||
|
||||
use crate::server::WorkerTask;
|
||||
use crate::api2::types::*;
|
||||
|
||||
use super::{
|
||||
DataStore, BackupGroup, BackupDir, BackupInfo, IndexFile,
|
||||
@ -50,7 +51,17 @@ fn verify_index_chunks(
|
||||
worker.fail_on_abort()?;
|
||||
|
||||
let info = index.chunk_info(pos).unwrap();
|
||||
let size = info.range.end - info.range.start;
|
||||
|
||||
if verified_chunks.contains(&info.digest) {
|
||||
continue; // already verified
|
||||
}
|
||||
|
||||
if corrupt_chunks.contains(&info.digest) {
|
||||
let digest_str = proxmox::tools::digest_to_hex(&info.digest);
|
||||
worker.log(format!("chunk {} was marked as corrupt", digest_str));
|
||||
errors += 1;
|
||||
continue;
|
||||
}
|
||||
|
||||
let chunk = match datastore.load_chunk(&info.digest) {
|
||||
Err(err) => {
|
||||
@ -81,8 +92,8 @@ fn verify_index_chunks(
|
||||
errors += 1;
|
||||
}
|
||||
|
||||
if !verified_chunks.contains(&info.digest) {
|
||||
if !corrupt_chunks.contains(&info.digest) {
|
||||
let size = info.range.end - info.range.start;
|
||||
|
||||
if let Err(err) = chunk.verify_unencrypted(size as usize, &info.digest) {
|
||||
corrupt_chunks.insert(info.digest);
|
||||
worker.log(format!("{}", err));
|
||||
@ -90,12 +101,6 @@ fn verify_index_chunks(
|
||||
} else {
|
||||
verified_chunks.insert(info.digest);
|
||||
}
|
||||
} else {
|
||||
let digest_str = proxmox::tools::digest_to_hex(&info.digest);
|
||||
worker.log(format!("chunk {} was marked as corrupt", digest_str));
|
||||
errors += 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if errors > 0 {
|
||||
@ -174,7 +179,7 @@ pub fn verify_backup_dir(
|
||||
worker: &WorkerTask
|
||||
) -> Result<bool, Error> {
|
||||
|
||||
let manifest = match datastore.load_manifest(&backup_dir) {
|
||||
let mut manifest = match datastore.load_manifest(&backup_dir) {
|
||||
Ok((manifest, _)) => manifest,
|
||||
Err(err) => {
|
||||
worker.log(format!("verify {}:{} - manifest load error: {}", datastore.name(), backup_dir, err));
|
||||
@ -186,6 +191,7 @@ pub fn verify_backup_dir(
|
||||
|
||||
let mut error_count = 0;
|
||||
|
||||
let mut verify_result = "ok";
|
||||
for info in manifest.files() {
|
||||
let result = proxmox::try_block!({
|
||||
worker.log(format!(" check {}", info.filename));
|
||||
@ -217,9 +223,20 @@ pub fn verify_backup_dir(
|
||||
if let Err(err) = result {
|
||||
worker.log(format!("verify {}:{}/{} failed: {}", datastore.name(), backup_dir, info.filename, err));
|
||||
error_count += 1;
|
||||
verify_result = "failed";
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
let verify_state = SnapshotVerifyState {
|
||||
state: verify_result.to_string(),
|
||||
upid: worker.upid().clone(),
|
||||
};
|
||||
manifest.unprotected["verify_state"] = serde_json::to_value(verify_state)?;
|
||||
datastore.store_manifest(&backup_dir, serde_json::to_value(manifest)?)
|
||||
.map_err(|err| format_err!("unable to store manifest blob - {}", err))?;
|
||||
|
||||
|
||||
Ok(error_count == 0)
|
||||
}
|
||||
|
||||
@ -267,7 +284,7 @@ pub fn verify_all_backups(datastore: &DataStore, worker: &WorkerTask) -> Result<
|
||||
|
||||
let mut errors = Vec::new();
|
||||
|
||||
let list = match BackupGroup::list_groups(&datastore.base_path()) {
|
||||
let mut list = match BackupGroup::list_groups(&datastore.base_path()) {
|
||||
Ok(list) => list,
|
||||
Err(err) => {
|
||||
worker.log(format!("verify datastore {} - unable to list backups: {}", datastore.name(), err));
|
||||
@ -275,6 +292,8 @@ pub fn verify_all_backups(datastore: &DataStore, worker: &WorkerTask) -> Result<
|
||||
}
|
||||
};
|
||||
|
||||
list.sort_unstable();
|
||||
|
||||
worker.log(format!("verify datastore {}", datastore.name()));
|
||||
|
||||
for group in list {
|
||||
|
@ -20,13 +20,19 @@ use proxmox_backup::tools::disks::{ DiskManage, zfs_pool_stats };
|
||||
|
||||
use proxmox_backup::api2::pull::do_sync_job;
|
||||
|
||||
fn main() {
|
||||
fn main() -> Result<(), Error> {
|
||||
proxmox_backup::tools::setup_safe_path_env();
|
||||
|
||||
if let Err(err) = proxmox_backup::tools::runtime::main(run()) {
|
||||
eprintln!("Error: {}", err);
|
||||
std::process::exit(-1);
|
||||
let backup_uid = proxmox_backup::backup::backup_user()?.uid;
|
||||
let backup_gid = proxmox_backup::backup::backup_group()?.gid;
|
||||
let running_uid = nix::unistd::Uid::effective();
|
||||
let running_gid = nix::unistd::Gid::effective();
|
||||
|
||||
if running_uid != backup_uid || running_gid != backup_gid {
|
||||
bail!("proxy not running as backup user or group (got uid {} gid {})", running_uid, running_gid);
|
||||
}
|
||||
|
||||
proxmox_backup::tools::runtime::main(run())
|
||||
}
|
||||
|
||||
async fn run() -> Result<(), Error> {
|
||||
@ -43,11 +49,6 @@ async fn run() -> Result<(), Error> {
|
||||
let mut config = ApiConfig::new(
|
||||
buildcfg::JS_DIR, &proxmox_backup::api2::ROUTER, RpcEnvironmentType::PUBLIC)?;
|
||||
|
||||
// add default dirs which includes jquery and bootstrap
|
||||
// my $base = '/usr/share/libpve-http-server-perl';
|
||||
// add_dirs($self->{dirs}, '/css/' => "$base/css/");
|
||||
// add_dirs($self->{dirs}, '/js/' => "$base/js/");
|
||||
// add_dirs($self->{dirs}, '/fonts/' => "$base/fonts/");
|
||||
config.add_alias("novnc", "/usr/share/novnc-pve");
|
||||
config.add_alias("extjs", "/usr/share/javascript/extjs");
|
||||
config.add_alias("fontawesome", "/usr/share/fonts-font-awesome");
|
||||
|
@ -239,7 +239,7 @@ pub fn zpool_commands() -> CommandLineInterface {
|
||||
.insert("create",
|
||||
CliCommand::new(&API_METHOD_CREATE_ZPOOL)
|
||||
.arg_param(&["name"])
|
||||
.completion_cb("devices", complete_disk_name) // fixme: comlete the list
|
||||
.completion_cb("devices", complete_disk_name) // fixme: complete the list
|
||||
);
|
||||
|
||||
cmd_def.into()
|
||||
|
@ -629,7 +629,7 @@ impl BackupWriter {
|
||||
})
|
||||
}
|
||||
|
||||
/// Upload speed test - prints result ot stderr
|
||||
/// Upload speed test - prints result to stderr
|
||||
pub async fn upload_speedtest(&self, verbose: bool) -> Result<f64, Error> {
|
||||
|
||||
let mut data = vec![];
|
||||
|
@ -133,7 +133,7 @@ impl DiskManage {
|
||||
})
|
||||
}
|
||||
|
||||
/// Information about file system type and unsed device for a path
|
||||
/// Information about file system type and used device for a path
|
||||
///
|
||||
/// Returns tuple (fs_type, device, mount_source)
|
||||
pub fn find_mounted_device(
|
||||
|
@ -111,7 +111,7 @@ fn parse_zpool_list_item(i: &str) -> IResult<&str, ZFSPoolInfo> {
|
||||
Ok((i, stat))
|
||||
}
|
||||
|
||||
/// Parse zpool list outout
|
||||
/// Parse zpool list output
|
||||
///
|
||||
/// Note: This does not reveal any details on how the pool uses the devices, because
|
||||
/// the zpool list output format is not really defined...
|
||||
|
@ -53,7 +53,7 @@ fn parse_zpool_status_vdev(i: &str) -> IResult<&str, ZFSPoolVDevState> {
|
||||
|
||||
let (i, vdev_name) = notspace1(i)?;
|
||||
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // sepecial device
|
||||
if let Ok((n, _)) = preceded(multispace0, line_ending)(i) { // special device
|
||||
let vdev = ZFSPoolVDevState {
|
||||
name: vdev_name.to_string(),
|
||||
lvl: indent_level,
|
||||
|
@ -80,6 +80,11 @@ impl From<usize> for HumanByte {
|
||||
HumanByte { b: v }
|
||||
}
|
||||
}
|
||||
impl From<u64> for HumanByte {
|
||||
fn from(v: u64) -> Self {
|
||||
HumanByte { b: v as usize }
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn correct_byte_convert() {
|
||||
|
@ -10,6 +10,7 @@ Ext.define('pbs-data-store-snapshots', {
|
||||
},
|
||||
'files',
|
||||
'owner',
|
||||
'verification',
|
||||
{ name: 'size', type: 'int', allowNull: true, },
|
||||
{
|
||||
name: 'crypt-mode',
|
||||
@ -209,6 +210,10 @@ Ext.define('PBS.DataStoreContent', {
|
||||
group.size = item.size;
|
||||
group.owner = item.owner;
|
||||
}
|
||||
if (item.verification &&
|
||||
(!group.verification || group.verification.state !== 'failed')) {
|
||||
group.verification = item.verification;
|
||||
}
|
||||
|
||||
}
|
||||
group.count = group.children.length;
|
||||
@ -563,6 +568,41 @@ Ext.define('PBS.DataStoreContent', {
|
||||
return (iconTxt + PBS.Utils.cryptText[v]) || Proxmox.Utils.unknownText
|
||||
}
|
||||
},
|
||||
{
|
||||
header: gettext('Verify State'),
|
||||
sortable: true,
|
||||
dataIndex: 'verification',
|
||||
renderer: (v, meta, record) => {
|
||||
if (v === undefined || v === null || !v.state) {
|
||||
//meta.tdCls = "x-grid-row-loading";
|
||||
return record.data.leaf ? '' : gettext('None');
|
||||
}
|
||||
let task = Proxmox.Utils.parse_task_upid(v.upid);
|
||||
let verify_time = Proxmox.Utils.render_timestamp(task.starttime);
|
||||
let iconCls = v.state === 'ok' ? 'check good' : 'times critical';
|
||||
let tip = `Verify task started on ${verify_time}`;
|
||||
if (record.parentNode.id === 'root') {
|
||||
tip = v.state === 'ok'
|
||||
? 'All verification OK in backup group'
|
||||
: 'At least one failed verification in backup group!';
|
||||
}
|
||||
return `<span data-qtip="${tip}">
|
||||
<i class="fa fa-fw fa-${iconCls}"></i> ${v.state}
|
||||
</span>`;
|
||||
},
|
||||
listeners: {
|
||||
dblclick: function(view, el, row, col, ev, rec) {
|
||||
let data = rec.data || {};
|
||||
let verify = data.verification;
|
||||
if (verify && verify.upid && rec.parentNode.id !== 'root') {
|
||||
let win = Ext.create('Proxmox.window.TaskViewer', {
|
||||
upid: verify.upid,
|
||||
});
|
||||
win.show();
|
||||
}
|
||||
},
|
||||
},
|
||||
},
|
||||
],
|
||||
|
||||
tbar: [
|
||||
|
Reference in New Issue
Block a user