diff options
author | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-01 03:06:26 +0000 |
---|---|---|
committer | GitLab Bot <gitlab-bot@gitlab.com> | 2019-11-01 03:06:26 +0000 |
commit | 56d96ad7fab4d4b95f5529d8080b3cc2873794a0 (patch) | |
tree | 7fe93fc8ff4d82d815000781ffb9c98d7259211a /spec/services | |
parent | 8078bd185fd9fce86cb5a8d9a6b6209e0c23ae44 (diff) | |
download | gitlab-ce-56d96ad7fab4d4b95f5529d8080b3cc2873794a0.tar.gz |
Add latest changes from gitlab-org/gitlab@master
Diffstat (limited to 'spec/services')
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 |