Skip to content
Snippets Groups Projects
Commit 82ade4e8 authored by baldwinlouie's avatar baldwinlouie Committed by Yas Naoi
Browse files

Issue #3014091 by baldwinlouie, yas, Xiaohua Guan: Add Attach/Detach volume functionality

parent a1e6a0e9
No related branches found
No related tags found
No related merge requests found
Showing
with 349 additions and 6 deletions
......@@ -193,9 +193,9 @@ function aws_cloud_cloud_config_delete(\Drupal\cloud\Entity\CloudConfig $cloud_c
*/
function aws_cloud_entity_operation(EntityInterface $entity) {
$operations = [];
$account = \Drupal::currentUser();
if ($entity->getEntityTypeId() == 'aws_cloud_instance') {
$account = \Drupal::currentUser();
if ($account->hasPermission('edit any aws cloud instance') || ($account->hasPermission('edit own aws cloud instance') && $account->id() == $entity->getOwner()->id())) {
if ($entity->instance_state() == 'running') {
$operations['stop'] = [
......@@ -215,6 +215,24 @@ function aws_cloud_entity_operation(EntityInterface $entity) {
}
}
}
else if ($entity->getEntityTypeId() == 'aws_cloud_volume') {
if ($account->hasPermission('edit aws cloud volume')) {
if ($entity->state() == 'available') {
$operations['attach'] = [
'title' => t('Attach'),
'url' => $entity->toUrl('attach-form'),
'weight' => 20,
];
}
else if ($entity->state() == 'in-use') {
$operations['detach'] = [
'title' => t('Detach'),
'url' => $entity->toUrl('detach-form'),
'weight' => 20,
];
}
}
}
return $operations;
}
......
......@@ -458,6 +458,22 @@ entity.aws_cloud_volume.delete_form:
requirements:
_entity_access: 'aws_cloud_volume.delete'
entity.aws_cloud_volume.attach_form:
path: '/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/attach'
defaults:
_entity_form: 'aws_cloud_volume.attach'
_title: 'Attach Volume'
requirements:
_entity_access: 'aws_cloud_volume.edit'
entity.aws_cloud_volume.detach_form:
path: '/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/detach'
defaults:
_entity_form: 'aws_cloud_volume.detach'
_title: 'Detach Volume'
requirements:
_entity_access: 'aws_cloud_volume.edit'
# AWS Cloud Snapshots Routes
entity.aws_cloud_snapshot.canonical:
......
......@@ -534,6 +534,71 @@ display:
entity_type: aws_cloud_volume
entity_field: state
plugin_id: field
attachment_information:
id: attachment_information
table: aws_cloud_volume
field: attachment_information
relationship: none
group_type: group
admin_label: ''
label: 'Attachment Information'
exclude: false
alter:
alter_text: false
text: ''
make_link: false
path: ''
absolute: false
external: false
replace_spaces: false
path_case: none
trim_whitespace: false
alt: ''
rel: ''
link_class: ''
prefix: ''
suffix: ''
target: ''
nl2br: false
max_length: 0
word_boundary: true
ellipsis: true
more_link: false
more_link_text: ''
more_link_path: ''
strip_tags: false
trim: false
preserve_tags: ''
html: false
element_type: ''
element_class: ''
element_label_type: ''
element_label_class: ''
element_label_colon: true
element_wrapper_type: ''
element_wrapper_class: ''
element_default_classes: true
empty: ''
hide_empty: false
empty_zero: false
hide_alter_empty: true
click_sort_column: value
type: string
settings:
link_to_entity: false
group_column: value
group_columns: { }
group_rows: true
delta_limit: 0
delta_offset: 0
delta_reversed: false
delta_first_last: false
multi_type: separator
separator: ', '
field_api_classes: false
entity_type: aws_cloud_volume
entity_field: attachment_information
plugin_id: field
created:
id: created
table: aws_cloud_volume
......
......@@ -40,6 +40,8 @@ use Drupal\Core\Field\BaseFieldDefinition;
* "add" = "Drupal\aws_cloud\Form\Ec2\VolumeCreateForm",
* "edit" = "Drupal\aws_cloud\Form\Ec2\VolumeEditForm" ,
* "delete" = "Drupal\aws_cloud\Form\Ec2\VolumeDeleteForm",
* "attach" = "Drupal\aws_cloud\Form\Ec2\VolumeAttachForm",
* "detach" = "Drupal\aws_cloud\Form\Ec2\VolumeDetachForm"
* },
* "access" = "Drupal\aws_cloud\Controller\Ec2\VolumeAccessControlHandler",
* },
......@@ -55,6 +57,8 @@ use Drupal\Core\Field\BaseFieldDefinition;
* "canonical" = "/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}" ,
* "edit-form" = "/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/edit" ,
* "delete-form" = "/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/delete",
* "attach-form" = "/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/attach",
* "detach-form" = "/clouds/aws_cloud/{cloud_context}/volume/{aws_cloud_volume}/detach",
* "collection" = "/clouds/aws_cloud/{cloud_context}/volume"
* },
* field_ui_base_route = "aws_cloud_volume.settings"
......@@ -202,6 +206,11 @@ class Volume extends CloudContentEntityBase implements VolumeInterface {
return $this->set('refreshed', $time);
}
public function set_attachment_information($attachment_information) {
return $this->set('attachment_information', $attachment_information);
}
/**
* {@inheritdoc}
*/
......
......@@ -8,6 +8,7 @@ use Drupal\Core\Entity\EntityManagerInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
/**
* Class AwsDeleteForm - Base Delete class. This class injects the
......@@ -22,6 +23,11 @@ class AwsDeleteForm extends CloudContentDeleteForm {
*/
protected $awsEc2Service;
/**
* @var \Drupal\Core\Entity\EntityTypeManagerInterface
*/
protected $entity_type_manager;
/**
* AwsDeleteForm constructor.
* @param \Drupal\Core\Entity\EntityManagerInterface $manager
......@@ -29,10 +35,12 @@ class AwsDeleteForm extends CloudContentDeleteForm {
* @param \Drupal\Core\Messenger\Messenger $messenger
* @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
* The entity repository service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface
*/
public function __construct(EntityManagerInterface $manager, AwsEc2ServiceInterface $aws_ec2_service, Messenger $messenger, EntityRepositoryInterface $entity_repository) {
public function __construct(EntityManagerInterface $manager, AwsEc2ServiceInterface $aws_ec2_service, Messenger $messenger, EntityRepositoryInterface $entity_repository, EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($manager, $entity_repository, $messenger);
$this->awsEc2Service = $aws_ec2_service;
$this->entity_type_manager = $entity_type_manager;
}
/**
......@@ -43,7 +51,8 @@ class AwsDeleteForm extends CloudContentDeleteForm {
$container->get('entity.manager'),
$container->get('aws_cloud.ec2'),
$container->get('messenger'),
$container->get('entity.repository')
$container->get('entity.repository'),
$container->get('entity_type.manager')
);
}
......
......@@ -19,12 +19,15 @@ use Drupal\Core\Form\FormStateInterface;
use Drupal\Core\Entity\EntityManagerInterface;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\cloud\Plugin\CloudConfigPluginManagerInterface;
use Drupal\aws_cloud\Service\AwsEc2ServiceInterface;
use Symfony\Component\DependencyInjection\ContainerInterface;
/**
* Provides a form for deleting a Image entity.
*
......@@ -50,13 +53,15 @@ class ImageDeleteForm extends AwsDeleteForm {
* The entity repository service.
* @param \Drupal\cloud\Plugin\CloudConfigPluginManagerInterface $cloud_config_plugin_manager
* The cloud config plugin manager service.
* @param \Drupal\Core\Entity\EntityTypeManagerInterface
*/
public function __construct(EntityManagerInterface $manager,
AwsEc2ServiceInterface $aws_ec2_service,
Messenger $messenger,
EntityRepositoryInterface $entity_repository,
CloudConfigPluginManagerInterface $cloud_config_plugin_manager) {
parent::__construct($manager, $aws_ec2_service, $messenger, $entity_repository);
CloudConfigPluginManagerInterface $cloud_config_plugin_manager,
EntityTypeManagerInterface $entity_type_manager) {
parent::__construct($manager, $aws_ec2_service, $messenger, $entity_repository, $entity_type_manager);
$this->cloudConfigPluginManager = $cloud_config_plugin_manager;
}
......@@ -69,7 +74,8 @@ class ImageDeleteForm extends AwsDeleteForm {
$container->get('aws_cloud.ec2'),
$container->get('messenger'),
$container->get('entity.repository'),
$container->get('plugin.manager.cloud_config_plugin')
$container->get('plugin.manager.cloud_config_plugin'),
$container->get('entity_type.manager')
);
}
......
<?php
namespace Drupal\aws_cloud\Form\Ec2;
use Drupal\Core\Form\FormStateInterface;
/**
* Volume Attach form
*/
class VolumeAttachForm extends AwsDeleteForm {
/**
* {@inheritdoc}
*/
public function getQuestion() {
$entity = $this->entity;
return $this->t('Are you sure you want to attach volume: %name?', [
'%name' => $entity->label(),
]);
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return t('Attach');
}
/**
* {@inheritdoc}
*/
public function getDescription() {
return '';
}
public function buildForm(array $form, FormStateInterface $form_state) {
$form = parent::buildForm($form, $form_state);
$instances = [];
$results = $this->getInstances($this->entity->availability_zone());
foreach ($results as $result) {
/* @var \Drupal\aws_cloud\Entity\Ec2\Instance $result */
$instances[$result->instance_id()] = $result->getName();
}
if (count($results) > 0) {
$form['device_name'] = [
'#title' => $this->t('Device Name'),
'#type' => 'textfield',
'#description' => $this->t('The device name (for example, /dev/sdh or xvdh).'),
'#required' => TRUE,
];
$form['instance_id'] = [
'#type' => 'select',
'#title' => $this->t('Instance Id'),
'#options' => $instances,
];
}
else {
$form['message'] = [
'#markup' => '<h1>' . $this->t('No instances available in the availability zone: %zone. Volume cannot be attached.', ['%zone' => $this->entity->availability_zone()]) . '</h1>',
];
}
return $form;
}
/**
* {@inheritdoc}
*/
protected function actions(array $form, FormStateInterface $form_state) {
$actions = parent::actions($form, $form_state);
$results = $this->getInstances($this->entity->availability_zone());
if (count($results) == 0) {
unset($actions['submit']);
}
return $actions;
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/* @var \Drupal\aws_cloud\Entity\Ec2\Volume $entity */
$entity = $this->entity;
$instance_id = $form_state->getValue('instance_id');
$volume_id = $entity->volume_id();
$device_name = $form_state->getValue('device_name');
$this->awsEc2Service->setCloudContext($this->entity->cloud_context());
$result = $this->awsEc2Service->attachVolume([
'InstanceId' => $instance_id,
'VolumeId' => $volume_id,
'Device' => $device_name,
]);
if ($result != NULL) {
// set the instance_id in the volume entity and save
$entity->set_attachment_information($instance_id);
$entity->setState($result['State']);
$entity->save();
$this->messenger->addMessage($this->t('Volume %volume is attaching to %instance', ['%volume' => $volume_id, '%instance' => $instance_id]));
$form_state->setRedirect('view.aws_volume.page_1', ['cloud_context' => $this->entity->cloud_context()]);
}
}
/**
* Query DB for aws_cloud_instances that are in the same zone
* as the volume. This method respects instance visibility
* @param $zone
* @return \Drupal\Core\Entity\EntityInterface[]
*/
private function getInstances($zone) {
$account = \Drupal::currentUser();
$properties = [
'availability_zone' => $zone
];
if (!$account->hasPermission('view any aws cloud instance')) {
$properties[] = ['user_id', $account->id()];
}
return $this->entity_type_manager->getStorage('aws_cloud_instance')->loadByProperties($properties);
}
}
\ No newline at end of file
<?php
namespace Drupal\aws_cloud\Form\Ec2;
use Drupal\Core\Form\FormStateInterface;
/**
* Volume detach form
*/
class VolumeDetachForm extends AwsDeleteForm {
/**
* {@inheritdoc}
*/
public function getQuestion() {
$entity = $this->entity;
return $this->t('Are you sure you want to detach volume: %name from %instance?.', [
'%name' => $entity->label(),
'%instance' => $entity->attachment_information(),
]);
}
/**
* {@inheritdoc}
*/
public function getConfirmText() {
return $this->t('Detach');
}
public function getDescription() {
return $this->t('Make sure to unmount any file systems on the device within your operating system before detaching the volume. Failure to do so can result in the volume becoming stuck in the busy state while detaching. If this happens, detachment can be delayed indefinitely until you unmount the volume, force detachment, reboot the instance, or all three. If an EBS volume is the root device of an instance, it can\'t be detached while the instance is running. To detach the root volume, stop the instance first.');
}
/**
* {@inheritdoc}
*/
public function submitForm(array &$form, FormStateInterface $form_state) {
/* @var \Drupal\aws_cloud\Entity\Ec2\Volume $entity */
$entity = $this->entity;
$volume_id = $entity->volume_id();
$instance_id = $entity->attachment_information();
$this->awsEc2Service->setCloudContext($this->entity->cloud_context());
$result = $this->awsEc2Service->detachVolume([
'VolumeId' => $volume_id,
]);
if ($result != NULL) {
// set the instance_id in the volume entity and save
$entity->set_attachment_information('');
$entity->setState($result['State']);
$entity->save();
$this->messenger->addMessage($this->t('Volume %volume is detaching from %instance', ['%volume' => $volume_id, '%instance' => $instance_id]));
$form_state->setRedirect('view.aws_volume.page_1', ['cloud_context' => $this->entity->cloud_context()]);
}
}
}
\ No newline at end of file
......@@ -486,6 +486,24 @@ class AwsEc2Service implements AwsEc2ServiceInterface {
return $results;
}
/**
* {@inheritdoc}
*/
public function attachVolume($params = []) {
$params += $this->getDefaultParameters();
$results = $this->execute('AttachVolume', $params);
return $results;
}
/**
* {@inheritdoc}
*/
public function detachVolume($params = []) {
$params += $this->getDefaultParameters();
$results = $this->execute('DetachVolume', $params);
return $results;
}
/**
* {@inheritdoc}
*/
......@@ -1071,9 +1089,11 @@ class AwsEc2Service implements AwsEc2ServiceInterface {
// Skip if $entity already exists, by updating 'refreshed' time.
if (!empty($entity_id)) {
/* @var \Drupal\aws_cloud\Entity\Ec2\Volume $entity */
$entity = Volume::load($entity_id);
$entity->setRefreshed($timestamp);
$entity->setState($volume['State']);
$entity->set_attachment_information(implode(', ', $attachments));
$entity->setCreated(strtotime($volume['CreateTime']));
$entity->save();
......
......@@ -417,6 +417,20 @@ interface AwsEc2ServiceInterface {
*/
public function startInstances($params = []);
/**
* Attaches an EBS volume to a running or stopped
* instance and exposes it to the instance with the
* specified device name.
* @param array $params
* @return mixed
*/
public function attachVolume($params = []);
/**
* Detaches an EBS volume
* @param array $params
* @return mixed
*/
public function detachVolume($params = []);
}
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment