Commit 5fa50bad authored by Grzegorz Bizon's avatar Grzegorz Bizon

Merge branch 'ali/serverless-domains-be-2' into 'master'

Allow users to assign a serverless domain to Knative (Backend)

See merge request gitlab-org/gitlab!21436
parents 6ea95c9b 9e7a0854
......@@ -33,6 +33,12 @@ module Clusters
FETCH_IP_ADDRESS_DELAY, application.name, application.id)
end
end
after_transition any => [:installed, :updated] do |application|
application.run_after_commit do
ClusterConfigureIstioWorker.perform_async(application.cluster_id)
end
end
end
default_value_for :version, VERSION
......@@ -41,6 +47,8 @@ module Clusters
scope :for_cluster, -> (cluster) { where(cluster: cluster) }
has_one :pages_domain, through: :serverless_domain_cluster
def chart
'knative/knative'
end
......@@ -49,6 +57,14 @@ module Clusters
{ "domain" => hostname }.to_yaml
end
def available_domains
PagesDomain.instance_serverless
end
def find_available_domain(pages_domain_id)
available_domains.find_by(id: pages_domain_id)
end
def allowed_to_uninstall?
!pre_installed?
end
......
......@@ -13,4 +13,6 @@ class ClusterApplicationEntity < Grape::Entity
expose :modsecurity_enabled, if: -> (e, _) { e.respond_to?(:modsecurity_enabled) }
expose :update_available?, as: :update_available, if: -> (e, _) { e.respond_to?(:update_available?) }
expose :can_uninstall?, as: :can_uninstall
expose :available_domains, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:available_domains) }
expose :pages_domain, using: Serverless::DomainEntity, if: -> (e, _) { e.respond_to?(:pages_domain) }
end
# frozen_string_literal: true
module Serverless
class DomainEntity < Grape::Entity
expose :id
expose :domain
end
end
......@@ -35,6 +35,12 @@ module Clusters
application.oauth_application = create_oauth_application(application, request)
end
if application.instance_of?(Knative)
Serverless::AssociateDomainService
.new(application, pages_domain_id: params[:pages_domain_id], creator: current_user)
.execute
end
worker = worker_class(application)
application.make_scheduled!
......
......@@ -27,6 +27,10 @@ module Clusters
return configure_certificates if serverless_domain_cluster
configure_passthrough
rescue Kubeclient::HttpError => e
knative.make_errored!(_('Kubernetes error: %{error_code}') % { error_code: e.error_code })
rescue StandardError
knative.make_errored!(_('Failed to update.'))
end
private
......
# frozen_string_literal: true
module Serverless
class AssociateDomainService
PLACEHOLDER_HOSTNAME = 'example.com'.freeze
def initialize(knative, pages_domain_id:, creator:)
@knative = knative
@pages_domain_id = pages_domain_id
@creator = creator
end
def execute
return if unchanged?
knative.hostname ||= PLACEHOLDER_HOSTNAME
knative.pages_domain = knative.find_available_domain(pages_domain_id)
knative.serverless_domain_cluster.update(creator: creator) if knative.pages_domain
end
private
attr_reader :knative, :pages_domain_id, :creator
def unchanged?
knative.pages_domain&.id == pages_domain_id
end
end
end
......@@ -39,9 +39,15 @@
"stack": { "type": ["string", "null"] },
"modsecurity_enabled": { "type": ["boolean", "null"] },
"update_available": { "type": ["boolean", "null"] },
"can_uninstall": { "type": "boolean" }
"can_uninstall": { "type": "boolean" },
"available_domains": {
"type": "array",
"items": { "$ref": "#/definitions/domain" }
},
"pages_domain": { "type": [ { "$ref": "#/definitions/domain" }, "null"] }
},
"required" : [ "name", "status" ]
}
},
"domain": { "id": "integer", "domain": "string" }
}
}
......@@ -14,6 +14,7 @@ describe Clusters::Applications::Knative do
before do
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_in)
allow(ClusterWaitForIngressIpAddressWorker).to receive(:perform_async)
allow(ClusterConfigureIstioWorker).to receive(:perform_async)
end
describe 'associations' do
......@@ -47,6 +48,32 @@ describe Clusters::Applications::Knative do
end
end
describe 'configuring istio ingress gateway' do
context 'after installed' do
let(:application) { create(:clusters_applications_knative, :installing) }
before do
application.make_installed!
end
it 'schedules a ClusterConfigureIstioWorker' do
expect(ClusterConfigureIstioWorker).to have_received(:perform_async).with(application.cluster_id)
end
end
context 'after updated' do
let(:application) { create(:clusters_applications_knative, :updating) }
before do
application.make_installed!
end
it 'schedules a ClusterConfigureIstioWorker' do
expect(ClusterConfigureIstioWorker).to have_received(:perform_async).with(application.cluster_id)
end
end
end
describe '#can_uninstall?' do
subject { knative.can_uninstall? }
......@@ -196,4 +223,34 @@ describe Clusters::Applications::Knative do
describe 'validations' do
it { is_expected.to validate_presence_of(:hostname) }
end
describe '#available_domains' do
let!(:domain) { create(:pages_domain, :instance_serverless) }
it 'returns all instance serverless domains' do
expect(PagesDomain).to receive(:instance_serverless).and_call_original
domains = subject.available_domains
expect(domains.length).to eq(1)
expect(domains).to include(domain)
end
end
describe '#find_available_domain' do
let!(:domain) { create(:pages_domain, :instance_serverless) }
it 'returns the domain scoped to available domains' do
expect(subject).to receive(:available_domains).and_call_original
expect(subject.find_available_domain(domain.id)).to eq(domain)
end
end
describe '#pages_domain' do
let!(:sdc) { create(:serverless_domain_cluster, knative: knative) }
it 'returns the the associated pages domain' do
expect(knative.reload.pages_domain).to eq(sdc.pages_domain)
end
end
end
......@@ -59,5 +59,23 @@ describe ClusterApplicationEntity do
expect(subject[:external_ip]).to eq('111.222.111.222')
end
end
context 'for knative application' do
let(:pages_domain) { create(:pages_domain, :instance_serverless) }
let(:application) { build(:clusters_applications_knative, :installed) }
before do
create(:serverless_domain_cluster, knative: application, pages_domain: pages_domain)
end
it 'includes available domains' do
expect(subject[:available_domains].length).to eq(1)
expect(subject[:available_domains].first).to eq(id: pages_domain.id, domain: pages_domain.domain)
end
it 'includes pages_domain' do
expect(subject[:pages_domain]).to eq(id: pages_domain.id, domain: pages_domain.domain)
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Serverless::DomainEntity do
describe '#as_json' do
let(:domain) { create(:pages_domain, :instance_serverless) }
subject { described_class.new(domain).as_json }
it 'has an id' do
expect(subject[:id]).to eq(domain.id)
end
it 'has a domain' do
expect(subject[:domain]).to eq(domain.domain)
end
end
end
......@@ -137,10 +137,14 @@ describe Clusters::Applications::CreateService do
let(:params) do
{
application: 'knative',
hostname: 'example.com'
hostname: 'example.com',
pages_domain_id: domain.id
}
end
let(:domain) { create(:pages_domain, :instance_serverless) }
let(:associate_domain_service) { double('AssociateDomainService') }
before do
expect_any_instance_of(Clusters::Applications::Knative)
.to receive(:make_scheduled!)
......@@ -158,6 +162,20 @@ describe Clusters::Applications::CreateService do
it 'sets the hostname' do
expect(subject.hostname).to eq('example.com')
end
it 'executes AssociateDomainService' do
expect(Serverless::AssociateDomainService).to receive(:new) do |knative, args|
expect(knative).to be_a(Clusters::Applications::Knative)
expect(args[:pages_domain_id]).to eq(params[:pages_domain_id])
expect(args[:creator]).to eq(user)
associate_domain_service
end
expect(associate_domain_service).to receive(:execute)
subject
end
end
context 'elastic stack application' do
......
......@@ -7,8 +7,9 @@ describe Clusters::Applications::UpdateService do
let(:cluster) { create(:cluster, :project, :provided_by_gcp) }
let(:user) { create(:user) }
let(:params) { { application: 'knative', hostname: 'udpate.example.com' } }
let(:params) { { application: 'knative', hostname: 'update.example.com', pages_domain_id: domain.id } }
let(:service) { described_class.new(cluster, user, params) }
let(:domain) { create(:pages_domain, :instance_serverless) }
subject { service.execute(test_request) }
......@@ -51,6 +52,24 @@ describe Clusters::Applications::UpdateService do
subject
end
context 'knative application' do
let(:associate_domain_service) { double('AssociateDomainService') }
it 'executes AssociateDomainService' do
expect(Serverless::AssociateDomainService).to receive(:new) do |knative, args|
expect(knative.id).to eq(application.id)
expect(args[:pages_domain_id]).to eq(params[:pages_domain_id])
expect(args[:creator]).to eq(user)
associate_domain_service
end
expect(associate_domain_service).to receive(:execute)
subject
end
end
end
context 'application is not schedulable' do
......
......@@ -194,4 +194,36 @@ describe Clusters::Kubernetes::ConfigureIstioIngressService, '#execute' do
)
end
end
context 'when there is an error' do
before do
cluster.application_knative = create(:clusters_applications_knative)
allow_next_instance_of(described_class) do |instance|
allow(instance).to receive(:configure_passthrough).and_raise(error)
end
end
context 'Kubeclient::HttpError' do
let(:error) { Kubeclient::HttpError.new(404, nil, nil) }
it 'puts Knative into an errored state' do
subject
expect(cluster.application_knative).to be_errored
expect(cluster.application_knative.status_reason).to eq('Kubernetes error: 404')
end
end
context 'StandardError' do
let(:error) { RuntimeError.new('something went wrong') }
it 'puts Knative into an errored state' do
subject
expect(cluster.application_knative).to be_errored
expect(cluster.application_knative.status_reason).to eq('Failed to update.')
end
end
end
end
# frozen_string_literal: true
require 'spec_helper'
describe Serverless::AssociateDomainService do
subject { described_class.new(knative, pages_domain_id: pages_domain_id, creator: creator) }
let(:sdc) { create(:serverless_domain_cluster, pages_domain: create(:pages_domain, :instance_serverless)) }
let(:knative) { sdc.knative }
let(:creator) { sdc.creator }
let(:pages_domain_id) { sdc.pages_domain_id }
context 'when the domain is unchanged' do
let(:creator) { create(:user) }
it 'does not update creator' do
expect { subject.execute }.not_to change { sdc.reload.creator }
end
end
context 'when domain is changed to nil' do
let(:pages_domain_id) { nil }
let(:creator) { create(:user) }
it 'removes the association between knative and the domain' do
expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil)
end
it 'does not attempt to update creator' do
expect { subject.execute }.not_to raise_error
end
end
context 'when a new domain is associated' do
let(:pages_domain_id) { create(:pages_domain, :instance_serverless).id }
let(:creator) { create(:user) }
it 'creates an association with the domain' do
expect { subject.execute }.to change { knative.pages_domain.id }.from(sdc.pages_domain.id).to(pages_domain_id)
end
it 'updates creator' do
expect { subject.execute }.to change { sdc.reload.creator }.from(sdc.creator).to(creator)
end
end
context 'when knative is not authorized to use the pages domain' do
let(:pages_domain_id) { create(:pages_domain).id }
before do
expect(knative).to receive(:available_domains).and_return(PagesDomain.none)
end
it 'sets pages_domain_id to nil' do
expect { subject.execute }.to change { knative.reload.pages_domain }.from(sdc.pages_domain).to(nil)
end
end
context 'when knative hostname is nil' do
let(:knative) { build(:clusters_applications_knative, hostname: nil) }
it 'sets hostname to a placeholder value' do
expect { subject.execute }.to change { knative.hostname }.to('example.com')
end
end
context 'when knative hostname exists' do
let(:knative) { build(:clusters_applications_knative, hostname: 'hostname.com') }
it 'does not change hostname' do
expect { subject.execute }.not_to change { knative.hostname }
end
end
end
Markdown is supported
0%
or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment