summaryrefslogtreecommitdiff
path: root/spec/models/clusters
diff options
context:
space:
mode:
authorShinya Maeda <shinya@gitlab.com>2017-10-30 03:48:45 +0900
committerShinya Maeda <shinya@gitlab.com>2017-10-30 03:48:45 +0900
commit478e59fe8d82b99800a2613aa4d153bf692fbd6b (patch)
tree5f734aee006c7cfee86c8151e3b6f94846b15299 /spec/models/clusters
parentd0cff7f5855f91b5479f9fdaa39d8d95ec691a9e (diff)
downloadgitlab-ce-478e59fe8d82b99800a2613aa4d153bf692fbd6b.tar.gz
specs for models. Improved details.
Diffstat (limited to 'spec/models/clusters')
-rw-r--r--spec/models/clusters/cluster_spec.rb180
-rw-r--r--spec/models/clusters/platforms/kubernetes_spec.rb304
-rw-r--r--spec/models/clusters/project_spec.rb6
-rw-r--r--spec/models/clusters/providers/gcp_spec.rb183
4 files changed, 673 insertions, 0 deletions
diff --git a/spec/models/clusters/cluster_spec.rb b/spec/models/clusters/cluster_spec.rb
new file mode 100644
index 00000000000..e53ce8497f5
--- /dev/null
+++ b/spec/models/clusters/cluster_spec.rb
@@ -0,0 +1,180 @@
+require 'spec_helper'
+
+describe Clusters::Cluster do
+ it { is_expected.to belong_to(:user) }
+ it { is_expected.to have_many(:projects) }
+ it { is_expected.to have_one(:provider_gcp) }
+ it { is_expected.to have_one(:platform_kubernetes) }
+ it { is_expected.to delegate_method(:status).to(:provider) }
+ it { is_expected.to delegate_method(:status_reason).to(:provider) }
+ it { is_expected.to delegate_method(:status_name).to(:provider) }
+ it { is_expected.to delegate_method(:on_creation?).to(:provider) }
+ it { is_expected.to respond_to :project }
+
+ describe '.enabled' do
+ subject { described_class.enabled }
+
+ let!(:cluster) { create(:cluster, enabled: true) }
+
+ before do
+ create(:cluster, enabled: false)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
+ describe '.disabled' do
+ subject { described_class.disabled }
+
+ let!(:cluster) { create(:cluster, enabled: false) }
+
+ before do
+ create(:cluster, enabled: true)
+ end
+
+ it { is_expected.to contain_exactly(cluster) }
+ end
+
+ describe 'validation' do
+ subject { cluster.valid? }
+
+ context 'when validates name' do
+ context 'when provided by user' do
+ let!(:cluster) { build(:cluster, :provided_by_user, name: name) }
+
+ context 'when name is empty' do
+ let(:name) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name is nil' do
+ let(:name) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name is present' do
+ let(:name) { 'cluster-name-1' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when provided by gcp' do
+ let!(:cluster) { build(:cluster, :provided_by_gcp, name: name) }
+
+ context 'when name is shorter than 1' do
+ let(:name) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name is longer than 63' do
+ let(:name) { 'a' * 64 }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name includes invalid character' do
+ let(:name) { '!!!!!!' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name is present' do
+ let(:name) { 'cluster-name-1' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when record is persisted' do
+ let(:name) { 'cluster-name-1' }
+
+ before do
+ cluster.save!
+ end
+
+ context 'when name is changed' do
+ before do
+ cluster.name = 'new-cluster-name'
+ end
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when name is same' do
+ before do
+ cluster.name = name
+ end
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+ end
+
+ context 'when validates restrict_modification' do
+ context 'when creation is on going' do
+ let!(:cluster) { create(:cluster, :providing_by_gcp) }
+
+ it { expect(cluster.update(enabled: false)).to be_falsey }
+ end
+
+ context 'when creation is done' do
+ let!(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ it { expect(cluster.update(enabled: false)).to be_truthy }
+ end
+ end
+ end
+
+ describe '#provider' do
+ subject { cluster.provider }
+
+ context 'when provider is gcp' do
+ let(:cluster) { create(:cluster, :provided_by_gcp) }
+
+ it 'returns a provider' do
+ is_expected.to eq(cluster.provider_gcp)
+ expect(subject.class.name.deconstantize).to eq(Clusters::Providers.to_s)
+ end
+ end
+
+ context 'when provider is user' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#platform' do
+ subject { cluster.platform }
+
+ context 'when platform is kubernetes' do
+ let(:cluster) { create(:cluster, :provided_by_user) }
+
+ it 'returns a platform' do
+ is_expected.to eq(cluster.platform_kubernetes)
+ expect(subject.class.name.deconstantize).to eq(Clusters::Platforms.to_s)
+ end
+ end
+ end
+
+ describe '#first_project' do
+ subject { cluster.first_project }
+
+ context 'when cluster belongs to a project' do
+ let(:cluster) { create(:cluster, :project) }
+ let(:project) { Clusters::Project.find_by_cluster_id(cluster.id).project }
+
+ it { is_expected.to eq(project) }
+ end
+
+ context 'when cluster does not belong to projects' do
+ let(:cluster) { create(:cluster) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end
diff --git a/spec/models/clusters/platforms/kubernetes_spec.rb b/spec/models/clusters/platforms/kubernetes_spec.rb
new file mode 100644
index 00000000000..ec6ecee6ff2
--- /dev/null
+++ b/spec/models/clusters/platforms/kubernetes_spec.rb
@@ -0,0 +1,304 @@
+require 'spec_helper'
+
+describe Clusters::Platforms::Kubernetes, :use_clean_rails_memory_store_caching do
+ include KubernetesHelpers
+ include ReactiveCachingHelpers
+
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to be_kind_of(Gitlab::Kubernetes) }
+ it { is_expected.to be_kind_of(ReactiveCaching) }
+ it { is_expected.to respond_to :ca_pem }
+
+ describe 'before_validation' do
+ context 'when namespace includes upper case' do
+ let(:kubernetes) { create(:platform_kubernetes, namespace: namespace) }
+ let(:namespace) { 'ABC' }
+
+ it 'converts to lower case' do
+ expect(kubernetes.namespace).to eq('abc')
+ end
+ end
+ end
+
+ describe 'validation' do
+ subject { kubernetes.valid? }
+
+ context 'when validates namespace' do
+ let(:kubernetes) { build(:platform_kubernetes, namespace: namespace) }
+
+ context 'when namespace is blank' do
+ let(:namespace) { '' }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when namespace is longer than 63' do
+ let(:namespace) { 'a' * 64 }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when namespace includes invalid character' do
+ let(:namespace) { '!!!!!!' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when namespace is vaild' do
+ let(:namespace) { 'namespace-123' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when validates api_url' do
+ context 'when updates a record' do
+ let(:kubernetes) { create(:platform_kubernetes) }
+
+ before do
+ kubernetes.api_url = api_url
+ end
+
+ context 'when api_url is invalid url' do
+ let(:api_url) { '!!!!!!' }
+
+ it { expect(kubernetes.save).to be_falsey }
+ end
+
+ context 'when api_url is nil' do
+ let(:api_url) { nil }
+
+ it { expect(kubernetes.save).to be_falsey }
+ end
+
+ context 'when api_url is valid url' do
+ let(:api_url) { 'https://111.111.111.111' }
+
+ it { expect(kubernetes.save).to be_truthy }
+ end
+ end
+
+ context 'when creates a record' do
+ let(:kubernetes) { build(:platform_kubernetes) }
+
+ before do
+ kubernetes.api_url = api_url
+ end
+
+ context 'when api_url is nil' do
+ let(:api_url) { nil }
+
+ it { expect(kubernetes.save).to be_truthy }
+ end
+ end
+ end
+
+ context 'when validates token' do
+ context 'when updates a record' do
+ let(:kubernetes) { create(:platform_kubernetes) }
+
+ before do
+ kubernetes.token = token
+ end
+
+ context 'when token is nil' do
+ let(:token) { nil }
+
+ it { expect(kubernetes.save).to be_falsey }
+ end
+ end
+
+ context 'when creates a record' do
+ let(:kubernetes) { build(:platform_kubernetes) }
+
+ before do
+ kubernetes.token = token
+ end
+
+ context 'when token is nil' do
+ let(:token) { nil }
+
+ it { expect(kubernetes.save).to be_truthy }
+ end
+ end
+ end
+ end
+
+ describe '#actual_namespace' do
+ subject { kubernetes.actual_namespace }
+
+ let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+ let(:kubernetes) { create(:platform_kubernetes, namespace: namespace) }
+
+ context 'when namespace is present' do
+ let(:namespace) { 'namespace-123' }
+
+ it { is_expected.to eq(namespace) }
+ end
+
+ context 'when namespace is not present' do
+ let(:namespace) { nil }
+
+ it { is_expected.to eq("#{cluster.project.path}-#{cluster.project.id}") }
+ end
+ end
+
+ describe '.namespace_for_project' do
+ subject { described_class.namespace_for_project(project) }
+
+ let(:project) { create(:project) }
+
+ it { is_expected.to eq("#{project.path}-#{project.id}") }
+ end
+
+ describe '#default_namespace' do
+ subject { kubernetes.default_namespace }
+
+ let(:kubernetes) { create(:platform_kubernetes) }
+
+ context 'when cluster belongs to a project' do
+ let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+
+ it { is_expected.to eq("#{cluster.project.path}-#{cluster.project.id}") }
+ end
+
+ context 'when cluster belongs to nothing' do
+ let!(:cluster) { create(:cluster, platform_kubernetes: kubernetes) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+
+ describe '#predefined_variables' do
+ let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
+ let(:kubernetes) { create(:platform_kubernetes, api_url: api_url, ca_cert: ca_pem, token: token) }
+ let(:api_url) { 'https://kube.domain.com' }
+ let(:ca_pem) { 'CA PEM DATA' }
+ let(:token) { 'token' }
+
+ let(:kubeconfig) do
+ config_file = expand_fixture_path('config/kubeconfig.yml')
+ config = YAML.load(File.read(config_file))
+ config.dig('users', 0, 'user')['token'] = token
+ config.dig('contexts', 0, 'context')['namespace'] = namespace
+ config.dig('clusters', 0, 'cluster')['certificate-authority-data'] =
+ Base64.strict_encode64(ca_pem)
+
+ YAML.dump(config)
+ end
+
+ shared_examples 'setting variables' do
+ it 'sets the variables' do
+ expect(kubernetes.predefined_variables).to include(
+ { key: 'KUBE_URL', value: api_url, public: true },
+ { key: 'KUBE_TOKEN', value: token, public: false },
+ { key: 'KUBE_NAMESPACE', value: namespace, public: true },
+ { key: 'KUBECONFIG', value: kubeconfig, public: false, file: true },
+ { key: 'KUBE_CA_PEM', value: ca_pem, public: true },
+ { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
+ )
+ end
+ end
+
+ context 'namespace is provided' do
+ let(:namespace) { 'my-project' }
+
+ before do
+ kubernetes.namespace = namespace
+ end
+
+ it_behaves_like 'setting variables'
+ end
+
+ context 'no namespace provided' do
+ let(:namespace) { kubernetes.actual_namespace }
+
+ it_behaves_like 'setting variables'
+
+ it 'sets the KUBE_NAMESPACE' do
+ kube_namespace = kubernetes.predefined_variables.find { |h| h[:key] == 'KUBE_NAMESPACE' }
+
+ expect(kube_namespace).not_to be_nil
+ expect(kube_namespace[:value]).to match(/\A#{Gitlab::PathRegex::PATH_REGEX_STR}-\d+\z/)
+ end
+ end
+ end
+
+ describe '#terminals' do
+ subject { service.terminals(environment) }
+
+ let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
+ let(:project) { cluster.project }
+ let(:service) { create(:platform_kubernetes) }
+ let(:environment) { build(:environment, project: project, name: "env", slug: "env-000000") }
+
+ context 'with invalid pods' do
+ it 'returns no terminals' do
+ stub_reactive_cache(service, pods: [{ "bad" => "pod" }])
+
+ is_expected.to be_empty
+ end
+ end
+
+ context 'with valid pods' do
+ let(:pod) { kube_pod(app: environment.slug) }
+ let(:terminals) { kube_terminals(service, pod) }
+
+ before do
+ stub_reactive_cache(
+ service,
+ pods: [pod, pod, kube_pod(app: "should-be-filtered-out")]
+ )
+ end
+
+ it 'returns terminals' do
+ is_expected.to eq(terminals + terminals)
+ end
+
+ it 'uses max session time from settings' do
+ stub_application_setting(terminal_max_session_time: 600)
+
+ times = subject.map { |terminal| terminal[:max_session_time] }
+ expect(times).to eq [600, 600, 600, 600]
+ end
+ end
+ end
+
+ describe '#calculate_reactive_cache' do
+ subject { service.calculate_reactive_cache }
+
+ let!(:cluster) { create(:cluster, :project, enabled: enabled, platform_kubernetes: service) }
+ let(:service) { create(:platform_kubernetes, :ca_cert) }
+ let(:enabled) { true }
+
+ context 'when cluster is disabled' do
+ let(:enabled) { false }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when kubernetes responds with valid pods' do
+ before do
+ stub_kubeclient_pods
+ end
+
+ it { is_expected.to eq(pods: [kube_pod]) }
+ end
+
+ context 'when kubernetes responds with 500s' do
+ before do
+ stub_kubeclient_pods(status: 500)
+ end
+
+ it { expect { subject }.to raise_error(KubeException) }
+ end
+
+ context 'when kubernetes responds with 404s' do
+ before do
+ stub_kubeclient_pods(status: 404)
+ end
+
+ it { is_expected.to eq(pods: []) }
+ end
+ end
+end
diff --git a/spec/models/clusters/project_spec.rb b/spec/models/clusters/project_spec.rb
new file mode 100644
index 00000000000..7d75d6ab345
--- /dev/null
+++ b/spec/models/clusters/project_spec.rb
@@ -0,0 +1,6 @@
+require 'spec_helper'
+
+describe Clusters::Project do
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to belong_to(:project) }
+end
diff --git a/spec/models/clusters/providers/gcp_spec.rb b/spec/models/clusters/providers/gcp_spec.rb
new file mode 100644
index 00000000000..99eb8c46e9a
--- /dev/null
+++ b/spec/models/clusters/providers/gcp_spec.rb
@@ -0,0 +1,183 @@
+require 'spec_helper'
+
+describe Clusters::Providers::Gcp do
+ it { is_expected.to belong_to(:cluster) }
+ it { is_expected.to validate_presence_of(:zone) }
+
+ describe 'default_value_for' do
+ let(:gcp) { build(:provider_gcp) }
+
+ it "has default value" do
+ expect(gcp.zone).to eq('us-central1-a')
+ expect(gcp.num_nodes).to eq(3)
+ expect(gcp.machine_type).to eq('n1-standard-4')
+ end
+ end
+
+ describe 'validation' do
+ subject { gcp.valid? }
+
+ context 'when validates gcp_project_id' do
+ let(:gcp) { build(:provider_gcp, gcp_project_id: gcp_project_id) }
+
+ context 'when gcp_project_id is shorter than 1' do
+ let(:gcp_project_id) { '' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when gcp_project_id is longer than 63' do
+ let(:gcp_project_id) { 'a' * 64 }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when gcp_project_id includes invalid character' do
+ let(:gcp_project_id) { '!!!!!!' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when gcp_project_id is valid' do
+ let(:gcp_project_id) { 'gcp-project-1' }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+
+ context 'when validates num_nodes' do
+ let(:gcp) { build(:provider_gcp, num_nodes: num_nodes) }
+
+ context 'when num_nodes is string' do
+ let(:num_nodes) { 'A3' }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when num_nodes is nil' do
+ let(:num_nodes) { nil }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when num_nodes is smaller than 1' do
+ let(:num_nodes) { 0 }
+
+ it { is_expected.to be_falsey }
+ end
+
+ context 'when num_nodes is valid' do
+ let(:num_nodes) { 3 }
+
+ it { is_expected.to be_truthy }
+ end
+ end
+ end
+
+ describe '#state_machine' do
+ context 'when any => [:created]' do
+ let(:gcp) { build(:provider_gcp, :creating) }
+
+ before do
+ gcp.make_created
+ end
+
+ it 'nullify access_token and operation_id' do
+ expect(gcp.access_token).to be_nil
+ expect(gcp.operation_id).to be_nil
+ expect(gcp).to be_created
+ end
+ end
+
+ context 'when any => [:creating]' do
+ let(:gcp) { build(:provider_gcp) }
+
+ context 'when operation_id is present' do
+ let(:operation_id) { 'operation-xxx' }
+
+ before do
+ gcp.make_creating(operation_id)
+ end
+
+ it 'sets operation_id' do
+ expect(gcp.operation_id).to eq(operation_id)
+ expect(gcp).to be_creating
+ end
+ end
+
+ context 'when operation_id is nil' do
+ let(:operation_id) { nil }
+
+ it 'raises an error' do
+ expect { gcp.make_creating(operation_id) }
+ .to raise_error('operation_id is required')
+ end
+ end
+ end
+
+ context 'when any => [:errored]' do
+ let(:gcp) { build(:provider_gcp, :creating) }
+ let(:status_reason) { 'err msg' }
+
+ it 'nullify access_token and operation_id' do
+ gcp.make_errored(status_reason)
+
+ expect(gcp.access_token).to be_nil
+ expect(gcp.operation_id).to be_nil
+ expect(gcp.status_reason).to eq(status_reason)
+ expect(gcp).to be_errored
+ end
+
+ context 'when status_reason is nil' do
+ let(:gcp) { build(:provider_gcp, :errored) }
+
+ it 'does not set status_reason' do
+ gcp.make_errored(nil)
+
+ expect(gcp.status_reason).not_to be_nil
+ end
+ end
+ end
+ end
+
+ describe '#on_creation?' do
+ subject { gcp.on_creation? }
+
+ context 'when status is creating' do
+ let(:gcp) { create(:provider_gcp, :creating) }
+
+ it { is_expected.to be_truthy }
+ end
+
+ context 'when status is created' do
+ let(:gcp) { create(:provider_gcp, :created) }
+
+ it { is_expected.to be_falsey }
+ end
+ end
+
+ describe '#api_client' do
+ subject { gcp.api_client }
+
+ context 'when status is creating' do
+ let(:gcp) { build(:provider_gcp, :creating) }
+
+ it 'returns Cloud Platform API clinet' do
+ expect(subject).to be_an_instance_of(GoogleApi::CloudPlatform::Client)
+ expect(subject.access_token).to eq(gcp.access_token)
+ end
+ end
+
+ context 'when status is created' do
+ let(:gcp) { build(:provider_gcp, :created) }
+
+ it { is_expected.to be_nil }
+ end
+
+ context 'when status is errored' do
+ let(:gcp) { build(:provider_gcp, :errored) }
+
+ it { is_expected.to be_nil }
+ end
+ end
+end