Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[nexus] Add stubs for snapshot APIs #748

Merged
merged 8 commits into from
Mar 15, 2022
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
29 changes: 29 additions & 0 deletions common/src/sql/dbinit.sql
Original file line number Diff line number Diff line change
Expand Up @@ -371,6 +371,35 @@ CREATE INDEX ON omicron.public.disk (
time_deleted IS NULL AND attach_instance_id IS NOT NULL;


CREATE TABLE omicron.public.snapshot (
/* Identity metadata (resource) */
id UUID PRIMARY KEY,
name STRING(63) NOT NULL,
description STRING(512) NOT NULL,
time_created TIMESTAMPTZ NOT NULL,
time_modified TIMESTAMPTZ NOT NULL,
/* Indicates that the object has been deleted */
time_deleted TIMESTAMPTZ,

/* Every Snapshot is in exactly one Project at a time. */
project_id UUID NOT NULL,

/* Every Snapshot originated from a single disk */
disk_id UUID NOT NULL,

/* Every Snapshot consists of a root volume */
volume_id UUID NOT NULL,

/* Disk configuration (from the time the snapshot was taken) */
size_bytes INT NOT NULL
);

CREATE UNIQUE INDEX ON omicron.public.snapshot (
project_id,
name
) WHERE
time_deleted IS NULL;

/*
* Oximeter collector servers.
*/
Expand Down
37 changes: 36 additions & 1 deletion nexus/src/db/model.rs
Original file line number Diff line number Diff line change
Expand Up @@ -9,12 +9,13 @@ use crate::db::identity::{Asset, Resource};
use crate::db::schema::{
console_session, dataset, disk, instance, metric_producer,
network_interface, organization, oximeter, project, rack, region,
role_assignment_builtin, role_builtin, router_route, sled,
role_assignment_builtin, role_builtin, router_route, sled, snapshot,
update_available_artifact, user_builtin, volume, vpc, vpc_firewall_rule,
vpc_router, vpc_subnet, zpool,
};
use crate::defaults;
use crate::external_api::params;
use crate::external_api::views;
use crate::internal_api;
use chrono::{DateTime, Utc};
use db_macros::{Asset, Resource};
Expand Down Expand Up @@ -1260,6 +1261,40 @@ impl Into<external::DiskState> for DiskState {
}
}

#[derive(
Queryable,
Insertable,
Selectable,
Clone,
Debug,
Resource,
Serialize,
Deserialize,
)]
#[table_name = "snapshot"]
pub struct Snapshot {
#[diesel(embed)]
identity: SnapshotIdentity,

project_id: Uuid,
disk_id: Uuid,
volume_id: Uuid,

#[column_name = "size_bytes"]
pub size: ByteCount,
}

impl From<Snapshot> for views::Snapshot {
fn from(snapshot: Snapshot) -> Self {
Self {
identity: snapshot.identity(),
project_id: snapshot.project_id,
disk_id: snapshot.disk_id,
size: snapshot.size.into(),
}
}
}

/// Information announced by a metric server, used so that clients can contact it and collect
/// available metric data from it.
#[derive(Queryable, Insertable, Debug, Clone, Selectable, Asset)]
Expand Down
16 changes: 16 additions & 0 deletions nexus/src/db/schema.rs
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,22 @@ table! {
}
}

table! {
snapshot (id) {
id -> Uuid,
name -> Text,
description -> Text,
time_created -> Timestamptz,
time_modified -> Timestamptz,
time_deleted -> Nullable<Timestamptz>,

project_id -> Uuid,
disk_id -> Uuid,
volume_id -> Uuid,
size_bytes -> Int8,
}
}

table! {
instance (id) {
id -> Uuid,
Expand Down
151 changes: 150 additions & 1 deletion nexus/src/external_api/http_entrypoints.rs
Original file line number Diff line number Diff line change
Expand Up @@ -12,7 +12,9 @@ use crate::ServerContext;

use super::{
console_api, params,
views::{Organization, Project, Rack, Role, Sled, User, Vpc, VpcSubnet},
views::{
Organization, Project, Rack, Role, Sled, Snapshot, User, Vpc, VpcSubnet,
},
};
use crate::context::OpContext;
use dropshot::endpoint;
Expand Down Expand Up @@ -103,6 +105,11 @@ pub fn external_api() -> NexusApiDescription {
api.register(instance_disks_attach)?;
api.register(instance_disks_detach)?;

api.register(project_snapshots_get)?;
api.register(project_snapshots_post)?;
api.register(project_snapshots_get_snapshot)?;
api.register(project_snapshots_delete_snapshot)?;

api.register(project_vpcs_get)?;
api.register(project_vpcs_post)?;
api.register(project_vpcs_get_vpc)?;
Expand Down Expand Up @@ -1261,6 +1268,148 @@ async fn instance_network_interfaces_get_interface(
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}

/*
* Snapshots
*/

/// List snapshots in a project.
#[endpoint {
method = GET,
path = "/organizations/{organization_name}/projects/{project_name}/snapshots",
tags = ["snapshots"],
}]
async fn project_snapshots_get(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
query_params: Query<PaginatedByName>,
path_params: Path<ProjectPathParam>,
) -> Result<HttpResponseOk<ResultsPage<Snapshot>>, HttpError> {
let apictx = rqctx.context();
let nexus = &apictx.nexus;
let query = query_params.into_inner();
let path = path_params.into_inner();
let organization_name = &path.organization_name;
let project_name = &path.project_name;
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let snapshots = nexus
.project_list_snapshots(
&opctx,
organization_name,
project_name,
&data_page_params_for(&rqctx, &query)?
.map_name(|n| Name::ref_cast(n)),
)
.await?
.into_iter()
.map(|d| d.into())
.collect();
Ok(HttpResponseOk(ScanByName::results_page(&query, snapshots)?))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}

/// Create a snapshot of a disk.
#[endpoint {
method = POST,
path = "/organizations/{organization_name}/projects/{project_name}/snapshots",
tags = ["snapshots"],
}]
async fn project_snapshots_post(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
path_params: Path<ProjectPathParam>,
new_snapshot: TypedBody<params::SnapshotCreate>,
) -> Result<HttpResponseCreated<Snapshot>, HttpError> {
let apictx = rqctx.context();
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let organization_name = &path.organization_name;
let project_name = &path.project_name;
let new_snapshot_params = &new_snapshot.into_inner();
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let snapshot = nexus
.project_create_snapshot(
&opctx,
&organization_name,
&project_name,
&new_snapshot_params,
)
.await?;
Ok(HttpResponseCreated(snapshot.into()))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}

/// Path parameters for Snapshot requests
#[derive(Deserialize, JsonSchema)]
struct SnapshotPathParam {
organization_name: Name,
project_name: Name,
snapshot_name: Name,
}

/// Get a snapshot in a project.
#[endpoint {
method = GET,
path = "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}",
tags = ["snapshots"],
}]
async fn project_snapshots_get_snapshot(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
path_params: Path<SnapshotPathParam>,
) -> Result<HttpResponseOk<Snapshot>, HttpError> {
let apictx = rqctx.context();
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let organization_name = &path.organization_name;
let project_name = &path.project_name;
let snapshot_name = &path.snapshot_name;
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
let snapshot = nexus
.snapshot_fetch(
&opctx,
&organization_name,
&project_name,
&snapshot_name,
)
.await?;
Ok(HttpResponseOk(snapshot.into()))
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}

/// Delete a snapshot from a project.
#[endpoint {
method = DELETE,
path = "/organizations/{organization_name}/projects/{project_name}/snapshots/{snapshot_name}",
tags = ["snapshots"],
}]
async fn project_snapshots_delete_snapshot(
rqctx: Arc<RequestContext<Arc<ServerContext>>>,
path_params: Path<SnapshotPathParam>,
) -> Result<HttpResponseDeleted, HttpError> {
let apictx = rqctx.context();
let nexus = &apictx.nexus;
let path = path_params.into_inner();
let organization_name = &path.organization_name;
let project_name = &path.project_name;
let snapshot_name = &path.snapshot_name;
let handler = async {
let opctx = OpContext::for_external_api(&rqctx).await?;
nexus
.project_delete_snapshot(
&opctx,
&organization_name,
&project_name,
&snapshot_name,
)
.await?;
Ok(HttpResponseDeleted())
};
apictx.external_latencies.instrument_dropshot_handler(&rqctx, handler).await
}

/*
* VPCs
*/
Expand Down
15 changes: 15 additions & 0 deletions nexus/src/external_api/params.rs
Original file line number Diff line number Diff line change
Expand Up @@ -291,6 +291,21 @@ pub struct NetworkInterfaceIdentifier {
pub interface_name: Name,
}

/*
* SNAPSHOTS
*/

/// Create-time parameters for a [`Snapshot`](omicron_common::api::external::Snapshot)
#[derive(Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct SnapshotCreate {
/// common identifying metadata
#[serde(flatten)]
pub identity: IdentityMetadataCreateParams,

/// The name of the disk to be snapshotted
pub disk: Name,
}

/*
* BUILT-IN USERS
*
Expand Down
8 changes: 7 additions & 1 deletion nexus/src/external_api/tag-config.json
Original file line number Diff line number Diff line change
Expand Up @@ -80,6 +80,12 @@
"url": "http://oxide.computer/docs/#xxx"
}
},
"snapshots": {
"description": "Snapshots of Virtual Disks at a particular point in time.",
"external_docs": {
"url": "http://oxide.computer/docs/#xxx"
}
},
"subnets": {
"description": "This tag should be moved into a generic network tag",
"external_docs": {
Expand All @@ -105,4 +111,4 @@
}
}
}
}
}
18 changes: 17 additions & 1 deletion nexus/src/external_api/views.rs
Original file line number Diff line number Diff line change
Expand Up @@ -11,7 +11,8 @@ use crate::db::identity::{Asset, Resource};
use crate::db::model;
use api_identity::ObjectIdentity;
use omicron_common::api::external::{
IdentityMetadata, Ipv4Net, Ipv6Net, Name, ObjectIdentity, RoleName,
ByteCount, IdentityMetadata, Ipv4Net, Ipv6Net, Name, ObjectIdentity,
RoleName,
};
use schemars::JsonSchema;
use serde::{Deserialize, Serialize};
Expand Down Expand Up @@ -64,6 +65,21 @@ impl Into<Project> for model::Project {
}
}

/*
* SNAPSHOTS
*/

/// Client view of a Snapshot
#[derive(ObjectIdentity, Clone, Debug, Deserialize, Serialize, JsonSchema)]
pub struct Snapshot {
#[serde(flatten)]
pub identity: IdentityMetadata,

pub project_id: Uuid,
pub disk_id: Uuid,
pub size: ByteCount,
}

/*
* VPCs
*/
Expand Down
Loading