Welcome to mirror list, hosted at ThFree Co, Russian Federation.

gitlab.com/gitlab-org/gitlab-foss.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorShinya Maeda <shinya@gitlab.com>2017-10-29 21:48:45 +0300
committerShinya Maeda <shinya@gitlab.com>2017-10-29 21:48:45 +0300
commit478e59fe8d82b99800a2613aa4d153bf692fbd6b (patch)
tree5f734aee006c7cfee86c8151e3b6f94846b15299
parentd0cff7f5855f91b5479f9fdaa39d8d95ec691a9e (diff)
specs for models. Improved details.
-rw-r--r--app/models/clusters/cluster.rb26
-rw-r--r--app/models/clusters/platforms/kubernetes.rb50
-rw-r--r--app/models/clusters/project.rb4
-rw-r--r--app/models/clusters/providers/gcp.rb3
-rw-r--r--app/validators/cluster_name_validator.rb2
-rw-r--r--app/views/projects/clusters/_form.html.haml2
-rw-r--r--spec/factories/clusters/cluster.rb39
-rw-r--r--spec/factories/clusters/platforms/gcp.rb28
-rw-r--r--spec/factories/clusters/providers/kubernetes.rb18
-rw-r--r--spec/factories/gcp/cluster.rb38
-rw-r--r--spec/fixtures/clusters/sample_cert.pem33
-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
-rw-r--r--spec/models/gcp/cluster_spec.rb264
16 files changed, 838 insertions, 342 deletions
diff --git a/app/models/clusters/cluster.rb b/app/models/clusters/cluster.rb
index f1eedad8795..4260fadb46d 100644
--- a/app/models/clusters/cluster.rb
+++ b/app/models/clusters/cluster.rb
@@ -6,15 +6,6 @@ module Clusters
belongs_to :user
- enum platform_type: {
- kubernetes: 1
- }
-
- enum provider_type: {
- user: 0,
- gcp: 1
- }
-
has_many :cluster_projects, class_name: 'Clusters::Project'
has_many :projects, through: :cluster_projects, class_name: '::Project'
@@ -32,6 +23,18 @@ module Clusters
delegate :status_name, to: :provider, allow_nil: true
delegate :on_creation?, to: :provider, allow_nil: true
+ enum platform_type: {
+ kubernetes: 1
+ }
+
+ enum provider_type: {
+ user: 0,
+ gcp: 1
+ }
+
+ scope :enabled, -> { where(enabled: true) }
+ scope :disabled, -> { where(enabled: false) }
+
def provider
return provider_gcp if gcp?
end
@@ -40,15 +43,12 @@ module Clusters
return platform_kubernetes if kubernetes?
end
- def project
- first_project
- end
-
def first_project
return @first_project if defined?(@first_project)
@first_project = projects.first
end
+ alias_method :project, :first_project
private
diff --git a/app/models/clusters/platforms/kubernetes.rb b/app/models/clusters/platforms/kubernetes.rb
index d9f8927f7cc..b20b00ff51b 100644
--- a/app/models/clusters/platforms/kubernetes.rb
+++ b/app/models/clusters/platforms/kubernetes.rb
@@ -1,13 +1,11 @@
module Clusters
module Platforms
class Kubernetes < ActiveRecord::Base
+ include Gitlab::CurrentSettings
include Gitlab::Kubernetes
include ReactiveCaching
self.table_name = 'cluster_platforms_kubernetes'
-
- TEMPLATE_PLACEHOLDER = 'Kubernetes namespace'.freeze
-
self.reactive_cache_key = ->(kubernetes) { [kubernetes.class.model_name.singular, kubernetes.cluster_id] }
belongs_to :cluster, inverse_of: :platform_kubernetes, class_name: 'Clusters::Cluster'
@@ -22,6 +20,8 @@ module Clusters
key: Gitlab::Application.secrets.db_key_base,
algorithm: 'aes-256-cbc'
+ before_validation :enforce_namespace_to_lower_case
+
validates :namespace,
allow_blank: true,
length: 1..63,
@@ -34,8 +34,19 @@ module Clusters
validates :token, presence: true, on: :update
after_save :clear_reactive_cache!
-
- before_validation :enforce_namespace_to_lower_case
+
+ alias_attribute :ca_pem, :ca_cert
+
+ delegate :project, to: :cluster, allow_nil: true
+ delegate :enabled?, to: :cluster, allow_nil: true
+
+ alias_method :active?, :enabled?
+
+ class << self
+ def namespace_for_project(project)
+ "#{project.path}-#{project.id}"
+ end
+ end
def actual_namespace
if namespace.present?
@@ -45,6 +56,10 @@ module Clusters
end
end
+ def default_namespace
+ self.class.namespace_for_project(project) if project
+ end
+
def predefined_variables
config = YAML.dump(kubeconfig)
@@ -55,9 +70,9 @@ module Clusters
{ key: 'KUBECONFIG', value: config, public: false, file: true }
]
- if ca_cert.present?
- variables << { key: 'KUBE_CA_PEM', value: ca_cert, public: true }
- variables << { key: 'KUBE_CA_PEM_FILE', value: ca_cert, public: true, file: true }
+ if ca_pem.present?
+ variables << { key: 'KUBE_CA_PEM', value: ca_pem, public: true }
+ variables << { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
end
variables
@@ -78,7 +93,7 @@ module Clusters
# Caches resources in the namespace so other calls don't need to block on
# network access
def calculate_reactive_cache
- return unless active? && cluster.project && !cluster.project.pending_delete?
+ return unless active? && project && !project.pending_delete?
# We may want to cache extra things in the future
{ pods: read_pods }
@@ -89,16 +104,7 @@ module Clusters
url: api_url,
namespace: actual_namespace,
token: token,
- ca_pem: ca_cert)
- end
-
- def namespace_placeholder
- default_namespace || TEMPLATE_PLACEHOLDER
- end
-
- def default_namespace(project = nil)
- project ||= cluster&.project
- "#{project.path}-#{project.id}" if project
+ ca_pem: ca_pem)
end
def read_secrets
@@ -123,9 +129,9 @@ module Clusters
def kubeclient_ssl_options
opts = { verify_ssl: OpenSSL::SSL::VERIFY_PEER }
- if ca_cert.present?
+ if ca_pem.present?
opts[:cert_store] = OpenSSL::X509::Store.new
- opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_cert))
+ opts[:cert_store].add_cert(OpenSSL::X509::Certificate.new(ca_pem))
end
opts
@@ -166,7 +172,7 @@ module Clusters
def terminal_auth
{
token: token,
- ca_pem: ca_cert,
+ ca_pem: ca_pem,
max_session_time: current_application_settings.terminal_max_session_time
}
end
diff --git a/app/models/clusters/project.rb b/app/models/clusters/project.rb
index 69088100420..eeb734b20b8 100644
--- a/app/models/clusters/project.rb
+++ b/app/models/clusters/project.rb
@@ -2,7 +2,7 @@ module Clusters
class Project < ActiveRecord::Base
self.table_name = 'cluster_projects'
- belongs_to :cluster, inverse_of: :projects, class_name: 'Clusters::Cluster'
- belongs_to :project, inverse_of: :project, class_name: 'Project'
+ belongs_to :cluster, class_name: 'Clusters::Cluster'
+ belongs_to :project, class_name: '::Project'
end
end
diff --git a/app/models/clusters/providers/gcp.rb b/app/models/clusters/providers/gcp.rb
index e4f109d2794..7700ba86f1a 100644
--- a/app/models/clusters/providers/gcp.rb
+++ b/app/models/clusters/providers/gcp.rb
@@ -55,7 +55,8 @@ module Clusters
before_transition any => [:creating] do |provider, transition|
operation_id = transition.args.first
- provider.operation_id = operation_id if operation_id
+ raise 'operation_id is required' unless operation_id
+ provider.operation_id = operation_id
end
before_transition any => [:errored] do |provider, transition|
diff --git a/app/validators/cluster_name_validator.rb b/app/validators/cluster_name_validator.rb
index 6c9850af30f..13ec342f399 100644
--- a/app/validators/cluster_name_validator.rb
+++ b/app/validators/cluster_name_validator.rb
@@ -8,7 +8,7 @@ class ClusterNameValidator < ActiveModel::EachValidator
record.errors.add(attribute, " has to be present")
end
elsif record.gcp?
- if record.persisted? && record.name != value
+ if record.persisted? && record.name_changed?
record.errors.add(attribute, " can not be changed because it's synchronized with provider")
end
diff --git a/app/views/projects/clusters/_form.html.haml b/app/views/projects/clusters/_form.html.haml
index b3020513abf..6b9f63b7515 100644
--- a/app/views/projects/clusters/_form.html.haml
+++ b/app/views/projects/clusters/_form.html.haml
@@ -35,7 +35,7 @@
= field.fields_for :platform_kubernetes, @cluster.platform_kubernetes do |platform_kubernetes_field|
.form-group
= platform_kubernetes_field.label :namespace, s_('ClusterIntegration|Project namespace (optional, unique)')
- = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: @cluster.platform_kubernetes.default_namespace(@project)
+ = platform_kubernetes_field.text_field :namespace, class: 'form-control', placeholder: Clusters::Platforms::Kubernetes.namespace_for_project(@project)
.form-group
= field.submit s_('ClusterIntegration|Create cluster'), class: 'btn btn-save'
diff --git a/spec/factories/clusters/cluster.rb b/spec/factories/clusters/cluster.rb
new file mode 100644
index 00000000000..8ba1eda8cc9
--- /dev/null
+++ b/spec/factories/clusters/cluster.rb
@@ -0,0 +1,39 @@
+FactoryGirl.define do
+ factory :cluster, class: Clusters::Cluster do
+ user
+ name 'test-cluster'
+ provider_type :user
+ platform_type :kubernetes
+
+ trait :project do
+ after(:create) do |cluster, evaluator|
+ cluster.projects << create(:project)
+ end
+ end
+
+ trait :provided_by_user do
+ provider_type :user
+ platform_type :kubernetes
+ platform_kubernetes
+ end
+
+ trait :provided_by_gcp do
+ provider_type :gcp
+ platform_type :kubernetes
+ platform_kubernetes
+
+ provider_gcp do
+ create(:provider_gcp, :created)
+ end
+ end
+
+ trait :providing_by_gcp do
+ provider_type :gcp
+ platform_type :kubernetes
+
+ provider_gcp do
+ create(:provider_gcp, :creating)
+ end
+ end
+ end
+end
diff --git a/spec/factories/clusters/platforms/gcp.rb b/spec/factories/clusters/platforms/gcp.rb
new file mode 100644
index 00000000000..c135bbb20a4
--- /dev/null
+++ b/spec/factories/clusters/platforms/gcp.rb
@@ -0,0 +1,28 @@
+FactoryGirl.define do
+ factory :provider_gcp, class: Clusters::Providers::Gcp do
+ cluster
+ gcp_project_id 'test-gcp-project'
+
+ trait :creating do
+ access_token 'access_token_123'
+
+ after(:build) do |gcp, evaluator|
+ gcp.make_creating('operation-123')
+ end
+ end
+
+ trait :created do
+ endpoint '111.111.111.111'
+
+ after(:build) do |gcp, evaluator|
+ gcp.make_created
+ end
+ end
+
+ trait :errored do
+ after(:build) do |gcp, evaluator|
+ gcp.make_errored('Something wrong')
+ end
+ end
+ end
+end
diff --git a/spec/factories/clusters/providers/kubernetes.rb b/spec/factories/clusters/providers/kubernetes.rb
new file mode 100644
index 00000000000..b4d413d32c1
--- /dev/null
+++ b/spec/factories/clusters/providers/kubernetes.rb
@@ -0,0 +1,18 @@
+FactoryGirl.define do
+ factory :platform_kubernetes, class: Clusters::Platforms::Kubernetes do
+ cluster
+ api_url 'https://kubernetes.example.com'
+ ca_cert nil
+ token 'a' * 40
+ username 'xxxxxx'
+ password 'xxxxxx'
+ namespace nil
+
+ trait :ca_cert do
+ after(:create) do |platform_kubernetes, evaluator|
+ pem_file = File.expand_path(Rails.root.join('spec/fixtures/clusters/sample_cert.pem'))
+ platform_kubernetes.ca_cert = File.read(pem_file)
+ end
+ end
+ end
+end
diff --git a/spec/factories/gcp/cluster.rb b/spec/factories/gcp/cluster.rb
deleted file mode 100644
index 5c062737ffc..00000000000
--- a/spec/factories/gcp/cluster.rb
+++ /dev/null
@@ -1,38 +0,0 @@
-# FactoryGirl.define do
-# factory :gcp_cluster, class: Gcp::Cluster do
-# project
-# user
-# enabled true
-# gcp_project_id 'gcp-project-12345'
-# gcp_cluster_name 'test-cluster'
-# gcp_cluster_zone 'us-central1-a'
-# gcp_cluster_size 1
-# gcp_machine_type 'n1-standard-4'
-
-# trait :with_kubernetes_service do
-# after(:create) do |cluster, evaluator|
-# create(:kubernetes_service, project: cluster.project).tap do |service|
-# cluster.update(service: service)
-# end
-# end
-# end
-
-# trait :custom_project_namespace do
-# project_namespace 'sample-app'
-# end
-
-# trait :created_on_gke do
-# status_event :make_created
-# endpoint '111.111.111.111'
-# ca_cert 'xxxxxx'
-# kubernetes_token 'xxxxxx'
-# username 'xxxxxx'
-# password 'xxxxxx'
-# end
-
-# trait :errored do
-# status_event :make_errored
-# status_reason 'general error'
-# end
-# end
-# end
diff --git a/spec/fixtures/clusters/sample_cert.pem b/spec/fixtures/clusters/sample_cert.pem
new file mode 100644
index 00000000000..e39a2b34416
--- /dev/null
+++ b/spec/fixtures/clusters/sample_cert.pem
@@ -0,0 +1,33 @@
+-----BEGIN CERTIFICATE-----
+MIIFtTCCA52gAwIBAgIJAOutg3Kf2y5dMA0GCSqGSIb3DQEBBQUAMEUxCzAJBgNV
+BAYTAkFVMRMwEQYDVQQIEwpTb21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBX
+aWRnaXRzIFB0eSBMdGQwHhcNMTcxMDI5MTgxOTU3WhcNMTgxMDI5MTgxOTU3WjBF
+MQswCQYDVQQGEwJBVTETMBEGA1UECBMKU29tZS1TdGF0ZTEhMB8GA1UEChMYSW50
+ZXJuZXQgV2lkZ2l0cyBQdHkgTHRkMIICIjANBgkqhkiG9w0BAQEFAAOCAg8AMIIC
+CgKCAgEAvQysroM3TLxaavadSPnFIltrYnxCnU4PvCR8971HMWXsq7Z4ShU4BbbE
+8yp7oUFjulSwW6DhdIvnQb8ihLKictLmrA0isQqrD/iNpKZ6/lI4DGWw4QzrvMnW
+V4yy2QZNpg9tzQHd4+xkeeIoG23RijDU/sPd5dqxF+rPHBfCVInmYvSzLvMhneNj
+Bt6gV02gU9e9hsnMatsDvEbvWKp7wcbPot0nWrfZulx2QAWyXy+zG9mJQUds6yc0
+4agAeT9JEb/xtRgR/kS0aUHSGnfSnhZiEn17s0PhTmbu7qSHgzgB+7oJrC9jPoUh
+S2Wo3n0xykAjHrA8wC/Ddw3L38S41VQ58GEfNchistPswyMmXo/Oenv9P3s/kCOI
+fndiksFNdqVo51y9Vjngj589hpOseFDyKmWPIEQZ9kxW/crjP6RZWWLHgz26KtxZ
+uJaoYL8VBbYfrk/bucw0Ma2GEOp8rTsBE7SvgejXZa78q+381Kzc/utW6VwSXqzY
+xeIitft0rXi17SZ+XoiTkIXtHn0ZwMtOXNDBADTpFmKa6wVACQilvcpOYD8gUHyH
+pB+EDRdST3M4Fiq1MBAVhk8Lj3tHSJ/1ymeF1PWSu57AnJlzerzq2fcfPotNNd37
+ZPNkPh0kxPLwxbAyrHflzx9qVVdI1irY9055mNSnhzlec4qJ9cECAwEAAaOBpzCB
+pDAdBgNVHQ4EFgQUnVa5dYPoIG/3+qXml0bX8+N16GwwdQYDVR0jBG4wbIAUnVa5
+dYPoIG/3+qXml0bX8+N16GyhSaRHMEUxCzAJBgNVBAYTAkFVMRMwEQYDVQQIEwpT
+b21lLVN0YXRlMSEwHwYDVQQKExhJbnRlcm5ldCBXaWRnaXRzIFB0eSBMdGSCCQDr
+rYNyn9suXTAMBgNVHRMEBTADAQH/MA0GCSqGSIb3DQEBBQUAA4ICAQAUg4cyxXi1
+VR8ejTpaAruRyJ1pEG9Kc3kiIRXODy60z3hJXnx9LkScPkWGiuL5XacfZ2rMd4bw
+oVXIyi8U1UHWfAH8EZdrFKkU92jCiL5soHUONxLAvQEJ/FTR/qijrpzLCxXBdVQE
+xFEDWUu6rxLFyjEwzwnRTLgpjR606fdb7qXHkuAMvZ/ezJj8j97hok3Odpn4lr2H
+6hMTpK7HmDBX+kmdJJ+yBrm9hG1Pzpl7QU0dkxZ+qJNFjYMLnziiTwkv0c5ZaA9E
+NykZUcOv3Sjb6spu1A/E2BSq4WTjkIjrogFlfimE1vmUmObTRJOqUB0Vky1kHEwN
+pg7QqIJQmof1EAIaSM/YpUWXyumBwGLDUEud1JUz05In9Q4IZjEwZSJwbQW4fUia
+A93m9rk3Lw3xsFcaUdPMFIXk0rPoF1IgmV/oqb0gK95lOWRLbN+AV8qpKPpcKXOc
+TkIdFE47ZisEDhIdF6wC1izEMLeMEsPAO7/Y6MY4nRxsinSe95lRaw+yQpzx+mvJ
+Q7n1kiHI9Pd5M3+CiQda0d/GO1o5ORJnUGJRvr9HKuNmE7Lif0As/N0AlywjzE7A
+6Z8AEiWyRV1ffshu1k2UKmzvZuZeGGKRtrIjbJIRAtpRVtVZZGzhq5/sojCLoJ+u
+texqFBUo/4mFRZa4pDItUdyOlDy2/LO/ag==
+-----END CERTIFICATE-----
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
diff --git a/spec/models/gcp/cluster_spec.rb b/spec/models/gcp/cluster_spec.rb
deleted file mode 100644
index 8f39fff6394..00000000000
--- a/spec/models/gcp/cluster_spec.rb
+++ /dev/null
@@ -1,264 +0,0 @@
-require 'spec_helper'
-
-describe Gcp::Cluster do
- it { is_expected.to belong_to(:project) }
- it { is_expected.to belong_to(:user) }
- it { is_expected.to belong_to(:service) }
-
- it { is_expected.to validate_presence_of(:gcp_cluster_zone) }
-
- describe '.enabled' do
- subject { described_class.enabled }
-
- let!(:cluster) { create(:gcp_cluster, enabled: true) }
-
- before do
- create(:gcp_cluster, enabled: false)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
- describe '.disabled' do
- subject { described_class.disabled }
-
- let!(:cluster) { create(:gcp_cluster, enabled: false) }
-
- before do
- create(:gcp_cluster, enabled: true)
- end
-
- it { is_expected.to contain_exactly(cluster) }
- end
-
- describe '#default_value_for' do
- let(:cluster) { described_class.new }
-
- it { expect(cluster.gcp_cluster_zone).to eq('us-central1-a') }
- it { expect(cluster.gcp_cluster_size).to eq(3) }
- it { expect(cluster.gcp_machine_type).to eq('n1-standard-4') }
- end
-
- describe '#validates' do
- subject { cluster.valid? }
-
- context 'when validates gcp_project_id' do
- let(:cluster) { build(:gcp_cluster, gcp_project_id: gcp_project_id) }
-
- context 'when valid' do
- let(:gcp_project_id) { 'gcp-project-12345' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when empty' do
- let(:gcp_project_id) { '' }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when too long' do
- let(:gcp_project_id) { 'A' * 64 }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when includes abnormal character' do
- let(:gcp_project_id) { '!!!!!!' }
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when validates gcp_cluster_name' do
- let(:cluster) { build(:gcp_cluster, gcp_cluster_name: gcp_cluster_name) }
-
- context 'when valid' do
- let(:gcp_cluster_name) { 'test-cluster' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when empty' do
- let(:gcp_cluster_name) { '' }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when too long' do
- let(:gcp_cluster_name) { 'A' * 64 }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when includes abnormal character' do
- let(:gcp_cluster_name) { '!!!!!!' }
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when validates gcp_cluster_size' do
- let(:cluster) { build(:gcp_cluster, gcp_cluster_size: gcp_cluster_size) }
-
- context 'when valid' do
- let(:gcp_cluster_size) { 1 }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when zero' do
- let(:gcp_cluster_size) { 0 }
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when validates project_namespace' do
- let(:cluster) { build(:gcp_cluster, project_namespace: project_namespace) }
-
- context 'when valid' do
- let(:project_namespace) { 'default-namespace' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when empty' do
- let(:project_namespace) { '' }
-
- it { is_expected.to be_truthy }
- end
-
- context 'when too long' do
- let(:project_namespace) { 'A' * 64 }
-
- it { is_expected.to be_falsey }
- end
-
- context 'when includes abnormal character' do
- let(:project_namespace) { '!!!!!!' }
-
- it { is_expected.to be_falsey }
- end
- end
-
- context 'when validates restrict_modification' do
- let(:cluster) { create(:gcp_cluster) }
-
- before do
- cluster.make_creating!
- end
-
- context 'when created' do
- before do
- cluster.make_created!
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when creating' do
- it { is_expected.to be_falsey }
- end
- end
- end
-
- describe '#state_machine' do
- let(:cluster) { build(:gcp_cluster) }
-
- context 'when transits to created state' do
- before do
- cluster.gcp_token = 'tmp'
- cluster.gcp_operation_id = 'tmp'
- cluster.make_created!
- end
-
- it 'nullify gcp_token and gcp_operation_id' do
- expect(cluster.gcp_token).to be_nil
- expect(cluster.gcp_operation_id).to be_nil
- expect(cluster).to be_created
- end
- end
-
- context 'when transits to errored state' do
- let(:reason) { 'something wrong' }
-
- before do
- cluster.make_errored!(reason)
- end
-
- it 'sets status_reason' do
- expect(cluster.status_reason).to eq(reason)
- expect(cluster).to be_errored
- end
- end
- end
-
- describe '#project_namespace_placeholder' do
- subject { cluster.project_namespace_placeholder }
-
- let(:cluster) { create(:gcp_cluster) }
-
- it 'returns a placeholder' do
- is_expected.to eq("#{cluster.project.path}-#{cluster.project.id}")
- end
- end
-
- describe '#on_creation?' do
- subject { cluster.on_creation? }
-
- let(:cluster) { create(:gcp_cluster) }
-
- context 'when status is creating' do
- before do
- cluster.make_creating!
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when status is created' do
- before do
- cluster.make_created!
- end
-
- it { is_expected.to be_falsey }
- end
- end
-
- describe '#api_url' do
- subject { cluster.api_url }
-
- let(:cluster) { create(:gcp_cluster, :created_on_gke) }
- let(:api_url) { 'https://' + cluster.endpoint }
-
- it { is_expected.to eq(api_url) }
- end
-
- describe '#restrict_modification' do
- subject { cluster.restrict_modification }
-
- let(:cluster) { create(:gcp_cluster) }
-
- context 'when status is created' do
- before do
- cluster.make_created!
- end
-
- it { is_expected.to be_truthy }
- end
-
- context 'when status is creating' do
- before do
- cluster.make_creating!
- end
-
- it { is_expected.to be_falsey }
-
- it 'sets error' do
- is_expected.to be_falsey
- expect(cluster.errors).not_to be_empty
- end
- end
- end
-end