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

Add snapshotting for instances in the API #13729

Merged
merged 1 commit into from
Feb 8, 2017
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
1 change: 1 addition & 0 deletions app/controllers/api/instances_controller.rb
Original file line number Diff line number Diff line change
@@ -1,6 +1,7 @@
module Api
class InstancesController < BaseController
include Subcollections::LoadBalancers
include Subcollections::Snapshots

def terminate_resource(type, id = nil, _data = nil)
raise BadRequestError, "Must specify an id for terminating a #{type} resource" unless id
Expand Down
20 changes: 20 additions & 0 deletions config/api.yml
Original file line number Diff line number Diff line change
Expand Up @@ -609,6 +609,7 @@
- :collection
:subcollections:
- :load_balancers
- :snapshots
Copy link
Member

@abellotti abellotti Feb 1, 2017

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

didn't see any identifiers specific to this, are both vms and instances using vm_snapshot_* ?

:collection_actions:
:get:
- :name: read
Expand Down Expand Up @@ -657,6 +658,25 @@
:get:
- :name: show
:identifier: load_balancer_show
:snapshots_subcollection_actions:
:get:
- :name: read
:identifier: cloud_volume_snapshot_view
:post:
- :name: create
:identifier: cloud_volume_snapshot_create
- :name: delete
:identifier: cloud_volume_snapshot_delete
:snapshots_subresource_actions:
:get:
- :name: read
:identifier: cloud_volume_snapshot_view
:post:
- :name: delete
:identifier: cloud_volume_snapshot_delete
:delete:
- :name: delete
:identifier: cloud_volume_snapshot_delete
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Nice!! Leveraging the new subcollection resource specific identifiers 👍

:load_balancers:
:description: Load Balancers
:options:
Expand Down
244 changes: 244 additions & 0 deletions spec/requests/api/snapshots_spec.rb
Original file line number Diff line number Diff line change
Expand Up @@ -240,4 +240,248 @@
end
end
end

describe "as a subcollection of instances" do
describe "GET /api/instances/:c_id/snapshots" do
it "can list the snapshots of an Instance" do
api_basic_authorize(subcollection_action_identifier(:instances, :snapshots, :read, :get))
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)
_other_snapshot = FactoryGirl.create(:snapshot)

run_get("#{instances_url(instance.id)}/snapshots")

expected = {
"count" => 2,
"name" => "snapshots",
"subcount" => 1,
"resources" => [
{"href" => a_string_matching("#{instances_url(instance.id)}/snapshots/#{snapshot.id}")}
]
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "will not list snapshots unless authorized" do
api_basic_authorize
instance = FactoryGirl.create(:vm_openstack)
_snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_get("#{instances_url(instance.id)}/snapshots")

expect(response).to have_http_status(:forbidden)
end
end

describe "GET /api/instances/:c_id/snapshots/:s_id" do
it "can show an Instance's snapshot" do
api_basic_authorize(subcollection_action_identifier(:instances, :snapshots, :read, :get))
instance = FactoryGirl.create(:vm_openstack)
create_time = Time.zone.parse("2017-01-11T00:00:00Z")
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance, :create_time => create_time)

run_get("#{instances_url(instance.id)}/snapshots/#{snapshot.id}")

expected = {
"create_time" => create_time.iso8601,
"href" => a_string_matching("#{instances_url(instance.id)}/snapshots/#{snapshot.id}"),
"id" => snapshot.id,
"vm_or_template_id" => instance.id
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "will not show a snapshot unless authorized" do
api_basic_authorize
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_get("#{instances_url(instance.id)}/snapshots/#{snapshot.id}")

expect(response).to have_http_status(:forbidden)
end

describe "POST /api/instances/:c_id/snapshots" do
it "can queue the creation of a snapshot" do
api_basic_authorize(subcollection_action_identifier(:instances, :snapshots, :create))
ems = FactoryGirl.create(:ems_openstack_infra)
host = FactoryGirl.create(:host_openstack_infra, :ext_management_system => ems)
instance = FactoryGirl.create(:vm_openstack, :name => "Alice's Instance", :ext_management_system => ems, :host => host)

run_post("#{instances_url(instance.id)}/snapshots", :name => "Alice's snapshot")

expected = {
"results" => [
a_hash_including(
"success" => true,
"message" => "Creating snapshot Alice's snapshot for Vm id:#{instance.id} name:'Alice's Instance'",
"task_id" => anything,
"task_href" => a_string_matching(tasks_url)
)
]
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "renders a failed action response if snapshotting is not supported" do
api_basic_authorize(subcollection_action_identifier(:instances, :snapshots, :create))
instance = FactoryGirl.create(:vm_openstack)

run_post("#{instances_url(instance.id)}/snapshots", :name => "Alice's snapsnot")

expected = {
"results" => [
a_hash_including(
"success" => false,
"message" => "The VM is not connected to an active Provider"
)
]
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "renders a failed action response if a name is not provided" do
api_basic_authorize(subcollection_action_identifier(:instances, :snapshots, :create))
ems = FactoryGirl.create(:ems_openstack_infra)
host = FactoryGirl.create(:host_openstack_infra, :ext_management_system => ems)
instance = FactoryGirl.create(:vm_openstack, :name => "Alice's Instance", :ext_management_system => ems, :host => host)

run_post("#{instances_url(instance.id)}/snapshots", :description => "Alice's snapshot")

expected = {
"results" => [
a_hash_including(
"success" => false,
"message" => "Must specify a name for the snapshot"
)
]
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "will not create a snapshot unless authorized" do
api_basic_authorize
instance = FactoryGirl.create(:vm_openstack)

run_post("#{instances_url(instance.id)}/snapshots", :description => "Alice's snapshot")

expect(response).to have_http_status(:forbidden)
end
end

describe "POST /api/instances/:c_id/snapshots/:s_id with delete action" do
it "can queue a snapshot for deletion" do
api_basic_authorize(action_identifier(:instances, :delete, :snapshots_subresource_actions, :delete))

ems = FactoryGirl.create(:ems_openstack_infra)
host = FactoryGirl.create(:host_openstack_infra, :ext_management_system => ems)
instance = FactoryGirl.create(:vm_openstack, :name => "Alice's Instance", :ext_management_system => ems, :host => host)
snapshot = FactoryGirl.create(:snapshot, :name => "Alice's snapshot", :vm_or_template => instance)

run_post("#{instances_url(instance.id)}/snapshots/#{snapshot.id}", :action => "delete")

expected = {
"message" => "Deleting snapshot Alice's snapshot for Vm id:#{instance.id} name:'Alice's Instance'",
"success" => true,
"task_href" => a_string_matching(tasks_url),
"task_id" => anything
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "renders a failed action response if deleting is not supported" do
api_basic_authorize(action_identifier(:instances, :delete, :snapshots_subresource_actions, :post))
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_post("#{instances_url(instance.id)}/snapshots/#{snapshot.id}", :action => "delete")

expected = {
"success" => false,
"message" => "The VM is not connected to an active Provider"
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end

it "will not delete a snapshot unless authorized" do
api_basic_authorize
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_post("#{instances_url(instance.id)}/snapshots/#{snapshot.id}", :action => "delete")

expect(response).to have_http_status(:forbidden)
end
end

describe "POST /api/instances/:c_id/snapshots with delete action" do
it "can queue multiple snapshots for deletion" do
api_basic_authorize(action_identifier(:instances, :delete, :snapshots_subresource_actions, :delete))

ems = FactoryGirl.create(:ems_openstack_infra)
host = FactoryGirl.create(:host_openstack_infra, :ext_management_system => ems)
instance = FactoryGirl.create(:vm_openstack, :name => "Alice and Bob's Instance", :ext_management_system => ems, :host => host)
snapshot1 = FactoryGirl.create(:snapshot, :name => "Alice's snapshot", :vm_or_template => instance)
snapshot2 = FactoryGirl.create(:snapshot, :name => "Bob's snapshot", :vm_or_template => instance)

run_post(
"#{instances_url(instance.id)}/snapshots",
:action => "delete",
:resources => [
{:href => "#{instances_url(instance.id)}/snapshots/#{snapshot1.id}"},
{:href => "#{instances_url(instance.id)}/snapshots/#{snapshot2.id}"}
]
)

expected = {
"results" => a_collection_containing_exactly(
a_hash_including(
"message" => "Deleting snapshot Alice's snapshot for Vm id:#{instance.id} name:'Alice and Bob's Instance'",
"success" => true,
"task_href" => a_string_matching(tasks_url),
"task_id" => anything
),
a_hash_including(
"message" => "Deleting snapshot Bob's snapshot for Vm id:#{instance.id} name:'Alice and Bob's Instance'",
"success" => true,
"task_href" => a_string_matching(tasks_url),
"task_id" => anything
)
)
}
expect(response.parsed_body).to include(expected)
expect(response).to have_http_status(:ok)
end
end

describe "DELETE /api/instances/:c_id/snapshots/:s_id" do
it "can delete a snapshot" do
api_basic_authorize(action_identifier(:instances, :delete, :snapshots_subresource_actions, :delete))
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_delete("#{instances_url(instance.id)}/snapshots/#{snapshot.id}")

expect(response).to have_http_status(:no_content)
end

it "will not delete a snapshot unless authorized" do
api_basic_authorize
instance = FactoryGirl.create(:vm_openstack)
snapshot = FactoryGirl.create(:snapshot, :vm_or_template => instance)

run_delete("#{instances_url(instance.id)}/snapshots/#{snapshot.id}")

expect(response).to have_http_status(:forbidden)
end
end
end
end
end