Skip to content
Snippets Groups Projects
Commit 787fc3d7 authored by Masami  Suzuki's avatar Masami Suzuki Committed by Yas Naoi
Browse files

Issue #3218529 by Masami, yas, Xiaohua Guan: Delete resources immediately by...

Issue #3218529 by Masami, yas, Xiaohua Guan: Delete resources immediately by "Delete the following Resources" option when launching K8s launch template
parent 048714f7
No related branches found
No related tags found
No related merge requests found
......@@ -25,6 +25,7 @@ use Drupal\Core\Plugin\ContainerFactoryPluginInterface;
use Drupal\Core\Session\AccountProxyInterface;
use Drupal\Core\Template\TwigEnvironment;
use Drupal\k8s\Entity\K8sEntityBase;
use Drupal\k8s\Entity\K8sNamespace;
use Drupal\k8s\Entity\K8sSchedule;
use Drupal\k8s\Service\K8sServiceException;
use Drupal\k8s\Service\K8sServiceInterface;
......@@ -32,6 +33,8 @@ use Drupal\k8s\Traits\K8sFormTrait;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Drupal\k8s\Plugin\EntityReferenceSelection\LaunchTemplateUserSelection;
use Drupal\user\Entity\User;
use GuzzleHttp\Promise\Promise;
use Drupal\Core\Render\Markup;
/**
* K8s Cloud launch template plugin.
......@@ -41,6 +44,10 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
use CloudContentEntityTrait;
use K8sFormTrait;
public const CHECK_RETRY_COUNT = 5;
public const CHECK_SLEEP_SECOND = 1;
/**
* The K8s Service.
*
......@@ -237,7 +244,7 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
}
}
if (!empty($entities)) {
$this->k8sService->deleteResourcesWithEntities($entities);
$this->k8sService->deleteResourcesWithEntities($entities, TRUE);
}
// Remove schedules.
......@@ -267,6 +274,29 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
return $route;
}
$errors = $this->checkExistence(
$cloud_server_template->get('field_detail')->value,
$cloud_server_template->getCloudContext(),
$cloud_server_template->get('field_namespace')->value
);
if (!empty($errors)) {
$resources = '';
foreach ($errors as $error) {
$resources .= "<li>{$error->render()}</li>";
}
$message = $this->formatPlural(count($errors),
'The following resource still exists. Please launch again after completely deleting it.<ul>@resources</ul>',
'The following resources still exist. Please launch again after completely deleting them.<ul>@resources</ul>', [
'@resources' => Markup::create($resources),
]);
$this->messenger->addError($message);
return $route;
}
return $this->launchK8sResources($cloud_server_template, $launch_uid);
}
......@@ -723,18 +753,17 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$id_plural = $entity_type->get('id_plural');
$namespaceable = $entity_type->get('namespaceable');
if ($namespaceable === NULL) {
$namespaceable = TRUE;
}
// We can't use empty() here since namespaceable can be
// one of two values such as FALSE or NULL.
$namespaceable = $entity_type->get('namespaceable') === NULL;
// Get the name of method createXXXX.
// Get the name of method create<ResourceName>.
$short_label = self::getCamelCaseWithWhitespace($object_type);
$name_camel = self::getCamelCaseWithoutWhitespace($short_label);
$create_method_name = "create{$name_camel}";
$create_method_name = "create${name_camel}";
// Get the name of method updateXXXXs.
$name_plural_camel = !empty($id_plural) ? self::getCamelCaseWithoutWhitespace($entity_type->getCollectionLabel()) : '';
// Get the name of method update<ResourceName>s.
$name_plural_camel = !empty($id_plural) ? self::getCamelCaseWithoutWhitespace($entity_type->getPluralLabel()) : '';
$update_method_name = "update${name_plural_camel}";
try {
......@@ -829,7 +858,7 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
// Parameters are 3 when called by the function.
// And the cloud launch template shouldn't be saved.
if (count(func_get_args()) === 2) {
$cloud_server_template->validate();
$cloud_server_template->setValidationRequired(FALSE);
$cloud_server_template->save();
}
}
......@@ -933,6 +962,7 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
$objects = [];
if (!empty($yaml_file_contents) && !empty($launch_resources) && !empty($yaml_file_names)) {
$templates = $this->k8sService->supportedCloudServerTemplates();
$errors = [];
foreach ($yaml_file_contents as $idx => $file_contents) {
if (empty($launch_resources[$idx])) {
continue;
......@@ -973,6 +1003,17 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
$validated = TRUE;
}
$result = !empty($validated)
? $this->checkExistence(
$file_contents,
$cloud_server_template->getCloudContext(),
$cloud_server_template->get('field_namespace')->value
) : [];
if ($validated && !empty($result)) {
$errors = array_merge($errors, $result);
continue;
}
if ($validated) {
$cloud_server_template->get('field_detail')->setValue($detailValue);
$errors_before = count($this->messenger->messagesByType($this->messenger::TYPE_ERROR));
......@@ -997,9 +1038,24 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
}
}
if (!empty($errors)) {
$resources = '';
foreach ($errors as $idx => $error) {
$resources .= "<li>{$error->render()}</li>";
}
$message = $this->formatPlural(count($errors),
'The following resource still exists. Please launch again after completely deleting it.<ul>@resources</ul>',
'The following resources still exist. Please launch again after completely deleting them.<ul>@resources</ul>', [
'@resources' => Markup::create($resources),
]);
$this->messenger->addError($message);
}
$cloud_server_template->get('field_object')->setValue($objects);
$cloud_server_template->get('field_detail')->setValue(NULL);
$cloud_server_template->validate();
$cloud_server_template->setValidationRequired(FALSE);
$cloud_server_template->save();
}
}
......@@ -1017,6 +1073,10 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
$form['actions']['submit']['#access'] = FALSE;
$form['description']['#access'] = FALSE;
if (!empty($form['confirm_message'])) {
unset($form['confirm_message']);
}
$this->cloudService->reorderForm($form, $fieldsets_def);
}
......@@ -1058,4 +1118,131 @@ class K8sCloudServerTemplatePlugin extends CloudPluginBase implements CloudServe
return $result;
}
/**
* Check the existence of resources.
*
* @param string $yaml_string
* The YAML string.
* @param string $cloud_context
* The cloud context.
* @param string $namespace
* The namespace.
*
* @return array
* YAMLS with resources still existing.
*/
private function checkExistence($yaml_string, $cloud_context, $namespace): array {
$self = $this;
$promise = new Promise(function () use (&$promise, $self, $yaml_string, $namespace) {
$object_types = $self->k8sService->supportedCloudServerTemplates();
$yamls = $self->k8sService->decodeMultipleDocYaml($yaml_string);
for ($count = 0; $count < $self::CHECK_RETRY_COUNT; $count++) {
if (empty($yamls)) {
break;
}
foreach ($yamls ?: [] as $idx => $yaml) {
$name = $yaml['metadata']['name'];
$kind = $yaml['kind'];
$object_type = array_search($kind, $object_types);
$entity_type_id = "k8s_$object_type";
$entity_type = $self->entityTypeManager->getDefinition($entity_type_id);
// We can't use empty() here since namespaceable can be
// one of two values such as FALSE or NULL.
$namespaceable = $entity_type->get('namespaceable') === NULL;
// Check existence.
$result = $self->k8sService->hasResource($entity_type_id,
!empty($namespaceable)
? [
'metadata.namespace' => $namespace,
'metadata.name' => $name,
] : [
'metadata.name' => $name,
]);
if (empty($result)) {
unset($yamls[$idx]);
continue;
}
$yamls[$idx]['entity_type_id'] = $entity_type_id;
}
sleep($self::CHECK_SLEEP_SECOND);
}
$promise->resolve($yamls);
});
$result = $promise->wait();
$messages = [];
foreach ($result ?: [] as $yaml) {
$name = $yaml['metadata']['name'];
$entity_type_id = $yaml['entity_type_id'];
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
// We can't use empty() here since namespaceable can be
// one of two values such as FALSE or NULL.
$namespaceable = $entity_type->get('namespaceable') === NULL;
$name_camel = $self::getCamelCaseWithoutWhitespace($entity_type->getPluralLabel());
$update_method_name = "update${name_camel}";
// Refresh resources.
$this->k8sService->$update_method_name([
'metadata.name' => $name,
], FALSE);
$params = [
'cloud_context' => $cloud_context,
'name' => $name,
];
$params += !empty($namespaceable) ? ['namespace' => $namespace] : [];
$entities = $this->entityTypeManager->getStorage($entity_type_id)->loadByProperties($params);
if (empty($entities)) {
$messages[] = $this->t('@type: %name', [
'@type' => $entity_type->getSingularLabel(),
'%name' => $name,
]);
continue;
}
$entity = reset($entities);
if (!empty($namespaceable) && !($entity instanceof K8sNamespace)) {
$namespaces = $this->entityTypeManager->getStorage('k8s_namespace')
->loadByProperties([
'name' => $entity->getNamespace(),
'cloud_context' => $entity->getCloudContext(),
]);
if (!empty($namespaces)) {
$namespace_entity = reset($namespaces);
$messages[] = $this->t('@type: <a href=":url">%name</a> (Namespace: <a href=":namespace_url">%namespace</a>)', [
'@type' => $entity->getEntityType()->getSingularLabel(),
'%name' => $entity->getName(),
':url' => $entity->toUrl('canonical')->toString(),
'%namespace' => $namespace_entity->getName(),
':namespace_url' => $namespace_entity->toUrl('canonical')->toString(),
]);
continue;
}
}
$messages[] = $this->t('@type: <a href=":url">%name</a>', [
'@type' => $entity->getEntityType()->getSingularLabel(),
'%name' => $entity->getName(),
':url' => $entity->toUrl('canonical')->toString(),
]);
}
return $messages;
}
}
......@@ -2041,26 +2041,24 @@ class K8sService extends CloudServiceBase implements K8sServiceInterface {
/**
* {@inheritdoc}
*/
public function deleteResourcesWithEntities(array $entities) {
foreach ($entities as $entity) {
public function deleteResourcesWithEntities(array $entities, $immediately = FALSE): void {
foreach ($entities ?: [] as $entity) {
$this->setCloudContext($entity->getCloudContext());
try {
$name_camel = self::getCamelCaseWithoutWhitespace($entity->getEntityType()->getSingularLabel());
$method_name = "delete{$name_camel}";
if (method_exists($entity, 'getNamespace')) {
$this->$method_name($entity->getNamespace(), [
'metadata' => [
'name' => $entity->getName(),
],
]);
if ($immediately) {
$this->deleteImmediately($entity);
}
else {
$this->$method_name([
'metadata' => [
'name' => $entity->getName(),
],
]);
$name_camel = self::getCamelCaseWithoutWhitespace($entity->getEntityType()->getSingularLabel());
$method_name = "delete{$name_camel}";
method_exists($entity, 'getNamespace')
? $this->$method_name($entity->getNamespace(), [
'metadata' => ['name' => $entity->getName()],
])
: $this->$method_name([
'metadata' => ['name' => $entity->getName()],
]);
}
$entity->delete();
......@@ -3462,4 +3460,62 @@ class K8sService extends CloudServiceBase implements K8sServiceInterface {
}
}
/**
* {@inheritdoc}
*/
public function deleteImmediately(K8sEntityBase $entity): array {
$this->setCloudContext($entity->getCloudContext());
$name_camel = self::getCamelCaseWithoutWhitespace(strtolower($entity->getEntityType()->getSingularLabel()));
$classPath = NULL;
if (class_exists("Maclof\\Kubernetes\\Models\\$name_camel")) {
$classPath = "Maclof\\Kubernetes\\Models\\$name_camel";
}
elseif (class_exists("Drupal\\k8s\\Service\\K8sClientExtension\\Models\\K8s${name_camel}Model")) {
$classPath = "Drupal\\k8s\\Service\\K8sClientExtension\\Models\\K8s${name_camel}Model";
}
if (empty($classPath)) {
// Using MessengerTrait::messenger().
$this->messenger->addError($this->t('There is no Model Class for @type %label on @cloud_context.', [
'@type' => $entity->getEntityType()->getSingularLabel(),
'%label' => $entity->toLink($entity->label())->toString(),
'@cloud_context' => $entity->getCloudContext(),
]));
return [];
}
$apiVersion = (new $classPath)->getApiVersion() !== 'v1' ? (new $classPath)->getApiVersion() : NULL;
$plural_label = $entity->getEntityType()->getPluralLabel();
$uri = str_replace(' ', '', (strtolower($plural_label)));
// We can't use empty() here since namespaceable can be
// one of two values such as FALSE or NULL.
$namespaceable = $entity->getEntityType()->get('namespaceable') === NULL;
$namespace = $namespaceable ? $entity->getNamespace() : '';
$query = ['gracePeriodSeconds' => 0];
return $this->getClient($namespace)->sendRequest('DELETE', '/' . $uri . '/' . $entity->getName(), $query, NULL, $namespaceable, $apiVersion);
}
/**
* {@inheritdoc}
*/
public function hasResource($entity_type_id, array $params):bool {
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$method = self::getCamelCaseWithoutWhitespace(strtolower($entity_type->getPluralLabel()), TRUE);
$result = $this->getClient()
->$method()
->setFieldSelector($params)
->find()
->toArray();
return !empty($result);
}
}
......@@ -2,6 +2,8 @@
namespace Drupal\k8s\Service;
use Drupal\k8s\Entity\K8sEntityBase;
/**
* Provides K8sService interface.
*/
......@@ -1354,8 +1356,10 @@ interface K8sServiceInterface {
*
* @param array $entities
* The array entities to be deleted.
* @param bool $immediately
* Whether delete immediately.
*/
public function deleteResourcesWithEntities(array $entities);
public function deleteResourcesWithEntities(array $entities, $immediately = FALSE): void;
/**
* Delete k8s schedule entities.
......@@ -1604,4 +1608,28 @@ interface K8sServiceInterface {
*/
public function updateK8sNamespaceResourceStoreEntity($cloud_context, $namespace, $cost_type = 'on_demand_yearly', array $extra_data = []): void;
/**
* Delete resource with 'gracePeriodSeconds' option.
*
* @param \Drupal\k8s\Entity\K8sEntityBase $entity
* The entity with the resource to be deleted.
*
* @return array
* An array of results.
*/
public function deleteImmediately(K8sEntityBase $entity): array;
/**
* Check existence of a resource.
*
* @param string $entity_type_id
* The entity type id.
* @param array $params
* Parameters array to send to API.
*
* @return bool
* TRUE if the resource exists, else FALSE.
*/
public function hasResource($entity_type_id, array $params): bool;
}
......@@ -3,6 +3,7 @@
namespace Drupal\k8s\Service;
use Drupal\Core\Form\ConfigFormBaseTrait;
use Drupal\k8s\Entity\K8sEntityBase;
/**
* K8sServiceMock service interacts with the K8s API.
......@@ -830,4 +831,74 @@ class K8sServiceMock extends K8sService {
return json_decode($this->config('k8s.settings')->get('k8s_mock_data'), TRUE)['deleteHorizontalPodAutoscaler'];
}
/**
* {@inheritdoc}
*/
public function deleteImmediately(K8sEntityBase $entity): array {
$mock_data = json_decode($this->config('k8s.settings')->get('k8s_mock_data'), TRUE);
$entity_type = $entity->getEntityType();
$plural_label = strpos($entity_type->getPluralLabel(), ' ') > 0
? strtolower($entity_type->getPluralLabel())
: $entity_type->getPluralLabel();
$name_camel = $this::getCamelCaseWithoutWhitespace($plural_label);
$key = "get${name_camel}";
if (!empty($mock_data[$key])) {
return [];
}
$namespaceable = $entity->getEntityType()->get('namespaceable') === NULL;
foreach ($mock_data[$key] ?: [] as $idx => $data) {
if ((!empty($namespaceable) && empty($data['metadata']['namespace']))
|| (empty($namespaceable) && !empty($data['metadata']['namespace']))
|| empty($data['metadata']['name'])) {
continue;
}
if ((!empty($namespaceable) && $entity->getNamespace() === $data['metadata']['namespace'])
&& $entity->getName() === $data['metadata']['name']) {
unset($mock_data[$key][$idx]);
$this->config('k8s.settings')->set('k8s_mock_data', json_encode($mock_data));
$this->config('k8s.settings')->save();
return [];
}
}
return [];
}
/**
* {@inheritdoc}
*/
public function hasResource($entity_type_id, array $params):bool {
$mock_data = json_decode($this->config('k8s.settings')->get('k8s_mock_data'), TRUE);
$entity_type = $this->entityTypeManager->getDefinition($entity_type_id);
$plural_label = strpos($entity_type->getPluralLabel(), ' ') > 0
? strtolower($entity_type->getPluralLabel())
: $entity_type->getPluralLabel();
$name_camel = $this::getCamelCaseWithoutWhitespace($plural_label);
$key = "get${name_camel}";
if (!empty($mock_data[$key])) {
return FALSE;
}
foreach ($mock_data[$key] ?: [] as $data) {
if (!empty($params['metadata.namespace']) && empty($data['metadata']['namespace'])
|| (empty($params['metadata.namespace']) && !empty($data['metadata']['namespace']))
|| (empty($params['metadata.name']) || empty($data['metadata']['name']))
|| (!empty($params['metadata.namespace']) && $params['metadata.namespace'] !== $data['metadata']['namespace'])
|| ($data['metadata']['name'] !== $params['metadata.name'])) {
continue;
}
}
return TRUE;
}
}
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