Project

General

Profile

Revision 9fb80abe

Added by Ondřej Pražák over 7 years ago

making promoting decisions more robust

View differences:

README.md
42 42
|:-------:|:-------:|:-------------------:|:------------------:|:----------------:|
43 43
|>= 1.9   | >= 2.3  | ~> 0.0.1            | < 2.0.0            |   0.0.1          |
44 44

  
45

  
45 46
##Usage##
46 47

  
47 48
See [wiki](https://github.com/xprazak2/foreman-pipeline/wiki/Jobs).
app/lib/actions/foreman_pipeline/job/cv_promote_job_hook.rb
7 7
          Katello::ContentView::Promote
8 8
        end
9 9

  
10
        def plan(version, environment, is_force = false)          
10
        def plan(version, environment, is_force = false)
11 11
          valid_jobs = version.content_view.jobs.select { |job| job.is_valid? }
12 12
          jobs_to_run = valid_jobs.select { |job| version.eql? job.target_cv_version }
13
          allowed_jobs = jobs_to_run.select { |job| job.levelup_trigger && !job.version_already_promoted? }
14
          
13
          allowed_jobs = jobs_to_run.select { |job| job.levelup_trigger && job.not_yet_promoted? }
14

  
15 15
          plan_self(:trigger => trigger.output,
16 16
                    :job_ids => allowed_jobs.map(&:id),
17
                    :job_names => allowed_jobs.map(&:name))    
18
        end        
17
                    :job_names => allowed_jobs.map(&:name))
18
        end
19 19
      end
20 20
    end
21 21
  end
app/lib/actions/foreman_pipeline/job/cv_publish_job_hook.rb
10 10
        def plan(content_view, descripton)
11 11
          valid_jobs = content_view.jobs.select { |job| job.is_valid? }
12 12
          jobs_to_run = valid_jobs.select { |job| job.environment.library? }
13
          allowed_jobs = jobs_to_run.select { |job| job.levelup_trigger && !job.version_already_promoted? }
13
          allowed_jobs = jobs_to_run.select { |job| job.levelup_trigger && job.not_yet_promoted? }
14 14

  
15 15
          plan_self(:trigger => trigger.output,
16 16
                    :job_ids => allowed_jobs.map(&:id),
app/lib/actions/foreman_pipeline/job/deploy_new_host.rb
7 7

  
8 8
        def plan(job)
9 9
          sequence do
10
            redeploy = plan_action(Redeploy, job)
10
            # redeploy = plan_action(Redeploy, job)
11

  
12
            data = {
13
              :host => {
14
                :id => "fake_id",
15
                :name => "fake-name.example.com",
16
                :ip => "192.168.100.236"
17
                },
18
              :activation_key => {
19
                :cp_id => "asdfasdf"
20
              }
21
            }
22

  
11 23

  
12 24
            packages = plan_action(FindPackagesToInstall, :job_id => job.id)
13 25

  
14 26
            bulk_build = plan_action(Jenkins::BulkBuild,
15 27
                                      job.jenkins_projects,
16 28
                                      :job_id => job.id,
17
                                      :data => redeploy.output,
29
                                      :data => data,#redeploy.output,
18 30
                                      :packages => packages.output[:package_names])
19 31
            plan_action(Promote, :job_id => job.id, :build_fails => bulk_build.output[:failed_count])
20 32
          end
app/lib/actions/foreman_pipeline/job/multiple_promotions.rb
6 6

  
7 7
        def plan(job)
8 8
          sequence do
9
            job.to_environments.each do |env|
9
            job.envs_for_promotion.each do |env|
10 10
              plan_action(::Actions::Katello::ContentView::Promote, job.target_cv_version, env, false)
11 11
            end
12 12
          end
13 13
        end
14

  
15 14
      end
16 15
    end
17 16
  end
app/lib/actions/foreman_pipeline/job/promote.rb
9 9
        end
10 10

  
11 11
        def run
12
          unless job.environment.successors.empty?
13
            fail "Content View promotion disabled" if job.to_environments.empty?
12
          unless job.environment.successors.empty? #when we are not at the end of lifecycle path
13
            fail "Content View promotion disabled" unless job.should_be_promoted?
14
            fail "Content view already promoted to the next environment(s), skipping promotion(s)" unless job.promotion_safe?
14 15
            promote_environment
15 16
          end
16 17
        end
app/lib/actions/foreman_pipeline/job/repo_sync_job_hook.rb
11 11
          valid_jobs = repo.jobs.select { |job| job.is_valid? }
12 12

  
13 13
          jobs_to_run = valid_jobs.select { |job| job.target_cv_version_avail? }
14
          allowed_jobs = jobs_to_run.select { |job| job.sync_trigger && !job.version_already_promoted? }
14
          allowed_jobs = jobs_to_run.select { |job| job.sync_trigger && job.not_yet_promoted? }
15 15
          grouped_jobs = allowed_jobs.group_by(&:target_cv_version).values
16 16

  
17 17
          if grouped_jobs.max_by(&:length).length > 1
app/lib/actions/foreman_pipeline/job/run_job_manually.rb
4 4
      class RunJobManually < Actions::EntryAction
5 5

  
6 6
        def plan(job)
7
          if job.is_valid? && job.target_cv_version_avail? && !job.version_already_promoted?
7
          if job.is_valid? && job.target_cv_version_avail? && job.not_yet_promoted?
8 8
            plan_action(DeployNewHost, job)
9 9
            plan_self(:info => "Manually triggered job started.", :name => job.name)
10 10
          else
11
            plan_self(:info => "Manually triggered job execution skipped, check job configuration.", :name => job.name)
11
            plan_self(:info => "Manually triggered job execution skipped, check job configuration.", :name => job.name, :fail => true)
12 12
          end
13 13
        end
14 14

  
15 15
        def run
16 16
          output = input
17
          fail input[:info] if input[:fail]
18
        end
19

  
20
        def rescue_strategy_for_self
21
          Dynflow::Action::Rescue::Skip
17 22
        end
18 23

  
19 24
        def humanized_name
app/models/foreman_pipeline/concerns/content_view_extension.rb
1
module ForemanPipeline
1
  module ForemanPipeline
2 2
  module Concerns
3 3
    module ContentViewExtension
4 4
      extend ActiveSupport::Concern
......
6 6
      included do
7 7
        has_many :jobs, :class_name => 'ForemanPipeline::Job', :inverse_of => :content_view, :dependent => :nullify
8 8
      end
9
            
9

  
10 10
    end
11 11
  end
12 12
end
app/models/foreman_pipeline/concerns/content_view_repository_extension.rb
4 4
      extend ActiveSupport::Concern
5 5

  
6 6
      included do
7
        belongs_to :job, :class_name => 'ForemanPipeline::Job', :foreign_key => :content_view_id, :primary_key => :content_view_id        
7
        belongs_to :job, :class_name => 'ForemanPipeline::Job', :foreign_key => :content_view_id, :primary_key => :content_view_id
8 8
      end
9
      
9

  
10 10
    end
11
  end  
11
  end
12 12
end
app/models/foreman_pipeline/concerns/kt_environment_extension.rb
8 8
         :dependent => :destroy, :foreign_key => :to_environment_id
9 9
        has_many :jobs, :through => :job_to_environments, :class_name => 'ForemanPipeline::Job', :dependent => :nullify
10 10
      end
11
      
11

  
12 12
      def full_paths
13 13
        return [self.full_path] unless library?
14 14
        successors.map(&:full_path)
app/models/foreman_pipeline/concerns/repository_extension.rb
4 4
      extend ActiveSupport::Concern
5 5

  
6 6
      included do
7
        has_many :jobs, :class_name => 'ForemanPipeline::Job', :source => :job, :through => :content_view_repositories     
7
        has_many :jobs, :class_name => 'ForemanPipeline::Job', :source => :job, :through => :content_view_repositories
8 8
      end
9 9

  
10 10
    end
11
  end   
11
  end
12 12
end
app/models/foreman_pipeline/job.rb
26 26

  
27 27
    validates :name, :presence => true
28 28
    validates :organization, :presence => true
29
    validate :no_composite_view
29
    validate :no_composite_view, :check_env_succession
30 30

  
31 31
    def is_valid?
32 32
      !self.attributes.values.include?(nil) && !self.jenkins_instance.jenkins_user.nil?
......
39 39
    def target_cv_version
40 40
      fail "Cannot fetch target version, no environment set" if environment.nil?
41 41
      fail "Cannot fetch target version, no content view set" if content_view.nil?
42
      fail "Content view has no versions!" if content_view.content_view_versions.empty?
42 43
      self.environment.content_view_versions.where(:content_view_id => self.content_view.id).first
43 44
    end
44 45

  
......
49 50
      jenkins_instance.create_client(jenkins_instance.jenkins_user.name, jenkins_instance.jenkins_user.token)
50 51
    end
51 52

  
52
    def version_already_promoted?
53
      self.target_cv_version.environments.include?(self.environment.successor)
53
    #Is any to_env set? (Do we want to promote to any env?)
54
    def should_be_promoted?
55
      !to_environments.empty?
54 56
    end
55 57

  
56
    def environment_in_paths?(env_id)
57
      paths.map(&:full_path).flatten.uniq.map(&:id).include? env_id
58
    # this shlould make sure not to trigger cyclic runs in hook actions
59
    def not_yet_promoted?
60
      # no to_envs means we do not want to promote, no need to check further here
61
      return true if to_environments.empty?
62
      #we want to promote, but are any of to_envs safe to promote to?
63
      can_be_promoted?
64
    end
65

  
66
    #If we want to promote, is it safe (or could we get a cycle)?
67
    def promotion_safe?
68
      should_be_promoted? ? can_be_promoted? : false
69
    end
70

  
71
    #we have some to_envs set (== we want to promote), but cv version may already be in those envs
72
    def envs_for_promotion
73
      to_environments.reject { |env| target_cv_version.environments.include?(env) }
74
    end
75

  
76
    def can_be_promoted?
77
      !envs_for_promotion.empty?
58 78
    end
59 79

  
60 80
    def available_compute_resources
......
68 88
       "Cannot add content view, only non-composites allowed.") if !content_view.nil? && content_view.composite?
69 89
    end
70 90

  
91
    def check_env_succession
92
      if environment && should_be_promoted?
93
        to_environments.each do |to_env|
94
          unless to_env.prior == environment
95
            errors.add(:base, "Environment succession violation: #{to_env.name}")
96
          end
97
        end
98
      end
99
    end
71 100
  end
72 101
end
db/migrate/20141014125836_make_content_view_and_hostgroup_optional_for_job.rb
1 1
class MakeContentViewAndHostgroupOptionalForJob < ActiveRecord::Migration
2
  def up    
2
  def up
3 3
    change_column_null :integration_jobs, :content_view_id, true
4 4
    change_column_null :integration_jobs, :hostgroup_id, true
5 5
  end
lib/foreman_pipeline/plugin.rb
1 1
Foreman::Plugin.register :foreman_pipeline do
2
  
2
  requires_foreman '>= 1.9'
3

  
3 4
  sub_menu :top_menu, :foreman_pipeline_menu, :caption => N_('Pipeline') do
4 5
    menu :top_menu,
5
         :jobs,       
6
         :jobs,
6 7
         :caption => N_("Jobs"),
7 8
         :url => '/jobs',
8 9
         :url_hash => {:controller => 'foreman_pipeline/api/jobs', :action => 'index'},
test/factories/hostgroup_factory.rb
1 1
FactoryGirl.define do
2 2
  factory :pipeline_hostgroup, :class => Hostgroup do
3 3
    sequence(:name) { |n| "hostgroup#{n}" }
4
    compute_profile
5 4
  end
6 5
end
test/foreman_pipeline_plugin_test_helper.rb
1 1
require "test_helper"
2 2

  
3
def katello_root
4
  "#{ForemanPipeline::Engine::Railties.engines
5
    .select {|engine| engine.railtie_name == "katello"}
6
    .first.config.root}"
7
end
8

  
9
require "#{katello_root}/spec/models/model_spec_helper"
10
FactoryGirl.definition_file_paths << File.join(katello_root, 'test', 'factories')
3
FactoryGirl.definition_file_paths << File.join(Katello::Engine.root, 'test', 'factories')
11 4
FactoryGirl.definition_file_paths << File.join(File.dirname(__FILE__), 'factories')
12 5
FactoryGirl.reload
13 6

  
7
require "#{Katello::Engine.root}/test/support/fixtures_support"
8

  
9
module FixtureTestCase
10
  extend ActiveSupport::Concern
11

  
12
  included do
13
    extend ActiveRecord::TestFixtures
14

  
15
    self.use_transactional_fixtures = true
16
    self.use_instantiated_fixtures = false
17
    self.pre_loaded_fixtures = true
18

  
19
    Katello::FixturesSupport.set_fixture_classes(self)
20

  
21
    self.fixture_path = Dir.mktmpdir("katello_fixtures")
22
    FileUtils.cp(Dir.glob("#{Katello::Engine.root}/test/fixtures/models/*"), self.fixture_path)
23
    FileUtils.cp(Dir.glob("#{Rails.root}/test/fixtures/*"), self.fixture_path)
24
    fixtures(:all)
25
    FIXTURES = load_fixtures
26

  
27
    # load_permissions
28

  
29
    Setting::Katello.load_defaults
30
  end
31

  
32
  module ClassMethods
33
    def before_suite
34
      @@admin = ::User.find(FIXTURES['users']['admin']['id'])
35
      User.current = @@admin
36
    end
37
  end
38
end
39

  
14 40
class ActiveSupport::TestCase
41
  include FixtureTestCase
15 42

  
16 43
  def get_organization(org = nil)
17 44
    saved_user = User.current
......
24 51
    User.current = saved_user
25 52
    organization
26 53
  end
27

  
28 54
end
test/unit/job_test.rb
5 5
  def setup
6 6
    @organization = get_organization
7 7
    @compute_resource = FactoryGirl.create(:compute_resource, :libvirt)
8
    @hostgroup = FactoryGirl.create(:pipeline_hostgroup, :compute_profile => @compute_profile)
8
    @hostgroup = FactoryGirl.create(:pipeline_hostgroup)
9 9
    @content_view = FactoryGirl.create(:katello_content_view)
10 10
    @jenkins_user = FactoryGirl.create(:jenkins_user, :organization => get_organization)
11 11
    @jenkins_instance = FactoryGirl.create(:jenkins_instance, :jenkins_user => @jenkins_user, :organization => get_organization)
......
23 23
    assert job.is_valid?
24 24
  end
25 25

  
26
  test "should find target cv" do
27
    cv = Katello::ContentView.find(katello_content_views(:acme_default))
28
    env = Katello::KTEnvironment.find(katello_environments(:library))
29
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
30

  
31
    job = ForemanPipeline::Job.create(:name => "test job",
32
                                      :organization => @organization,
33
                                      :hostgroup => @hostgroup,
34
                                      :compute_resource => @compute_resource,
35
                                      :content_view => cv,
36
                                      :jenkins_instance => @jenkins_instance,
37
                                      :environment => env,
38
                                      :to_environments => [to_env])
39
    target_v = cv.content_view_versions.select { |v| v.environments.include?(env) && !v.environments.include?(to_env) }.first
40
    assert_equal target_v, job.target_cv_version
41
  end
42

  
43
  test "should not find target cv" do
44
    cv = Katello::ContentView.find(katello_content_views(:acme_default))
45
    env = Katello::KTEnvironment.find(katello_environments(:test))
46

  
47
    job = ForemanPipeline::Job.create(:name => "test job",
48
                                      :organization => @organization,
49
                                      :hostgroup => @hostgroup,
50
                                      :compute_resource => @compute_resource,
51
                                      :content_view => cv,
52
                                      :jenkins_instance => @jenkins_instance,
53
                                      :environment => env)
54

  
55
    assert_equal nil, job.target_cv_version
56
  end
57

  
58
  test "should find envs for promotion" do
59
    cv = Katello::ContentView.find(katello_content_views(:acme_default))
60
    env = Katello::KTEnvironment.find(katello_environments(:library))
61
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
62
    to_env2 = Katello::KTEnvironment.find(katello_environments(:staging))
63

  
64
    job = ForemanPipeline::Job.create(:name => "test job",
65
                                      :organization => @organization,
66
                                      :hostgroup => @hostgroup,
67
                                      :compute_resource => @compute_resource,
68
                                      :content_view => cv,
69
                                      :jenkins_instance => @jenkins_instance,
70
                                      :environment => env,
71
                                      :to_environments => [to_env, to_env2])
72
    assert_equal [to_env, to_env2], job.envs_for_promotion
73
  end
74

  
75
  test "envs_for_promotion should exclude one env from promotions" do
76
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
77
    env = Katello::KTEnvironment.find(katello_environments(:library))
78
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
79
    to_env2 = Katello::KTEnvironment.find(katello_environments(:staging))
80

  
81
    job = ForemanPipeline::Job.create(:name => "test job",
82
                                      :organization => @organization,
83
                                      :hostgroup => @hostgroup,
84
                                      :compute_resource => @compute_resource,
85
                                      :content_view => cv,
86
                                      :jenkins_instance => @jenkins_instance,
87
                                      :environment => env,
88
                                      :to_environments => [to_env, to_env2])
89
    assert_equal [to_env2], job.envs_for_promotion
90
  end
91

  
92
  test "job can be promoted" do
93
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
94
    env = Katello::KTEnvironment.find(katello_environments(:library))
95
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
96
    to_env2 = Katello::KTEnvironment.find(katello_environments(:staging))
97

  
98
    job = ForemanPipeline::Job.create(:name => "test job",
99
                                      :organization => @organization,
100
                                      :hostgroup => @hostgroup,
101
                                      :compute_resource => @compute_resource,
102
                                      :content_view => cv,
103
                                      :jenkins_instance => @jenkins_instance,
104
                                      :environment => env,
105
                                      :to_environments => [to_env, to_env2])
106
    assert job.can_be_promoted?
107
  end
108

  
109
  test "job cannot be promoted" do
110
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
111
    env = Katello::KTEnvironment.find(katello_environments(:library))
112
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
113

  
114
    job = ForemanPipeline::Job.create(:name => "test job",
115
                                      :organization => @organization,
116
                                      :hostgroup => @hostgroup,
117
                                      :compute_resource => @compute_resource,
118
                                      :content_view => cv,
119
                                      :jenkins_instance => @jenkins_instance,
120
                                      :environment => env,
121
                                      :to_environments => [to_env])
122
    refute job.can_be_promoted?
123
  end
124

  
125
  test "is promotion safe" do
126
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
127
    env = Katello::KTEnvironment.find(katello_environments(:library))
128
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
129
    to_env2 = Katello::KTEnvironment.find(katello_environments(:staging))
130

  
131
    job = ForemanPipeline::Job.create(:name => "test job",
132
                                      :organization => @organization,
133
                                      :hostgroup => @hostgroup,
134
                                      :compute_resource => @compute_resource,
135
                                      :content_view => cv,
136
                                      :jenkins_instance => @jenkins_instance,
137
                                      :environment => env,
138
                                      :to_environments => [to_env, to_env2])
139
    assert job.promotion_safe?
140
  end
141

  
142
  test "is not promotion safe" do
143
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
144
    env = Katello::KTEnvironment.find(katello_environments(:library))
145

  
146
    job = ForemanPipeline::Job.create(:name => "test job",
147
                                      :organization => @organization,
148
                                      :hostgroup => @hostgroup,
149
                                      :compute_resource => @compute_resource,
150
                                      :content_view => cv,
151
                                      :jenkins_instance => @jenkins_instance,
152
                                      :environment => env)
153
    refute job.promotion_safe?
154
  end
155

  
156
  test "is not promotion safe again" do
157
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
158
    env = Katello::KTEnvironment.find(katello_environments(:library))
159
    to_env = Katello::KTEnvironment.find(katello_environments(:dev))
160

  
161
    job = ForemanPipeline::Job.create(:name => "test job",
162
                                      :organization => @organization,
163
                                      :hostgroup => @hostgroup,
164
                                      :compute_resource => @compute_resource,
165
                                      :content_view => cv,
166
                                      :jenkins_instance => @jenkins_instance,
167
                                      :environment => env,
168
                                      :to_environments => [to_env])
169
    refute job.promotion_safe?
170
  end
171

  
172
  test "should detect env succession violation" do
173
    cv = Katello::ContentView.find(katello_content_views(:library_dev_view))
174
    env = Katello::KTEnvironment.find(katello_environments(:library))
175
    to_env = Katello::KTEnvironment.find(katello_environments(:test))
176

  
177
    job = ForemanPipeline::Job.new(:name => "test job",
178
                                      :organization => @organization,
179
                                      :hostgroup => @hostgroup,
180
                                      :compute_resource => @compute_resource,
181
                                      :content_view => cv,
182
                                      :jenkins_instance => @jenkins_instance,
183
                                      :environment => env,
184
                                      :to_environments => [to_env])
185
    job.save
186
    assert_equal "Environment succession violation: #{to_env.name}", job.errors[:base].first
187
  end
26 188
end

Also available in: Unified diff