Skip to content
Snippets Groups Projects
Commit 64071a7f authored by Andrejs Cunskis's avatar Andrejs Cunskis
Browse files

UpdateFromPrevious scenario type for N - 1 version update testing

parent dc25dbd1
No related branches found
No related tags found
No related merge requests found
Loading
Loading
@@ -17,7 +17,7 @@ module Gitlab
using Rainbow
 
attr_reader :release, :omnibus_configuration, :omnibus_gitlab_rails_env
attr_accessor :tls, :skip_availability_check, :runner_network, :seed_admin_token, :seed_db
attr_accessor :tls, :skip_availability_check, :runner_network, :seed_admin_token, :seed_db, :skip_server_hooks
attr_writer :name, :relative_path
 
def_delegators :release, :tag, :image, :edition
Loading
Loading
@@ -47,6 +47,7 @@ module Gitlab
 
@seed_admin_token = Runtime::Scenario.seed_admin_token
@seed_db = Runtime::Scenario.seed_db
@skip_server_hooks = Runtime::Scenario.skip_server_hooks
 
self.release = 'CE'
end
Loading
Loading
@@ -201,7 +202,7 @@ module Gitlab
exec_commands << seed_admin_token_command if seed_admin_token
exec_commands << seed_test_data_command if seed_db
exec_commands << Runtime::Scenario.omnibus_exec_commands
exec_commands << add_git_server_hooks unless Runtime::Scenario.skip_server_hooks
exec_commands << add_git_server_hooks unless skip_server_hooks
 
commands = exec_commands.flatten.uniq
return if commands.empty?
Loading
Loading
# frozen_string_literal: true
module Gitlab
module QA
module Scenario
module Test
module Omnibus
class UpdateFromPrevious < Scenario::Template
using Rainbow
# Test update from N - 1 (major|minor|patch) version to current release
# Run smoke test suite on previous release to populate some data in database before update
#
# @example
# perform(gitlab-ee:dev-tag, 15.3.0-pre, major)
# => will perform upgrades 14.9.5 -> 15.0.5 -> gitlab-ee:dev-tag
#
# @param [String] release current release docker image
# @param [String] current_version current gitlab version associated with docker image
# @param [String] semver_component semver component for N - 1 version detection, major|minor|patch
# @param [Array] *rspec_args rspec arguments
# @return [void]
def perform(release, current_version, semver_component, *rspec_args)
@current_release = QA::Release.new(release)
@upgrade_path = Support::GitlabUpgradePath.new(
current_version,
semver_component,
@current_release.edition
).fetch
upgrade_info = "#{[*upgrade_path, current_release].join(' => ')} (#{current_version})".bright
Runtime::Logger.info("Performing gitlab update: #{upgrade_info}")
update(rspec_args)
end
private
attr_reader :current_release, :upgrade_path
# Perform update
#
# @param [Array] rspec_args
# @return [void]
def update(rspec_args)
Docker::Volumes.new.with_temporary_volumes do |volumes|
# deploy first release in upgrade path and run specs to populate db
run_gitlab(upgrade_path.first, volumes, ["--", "--tag", "smoke"])
# deploy releases in upgrade path
upgrade_path[1..].each { |release| run_gitlab(release, volumes, skip_setup: true) }
# deploy current release and run tests
run_gitlab(current_release, volumes, rspec_args, skip_setup: true)
end
end
# Deploy gitlab instance and optionally run specs
#
# @param [Gitlab::QA::Release] release
# @param [Hash] volumes
# @return [void]
def run_gitlab(release, volumes, rspec_args = [], skip_setup: false)
Runtime::Logger.info("Deploying release: #{release.to_s.bright}")
Component::Gitlab.perform do |gitlab|
gitlab.release = release
gitlab.volumes = volumes
gitlab.network = 'test'
if skip_setup
gitlab.skip_server_hooks = true
gitlab.seed_db = false
gitlab.seed_admin_token = false
end
next gitlab.launch_and_teardown_instance unless run_specs?(release)
gitlab.instance { run_specs(gitlab, release, rspec_args) }
end
end
# Run specs
#
# @param [Gitlab::QA::Component::Gitlab] gitlab
# @param [Gitlab::QA::Release] release
# @param [Array] rspec_args
# @return [void]
def run_specs(gitlab, release, rspec_args)
Component::Specs.perform do |specs|
specs.release = release
specs.suite = 'Test::Instance::All'
specs.hostname = "qa-e2e-specs.#{gitlab.network}"
specs.network = gitlab.network
specs.args = [gitlab.address, *rspec_args]
end
rescue Docker::Shellout::StatusError => e
raise e if release == current_release # only fail on current release
Runtime::Logger.warn("Test run for release '#{gitlab.release}' finished with errors!")
end
# Run specs on first release to populate database and release being tested
#
# @param [Gitlab::QA::Release] release
# @return [Boolean]
def run_specs?(release)
[upgrade_path.first, current_release].any? { |rel| rel == release }
end
end
end
end
end
end
end
# frozen_string_literal: true
require "active_support/core_ext/module/delegation"
require "yaml"
module Gitlab
module QA
module Support
class GitlabUpgradePath
# Get upgrade path between N - 1 and current version not including current release
#
# @param [String] current_version
# @param [String] semver_component version number component for previous version detection - major|minor|patch
# @param [String] edition GitLab edition - ee or ce
def initialize(current_version, semver_component, edition)
@version_info = GitlabVersionInfo.new(current_version, edition)
@current_version = Gem::Version.new(current_version)
@semver_component = semver_component
@edition = edition
@logger = Runtime::Logger.logger
end
# Get upgrade path between releases
#
# Return array with only previous version for updates from previous minor, patch versions
#
# @return [Array<QA::Release>]
def fetch
return [release(latest_patch(previous_version))] unless major_upgrade?
# get versions between previous major and current version in gitlab upgrade path
path = full_upgrade_path.each_with_object([]) do |ver, arr|
next if ver <= previous_version || ver >= current_version
arr << ver
end
[previous_version, *path].map do |ver|
release(version_info.latest_patch(ver))
end
end
private
delegate :latest_patch, to: :version_info
attr_reader :version_info, :current_version, :semver_component, :edition, :logger
# Upgrade from previous major
#
# @return [Boolean]
def major_upgrade?
semver_component == "major"
end
# Docker release image
#
# @param [String] version
# @return [QA::Release]
def release(version)
QA::Release.new("gitlab/gitlab-#{edition}:#{version}-#{edition}.0")
end
# Previous gitlab version
#
# @return [Gem::Version]
def previous_version
@previous_version ||= version_info.previous_version(semver_component)
end
# Gitlab upgrade path
#
# @return [Array<Gem::Version>]
def full_upgrade_path
@full_upgrade_path ||= ::YAML
.safe_load(upgrade_path_yml, symbolize_names: true)
.map { |version| Gem::Version.new("#{version[:major]}.#{version[:minor]}") }
end
# Upgrade path yml
#
# @return [String]
def upgrade_path_yml
@upgrade_path_yml ||= begin
logger.info("Fetching gitlab upgrade path from 'gitlab-com/support/toolbox/upgrade-path' project")
HttpRequest.make_http_request(
url: "https://gitlab.com/gitlab-com/support/toolbox/upgrade-path/-/raw/main/upgrade-path.yml"
).body
end
end
end
end
end
end
# frozen_string_literal: true
module Gitlab
module QA
module Support
class GitlabVersionInfo
VERSION_PATTERN = /^(?<version>\d+\.\d+\.\d+)/.freeze
COMPONENT_PATTERN = /^(?<major>\d+)\.(?<minor>\d+)\.(?<patch>\d+)/.freeze
# Get previous gitlab version
#
# @param [String] current_version
# @param [String] edition GitLab edition - ee or ce
def initialize(current_version, edition)
@current_version = current_version
@edition = edition
@logger = Runtime::Logger.logger
end
# Get N - 1 version number
#
# @param [String] semver_component version number component for previous version detection - major|minor|patch
# @return [Gem::Version]
def previous_version(semver_component)
case semver_component
when "major"
previous_major
when "minor"
previous_minor
when "patch"
previous_patch
else
raise("Unsupported semver component, must be major|minor|patch")
end
end
# Get latest patch for specific version number
#
# @example
# latest_patch(Gem::Version.new("14.10")) => "14.10.5"
#
# @param [Gem::Version] version
# @return [String]
def latest_patch(version)
versions.find { |ver| ver.to_s.match?(/^#{version}/) }
end
private
attr_reader :current_version, :edition, :logger
# Current versions major version
#
# @return [Integer]
def current_major
@current_major ||= current_version.match(COMPONENT_PATTERN)[:major].to_i
end
# Current versions minor version
#
# @return [Integer]
def current_minor
@current_minor ||= current_version.match(COMPONENT_PATTERN)[:minor].to_i
end
# Current versions patch version
#
# @return [Integer]
def current_patch
@current_patch ||= current_version.match(COMPONENT_PATTERN)[:patch].to_i
end
# Previous major version
#
# @return [String]
def previous_major
return fallback_major unless tags
versions.find { |version| version.to_s.start_with?((current_major - 1).to_s) }
end
# Previous first major version image
#
# @return [String]
def fallback_major
previous_fallback_version(current_major - 1)
end
# Previous minor version
#
# @return [String]
def previous_minor
return fallback_minor unless tags
return previous_major if current_minor.zero?
versions.find { |version| version.to_s.match?(/^#{current_major}\.#{current_minor - 1}/) }
end
# Previous first minor version
#
# @return [String]
def fallback_minor
return previous_fallback_version(current_major, current_minor - 1) unless current_minor.zero?
previous_major
end
# Previous patch version
#
# @return [String]
def previous_patch
return fallback_patch unless tags
return previous_minor if current_patch.zero?
versions.find { |version| version.to_s.match?(/^#{current_major}\.#{current_minor}\.#{current_patch - 1}/) }
end
# Previous first patch version
#
# @return [String]
def fallback_patch
return previous_fallback_version(current_major, current_minor, current_patch - 1) unless current_patch.zero?
previous_minor
end
# Version number from docker tag
#
# @param [String] tag
# @return [String]
def version(tag)
tag.match(VERSION_PATTERN)[:version]
end
# Fallback version
#
# @param [Integer] major_component
# @param [Integer] minor_component
# @param [Integer] patch_component
# @return [Gem::Version]
def previous_fallback_version(major_component, minor_component = 0, patch_component = 0)
Gem::Version.new("#{major_component}.#{minor_component}.#{patch_component}")
end
# All available gitlab versions
#
# @return [Array<String>]
def versions
@versions = tags
.map { |tag| Gem::Version.new(tag.match(VERSION_PATTERN)[:version]) }
.sort
.reverse # reverse array so first match by .find always returns latest version
end
# All available docker tags
#
# @return [Array<String>]
def tags
return @tags if defined?(@tags)
logger.info("Fetching docker tags from 'gitlab/gitlab-#{edition}' registry")
response = HttpRequest.make_http_request(
url: "https://registry.hub.docker.com/v1/repositories/gitlab/gitlab-#{edition}/tags",
fail_on_error: false
)
unless response.code == 200
logger.error(" failed to fetch docker tags - code: #{response.code}, response: '#{response.body}'")
return @tags = nil
end
@tags = JSON
.parse(response.body, symbolize_names: true)
.map { |tag| tag[:name] }
.select { |tag| tag.match?(VERSION_PATTERN) }
end
end
end
end
end
# frozen_string_literal: true
require "logger"
describe Gitlab::QA::Support::GitlabUpgradePath do
subject(:upgrade_path) { described_class.new(current_version, semver, "ee").fetch }
let(:current_version) { "15.3.0-pre" }
let(:tags) do
<<~JSON
[
{"layer": "", "name": "latest"},
{"layer": "", "name": "14.8.0-ee.0"},
{"layer": "", "name": "14.9.5-ee.0"},
{"layer": "", "name": "14.10.5-ee.0"},
{"layer": "", "name": "15.0.5-ee.0"},
{"layer": "", "name": "15.2.1-ee.0"}
]
JSON
end
let(:upgrade_path_yml) do
<<~YML
- major: 13
minor: 8
- major: 13
minor: 12
- major: 14
minor: 0
comments: "**Migrations can take a long time!**"
- major: 14
minor: 3
- major: 14
minor: 9
- major: 14
minor: 10
- major: 15
minor: 0
YML
end
before do
allow(Gitlab::QA::Runtime::Logger).to receive(:logger) { Logger.new(StringIO.new) }
stub_request(:get, "https://registry.hub.docker.com/v1/repositories/gitlab/gitlab-ee/tags")
.with(body: "{}")
.to_return(status: 200, body: tags)
stub_request(:get, "https://gitlab.com/gitlab-com/support/toolbox/upgrade-path/-/raw/main/upgrade-path.yml")
.with(body: {})
.to_return(status: 200, body: upgrade_path_yml)
end
context "with upgrade from previous major" do
let(:semver) { "major" }
it "returns upgrade path between major versions" do
expect(upgrade_path.map(&:to_s)).to eq(["gitlab/gitlab-ee:14.10.5-ee.0", "gitlab/gitlab-ee:15.0.5-ee.0"])
end
end
context "with upgrade from previous minor" do
let(:semver) { "minor" }
it "returns upgrade path between minor versions" do
expect(upgrade_path.map(&:to_s)).to eq(["gitlab/gitlab-ee:15.2.1-ee.0"])
end
end
end
# frozen_string_literal: true
require "logger"
describe Gitlab::QA::Support::GitlabVersionInfo do
subject(:previous_version) { described_class.new(current_version, edition).previous_version(semver_component) }
let(:current_version) { "15.3.0-pre" }
let(:edition) { "ee" }
let(:tags) do
<<~JSON
[
{"layer": "", "name": "latest"},
{"layer": "", "name": "14.8.0-ee.0"},
{"layer": "", "name": "14.9.5-ee.0"},
{"layer": "", "name": "15.1.0-ee.0"},
{"layer": "", "name": "15.2.1-ee.0"}
]
JSON
end
before do
allow(Gitlab::QA::Runtime::Logger).to receive(:logger) { Logger.new(StringIO.new) }
stub_request(:get, "https://registry.hub.docker.com/v1/repositories/gitlab/gitlab-ee/tags")
.with(body: "{}")
.to_return(status: code, body: tags)
end
[
{ semver: "major", latest: Gem::Version.new("14.9.5"), latest_fallback: Gem::Version.new("14.0.0") },
{ semver: "minor", latest: Gem::Version.new("15.2.1"), latest_fallback: Gem::Version.new("15.2.0") },
{ semver: "patch", latest: Gem::Version.new("15.2.1"), latest_fallback: Gem::Version.new("15.2.0") }
].each do |params|
context "with previous major version" do
let(:semver_component) { params[:semver] }
context "with successfull response" do
let(:code) { 200 }
it "returns correct previous latest major version" do
expect(previous_version).to eq(params[:latest])
end
end
context "with unsuccessfull response" do
let(:code) { 500 }
it "returns correct previous fallback major version" do
expect(previous_version).to eq(params[:latest_fallback])
end
end
end
end
end
0% Loading or .
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment