Commit e6bc356f authored by Masami  Suzuki's avatar Masami Suzuki Committed by Yas Naoi
Browse files

Issue #3308999 by Masami, Ryo Yamashita, yas: Add the function to...

Issue #3308999 by Masami, Ryo Yamashita, yas: Add the function to attach/detach Aws Cloud Volume in the SPA
parent beb658ca
Loading
Loading
Loading
Loading
+51 −0
Original line number Diff line number Diff line
@@ -71,6 +71,57 @@ const AWS_CLOUD_VOLUME_TEMPLATE: EntityFormTemplate[] = [
      }
    ]
  },
  {
    cloudServiceProvider: 'aws_cloud',
    entityName: 'volume',
    actionType: 'attach',
    entityRecords: [
      {
        type: 'label',
        text: 'Are you sure you want to attach {{entityName}}: {{name}}?'
      },
      {
        type: 'panel',
        panelName: 'Volume Information',
        keyValueRecords: [
          { type: 'default', labelName: 'Volume id', name: 'volume_id', defaultValue: '', readOnly: true },
          { type: 'default', labelName: 'Volume name', name: 'name', defaultValue: '', readOnly: true },
		      { type: 'default', labelName: 'Device Name', name: 'device_name', defaultValue: '' },
		      { type: 'select', labelName: 'Instance ID', name: 'instance_id',
		        url: '/cloud_dashboard/aws_cloud/{cloud_context}/aws_cloud_volume/{entity_id}/instances',
		        defaultValue: '', required: true
		      },
        ]
      },
    ],
    submitButtonLabel: 'Attach'
  },
  {
    cloudServiceProvider: 'aws_cloud',
    entityName: 'volume',
    actionType: 'detach',
    entityRecords: [
      {
        type: 'label',
        text: 'Are you sure you want to detach {{entityName}}: {{name}}?'
      },
      {
        type: 'panel',
        panelName: 'Volume Information',
        keyValueRecords: [
          { type: 'default', labelName: 'Volume ID', name: 'volume_id', defaultValue: '', readOnly: true },
          { type: 'default', labelName: 'Volume name', name: 'name', defaultValue: '', readOnly: true },
          {
            type: 'join', labelName: 'Attached to instance', name: 'attachment_information', info: {
              entityTypeId: 'aws_cloud_instance',
              keyColumn: 'instance_id',
            }, defaultValue: '', readOnly: true
          },
        ]
      },
    ],
    submitButtonLabel: 'Detach'
  },
]

export default AWS_CLOUD_VOLUME_TEMPLATE;
+29 −0
Original line number Diff line number Diff line
@@ -1631,3 +1631,32 @@ entity.aws_cloud_volume.edit:
    _custom_access: '\Drupal\cloud\Controller\CloudConfigController::access'
  options:
      perm: 'edit any aws cloud volume+edit own aws cloud volume'

entity.aws_cloud_volume.attach:
  path: '/cloud_dashboard/aws_cloud/{cloud_context}/aws_cloud_volume/{entity_id}/attach'
  defaults:
    _controller: '\Drupal\aws_cloud\Controller\Ec2\ApiController::operateEntity'
    entity_type_id: aws_cloud_volume
    command: attach
  methods: [POST]
  requirements:
    _custom_access: '\Drupal\aws_cloud\Controller\Ec2\ApiController::access'

entity.aws_cloud_volume.detach:
  path: '/cloud_dashboard/aws_cloud/{cloud_context}/aws_cloud_volume/{entity_id}/detach'
  defaults:
    _controller: '\Drupal\aws_cloud\Controller\Ec2\ApiController::operateEntity'
    entity_type_id: aws_cloud_volume
    command: detach
  methods: [POST]
  requirements:
    _custom_access: '\Drupal\aws_cloud\Controller\Ec2\ApiController::access'

entity.aws_cloud_volume.instances:
  path: '/cloud_dashboard/aws_cloud/{cloud_context}/aws_cloud_volume/{entity_id}/instances'
  defaults:
    _controller: '\Drupal\aws_cloud\Controller\Ec2\ApiController::getInstanceOptions'
    entity_type_id: aws_cloud_volume
    command: attach
  requirements:
    _custom_access: '\Drupal\aws_cloud\Controller\Ec2\ApiController::access'
+90 −0
Original line number Diff line number Diff line
@@ -17,7 +17,10 @@ use Drupal\cloud\Service\CloudService;
use Drupal\cloud\Service\CloudServiceInterface;
use Drupal\cloud\Traits\CloudContentEntityTrait;
use Drupal\cloud\Traits\CloudResourceTrait;
use Drupal\Core\Access\AccessResult;
use Drupal\Core\Access\AccessResultInterface;
use Drupal\Core\Controller\ControllerBase;
use Drupal\Core\Entity\EntityInterface;
use Drupal\Core\Entity\EntityTypeManagerInterface;
use Drupal\Core\Form\FormState;
use Drupal\Core\Link;
@@ -31,6 +34,7 @@ use Symfony\Component\HttpFoundation\RequestStack;
use Symfony\Component\HttpFoundation\Response;
use Symfony\Component\HttpFoundation\JsonResponse;
use Symfony\Component\HttpFoundation\RedirectResponse;
use Symfony\Component\Routing\Route;

/**
 * Controller responsible for "update" URLs.
@@ -196,6 +200,37 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
    );
  }

  /**
   * Checks user access for a specific request based on the cloud context.
   *
   * @param string $cloud_context
   *   Cloud context to check.
   * @param \Drupal\Core\Session\AccountInterface $account
   *   Run access checks for this account.
   * @param \Symfony\Component\Routing\Route $route
   *   The route object.
   * @param string $entity_type_id
   *   The entity type.
   * @param string $entity_id
   *   The entity id.
   * @param string $command
   *   The command name.
   *
   * @return \Drupal\Core\Access\AccessResultInterface
   *   The access result.
   */
  public function access(string $cloud_context, AccountInterface $account, Route $route, string $entity_type_id, string $entity_id, string $command): AccessResultInterface {
    $entity = $this->entityTypeManager
      ->getStorage($entity_type_id)
      ->load($entity_id);

    if (!empty($entity) && $entity instanceof EntityInterface) {
      return $entity->access($command, $account, TRUE);
    }
    // Unknown operation, no opinion.
    return AccessResult::neutral();
  }

  /**
   * {@inheritdoc}
   *
@@ -1090,6 +1125,17 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
        case 'stop_aws_cloud_instance':
          $method_name = 'stopInstance';
          break;

        case 'attach_aws_cloud_volume':
          $form_state->setValue('device_name', $request->get('device_name', ''));
          $form_state->setValue('instance_id', $request->get('instance_id', ''));
          $method_name = 'attachVolume';
          break;

        case 'detach_aws_cloud_volume':
          $method_name = 'detachVolume';
          break;

      }
    }
    catch (\JsonException $e) {
@@ -1811,4 +1857,48 @@ class ApiController extends ControllerBase implements ApiControllerInterface {
    return new JsonResponse($options);
  }

  /**
   * {@inheritdoc}
   */
  public function getInstanceOptions(string $cloud_context, string $entity_id): JsonResponse {
    /** @var \Drupal\openstack\Entity\OpenStackVolume $entity */
    $entity = $this->entityTypeManager
      ->getStorage('aws_cloud_volume')
      ->load($entity_id);

    if (empty($entity)) {
      return new JsonResponse([
        'result' => 'NG',
        'reason' => 'The volume has already been deleted.',
      ], 404);
    }

    $account = $this->currentUser();
    $properties = [
      'availability_zone' => $entity->getAvailabilityZone(),
      'cloud_context' => $cloud_context,
    ];

    if (!$account->hasPermission("view any aws cloud instance")) {
      $properties['uid'] = $account->id();
    }

    $results = $this->entityTypeManager
      ->getStorage('aws_cloud_instance')
      ->loadByProperties($properties);

    $options = [];
    foreach ($results ?: [] as $result) {
      $options[] = [
        'value' => $result->getInstanceId(),
        'label' => $this->t('@name - @instance_id', [
          '@name' => $result->getName(),
          '@instance_id' => $result->getInstanceId(),
        ]),
      ];
    }

    return new JsonResponse($options);
  }

}
+16 −0
Original line number Diff line number Diff line
@@ -346,4 +346,20 @@ interface ApiControllerInterface {
   */
  public function getVolumeTypes($cloud_context): JsonResponse;

  /**
   * Helper function that loads all instances.
   *
   * @param string $cloud_context
   *   Cloud context to use in the query.
   * @param string $entity_id
   *   The entity id of the volume.
   *
   * @return \Symfony\Component\HttpFoundation\JsonResponse
   *   The JSON response.
   *
   * @throws \Drupal\Component\Plugin\Exception\InvalidPluginDefinitionException
   * @throws \Drupal\Component\Plugin\Exception\PluginNotFoundException
   */
  public function getInstanceOptions(string $cloud_context, string $entity_id): JsonResponse;

}
+94 −36
Original line number Diff line number Diff line
@@ -2,10 +2,22 @@

namespace Drupal\aws_cloud\Form\Ec2;

use Drupal\aws_cloud\Service\AwsCloud\AwsCloudOperationsServiceInterface;
use Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface;
use Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface;
use Drupal\cloud\Service\CloudService;
use Drupal\cloud\Service\EntityLinkRendererInterface;
use Drupal\Component\Datetime\TimeInterface;
use Drupal\Core\Cache\CacheBackendInterface;
use Drupal\Core\Entity\EntityRepositoryInterface;
use Drupal\Core\Entity\EntityTypeBundleInfoInterface;
use Drupal\Core\Entity\EntityTypeManager;
use Drupal\Core\Form\FormStateInterface;
use Drupal\cloud\Traits\CloudContentEntityTrait;
use Drupal\Core\Messenger\Messenger;
use Drupal\Core\Plugin\CachedDiscoveryClearerInterface;
use Drupal\Core\StringTranslation\TranslatableMarkup;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
 * Volume Attach form.
@@ -14,6 +26,87 @@ class VolumeAttachForm extends AwsDeleteForm {

  use CloudContentEntityTrait;

  /**
   * The AWS Cloud Operations service.
   *
   * @var \Drupal\aws_cloud\Service\AwsCloud\AwsCloudOperationsServiceInterface
   */
  protected $awsCloudOperationsService;

  /**
   * VolumeAttachForm constructor.
   *
   * @param \Drupal\aws_cloud\Service\AwsCloud\AwsCloudOperationsServiceInterface $aws_cloud_operations_service
   *   The AWS Cloud Operations service.
   * @param \Drupal\aws_cloud\Service\Ec2\Ec2ServiceInterface $ec2_service
   *   The AWS Cloud EC2 service.
   * @param \Drupal\Core\Entity\EntityRepositoryInterface $entity_repository
   *   The entity repository service.
   * @param \Drupal\Core\Entity\EntityTypeBundleInfoInterface $entity_type_bundle_info
   *   The entity type bundle service.
   * @param \Drupal\Component\Datetime\TimeInterface $time
   *   The time service.
   * @param \Drupal\Core\Messenger\Messenger $messenger
   *   The messenger service.
   * @param \Drupal\Core\Entity\EntityTypeManager $entity_type_manager
   *   The Entity Type Manager.
   * @param \Drupal\Core\Cache\CacheBackendInterface $cacheRender
   *   A cache backend interface instance.
   * @param \Drupal\Core\Plugin\CachedDiscoveryClearerInterface $plugin_cache_clearer
   *   A plugin cache clear instance.
   * @param \Drupal\cloud\Service\EntityLinkRendererInterface $entity_link_renderer
   *   The entity link render service.
   * @param \Drupal\cloud\Plugin\cloud\config\CloudConfigPluginManagerInterface $cloud_config_plugin_manager
   *   The cloud service provider plugin manager (CloudConfigPluginManager).
   */
  public function __construct(
    AwsCloudOperationsServiceInterface $aws_cloud_operations_service,
    Ec2ServiceInterface $ec2_service,
    EntityRepositoryInterface $entity_repository,
    EntityTypeBundleInfoInterface $entity_type_bundle_info,
    TimeInterface $time,
    Messenger $messenger,
    EntityTypeManager $entity_type_manager,
    CacheBackendInterface $cacheRender,
    CachedDiscoveryClearerInterface $plugin_cache_clearer,
    EntityLinkRendererInterface $entity_link_renderer,
    CloudConfigPluginManagerInterface $cloud_config_plugin_manager
  ) {
    parent::__construct(
      $ec2_service,
      $entity_repository,
      $entity_type_bundle_info,
      $time,
      $messenger,
      $entity_type_manager,
      $cacheRender,
      $plugin_cache_clearer,
      $entity_link_renderer,
      $cloud_config_plugin_manager
    );

    $this->awsCloudOperationsService = $aws_cloud_operations_service;
  }

  /**
   * {@inheritdoc}
   */
  public static function create(ContainerInterface $container) {
    return new static(
      $container->get('aws_cloud.operations'),
      $container->get('aws_cloud.ec2'),
      $container->get('entity.repository'),
      $container->get('entity_type.bundle.info'),
      $container->get('datetime.time'),
      $container->get('messenger'),
      $container->get('entity_type.manager'),
      $container->get('cache.render'),
      $container->get('plugin.cache_clearer'),
      $container->get('entity.link_renderer'),
      $container->get('plugin.manager.cloud_config_plugin')
    );
  }

  /**
   * {@inheritdoc}
   */
@@ -111,42 +204,7 @@ class VolumeAttachForm extends AwsDeleteForm {
   * {@inheritdoc}
   */
  public function submitForm(array &$form, FormStateInterface $form_state): void {
    /** @var \Drupal\aws_cloud\Entity\Ec2\Volume $entity */
    $entity = $this->entity;

    $instance_id = $form_state->getValue('instance_id');
    $volume_id = $entity->getVolumeId();
    $device_name = $form_state->getValue('device_name');

    $this->ec2Service->setCloudContext($this->entity->getCloudContext());
    $result = $this->ec2Service->attachVolume([
      'InstanceId' => $instance_id,
      'VolumeId' => $volume_id,
      'Device' => $device_name,
    ]);

    if ($result !== NULL) {
      $form_state->setRedirect("view.{$entity->getEntityTypeId()}.list", ['cloud_context' => $entity->getCloudContext()]);

      if (!empty($result['SendToWorker'])) {
        $this->messenger->addStatus($this->t('The Volume %volume is attaching to %instance remotely.', [
          '%volume' => $volume_id,
          '%instance' => $instance_id,
        ]));
        return;
      }

      // Set the instance_id in the volume entity and save.
      $entity->setAttachmentInformation($instance_id);
      $entity->setState($result['State'] ?? 'in-use');
      $entity->save();
      $this->clearCacheValues($entity->getCacheTags());
      $this->dispatchSubmitEvent($entity);
      $this->messenger->addStatus($this->t('The Volume %volume is attaching to %instance.', [
        '%volume' => $volume_id,
        '%instance' => $instance_id,
      ]));
    }
    $this->awsCloudOperationsService->attachVolume($this->entity, $form, $form_state);
  }

  /**
Loading