cloud-8.x-2.0-beta1/modules/cloud_service_providers/aws_cloud/tests/src/Functional/Ec2/SecurityGroupTest.php

modules/cloud_service_providers/aws_cloud/tests/src/Functional/Ec2/SecurityGroupTest.php
<?php

namespace Drupal\Tests\aws_cloud\Functional\Ec2;

use Drupal\Tests\aws_cloud\Functional\AwsCloudTestCase;
use Drupal\Tests\aws_cloud\Functional\Utils;

/**
 * Tests AWS Cloud Security Group.
 *
 * @group AWS Cloud
 */
class SecurityGroupTest extends AwsCloudTestCase {

  /**
   * {@inheritdoc}
   */
  protected function getPermissions() {
    return [
      'list aws cloud security group',
      'add aws cloud security group',
      'view any aws cloud security group',
      'edit any aws cloud security group',
      'delete any aws cloud security group',
    ];
  }

  /**
   * {@inheritdoc}
   */
  protected function getMockDataTemplateVars() {
    return [
      'vpc_id' => 'vpc-' . $this->getRandomAwsId(),
      'cidr_block' => Utils::getRandomCidr(),
      'group_id' => 'sg-' . $this->getRandomAwsId(),
      'group_name' => $this->random->name(8, TRUE),
    ];
  }

  /**
   * Tests CRUD for Security Group information.
   */
  public function testSecurityGroup() {
    $cloud_context = $this->cloudContext;

    $this->deleteVpcMockData(0);

    // List Security Group for Amazon EC2.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    $this->assertResponse(200, t('List | HTTP 200: Security Group'));
    $this->assertNoText(t('Notice'), t('List | Make sure w/o Notice'));
    $this->assertNoText(t('warning'), t('List | Make sure w/o Warnings'));

    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/add");
    $this->assertText(t('You do not have any VPCs. You need a VPC in order to create a security group. You can create a VPC.'));

    // Add a new Security Group.
    $add = $this->createSecurityGroupTestFormData(self::$awsCloudSecurityGroupRepeatCount);
    $addVpc = $this->createVpcTestFormData(self::$awsCloudSecurityGroupRepeatCount);

    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->reloadMockData();

      $num = $i + 1;

      $this->drupalPostForm("/clouds/aws_cloud/$cloud_context/security_group/add",
          $add[$i],
          t('Save'));

      $this->assertText(t('VPC CIDR (ID) field is required.'), t('VPC CIDR is required test.'));

      $defaults = $this->latestTemplateVars;
      $add[$i]['vpc_id'] = $defaults['vpc_id'];

      // Create VPC.
      $this->addVpcMockData($addVpc[$i], $add[$i]['vpc_id']);

      $this->drupalPostForm("/clouds/aws_cloud/$cloud_context/security_group/add",
                            $add[$i],
                            t('Save'));

      $this->assertResponse(200, t('Add | HTTP 200: The new AWS Cloud Security Group', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Add | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Add | Make sure w/o Warnings'));
      $this->assertText($add[$i]['group_name'], t('Add | Key Pair: @group_name', ['@group_name' => $add[$i]['group_name']]));
      $this->assertText(
        t('The AWS Cloud Security Group "@group_name" has been created.', [
          '@group_name' => $add[$i]['group_name'],
        ]),
        t('Confirm Message: Add | The AWS Cloud Security Group "@group_name" has been created.', [
          '@group_name' => $add[$i]['group_name'],
        ])
      );

      // Make sure listing.
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
      $this->assertResponse(200, t('Add | List | HTTP 200: List | Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Add | List | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Add | List | Make sure w/o Warnings'));
      // 3 times.
      for ($j = 0; $j < $i + 1; $j++) {
        $this->assertText($add[$i]['group_name'],
                        t('Add | List | Make sure w/ Listing: @group_name', [
                          '@group_name' => $add[$i]['group_name'],
                        ]));
      }
    }

    // Security Group doesn't have an edit operation.
    // Edit an Security Group information.
    $edit = $this->createSecurityGroupTestFormData(self::$awsCloudSecurityGroupRepeatCount, TRUE);
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {

      $num = $i + 1;

      unset($edit[$i]['description']);

      // Initialize the mock data. Run security_group update so the data
      // gets imported.
      $this->reloadMockData();

      $this->drupalPostForm("/clouds/aws_cloud/$cloud_context/security_group/$num/edit",
                            $edit[$i],
                            t('Save'));

      $this->assertResponse(200, t('Edit | HTTP 200: The new AWS Cloud Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Edit | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Edit | Make sure w/o Warnings'));
      $this->assertText(
        t('The AWS Cloud Security Group "@name" has been saved.', [
          '@name' => $edit[$i]['name'],
        ]),
        t('Confirm Message: Edit | The AWS Cloud Security Group "@name" has been saved.', [
          '@name' => $edit[$i]['name'],
        ])
      );

      // Make sure listing.
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
      $this->assertResponse(200, t('Edit | List | HTTP 200: Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Edit | List | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Edit | List | Make sure w/o Warnings'));

      $this->assertText($edit[$i]['name'], t('key_pair: @name',
                        ['@name' => $edit[$i]['name']]));
    }

    // Delete Security Group.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $num = $i + 1;
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/$num/delete");
      $this->assertResponse(200, t('Delete | HTTP 200: Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Delete | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Delete | Make sure w/o Warnings'));
      $this->drupalPostForm("/clouds/aws_cloud/$cloud_context/security_group/$num/delete",
                            [],
                            t('Delete'));

      $this->assertResponse(200, t('Delete | HTTP 200: The Cloud Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Delete | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Delete | Make sure w/o Warnings'));
      $this->assertText($add[$i]['group_name'], t('Group Name: @group_name', ['@group_name' => $add[$i]['group_name']]));
      $this->assertText(
        t('The AWS Cloud Security Group "@group_name" has been deleted.', [
          '@group_name' => $add[$i]['group_name'],
        ]),
        t('Confirm Message: Delete | The AWS Cloud Security Group "@group_name" has been deleted.', [
          '@group_name' => $add[$i]['group_name'],
        ])
      );

      // Make sure listing.
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
      $this->assertResponse(200, t('HTTP 200: Delete | Security Group #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Delete | List | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Delete | List | Make sure w/o Warnings'));
    }
  }

  /**
   * Tests deleting security groups with bulk operation.
   */
  public function testSecurityGroupBulk() {
    $cloud_context = $this->cloudContext;

    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      // Create security groups.
      $security_groups = $this->createSecurityGroupRandomTestFormData();
      $index = 0;
      $entities = [];
      foreach ($security_groups as $security_group) {
        $entities[] = $this->createSecurityGroupTestEntity($index++, $security_group['GroupId'], $security_group['Name'], '', $cloud_context);
      }

      $this->doTestEntityBulk('security_group', $entities);
    }
  }

  /**
   * Tests updating security groups.
   */
  public function testUpdateSecurityGroupList() {
    $cloud_context = $this->cloudContext;

    // Delete init mock data.
    $this->deleteFirstSecurityGroupMockData();

    // Add new Security Groups.
    $add = $this->createSecurityGroupTestFormData(self::$awsCloudSecurityGroupRepeatCount);
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $num = $i + 1;
      $this->addSecurityGroupMockData($add[$i]['group_name'], $add[$i]['description']);
    }

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    $this->assertResponse(200, t('Edit | List | HTTP 200: SecurityGroup #@num', ['@num' => $num]));
    $this->assertNoText(t('Notice'), t('Edit | List | Make sure w/o Notice'));
    $this->assertNoText(t('warning'), t('Edit | List | Make sure w/o Warnings'));
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertNoText($add[$i]['group_name'],
        t('Edit | List | Make sure w/ Listing: @name', [
          '@name' => $add[$i]['group_name'],
        ]));
    }

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    $this->assertText(t('Updated Security Groups.'));
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertText($add[$i]['group_name'],
        t('Edit | List | Make sure w/ Listing: @name', [
          '@name' => $add[$i]['group_name'],
        ]));
    }

    // Make sure detailed and edit view.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {

      $num = $i + 1;

      // Confirm the detailed view.
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/$num");
      $this->assertLink(t('Edit'));
      $this->assertLinkByHref("/clouds/aws_cloud/$cloud_context/security_group/$num/edit");
      $this->assertLink(t('Delete'));
      $this->assertLinkByHref("/clouds/aws_cloud/$cloud_context/security_group/$num/delete");
      $this->assertLink(t('List AWS Cloud Security Groups'));
      // Click 'Refresh'.
      $this->clickLink(t('List AWS Cloud Security Groups'));
      $this->assertResponse(200, t('Edit | List | HTTP 200: SecurityGroup #@num', ['@num' => $num]));
      $this->assertNoText(t('Notice'), t('Edit | List | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Edit | List | Make sure w/o Warnings'));

      // Confirm the edit view.
      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/$num/edit");
      $this->assertNoLink(t('Edit'));
      $this->assertLink(t('Delete'));
      $this->assertLinkByHref("/clouds/aws_cloud/$cloud_context/security_group/$num/delete");
      $this->assertNoLink('Edit');
    }

    // Add a new Security Group.
    $num++;
    $data = [
      'description' => "Description #$num - {$this->random->name(64, TRUE)}",
      'group_name' => "group-name-#$num - {$this->random->name(15, TRUE)}",
    ];
    $this->addSecurityGroupMockData($data['group_name'], $data['description']);

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    $this->assertResponse(200, t('Edit | List | HTTP 200: SecurityGroup #@num', ['@num' => $num]));
    $this->assertNoText(t('Notice'), t('Edit | List | Make sure w/o Notice'));
    $this->assertNoText(t('warning'), t('Edit | List | Make sure w/o Warnings'));
    $this->assertNoText($data['group_name'],
      t('Edit | List | Make sure w/ Listing: @name', [
        '@name' => $data['group_name'],
      ]));

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    $this->assertText(t('Updated Security Groups.'));
    $add = array_merge($add, [$data]);
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount + 1; $i++) {
      $this->assertText($add[$i]['group_name'],
        t('Edit | List | Make sure w/ Listing: @name', [
          '@name' => $add[$i]['group_name'],
        ]));
    }

    // Update tags.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {

      $num = $i + 1;

      $add[$i]['tags_name'] = $this->getRandomAwsId();
      $this->updateTagsInMockData($num - 1, 'SecurityGroups', 'Name', $add[$i]['tags_name'], FALSE);
    }

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertNoLink($add[$i]['tags_name']);
    }

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertLink($add[$i]['tags_name']);
    }

    // Update tags for empty.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {

      $num = $i + 1;

      // Update tags.
      $this->updateTagsInMockData($num - 1, 'SecurityGroups', 'Name', '', FALSE);
    }

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertLink($add[$i]['tags_name']);
    }

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertNoLink($add[$i]['tags_name']);
      $this->assertLink($add[$i]['group_name']);
    }

    // Delete name tags.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {

      $num = $i + 1;

      // Update tags.
      $this->updateTagsInMockData($num - 1, 'SecurityGroups', 'Name', '', TRUE);
    }

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertNoLink($add[$i]['tags_name']);
      $this->assertLink($add[$i]['group_name']);
    }

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $this->assertNoLink($add[$i]['tags_name']);
      $this->assertLink($add[$i]['group_name']);
    }

    // Delete SecurityGroup in mock data.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount + 1; $i++) {
      $this->deleteFirstSecurityGroupMockData();
    }

    // Make sure listing.
    $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
    $this->assertResponse(200, t('Edit | List | HTTP 200: SecurityGroup #@num', ['@num' => $num + 1]));
    $this->assertNoText(t('Notice'), t('Edit | List | Make sure w/o Notice'));
    $this->assertNoText(t('warning'), t('Edit | List | Make sure w/o Warnings'));
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount + 1; $i++) {
      $this->assertText($add[$i]['group_name'],
        t('Edit | List | Make sure w/ Listing: @name', [
          '@name' => $add[$i]['group_name'],
        ]));
    }

    // Click 'Refresh'.
    $this->clickLink(t('Refresh'));
    $this->assertText(t('Updated Security Groups.'));
    // Make sure listing.
    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount + 1; $i++) {
      $this->assertNoText($add[$i]['group_name'],
        t('Edit | List | Make sure w/ Listing: @name', [
          '@name' => $add[$i]['group_name'],
        ]));
    }

  }

  /**
   * Test for copying security groups.
   */
  public function testSecurityGroupCopy() {
    $this->repeatTestSecurityGroupCopy(self::$awsCloudSecurityGroupRepeatCount);
  }

  /**
   * Test for copying security groups.
   *
   * @param int $max_test_repeat_count
   *   Max test repeating count.
   */
  private function repeatTestSecurityGroupCopy($max_test_repeat_count = 1) {
    $cloud_context = $this->cloudContext;

    // Delete init mock data.
    $this->deleteFirstSecurityGroupMockData();

    $add = $this->createSecurityGroupTestFormData($max_test_repeat_count);
    $addVpc = $this->createVpcTestFormData($max_test_repeat_count);
    $copy = $this->createSecurityGroupTestFormData($max_test_repeat_count);

    $num = 1;
    for ($i = 0; $i < $max_test_repeat_count; $i++) {

      $defaults = $this->getMockDataTemplateVars();
      $add[$i]['vpc_id'] = $defaults['vpc_id'];

      // Create VPC.
      $this->addVpcMockData($addVpc[$i], $add[$i]['vpc_id']);
      $vpc = $this->createVpcTestEntity($i, $add[$i]['vpc_id'], $addVpc[$i]['name'], $cloud_context);
      $vpc->setCidrBlock($addVpc[$i]['cidr_block']);
      $vpc->save();

      // Add mock data.
      $add[$i]['group_id'] = $this->addSecurityGroupMockData($add[$i]['group_name'], $add[$i]['description'], $add[$i]['vpc_id']);
      // Create entity.
      $security_group = $this->createSecurityGroupTestEntity($i, $add[$i]['group_id'], $add[$i]['group_name'], $add[$i]['vpc_id'], $cloud_context);
      $security_group->set('description', $add[$i]['description']);
      $security_group->save();

      $edit_url = "/clouds/aws_cloud/$cloud_context/security_group/$num/edit";

      // Create rules.
      $add[$i]['rules'] = $this->createRulesTestFormData(self::$awsCloudSecurityGroupRulesMix, $edit_url, $add[$i]['group_id'], self::$awsCloudSecurityGroupRulesRepeatCount);

      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/$num");
      $this->assertLink(t('Copy'));
      $this->assertLinkByHref("/clouds/aws_cloud/$cloud_context/security_group/$num/copy");

      // Click 'Copy'.
      $this->clickLink(t('Copy'));
      $this->assertText('Copy AWS Cloud Security Group');
      $this->assertFieldByName('group_name', "Copy of {$add[$i]['group_name']}");

      $copy[$i]['vpc_id'] = $add[$i]['vpc_id'];

      $params = $this->createRuleParams($add[$i]['rules'], $add[$i]['group_id']);

      $copy[$i] = array_merge($copy[$i], $params);

      $this->drupalPostForm("/clouds/aws_cloud/$cloud_context/security_group/$num/copy",
        $copy[$i],
        t('Copy'));

      $this->assertNoText(t('Notice'), t('Copy | Make sure w/o Notice'));
      $this->assertNoText(t('warning'), t('Copy | Make sure w/o Warnings'));
      $this->assertText($copy[$i]['group_name'], t('Copy | Security Group: @group_name', ['@group_name' => $copy[$i]['group_name']]));
      $this->assertText(
        t('The AWS Cloud Security Group "@group_name" has been created.', [
          '@group_name' => $copy[$i]['group_name'],
        ]),
        t('Confirm Message: Copy | The AWS Cloud Security Group "@group_name" has been created.', [
          '@group_name' => $copy[$i]['group_name'],
        ])
          );

      foreach ($params as $key => $value) {
        if (strpos($key, 'source') === FALSE) {
          $this->assertText($value);
        }
      }

      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group");
      $this->assertText($copy[$i]['group_name']);

      $num += 2;
    }

  }

  /**
   * Test for copying security groups without vpc.
   */
  public function testSecurityGroupCopyNoVpc() {
    $cloud_context = $this->cloudContext;

    // Delete init mock data.
    $this->deleteFirstSecurityGroupMockData();
    $this->deleteVpcMockData(0);

    $add = $this->createSecurityGroupTestFormData(self::$awsCloudSecurityGroupRepeatCount);

    for ($i = 0; $i < self::$awsCloudSecurityGroupRepeatCount; $i++) {
      $num = $i + 1;

      // Add mock data.
      $add[$i]['group_id'] = $this->addSecurityGroupMockData($add[$i]['group_name'], $add[$i]['description']);
      // Create entity.
      $security_group = $this->createSecurityGroupTestEntity($i, $add[$i]['group_id'], $add[$i]['group_name'], '', $cloud_context);
      $security_group->set('description', $add[$i]['description']);
      $security_group->save();

      $this->drupalGet("/clouds/aws_cloud/$cloud_context/security_group/$num/copy");
      $this->assertText(t('You do not have any VPCs. You need a VPC in order to create a security group. You can create a VPC.'));
    }

  }

  /**
   * Create rule parameters.
   *
   * @param array $rules
   *   The array of rules.
   * @param string $source_group_id
   *   The group ID of copied security group.
   *
   * @return array
   *   The params created.
   */
  private function createRuleParams(array $rules, $source_group_id) {
    $params = [];
    $inbound_index = 0;
    $outbound_index = 0;

    foreach ($rules as $rule) {
      $perm = $this->getRulePermission($rule['source'], $rule['type'], $source_group_id);
      unset($perm['source']);
      unset($perm['type']);
      $keys = array_rand($perm, rand(1, count($perm) - 1));
      if (is_array($keys)) {
        foreach ($keys as $key) {
          $rule[$key] = $perm[$key];
        }
      }
      else {
        $rule[$keys] = $perm[$keys];
      }
    }

    foreach ($rules as $rule) {
      if ($rule['type'] == self::$awsCloudSecurityGroupRulesInbound) {
        $index = $inbound_index++;
        $prefix = 'ip_permission';
      }
      else {
        $index = $outbound_index++;
        $prefix = 'outbound_permission';
      }
      foreach ($rule as $key => $value) {
        if ($key == 'type') {
          continue;
        }
        $params["${prefix}[${index}][${key}]"] = $value;
      }
    }

    return $params;
  }

  /**
   * Get random rule permission.
   *
   * @param string $source
   *   The permission source.
   * @param string $type
   *   The permission type.
   * @param string $group_id
   *   The group ID.
   *
   * @return array
   *   The array of rule permission.
   */
  private function getRulePermission($source = NULL, $type = 0, $group_id = NULL) {
    $type = $type ?: self::$awsCloudSecurityGroupRulesInbound;
    if (!isset($source)) {
      $idx = rand(0, 2);
      $source = ['ip4', 'ip6', 'group'][$idx];
    }
    if (!isset($group_id)) {
      $group_id = 'sg-' . $this->getRandomAwsId();
    }
    $permissions = [
      'ip4' => [
        'type' => $type,
        'source' => 'ip4',
        'cidr_ip' => Utils::getRandomCidr(),
        'from_port' => Utils::getRandomFromPort(),
        'to_port' => Utils::getRandomToPort(),
      ],
      'ip6' => [
        'type' => $type,
        'source' => 'ip6',
        'cidr_ip_v6' => Utils::getRandomCidrV6(),
        'from_port' => Utils::getRandomFromPort(),
        'to_port' => Utils::getRandomToPort(),
      ],
      'group' => [
        'type' => $type,
        'source' => 'group',
        'user_id' => $this->random->name(8, TRUE),
        'group_id' => $group_id,
        'vpc_id' => 'vpc-' . $this->getRandomAwsId(),
        'peering_connection_id' => 'pcx-' . $this->getRandomAwsId(),
        'peering_status' => 'active',
        'from_port' => Utils::getRandomFromPort(),
        'to_port' => Utils::getRandomToPort(),
      ],
    ];
    return $permissions[$source];
  }

}

Главная | Обратная связь

drupal hosting | друпал хостинг | it patrol .inc