Skip to content

Commit

Permalink
Merge #2993
Browse files Browse the repository at this point in the history
2993: Snapshots r=townsend2010 a=ricab

Fixes #208

Co-authored-by: Ricardo Abreu <[email protected]>
Co-authored-by: ScottH <[email protected]>
  • Loading branch information
3 people authored and luis4a0 committed Nov 7, 2023
2 parents 9740dfd + 471aa9f commit 6c84e07
Show file tree
Hide file tree
Showing 122 changed files with 7,228 additions and 1,442 deletions.
2 changes: 1 addition & 1 deletion 3rd-party/CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -54,7 +54,7 @@ function(generate_grpc_cpp SRCS DEST)
"${DEST}/${FIL_WE}.pb.cc"
"${DEST}/${FIL_WE}.pb.h"
COMMAND $<TARGET_FILE:protoc>
ARGS --grpc_out=${DEST} --cpp_out=${DEST} --proto_path=${FIL_DIR} --plugin=protoc-gen-grpc=$<TARGET_FILE:grpc_cpp_plugin> ${ABS_FIL}
ARGS --grpc_out=${DEST} --cpp_out=${DEST} --proto_path=${FIL_DIR} --proto_path=${grpc_SOURCE_DIR}/third_party/protobuf/src --plugin=protoc-gen-grpc=$<TARGET_FILE:grpc_cpp_plugin> ${ABS_FIL}
DEPENDS ${ABS_FIL} protoc grpc_cpp_plugin
COMMENT "Running gRPC C++ protocol buffer compiler on ${FIL}"
VERBATIM)
Expand Down
59 changes: 53 additions & 6 deletions completions/bash/multipass
Original file line number Diff line number Diff line change
Expand Up @@ -18,14 +18,43 @@ _multipass_complete()
{
local state=$1

local cmd="multipass list --format=csv --no-ipv4"
[ -n "$state" ] && cmd="$cmd | \tail -n +2 | \awk -F',' '\$2 == \"$state\"'"
local cmd="multipass list --format=csv --no-ipv4 | \tail -n +2"
[ -n "$state" ] && cmd="$cmd | \awk -F',' '\$2 == \"$state\"'"

local instances=$( \eval $cmd | \cut -d',' -f 1 | \tr '\r\n' ' ')

_add_nonrepeating_args "$instances"
}

_multipass_snapshots()
{
local instance=$1
local cmd="multipass list --snapshots --format=csv 2>/dev/null | \tail -n +2"
[ -n "$instance" ] && cmd="$cmd | \awk -F',' '\$1 == \"$instance\"'"
local snapshots=$( \eval $cmd | \cut -d',' -f 1,2 | \tr ',' '.' | \tr '\r\n' ' ' )

_add_nonrepeating_args "$snapshots"
}

_multipass_instances_and_snapshots()
{
_multipass_snapshots
_multipass_instances
}

_multipass_restorable_snapshots()
{
local instances=$( multipass info --no-runtime-information --format=csv \
| \tail -n +2 \
| \awk -F',|\r?\n' '$2 == "Stopped" && $16 > 0' \
| \cut -d',' -f 1 \
| \tr '\r\n' ' ')

for instance in ${instances}; do
_multipass_snapshots "${instance}"
done
}

# Set $opts to the list of available networks.
_multipass_networks()
{
Expand Down Expand Up @@ -186,7 +215,7 @@ _multipass_complete()
cmd="${COMP_WORDS[1]}"
prev_opts=false
multipass_cmds="authenticate transfer delete exec find help info launch list mount networks \
purge recover shell start stop suspend restart umount version get set \
purge recover restart restore shell snapshot start stop suspend umount version get set \
alias aliases unalias"

if [[ "${multipass_cmds}" =~ " ${cmd} " || "${multipass_cmds}" =~ ^${cmd} || "${multipass_cmds}" =~ \ ${cmd}$ ]];
Expand All @@ -200,9 +229,12 @@ _multipass_complete()
opts="${opts} --working-directory --no-map-working-directory"
;;
"info")
_add_nonrepeating_args "--all --format"
_add_nonrepeating_args "--format --snapshots"
;;
"list"|"ls"|"networks"|"aliases")
"list"|"ls")
_add_nonrepeating_args "--format --snapshots"
;;
"networks"|"aliases")
_add_nonrepeating_args "--format"
;;
"delete")
Expand Down Expand Up @@ -231,6 +263,12 @@ _multipass_complete()
"transfer"|"copy-files")
_add_nonrepeating_args "--parents --recursive"
;;
"snapshot")
_add_nonrepeating_args "--name --comment"
;;
"restore")
_add_nonrepeating_args "--destructive"
;;
esac

if [[ ${prev} == -* ]]; then
Expand Down Expand Up @@ -302,12 +340,21 @@ _multipass_complete()
_multipass_instances "Stopped"
_multipass_instances "Suspended"
;;
"delete"|"info"|"umount"|"unmount")
"umount"|"unmount")
_multipass_instances
;;
"delete"|"info")
_multipass_instances_and_snapshots
;;
"recover")
_multipass_instances "Deleted"
;;
"snapshot")
_multipass_instances "Stopped"
;;
"restore")
_multipass_restorable_snapshots
;;
"mount")
local source_set=0
local prev
Expand Down
70 changes: 52 additions & 18 deletions include/multipass/cli/format_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -23,6 +23,7 @@
#include <multipass/settings/settings.h>

#include <fmt/format.h>
#include <google/protobuf/util/time_util.h>

#include <algorithm>
#include <string>
Expand All @@ -33,44 +34,77 @@ class Formatter;

namespace format
{
static constexpr int col_buffer = 3;

std::string status_string_for(const InstanceStatus& status);
std::string image_string_for(const multipass::FindReply_AliasInfo& alias);
Formatter* formatter_for(const std::string& format);

template <typename Instances>
Instances sorted(const Instances& instances);
template <typename Container>
Container sorted(const Container& items);

void filter_aliases(google::protobuf::RepeatedPtrField<multipass::FindReply_AliasInfo>& aliases);

// Computes the column width needed to display all the elements of a range [begin, end). get_width is a function
// which takes as input the element in the range and returns its width in columns.
static constexpr auto column_width = [](const auto begin, const auto end, const auto get_width, int minimum_width,
int space = 2) {
if (0 == std::distance(begin, end))
return minimum_width;

auto max_width =
std::max_element(begin, end, [&get_width](auto& lhs, auto& rhs) { return get_width(lhs) < get_width(rhs); });
return std::max(get_width(*max_width) + space, minimum_width);
};
static constexpr auto column_width =
[](const auto begin, const auto end, const auto get_width, int header_width, int minimum_width = 0) {
if (0 == std::distance(begin, end))
return std::max({header_width + col_buffer, minimum_width});

auto max_width = std::max_element(begin, end, [&get_width](auto& lhs, auto& rhs) {
return get_width(lhs) < get_width(rhs);
});
return std::max({get_width(*max_width) + col_buffer, header_width + col_buffer, minimum_width});
};
} // namespace format
} // namespace multipass

template <typename Instances>
Instances multipass::format::sorted(const Instances& instances)
template <typename Container>
Container multipass::format::sorted(const Container& items)
{
if (instances.empty())
return instances;
if (items.empty())
return items;

auto ret = instances;
auto ret = items;
const auto petenv_name = MP_SETTINGS.get(petenv_key).toStdString();
std::sort(std::begin(ret), std::end(ret), [&petenv_name](const auto& a, const auto& b) {
if (a.name() == petenv_name)
using T = std::decay_t<decltype(a)>;
using google::protobuf::util::TimeUtil;

// Put instances first when sorting info reply
if constexpr (std::is_same_v<T, multipass::DetailedInfoItem>)
{
if (a.has_instance_info() && b.has_snapshot_info())
return true;
else if (a.has_snapshot_info() && b.has_instance_info())
return false;
}

// Put petenv related entries first
if (a.name() == petenv_name && b.name() != petenv_name)
return true;
else if (b.name() == petenv_name)
else if (b.name() == petenv_name && a.name() != petenv_name)
return false;
else
{
// Sort by timestamp when names are the same for snapshots
if constexpr (std::is_same_v<T, multipass::DetailedInfoItem>)
{
if (a.has_snapshot_info() && a.name() == b.name())
return TimeUtil::TimestampToNanoseconds(a.snapshot_info().fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.snapshot_info().fundamentals().creation_timestamp());
}
else if constexpr (std::is_same_v<T, multipass::ListVMSnapshot>)
{
if (a.name() == b.name())
return TimeUtil::TimestampToNanoseconds(a.fundamentals().creation_timestamp()) <
TimeUtil::TimestampToNanoseconds(b.fundamentals().creation_timestamp());
}

// Lastly, sort by name
return a.name() < b.name();
}
});

return ret;
Expand Down
37 changes: 37 additions & 0 deletions include/multipass/exceptions/file_open_failed_exception.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,37 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H
#define MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H

#include <cerrno>
#include <cstring>
#include <stdexcept>

namespace multipass
{
class FileOpenFailedException : public std::runtime_error
{
public:
explicit FileOpenFailedException(const std::string& name)
: std::runtime_error(fmt::format("failed to open file '{}': {}({})", name, strerror(errno), errno))
{
}
};
} // namespace multipass

#endif // MULTIPASS_FILE_OPEN_FAILED_EXCEPTION_H
47 changes: 47 additions & 0 deletions include/multipass/exceptions/snapshot_exceptions.h
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
/*
* Copyright (C) Canonical, Ltd.
*
* This program is free software; you can redistribute it and/or modify
* it under the terms of the GNU General Public License as published by
* the Free Software Foundation; version 3.
*
* This program is distributed in the hope that it will be useful,
* but WITHOUT ANY WARRANTY; without even the implied warranty of
* MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
* GNU General Public License for more details.
*
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
*/

#ifndef MULTIPASS_SNAPSHOT_EXCEPTIONS_H
#define MULTIPASS_SNAPSHOT_EXCEPTIONS_H

#include <stdexcept>
#include <string>

#include <multipass/format.h>

namespace multipass
{
class SnapshotNameTakenException : public std::runtime_error
{
public:
SnapshotNameTakenException(const std::string& vm_name, const std::string& snapshot_name)
: std::runtime_error{fmt::format("Snapshot already exists: {}.{}", vm_name, snapshot_name)}
{
}
};

class NoSuchSnapshotException : public std::runtime_error
{
public:
NoSuchSnapshotException(const std::string& vm_name, const std::string& snapshot_name)
: std::runtime_error{fmt::format("No such snapshot: {}.{}", vm_name, snapshot_name)}
{
}
};
} // namespace multipass

#endif // MULTIPASS_SNAPSHOT_EXCEPTIONS_H
5 changes: 5 additions & 0 deletions include/multipass/file_ops.h
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@
#include <QByteArray>
#include <QDir>
#include <QFileDevice>
#include <QFileInfoList>
#include <QSaveFile>
#include <QString>
#include <QTextStream>
Expand All @@ -45,6 +46,10 @@ class FileOps : public Singleton<FileOps>
// QDir operations
virtual bool exists(const QDir& dir) const;
virtual bool isReadable(const QDir& dir) const;
virtual QFileInfoList entryInfoList(const QDir& dir,
const QStringList& nameFilters,
QDir::Filters filters = QDir::NoFilter,
QDir::SortFlags sort = QDir::NoSort) const;
virtual bool mkpath(const QDir& dir, const QString& dirName) const;
virtual bool rmdir(QDir& dir, const QString& dirName) const;

Expand Down
20 changes: 14 additions & 6 deletions include/multipass/json_utils.h
Original file line number Diff line number Diff line change
Expand Up @@ -13,21 +13,29 @@
* You should have received a copy of the GNU General Public License
* along with this program. If not, see <http://www.gnu.org/licenses/>.
*
* Authored by: Alberto Aguirre <[email protected]>
*
*/

#ifndef MULTIPASS_JSON_UTILS_H
#define MULTIPASS_JSON_UTILS_H

#include <string>
#include "singleton.h"

#include <QJsonObject>
#include <QString>

#include <string>

#define MP_JSONUTILS multipass::JsonUtils::instance()

namespace multipass
{
void write_json(const QJsonObject& root, QString file_name);
std::string json_to_string(const QJsonObject& root);
}
class JsonUtils : public Singleton<JsonUtils>
{
public:
explicit JsonUtils(const Singleton<JsonUtils>::PrivatePass&) noexcept;

virtual void write_json(const QJsonObject& root, QString file_name) const; // transactional; creates parent dirs
virtual std::string json_to_string(const QJsonObject& root) const;
};
} // namespace multipass
#endif // MULTIPASS_JSON_UTILS_H
2 changes: 1 addition & 1 deletion include/multipass/memory_size.h
Original file line number Diff line number Diff line change
Expand Up @@ -32,7 +32,7 @@ class MemorySize
friend bool operator<=(const MemorySize& a, const MemorySize& b);
friend bool operator>=(const MemorySize& a, const MemorySize& b);

MemorySize();
MemorySize() noexcept;
explicit MemorySize(const std::string& val);
long long in_bytes() const noexcept;
long long in_kilobytes() const noexcept;
Expand Down
Loading

0 comments on commit 6c84e07

Please sign in to comment.