Commit 51d1719d authored by Kalle Vuorjoki's avatar Kalle Vuorjoki
Browse files

Resolve #3186932: by sokru, ayalon, Marios Anagnostopoulos: Use...

Resolve #3186932: by sokru, ayalon, Marios Anagnostopoulos: Use ruflin/Elastica instead of nodespark/des-connector
parent 808b25c4
Loading
Loading
Loading
Loading
+3 −2
Original line number Diff line number Diff line
@@ -4,8 +4,9 @@
  "type": "drupal-module",
  "homepage": "https://www.drupal.org/project/elasticsearch_connector",
  "require": {
    "nodespark/des-connector": "7.x-dev",
    "makinacorpus/php-lucene": "^1.0.2"
    "php": "^7.2 || ^8.0",
    "illuminate/support": "^8.64",
    "ruflin/elastica": "^7.1"
  },
  "repositories": {
    "drupal": {
+2 −2
Original line number Diff line number Diff line
@@ -26,10 +26,10 @@ function elasticsearch_connector_requirements($phase) {
  }

  if ($phase == 'runtime') {
    if (!interface_exists('\nodespark\DESConnector\ClientInterface')) {
    if (!class_exists('\Elastica\Index')) {
      return array(
        'elasticsearch_connector' => array(
          'title' => t('The Elasticsearch client library is missing.'),
          'title' => t('The Elasticsearch client library ruflin/Elastica is missing.'),
          'description' => t('The client library for Elasticsearch connection is missing.'),
          'severity' => REQUIREMENT_ERROR,
          'value' => t('Elasticsearch library missing.'),
+6 −4
Original line number Diff line number Diff line
services:
  elasticsearch_connector.client_factory:
    class: nodespark\DESConnector\ClientFactory
  logger.channel.elasticsearch:
    class: Drupal\Core\Logger\LoggerChannel
    factory: logger.factory:get
    arguments: ['elasticsearch']

  elasticsearch_connector.client_manager:
    class: Drupal\elasticsearch_connector\ElasticSearch\ClientManager
    arguments:
      - '@module_handler'
      - '@elasticsearch_connector.client_factory'
      - '@logger.channel.elasticsearch'

  elasticsearch_connector.cluster_manager:
    class: Drupal\elasticsearch_connector\ClusterManager
    class: Drupal\elasticsearch_connector\ElasticSearch\ClusterManager
    arguments: ['@state', '@entity_type.manager']

  elasticsearch_connector.index_factory:
+25 −17
Original line number Diff line number Diff line
@@ -10,6 +10,8 @@ use Drupal\Core\Url;
use Drupal\elasticsearch_connector\ElasticSearch\ClientManagerInterface;
use Drupal\elasticsearch_connector\Entity\Cluster;
use Drupal\elasticsearch_connector\Entity\Index;
use Elastica\Exception\ConnectionException;
use Elastica\Exception\ResponseException;
use Symfony\Component\DependencyInjection\ContainerInterface;
use Symfony\Component\HttpKernel\Exception\NotFoundHttpException;

@@ -81,7 +83,7 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
   *   itself an array with the cluster and any indices as values.
   *   - lone_indexes: Array of indices without a cluster.
   */
  public function group() {
  public function group(): array {
    /** @var \Drupal\elasticsearch_connector\Entity\Cluster[] $clusters */
    $clusters = $this->storage->loadMultiple();
    /** @var \Drupal\elasticsearch_connector\Entity\Index[] $indices */
@@ -95,10 +97,10 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
      ];

      foreach ($indices as $index) {
        if ($index->server == $cluster->cluster_id) {
        if ($index->server === $cluster->cluster_id) {
          $cluster_group['index.' . $index->index_id] = $index;
        }
        elseif ($index->server == NULL) {
        elseif ($index->server === NULL) {
          $lone_indices['index.' . $index->index_id] = $index;
        }
      }
@@ -115,7 +117,7 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
  /**
   * {@inheritdoc}
   */
  public function buildHeader() {
  public function buildHeader(): array {
    return [
      'type' => $this->t('Type'),
      'title' => $this->t('Name'),
@@ -128,13 +130,14 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
  /**
   * {@inheritdoc}
   */
  public function buildRow(EntityInterface $entity) {
  public function buildRow(EntityInterface $entity): array {
    if ($entity instanceof Cluster) {
      $client_connector = $this->clientManager->getClientForCluster($entity);
      $client = $this->clientManager->getClient($entity);
    }
    elseif ($entity instanceof Index) {
      /** @var \Drupal\elasticsearch_connector\Entity\Cluster $cluster */
      $cluster = $this->clusterStorage->load($entity->server);
      $client_connector = $this->clientManager->getClientForCluster($cluster);
      $client = $this->clientManager->getClient($cluster);
    }
    else {
      throw new NotFoundHttpException();
@@ -145,15 +148,20 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
    $status = NULL;
    if (isset($entity->cluster_id)) {
      $cluster = $this->clusterStorage->load($entity->cluster_id);

      if ($client_connector->isClusterOk()) {
        $cluster_health = $client_connector->cluster()->health();
        $version_number = $client_connector->getServerVersion();
        $status = $cluster_health['status'];
      }
      else {
      $status = $this->t('Not available');
      $version_number = $this->t('Unknown version');
      try {
        if ($client->hasConnection()) {
          $version_number = $client->getVersion();
          $status = $client->getCluster()->getHealth()->getStatus();
        }
      }
      catch (ResponseException | ConnectionException $e) {
        $this->messenger()->addError(
          $this->t('Elasticsearch connection failed due to: @error', [
            '@error' => $e->getMessage(),
          ])
        );
      }
      $result = [
        'data' => [
@@ -218,7 +226,7 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
  /**
   * {@inheritdoc}
   */
  public function getDefaultOperations(EntityInterface $entity) {
  public function getDefaultOperations(EntityInterface $entity): array {
    $operations = [];

    if (isset($entity->cluster_id)) {
@@ -259,7 +267,7 @@ class ClusterListBuilder extends ConfigEntityListBuilder {
    foreach ($entity_groups['clusters'] as $cluster_group) {
      /** @var \Drupal\elasticsearch_connector\Entity\Cluster|\Drupal\elasticsearch_connector\Entity\Index $entity */
      foreach ($cluster_group as $entity) {
        $rows[$entity->id()] = $this->buildRow($entity);
        $rows[$entity->uuid()] = $this->buildRow($entity);
      }
    }

+145 −86
Original line number Diff line number Diff line
@@ -5,6 +5,8 @@ namespace Drupal\elasticsearch_connector\Controller;
use Drupal\Core\Controller\ControllerBase;
use Drupal\elasticsearch_connector\ElasticSearch\ClientManagerInterface;
use Drupal\elasticsearch_connector\Entity\Cluster;
use Elastica\Exception\ConnectionException;
use Elastica\Exception\ResponseException;
use Symfony\Component\DependencyInjection\ContainerInterface;

/**
@@ -47,7 +49,7 @@ class ElasticsearchController extends ControllerBase {
   * @return array
   *   An array suitable for drupal_render().
   */
  public function page(Cluster $elasticsearch_cluster) {
  public function page(Cluster $elasticsearch_cluster): array {
    // Build the Search API index information.
    $render = [
      'view' => [
@@ -72,10 +74,10 @@ class ElasticsearchController extends ControllerBase {
   * @param \Drupal\elasticsearch_connector\Entity\Cluster $elasticsearch_cluster
   *   The cluster that is displayed.
   *
   * @return string
   * @return string|null
   *   The page title.
   */
  public function pageTitle(Cluster $elasticsearch_cluster) {
  public function pageTitle(Cluster $elasticsearch_cluster): ?string {
    // TODO: Check if we need string escaping.
    return $elasticsearch_cluster->label();
  }
@@ -89,102 +91,147 @@ class ElasticsearchController extends ControllerBase {
   * @return array
   *   Render array.
   */
  public function getInfo(Cluster $elasticsearch_cluster) {
    // TODO: Get the statistics differently.
    $client_connector = $this->clientManager->getClientForCluster($elasticsearch_cluster);

  public function getInfo(Cluster $elasticsearch_cluster): array {
    try {
      $client = $this->clientManager->getClient($elasticsearch_cluster);
    }
    catch (ConnectionException $e) {
      $this->messenger()->addError(
        $this->t('Elasticsearch connection failed due to: @error', [
          '@error' => $e->getMessage(),
        ])
      );
    }
    try {
      $cluster = $client->getCluster();
    }
    catch (ResponseException | ConnectionException $e) {
      $this->messenger()->addError(
        $this->t('Elasticsearch connection failed due to: @error', [
          '@error' => $e->getMessage(),
        ])
      );
    }
    $total_docs = 0;
    $total_size = 0;
    $plugin_rows = [];
    $node_rows = [];
    $cluster_statistics_rows = [];
    $cluster_health_rows = [];

    if ($client_connector->isClusterOk()) {
    try {
      if ($client->hasConnection()) {
        // Nodes.
      $es_node_namespace = $client_connector->getNodesProperties();
      $node_stats = $es_node_namespace['stats'];
        try {
          $nodes = $client->getCluster()->getNodes();
        }
        catch (ResponseException | ConnectionException $e) {
          $this->messenger()->addError(
            $this->t('Elasticsearch connection failed due to: @error', [
              '@error' => $e->getMessage(),
            ])
          );
        }
        foreach ($nodes as $node) {
          $node_info = $node->getInfo()->getData();
          $node_stats = $node->getStats()->getData();

      $total_docs = 0;
      $total_size = 0;
      $node_rows = [];
      if (!empty($node_stats['nodes'])) {
        // TODO: Better format the results in order to build the
        // correct output.
        foreach ($node_stats['nodes'] as $node_id => $node_properties) {
          $row = [];
          $row[] = ['data' => $node_properties['name']];
          $row[] = ['data' => $node_properties['indices']['docs']['count']];
          $row[] = ['data' => $node->getName()];
          $row[] = ['data' => $node_stats['indices']['docs']['count']];
          $row[] = [
            'data' => format_size(
              $node_properties['indices']['store']['size_in_bytes']
              $node_stats['indices']['store']['size_in_bytes']
            ),
          ];
          $total_docs += $node_properties['indices']['docs']['count'];
          $total_size += $node_properties['indices']['store']['size_in_bytes'];
          $total_docs += $node_stats['indices']['docs']['count'];
          $total_size += $node_stats['indices']['store']['size_in_bytes'];
          $node_rows[] = $row;

          foreach ($node_info['plugins'] as $plugin) {
            $row = [];
            $row[] = ['data' => $plugin['name']];
            $row[] = ['data' => $plugin['version']];
            $row[] = ['data' => $plugin['description']];
            $row[] = ['data' => $node->getName()];
            $plugin_rows[] = $row;
          }
        }

      $cluster_status = $client_connector->getClusterInfo();
        // Cluster:
        $health = $client->getCluster()->getHealth()->getData();
        $state = $client->getCluster()->getState();
        $cluster_statistics_rows = [
          [
            [
            'data' => $cluster_status['health']['number_of_nodes'] . ' ' . t(
              'data' => $health['number_of_nodes'] . ' ' . $this->t(
                  'Nodes'
              ),
            ],
            [
            'data' => $cluster_status['health']['active_shards'] + $cluster_status['health']['unassigned_shards'] . ' ' . t(
              'data' => $health['active_shards'] + $health['unassigned_shards'] . ' ' . $this->t(
                  'Total Shards'
              ),
            ],
            [
            'data' => $cluster_status['health']['active_shards'] . ' ' . t(
              'data' => $health['active_shards'] . ' ' . $this->t(
                  'Successful Shards'
              ),
            ],
            [
            'data' => count(
                $cluster_status['state']['metadata']['indices']
            ) . ' ' . t('Indices'),
              'data' => count($state['metadata']['indices']) . ' ' . $this->t('Indices'),
            ],
          ['data' => $total_docs . ' ' . t('Total Documents')],
          ['data' => format_size($total_size) . ' ' . t('Total Size')],
            ['data' => $total_docs . ' ' . $this->t('Total Documents')],
            ['data' => format_size($total_size) . ' ' . $this->t('Total Size')],
          ],
        ];

        $cluster_health_rows = [];
        $cluster_health_mapping = [
        'cluster_name' => t('Cluster name'),
        'status' => t('Status'),
        'timed_out' => t('Time out'),
        'number_of_nodes' => t('Number of nodes'),
        'number_of_data_nodes' => t('Number of data nodes'),
        'active_primary_shards' => t('Active primary shards'),
        'active_shards' => t('Active shards'),
        'relocating_shards' => t('Relocating shards'),
        'initializing_shards' => t('Initializing shards'),
        'unassigned_shards' => t('Unassigned shards'),
        'delayed_unassigned_shards' => t('Delayed unassigned shards'),
        'number_of_pending_tasks' => t('Number of pending tasks'),
        'number_of_in_flight_fetch' => t('Number of in-flight fetch'),
        'task_max_waiting_in_queue_millis' => t(
          'cluster_name' => $this->t('Cluster name'),
          'status' => $this->t('Status'),
          'timed_out' => $this->t('Time out'),
          'number_of_nodes' => $this->t('Number of nodes'),
          'number_of_data_nodes' => $this->t('Number of data nodes'),
          'active_primary_shards' => $this->t('Active primary shards'),
          'active_shards' => $this->t('Active shards'),
          'relocating_shards' => $this->t('Relocating shards'),
          'initializing_shards' => $this->t('Initializing shards'),
          'unassigned_shards' => $this->t('Unassigned shards'),
          'delayed_unassigned_shards' => $this->t('Delayed unassigned shards'),
          'number_of_pending_tasks' => $this->t('Number of pending tasks'),
          'number_of_in_flight_fetch' => $this->t('Number of in-flight fetch'),
          'task_max_waiting_in_queue_millis' => $this->t(
            'Task max waiting in queue millis'
          ),
        'active_shards_percent_as_number' => t(
          'active_shards_percent_as_number' => $this->t(
            'Active shards percent as number'
          ),
        ];

      foreach ($cluster_status['health'] as $health_key => $health_value) {
        foreach ($health as $health_key => $health_value) {
          if (!isset($cluster_health_mapping[$health_key])) {
            continue;
          }

          $row = [];
          $row[] = ['data' => $cluster_health_mapping[$health_key]];
          $row[] = ['data' => ($health_value === FALSE ? 'False' : $health_value)];
          $cluster_health_rows[] = $row;
        }
      }
    }
    catch (ResponseException | ConnectionException $e) {
      $this->messenger()->addError(
        $this->t('Elasticsearch connection failed due to: @error', [
          '@error' => $e->getMessage(),
        ])
      );
    }

    $output['cluster_statistics_wrapper'] = [
      '#type' => 'fieldset',
      '#title' => t('Cluster statistics'),
      '#title' => $this->t('Cluster statistics'),
      '#collapsible' => TRUE,
      '#collapsed' => FALSE,
      '#attributes' => [],
@@ -193,9 +240,9 @@ class ElasticsearchController extends ControllerBase {
    $output['cluster_statistics_wrapper']['nodes'] = [
      '#theme' => 'table',
      '#header' => [
        ['data' => t('Node name')],
        ['data' => t('Documents')],
        ['data' => t('Size')],
        ['data' => $this->t('Node name')],
        ['data' => $this->t('Documents')],
        ['data' => $this->t('Size')],
      ],
      '#rows' => $node_rows,
      '#attributes' => [],
@@ -204,16 +251,28 @@ class ElasticsearchController extends ControllerBase {
    $output['cluster_statistics_wrapper']['cluster_statistics'] = [
      '#theme' => 'table',
      '#header' => [
        ['data' => t('Total'), 'colspan' => 6],
        ['data' => $this->t('Total'), 'colspan' => 6],
      ],
      '#rows' => $cluster_statistics_rows,
      '#attributes' => ['class' => ['admin-elasticsearch-statistics']],
    ];

    $output['cluster_statistics_wrapper']['cluster_plugins'] = [
      '#theme'      => 'table',
      '#header'     => [
        ['data' => $this->t('Plugin name')],
        ['data' => $this->t('Plugin Version')],
        ['data' => $this->t('Plugin Description')],
        ['data' => $this->t('Node')],
      ],
      '#rows'       => $plugin_rows,
      '#attributes' => [],
    ];

    $output['cluster_health'] = [
      '#theme' => 'table',
      '#header' => [
        ['data' => t('Cluster Health'), 'colspan' => 2],
        ['data' => $this->t('Cluster Health'), 'colspan' => 2],
      ],
      '#rows' => $cluster_health_rows,
      '#attributes' => ['class' => ['admin-elasticsearch-health']],
Loading