kubernetes_spec.rb 13.2 KB
Newer Older
1 2 3 4 5 6 7
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) }
8 9
  it { is_expected.to be_kind_of(Gitlab::Kubernetes) }
  it { is_expected.to be_kind_of(ReactiveCaching) }
10 11
  it { is_expected.to respond_to :ca_pem }

12 13 14 15 16 17 18 19 20
  it { is_expected.to validate_exclusion_of(:namespace).in_array(%w(gitlab-managed-apps)) }
  it { is_expected.to validate_presence_of(:api_url) }
  it { is_expected.to validate_presence_of(:token) }

  it { is_expected.to delegate_method(:project).to(:cluster) }
  it { is_expected.to delegate_method(:enabled?).to(:cluster) }
  it { is_expected.to delegate_method(:managed?).to(:cluster) }
  it { is_expected.to delegate_method(:kubernetes_namespace).to(:cluster) }

Shinya Maeda's avatar
Shinya Maeda committed
21 22
  it_behaves_like 'having unique enum values'

23 24
  describe 'before_validation' do
    context 'when namespace includes upper case' do
25
      let(:kubernetes) { create(:cluster_platform_kubernetes, :configured, namespace: namespace) }
26 27 28 29 30 31 32 33 34 35 36 37
      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
38
      let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: namespace) }
39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62

      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
63 64 65 66 67 68 69 70 71 72 73 74

      context 'for group cluster' do
        let(:namespace) { 'namespace-123' }
        let(:cluster) { build(:cluster, :group, :provided_by_user) }
        let(:kubernetes) { cluster.platform_kubernetes }

        before do
          kubernetes.namespace = namespace
        end

        it { is_expected.to be_falsey }
      end
75 76
    end

77
    context 'when validates api_url' do
78
      let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
79

80 81 82
      before do
        kubernetes.api_url = api_url
      end
83

84 85
      context 'when api_url is invalid url' do
        let(:api_url) { '!!!!!!' }
86

87 88
        it { expect(kubernetes.save).to be_falsey }
      end
89

90 91
      context 'when api_url is nil' do
        let(:api_url) { nil }
92

93 94
        it { expect(kubernetes.save).to be_falsey }
      end
95

96 97
      context 'when api_url is valid url' do
        let(:api_url) { 'https://111.111.111.111' }
98

99 100
        it { expect(kubernetes.save).to be_truthy }
      end
101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116

      context 'when api_url is localhost' do
        let(:api_url) { 'http://localhost:22' }

        it { expect(kubernetes.save).to be_falsey }

        context 'Application settings allows local requests' do
          before do
            allow(ApplicationSetting)
              .to receive(:current)
              .and_return(ApplicationSetting.build_from_defaults(allow_local_requests_from_hooks_and_services: true))
          end

          it { expect(kubernetes.save).to be_truthy }
        end
      end
117
    end
118

119
    context 'when validates token' do
120
      let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }
121

122 123 124
      before do
        kubernetes.token = token
      end
125

126 127
      context 'when token is nil' do
        let(:token) { nil }
128

129 130 131
        it { expect(kubernetes.save).to be_falsey }
      end
    end
132

133 134 135 136 137 138 139 140 141 142 143 144 145 146 147 148 149 150 151 152 153 154 155 156 157 158 159 160 161 162
    context 'ca_cert' do
      let(:kubernetes) { build(:cluster_platform_kubernetes, ca_pem: ca_pem) }

      context 'with a valid certificate' do
        let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }

        it { is_expected.to be_truthy }
      end

      context 'with an invalid certificate' do
        let(:ca_pem) { "invalid" }

        it { is_expected.to be_falsey }

        context 'but the certificate is not being updated' do
          before do
            allow(kubernetes).to receive(:ca_cert_changed?).and_return(false)
          end

          it { is_expected.to be_truthy }
        end
      end

      context 'with no certificate' do
        let(:ca_pem) { "" }

        it { is_expected.to be_truthy }
      end
    end

163 164 165 166 167 168 169 170 171 172 173 174 175 176 177 178 179 180 181 182 183
    describe 'when using reserved namespaces' do
      subject { build(:cluster_platform_kubernetes, namespace: namespace) }

      context 'when no namespace is manually assigned' do
        let(:namespace) { nil }

        it { is_expected.to be_valid }
      end

      context 'when no reserved namespace is assigned' do
        let(:namespace) { 'my-namespace' }

        it { is_expected.to be_valid }
      end

      context 'when reserved namespace is assigned' do
        let(:namespace) { 'gitlab-managed-apps' }

        it { is_expected.not_to be_valid }
      end
    end
184 185
  end

186
  describe '#kubeclient' do
187 188 189
    let(:cluster) { create(:cluster, :project) }
    let(:kubernetes) { build(:cluster_platform_kubernetes, :configured, namespace: 'a-namespace', cluster: cluster) }

190 191
    subject { kubernetes.kubeclient }

192 193 194 195 196 197
    before do
      create(:cluster_kubernetes_namespace,
             cluster: kubernetes.cluster,
             cluster_project: kubernetes.cluster.cluster_project,
             project: kubernetes.cluster.cluster_project.project)
    end
198 199 200 201 202 203 204

    it { is_expected.to be_an_instance_of(Gitlab::Kubernetes::KubeClient) }
  end

  describe '#rbac?' do
    let(:kubernetes) { build(:cluster_platform_kubernetes, :configured) }

205
    subject { kubernetes.rbac? }
206

207
    it { is_expected.to be_truthy }
208 209
  end

210
  describe '#actual_namespace' do
211
    let(:cluster) { create(:cluster, :project) }
212
    let(:project) { cluster.project }
213

214 215 216 217 218 219 220 221 222
    let(:platform) do
      create(:cluster_platform_kubernetes,
             cluster: cluster,
             namespace: namespace)
    end

    subject { platform.actual_namespace }

    context 'with a namespace assigned' do
223 224 225 226 227
      let(:namespace) { 'namespace-123' }

      it { is_expected.to eq(namespace) }
    end

228
    context 'with no namespace assigned' do
229 230
      let(:namespace) { nil }

231 232
      context 'when kubernetes namespace is present' do
        let(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }
233

234 235 236
        before do
          kubernetes_namespace
        end
237

238 239
        it { is_expected.to eq(kubernetes_namespace.namespace) }
      end
240

241 242 243
      context 'when kubernetes namespace is not present' do
        it { is_expected.to eq("#{project.path}-#{project.id}") }
      end
244 245
    end
  end
246 247 248

  describe '#predefined_variables' do
    let!(:cluster) { create(:cluster, :project, platform_kubernetes: kubernetes) }
249
    let(:kubernetes) { create(:cluster_platform_kubernetes, api_url: api_url, ca_cert: ca_pem) }
250
    let(:api_url) { 'https://kube.domain.com' }
251
    let(:ca_pem) { File.read(Rails.root.join('spec/fixtures/clusters/sample_cert.pem')) }
252

253 254
    subject { kubernetes.predefined_variables(project: cluster.project) }

255 256
    shared_examples 'setting variables' do
      it 'sets the variables' do
257
        expect(subject).to include(
258 259 260 261 262 263 264
          { key: 'KUBE_URL', value: api_url, public: true },
          { key: 'KUBE_CA_PEM', value: ca_pem, public: true },
          { key: 'KUBE_CA_PEM_FILE', value: ca_pem, public: true, file: true }
        )
      end
    end

265 266 267 268 269 270 271 272 273 274 275 276 277 278 279 280 281 282 283 284 285 286 287 288
    context 'kubernetes namespace is created with no service account token' do
      let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, cluster: cluster) }

      it_behaves_like 'setting variables'

      it 'sets KUBE_TOKEN' do
        expect(subject).to include(
          { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
        )
      end
    end

    context 'kubernetes namespace is created with no service account token' do
      let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster) }

      it_behaves_like 'setting variables'

      it 'sets KUBE_TOKEN' do
        expect(subject).to include(
          { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
        )
      end
    end

289 290 291 292 293 294 295 296
    context 'namespace is provided' do
      let(:namespace) { 'my-project' }

      before do
        kubernetes.namespace = namespace
      end

      it_behaves_like 'setting variables'
297 298 299 300 301 302

      it 'sets KUBE_TOKEN' do
        expect(subject).to include(
          { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
        )
      end
303 304 305 306 307 308
    end

    context 'no namespace provided' do
      let(:namespace) { kubernetes.actual_namespace }

      it_behaves_like 'setting variables'
309 310 311 312 313 314

      it 'sets KUBE_TOKEN' do
        expect(subject).to include(
          { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
        )
      end
315
    end
316 317 318 319 320 321 322 323 324 325 326 327 328 329 330 331 332 333 334 335 336 337 338 339 340 341 342 343 344 345

    context 'group level cluster' do
      let!(:cluster) { create(:cluster, :group, platform_kubernetes: kubernetes) }

      let(:project) { create(:project, group: cluster.group) }

      subject { kubernetes.predefined_variables(project: project) }

      context 'no kubernetes namespace for the project' do
        it_behaves_like 'setting variables'

        it 'does not return KUBE_TOKEN' do
          expect(subject).not_to include(
            { key: 'KUBE_TOKEN', value: kubernetes.token, public: false }
          )
        end
      end

      context 'kubernetes namespace exists for the project' do
        let!(:kubernetes_namespace) { create(:cluster_kubernetes_namespace, :with_token, cluster: cluster, project: project) }

        it_behaves_like 'setting variables'

        it 'sets KUBE_TOKEN' do
          expect(subject).to include(
            { key: 'KUBE_TOKEN', value: kubernetes_namespace.service_account_token, public: false }
          )
        end
      end
    end
346 347 348 349 350 351 352 353 354 355 356 357 358

    context 'with a domain' do
      let!(:cluster) do
        create(:cluster, :provided_by_gcp, :with_domain,
               platform_kubernetes: kubernetes)
      end

      it 'sets KUBE_INGRESS_BASE_DOMAIN' do
        expect(subject).to include(
          { key: 'KUBE_INGRESS_BASE_DOMAIN', value: cluster.domain, public: true }
        )
      end
    end
359 360 361 362 363 364 365
  end

  describe '#terminals' do
    subject { service.terminals(environment) }

    let!(:cluster) { create(:cluster, :project, platform_kubernetes: service) }
    let(:project) { cluster.project }
366
    let(:service) { create(:cluster_platform_kubernetes, :configured) }
367 368 369 370 371 372 373 374 375 376 377 378
    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) }
379
      let(:pod_with_no_terminal) { kube_pod(app: environment.slug, status: "Pending") }
380 381 382 383 384
      let(:terminals) { kube_terminals(service, pod) }

      before do
        stub_reactive_cache(
          service,
385
          pods: [pod, pod, pod_with_no_terminal, kube_pod(app: "should-be-filtered-out")]
386 387 388 389 390 391 392 393 394 395 396 397 398 399 400 401 402 403 404 405
        )
      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) }
406
    let(:service) { create(:cluster_platform_kubernetes, :configured) }
407 408 409 410 411 412 413 414
    let(:enabled) { true }

    context 'when cluster is disabled' do
      let(:enabled) { false }

      it { is_expected.to be_nil }
    end

415
    context 'when kubernetes responds with valid pods and deployments' do
416 417
      before do
        stub_kubeclient_pods
418
        stub_kubeclient_deployments
419 420
      end

421
      it { is_expected.to include(pods: [kube_pod]) }
422 423 424 425 426
    end

    context 'when kubernetes responds with 500s' do
      before do
        stub_kubeclient_pods(status: 500)
427
        stub_kubeclient_deployments(status: 500)
428 429
      end

430
      it { expect { subject }.to raise_error(Kubeclient::HttpError) }
431 432 433 434 435
    end

    context 'when kubernetes responds with 404s' do
      before do
        stub_kubeclient_pods(status: 404)
436
        stub_kubeclient_deployments(status: 404)
437 438
      end

439
      it { is_expected.to include(pods: []) }
440 441
    end
  end
442 443 444 445 446 447 448

  describe '#update_kubernetes_namespace' do
    let(:cluster) { create(:cluster, :provided_by_gcp) }
    let(:platform) { cluster.platform }

    context 'when namespace is updated' do
      it 'should call ConfigureWorker' do
449
        expect(ClusterConfigureWorker).to receive(:perform_async).with(cluster.id).once
450 451 452 453 454 455 456 457

        platform.namespace = 'new-namespace'
        platform.save
      end
    end

    context 'when namespace is not updated' do
      it 'should not call ConfigureWorker' do
458
        expect(ClusterConfigureWorker).not_to receive(:perform_async)
459 460 461 462 463 464

        platform.username = "new-username"
        platform.save
      end
    end
  end
465
end