summaryrefslogtreecommitdiff
path: root/spec/services
diff options
context:
space:
mode:
authorGitLab Bot <gitlab-bot@gitlab.com>2019-11-01 03:06:26 +0000
committerGitLab Bot <gitlab-bot@gitlab.com>2019-11-01 03:06:26 +0000
commit56d96ad7fab4d4b95f5529d8080b3cc2873794a0 (patch)
tree7fe93fc8ff4d82d815000781ffb9c98d7259211a /spec/services
parent8078bd185fd9fce86cb5a8d9a6b6209e0c23ae44 (diff)
downloadgitlab-ce-56d96ad7fab4d4b95f5529d8080b3cc2873794a0.tar.gz
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/services')
-rw-r--r--spec/services/clusters/aws/fetch_credentials_service_spec.rb65
-rw-r--r--spec/services/clusters/aws/finalize_creation_service_spec.rb124
-rw-r--r--spec/services/clusters/aws/provision_service_spec.rb138
-rw-r--r--spec/services/clusters/aws/verify_provision_status_service_spec.rb76
4 files changed, 403 insertions, 0 deletions
diff --git a/spec/services/clusters/aws/fetch_credentials_service_spec.rb b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
new file mode 100644
index 00000000000..6476130ab32
--- /dev/null
+++ b/spec/services/clusters/aws/fetch_credentials_service_spec.rb
@@ -0,0 +1,65 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Aws::FetchCredentialsService do
+ describe '#execute' do
+ let(:provider) { create(:cluster_provider_aws) }
+
+ let(:gitlab_access_key_id) { 'gitlab-access-key-id' }
+ let(:gitlab_secret_access_key) { 'gitlab-secret-access-key' }
+
+ let(:gitlab_credentials) { Aws::Credentials.new(gitlab_access_key_id, gitlab_secret_access_key) }
+ let(:sts_client) { Aws::STS::Client.new(credentials: gitlab_credentials, region: provider.region) }
+ let(:assumed_role) { instance_double(Aws::AssumeRoleCredentials, credentials: assumed_role_credentials) }
+
+ let(:kubernetes_provisioner_settings) do
+ {
+ aws: {
+ access_key_id: gitlab_access_key_id,
+ secret_access_key: gitlab_secret_access_key
+ }
+ }
+ end
+
+ let(:assumed_role_credentials) { double }
+
+ subject { described_class.new(provider).execute }
+
+ context 'provision role is configured' do
+ let(:provision_role) { create(:aws_role, user: provider.created_by_user) }
+
+ before do
+ stub_config(kubernetes: { provisioners: kubernetes_provisioner_settings })
+
+ expect(Aws::Credentials).to receive(:new)
+ .with(gitlab_access_key_id, gitlab_secret_access_key)
+ .and_return(gitlab_credentials)
+
+ expect(Aws::STS::Client).to receive(:new)
+ .with(credentials: gitlab_credentials, region: provider.region)
+ .and_return(sts_client)
+
+ expect(Aws::AssumeRoleCredentials).to receive(:new)
+ .with(
+ client: sts_client,
+ role_arn: provision_role.role_arn,
+ role_session_name: "gitlab-eks-cluster-#{provider.cluster_id}-user-#{provider.created_by_user_id}",
+ external_id: provision_role.role_external_id
+ ).and_return(assumed_role)
+ end
+
+ it { is_expected.to eq assumed_role_credentials }
+ end
+
+ context 'provision role is not configured' do
+ before do
+ expect(provider.created_by_user.aws_role).to be_nil
+ end
+
+ it 'raises an error' do
+ expect { subject }.to raise_error(described_class::MissingRoleError, 'AWS provisioning role not configured')
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/aws/finalize_creation_service_spec.rb b/spec/services/clusters/aws/finalize_creation_service_spec.rb
new file mode 100644
index 00000000000..8d7341483e3
--- /dev/null
+++ b/spec/services/clusters/aws/finalize_creation_service_spec.rb
@@ -0,0 +1,124 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Aws::FinalizeCreationService do
+ describe '#execute' do
+ let(:provider) { create(:cluster_provider_aws, :creating) }
+ let(:platform) { provider.cluster.platform_kubernetes }
+
+ let(:create_service_account_service) { double(execute: true) }
+ let(:fetch_token_service) { double(execute: gitlab_token) }
+ let(:kube_client) { double(create_config_map: true) }
+ let(:cluster_stack) { double(outputs: [endpoint_output, cert_output, node_role_output]) }
+ let(:node_auth_config_map) { double }
+
+ let(:endpoint_output) { double(output_key: 'ClusterEndpoint', output_value: api_url) }
+ let(:cert_output) { double(output_key: 'ClusterCertificate', output_value: Base64.encode64(ca_pem)) }
+ let(:node_role_output) { double(output_key: 'NodeInstanceRole', output_value: node_role) }
+
+ let(:api_url) { 'https://kubernetes.example.com' }
+ let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
+ let(:gitlab_token) { 'gitlab-token' }
+ let(:iam_token) { 'iam-token' }
+ let(:node_role) { 'arn::aws::iam::123456789012:role/node-role' }
+
+ subject { described_class.new.execute(provider) }
+
+ before do
+ allow(Clusters::Kubernetes::CreateOrUpdateServiceAccountService).to receive(:gitlab_creator)
+ .with(kube_client, rbac: true)
+ .and_return(create_service_account_service)
+
+ allow(Clusters::Kubernetes::FetchKubernetesTokenService).to receive(:new)
+ .with(
+ kube_client,
+ Clusters::Kubernetes::GITLAB_ADMIN_TOKEN_NAME,
+ Clusters::Kubernetes::GITLAB_SERVICE_ACCOUNT_NAMESPACE)
+ .and_return(fetch_token_service)
+
+ allow(Gitlab::Kubernetes::KubeClient).to receive(:new)
+ .with(
+ api_url,
+ auth_options: { bearer_token: iam_token },
+ ssl_options: {
+ verify_ssl: OpenSSL::SSL::VERIFY_PEER,
+ cert_store: instance_of(OpenSSL::X509::Store)
+ },
+ http_proxy_uri: nil
+ )
+ .and_return(kube_client)
+
+ allow(provider.api_client).to receive(:describe_stacks)
+ .with(stack_name: provider.cluster.name)
+ .and_return(double(stacks: [cluster_stack]))
+
+ allow(Kubeclient::AmazonEksCredentials).to receive(:token)
+ .with(provider.credentials, provider.cluster.name)
+ .and_return(iam_token)
+
+ allow(Gitlab::Kubernetes::ConfigMaps::AwsNodeAuth).to receive(:new)
+ .with(node_role).and_return(double(generate: node_auth_config_map))
+ end
+
+ it 'configures the provider and platform' do
+ subject
+
+ expect(provider).to be_created
+ expect(platform.api_url).to eq(api_url)
+ expect(platform.ca_pem).to eq(ca_pem)
+ expect(platform.token).to eq(gitlab_token)
+ expect(platform).to be_rbac
+ end
+
+ it 'calls the create_service_account_service' do
+ expect(create_service_account_service).to receive(:execute).once
+
+ subject
+ end
+
+ it 'configures cluster node authentication' do
+ expect(kube_client).to receive(:create_config_map).with(node_auth_config_map).once
+
+ subject
+ end
+
+ describe 'error handling' do
+ shared_examples 'provision error' do |message|
+ it "sets the status to :errored with an appropriate error message" do
+ subject
+
+ expect(provider).to be_errored
+ expect(provider.status_reason).to include(message)
+ end
+ end
+
+ context 'failed to request stack details from AWS' do
+ before do
+ allow(provider.api_client).to receive(:describe_stacks)
+ .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, "Error message"))
+ end
+
+ include_examples 'provision error', 'Failed to fetch CloudFormation stack'
+ end
+
+ context 'failed to create auth config map' do
+ before do
+ allow(kube_client).to receive(:create_config_map)
+ .and_raise(Kubeclient::HttpError.new(500, 'Error', nil))
+ end
+
+ include_examples 'provision error', 'Failed to run Kubeclient'
+ end
+
+ context 'failed to save records' do
+ before do
+ allow(provider.cluster).to receive(:save!)
+ .and_raise(ActiveRecord::RecordInvalid)
+ end
+
+ include_examples 'provision error', 'Failed to configure EKS provider'
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/aws/provision_service_spec.rb b/spec/services/clusters/aws/provision_service_spec.rb
new file mode 100644
index 00000000000..5c044b8732e
--- /dev/null
+++ b/spec/services/clusters/aws/provision_service_spec.rb
@@ -0,0 +1,138 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Aws::ProvisionService do
+ describe '#execute' do
+ let(:provider) { create(:cluster_provider_aws) }
+
+ let(:client) { instance_double(Aws::CloudFormation::Client, create_stack: true) }
+ let(:cloudformation_template) { double }
+ let(:credentials) do
+ instance_double(
+ Aws::Credentials,
+ access_key_id: 'key',
+ secret_access_key: 'secret',
+ session_token: 'token'
+ )
+ end
+
+ let(:parameters) do
+ [
+ { parameter_key: 'ClusterName', parameter_value: provider.cluster.name },
+ { parameter_key: 'ClusterRole', parameter_value: provider.role_arn },
+ { parameter_key: 'ClusterControlPlaneSecurityGroup', parameter_value: provider.security_group_id },
+ { parameter_key: 'VpcId', parameter_value: provider.vpc_id },
+ { parameter_key: 'Subnets', parameter_value: provider.subnet_ids.join(',') },
+ { parameter_key: 'NodeAutoScalingGroupDesiredCapacity', parameter_value: provider.num_nodes.to_s },
+ { parameter_key: 'NodeInstanceType', parameter_value: provider.instance_type },
+ { parameter_key: 'KeyName', parameter_value: provider.key_name }
+ ]
+ end
+
+ subject { described_class.new.execute(provider) }
+
+ before do
+ allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
+ .with(provider).and_return(double(execute: credentials))
+
+ allow(provider).to receive(:api_client)
+ .and_return(client)
+
+ allow(File).to receive(:read)
+ .with(Rails.root.join('vendor', 'aws', 'cloudformation', 'eks_cluster.yaml'))
+ .and_return(cloudformation_template)
+ end
+
+ it 'updates the provider status to :creating and configures the provider with credentials' do
+ subject
+
+ expect(provider).to be_creating
+ expect(provider.access_key_id).to eq 'key'
+ expect(provider.secret_access_key).to eq 'secret'
+ expect(provider.session_token).to eq 'token'
+ end
+
+ it 'creates a CloudFormation stack' do
+ expect(client).to receive(:create_stack).with(
+ stack_name: provider.cluster.name,
+ template_body: cloudformation_template,
+ parameters: parameters,
+ capabilities: ["CAPABILITY_IAM"]
+ )
+
+ subject
+ end
+
+ it 'schedules a worker to monitor creation status' do
+ expect(WaitForClusterCreationWorker).to receive(:perform_in)
+ .with(Clusters::Aws::VerifyProvisionStatusService::INITIAL_INTERVAL, provider.cluster_id)
+
+ subject
+ end
+
+ describe 'error handling' do
+ shared_examples 'provision error' do |message|
+ it "sets the status to :errored with an appropriate error message" do
+ subject
+
+ expect(provider).to be_errored
+ expect(provider.status_reason).to include(message)
+ end
+ end
+
+ context 'invalid state transition' do
+ before do
+ allow(provider).to receive(:make_creating).and_return(false)
+ end
+
+ include_examples 'provision error', 'Failed to update provider record'
+ end
+
+ context 'AWS role is not configured' do
+ before do
+ allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
+ .and_raise(Clusters::Aws::FetchCredentialsService::MissingRoleError)
+ end
+
+ include_examples 'provision error', 'Amazon role is not configured'
+ end
+
+ context 'AWS credentials are not configured' do
+ before do
+ allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
+ .and_raise(Aws::Errors::MissingCredentialsError)
+ end
+
+ include_examples 'provision error', 'Amazon credentials are not configured'
+ end
+
+ context 'AWS credentials are not configured' do
+ before do
+ allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
+ .and_raise(Settingslogic::MissingSetting)
+ end
+
+ include_examples 'provision error', 'Amazon credentials are not configured'
+ end
+
+ context 'Authentication failure' do
+ before do
+ allow(Clusters::Aws::FetchCredentialsService).to receive(:new)
+ .and_raise(Aws::STS::Errors::ServiceError.new(double, 'Error message'))
+ end
+
+ include_examples 'provision error', 'Amazon authentication failed'
+ end
+
+ context 'CloudFormation failure' do
+ before do
+ allow(client).to receive(:create_stack)
+ .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, 'Error message'))
+ end
+
+ include_examples 'provision error', 'Amazon CloudFormation request failed'
+ end
+ end
+ end
+end
diff --git a/spec/services/clusters/aws/verify_provision_status_service_spec.rb b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
new file mode 100644
index 00000000000..b62b0875bf3
--- /dev/null
+++ b/spec/services/clusters/aws/verify_provision_status_service_spec.rb
@@ -0,0 +1,76 @@
+# frozen_string_literal: true
+
+require 'spec_helper'
+
+describe Clusters::Aws::VerifyProvisionStatusService do
+ describe '#execute' do
+ let(:provider) { create(:cluster_provider_aws) }
+
+ let(:stack) { double(stack_status: stack_status, creation_time: creation_time) }
+ let(:creation_time) { 1.minute.ago }
+
+ subject { described_class.new.execute(provider) }
+
+ before do
+ allow(provider.api_client).to receive(:describe_stacks)
+ .with(stack_name: provider.cluster.name)
+ .and_return(double(stacks: [stack]))
+ end
+
+ shared_examples 'provision error' do |message|
+ it "sets the status to :errored with an appropriate error message" do
+ subject
+
+ expect(provider).to be_errored
+ expect(provider.status_reason).to include(message)
+ end
+ end
+
+ context 'stack creation is still in progress' do
+ let(:stack_status) { 'CREATE_IN_PROGRESS' }
+ let(:verify_service) { double(execute: true) }
+
+ it 'schedules a worker to check again later' do
+ expect(WaitForClusterCreationWorker).to receive(:perform_in)
+ .with(described_class::POLL_INTERVAL, provider.cluster_id)
+
+ subject
+ end
+
+ context 'stack creation is taking too long' do
+ let(:creation_time) { 1.hour.ago }
+
+ include_examples 'provision error', 'Kubernetes cluster creation time exceeds timeout'
+ end
+ end
+
+ context 'stack creation is complete' do
+ let(:stack_status) { 'CREATE_COMPLETE' }
+ let(:finalize_service) { double(execute: true) }
+
+ it 'finalizes creation' do
+ expect(Clusters::Aws::FinalizeCreationService).to receive(:new).and_return(finalize_service)
+ expect(finalize_service).to receive(:execute).with(provider).once
+
+ subject
+ end
+ end
+
+ context 'stack creation failed' do
+ let(:stack_status) { 'CREATE_FAILED' }
+
+ include_examples 'provision error', 'Unexpected status'
+ end
+
+ context 'error communicating with CloudFormation API' do
+ let(:stack_status) { 'CREATE_IN_PROGRESS' }
+
+ before do
+ allow(provider.api_client).to receive(:describe_stacks)
+ .and_raise(Aws::CloudFormation::Errors::ServiceError.new(double, 'Error message'))
+ end
+
+ include_examples 'provision error', 'Amazon CloudFormation request failed'
+ end
+ end
+end