Skip to content

Commit

Permalink
[api] support maintenance incident requests with package selected sou…
Browse files Browse the repository at this point in the history
…rces.

[api] support multiple actions into one incident per request
[api] support request to merge into existing incident
[api] support redirecting maintenance incident request into define incident project
  • Loading branch information
adrianschroeter committed Jan 19, 2012
1 parent d22d66d commit 72dd892
Show file tree
Hide file tree
Showing 3 changed files with 226 additions and 17 deletions.
93 changes: 80 additions & 13 deletions src/api/app/controllers/request_controller.rb
Original file line number Diff line number Diff line change
Expand Up @@ -555,11 +555,6 @@ def create_create
end

if action.value("type") == "maintenance_incident"
if action.source.has_attribute?(:package)
render_error :status => 400, :errorcode => 'illegal_request',
:message => "Maintenance requests accept only entire projects as source"
return
end
# find target project via attribute, if not specified
unless action.has_element? 'target'
action.add_element 'target'
Expand Down Expand Up @@ -587,7 +582,7 @@ def create_create
end
# validate project type
prj = DbProject.get_by_name(action.target.project)
unless prj.project_type == "maintenance"
unless [ "maintenance", "maintenance_incident" ].include? prj.project_type
render_error :status => 400, :errorcode => "incident_has_no_maintenance_project",
:message => "incident projects shall only create below maintenance projects"
return
Expand Down Expand Up @@ -925,6 +920,10 @@ def command_diff
end
end

def command_setincident
command_changestate# :cmd => "setincident",
# :incident
end
def command_addreview
command_changestate# :cmd => "addreview",
# :by_user => params[:by_user], :by_group => params[:by_group], :by_project => params[:by_project], :by_package => params[:by_package]
Expand Down Expand Up @@ -1022,7 +1021,7 @@ def command_changestate
render_error :status => 403, :errorcode => "post_request_no_permission",
:message => "Deletion of a request is only permitted for administrators. Please revoke the request instead."
return
elsif params[:cmd] == "addreview"
elsif params[:cmd] == "addreview" or params[:cmd] == "setincident"
unless [ "review", "new" ].include? req.state.name
render_error :status => 403, :errorcode => "add_review_no_permission",
:message => "The request is not in state new or review"
Expand Down Expand Up @@ -1129,6 +1128,28 @@ def command_changestate
return
end
end
# maintenance incident target permission checks
if [ "maintenance_incident" ].include? action.value("type")
if params[:cmd] == "setincident"
unless target_project.project_type == "maintenance"
render_error :status => 404, :errorcode => "target_not_maintenance",
:message => "The target project is not of type maintenance but #{target_project.project_type}"
return
end
tip = DbProject.get_by_name(action.target.project + ":" + params[:incident])
if tip.is_locked?
render_error :status => 403, :errorcode => "project_locked",
:message => "The target project is locked"
return
end
else
unless [ "maintenance", "maintenance_incident" ].include? target_project.project_type
render_error :status => 404, :errorcode => "target_not_maintenance_or_incident",
:message => "The target project is not of type maintenance or incident but #{target_project.project_type}"
return
end
end
end
# write access check in release targets
if [ "maintenance_release" ].include? action.value("type") and params[:cmd] == "changestate" and params[:newstate] == "accepted"
source_project.repositories.each do |repo|
Expand Down Expand Up @@ -1277,6 +1298,42 @@ def command_changestate

# permission granted for the request at this point

# special command defining an incident to be merged
req.each_action do |action|
incident_project = nil
if action.value("type") == "maintenance_incident"
tprj = DbProject.get_by_name action.target.project

if params[:cmd] == "setincident"
# use an existing incident
if tprj.project_type == "maintenance"
tprj = DbProject.get_by_name(action.target.project + ":" + params[:incident])
action.target.set_attribute("project", tprj.name)
end
else # the accept case, create a new incident if needed
if tprj.project_type == "maintenance"
# create incident if it is a maintenance project
unless incident_project
source = DbProject.get_by_name(action.source.project)
incident_project = create_new_maintenance_incident(tprj, source, req ).db_project
end
unless incident_project.name.start_with?(tprj.name)
render_error :status => 404, :errorcode => "multiple_maintenance_incidents",
:message => "This request handles different maintenance incidents, this is not allowed !"
return
end
action.target.set_attribute("project", incident_project.name)
end
end
req.save
end
end
# job done by changing target
if params[:cmd] == "setincident"
render_ok
return
end

# All commands are process by the backend. Just the request accept is controlled by the api.
path = request.path + build_query_from_hash(params, [:cmd, :user, :newstate, :by_user, :by_group, :by_project, :by_package, :superseded_by, :comment])
unless params[:cmd] == "changestate" and params[:newstate] == "accepted"
Expand All @@ -1287,6 +1344,8 @@ def command_changestate

# have a unique time stamp for release
acceptTimeStamp = Time.now.utc.strftime "%Y-%m-%d %H:%M:%S"
# all maintenance_incident actions go into the same incident project
incident_project = nil

# use the request description as comments for history
params[:comment] = req.value(:description)
Expand Down Expand Up @@ -1432,13 +1491,21 @@ def command_changestate
Suse::Backend.delete delete_path

elsif action.value("type") == "maintenance_incident"
# create incident project
source_project = DbProject.get_by_name(action.source.project)
target_project = DbProject.get_by_name(action.target.project)
incident = create_new_maintenance_incident(target_project, source_project, req )
# create or merge into incident project
source = nil
if action.source.has_attribute? :package
source = DbPackage.get_by_project_and_name(action.source.project, action.source.package)
else
source = DbProject.get_by_name(action.source.project)
end

incident_project = DbProject.get_by_name(action.target.project)

# the incident got created before
merge_into_maintenance_incident(incident_project, source, request = nil)

# update request with real target project
action.target.set_attribute("project", incident.db_project.name)
# update action with real target project
action.target.set_attribute("project", incident_project.name)
req.save

elsif action.value("type") == "maintenance_release"
Expand Down
1 change: 0 additions & 1 deletion src/api/app/helpers/maintenance_helper.rb
Original file line number Diff line number Diff line change
Expand Up @@ -50,7 +50,6 @@ def create_new_maintenance_incident( maintenanceProject, baseProject = nil, requ
mi.db_project_id = tprj.id
mi.save!
end
merge_into_maintenance_incident(tprj, baseProject, request ) if baseProject
return mi
end

Expand Down
149 changes: 146 additions & 3 deletions src/api/test/functional/maintenance_test.rb
Original file line number Diff line number Diff line change
Expand Up @@ -85,7 +85,150 @@ def test_branch_package
assert_response :success
end

def test_mbranch_and_maintenance_request
def test_mbranch_and_maintenance_per_package_request
# setup maintained attributes
prepare_request_with_user "maintenance_coord", "power"
# single packages
post "/source/BaseDistro2.0/pack2/_attribute", "<attributes><attribute namespace='OBS' name='Maintained' /></attributes>"
assert_response :success
post "/source/BaseDistro3/pack2/_attribute", "<attributes><attribute namespace='OBS' name='Maintained' /></attributes>"
assert_response :success

# search for maintained packages like osc is doing
get "/search/package?match=%28%40name+%3D+%27pack2%27%29+and+%28project%2Fattribute%2F%40name%3D%27OBS%3AMaintained%27+or+attribute%2F%40name%3D%27OBS%3AMaintained%27%29"
assert_response :success
ret = ActiveXML::XMLNode.new @response.body
assert_equal ret.package.each.count, 3

# do the real mbranch for default maintained packages
prepare_request_with_user "tom", "thunder"
post "/source", :cmd => "branch", :package => "pack2"
assert_response :success

# validate result is done in project wide test case

# do some file changes
put "/source/home:tom:branches:OBS_Maintained:pack2/pack2.BaseDistro2.0_LinkedUpdateProject/new_file", "new_content_0815"
assert_response :success
put "/source/home:tom:branches:OBS_Maintained:pack2/pack2.BaseDistro3/new_file", "new_content_2137"
assert_response :success

# create maintenance request for one package
# without specifing target, the default target must get found via attribute
post "/request?cmd=create", '<request>
<action type="maintenance_incident">
<source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro3" />
<options>
<sourceupdate>cleanup</sourceupdate>
</options>
</action>
<state name="new" />
</request>'
assert_response :success
assert_tag( :tag => "target", :attributes => { :project => "My:Maintenance" } )
node = ActiveXML::XMLNode.new(@response.body)
assert node.has_attribute?(:id)
id1 = node.value(:id)

# accept request
prepare_request_with_user "maintenance_coord", "power"
post "/request/#{id1}?cmd=changestate&newstate=accepted"
assert_response :success

get "/request/#{id1}"
assert_response :success
data = REXML::Document.new(@response.body)
maintenanceProject=data.elements["/request/action/target"].attributes.get_attribute("project").to_s
assert_not_equal maintenanceProject, "My:Maintenance"

#validate cleanup
get "/source/home:tom:branches:OBS_Maintained:pack2/pack2.BaseDistro3"
assert_response 404
get "/source/home:tom:branches:OBS_Maintained:pack2"
assert_response :success

# create maintenance request with invalid target
post "/request?cmd=create", '<request>
<action type="maintenance_incident">
<source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
<target project="home:tom" />
</action>
</request>'
assert_response 400
assert_tag :tag => "status", :attributes => { :code => "incident_has_no_maintenance_project" }
# valid target..
post "/request?cmd=create", '<request>
<action type="maintenance_incident">
<source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
<target project="'+maintenanceProject+'" />
</action>
</request>'
assert_response :success
assert_tag( :tag => "target", :attributes => { :project => maintenanceProject } )
node = ActiveXML::XMLNode.new(@response.body)
assert node.has_attribute?(:id)
id2 = node.value(:id)
# ... but do not use it
post "/request/#{id2}?cmd=changestate&newstate=revoked"
assert_response :success

# create maintenance request for two further packages
# without specifing target, the default target must get found via attribute
post "/request?cmd=create", '<request>
<action type="maintenance_incident">
<source project="home:tom:branches:OBS_Maintained:pack2" package="pack2.BaseDistro2.0_LinkedUpdateProject" />
<options>
<sourceupdate>cleanup</sourceupdate>
</options>
</action>
<action type="maintenance_incident">
<source project="home:tom:branches:OBS_Maintained:pack2" package="pack2_linked.BaseDistro2.0_LinkedUpdateProject" />
<options>
<sourceupdate>cleanup</sourceupdate>
</options>
</action>
<state name="new" />
</request>'
assert_response :success
assert_tag( :tag => "target", :attributes => { :project => "My:Maintenance" } )
node = ActiveXML::XMLNode.new(@response.body)
assert node.has_attribute?(:id)
id2 = node.value(:id)

# set incident to merge into existing one
prepare_request_with_user "maintenance_coord", "power"
post "/request/#{id2}?cmd=setincident&incident=#{maintenanceProject.gsub(/.*:/,'')}"
assert_response :success

get "/request/#{id2}"
assert_response :success
data = REXML::Document.new(@response.body)
maintenanceNotNewProject=data.elements["/request/action/target"].attributes.get_attribute("project").to_s
assert_equal maintenanceProject, maintenanceNotNewProject

# try to do it again
prepare_request_with_user "maintenance_coord", "power"
post "/request/#{id2}?cmd=setincident&incident=#{maintenanceProject.gsub(/.*:/,'')}"
assert_response 404
assert_tag :tag => "status", :attributes => { :code => "target_not_maintenance" }

# accept request
prepare_request_with_user "maintenance_coord", "power"
post "/request/#{id2}?cmd=changestate&newstate=accepted&force=1" # ignore reviews and accept
assert_response :success

get "/request/#{id2}"
assert_response :success
data = REXML::Document.new(@response.body)
maintenanceNotNewProject=data.elements["/request/action/target"].attributes.get_attribute("project").to_s
assert_equal maintenanceProject, maintenanceNotNewProject

#validate cleanup
get "/source/home:tom:branches:OBS_Maintained:pack2"
assert_response 404
end

def test_mbranch_and_maintenance_entire_project_request
prepare_request_with_user "king", "sunflower"
put "/source/ServicePack/_meta", "<project name='ServicePack'><title/><description/><link project='kde4'/></project>"
assert_response :success
Expand Down Expand Up @@ -218,9 +361,9 @@ def test_mbranch_and_maintenance_request
assert_match(/branch target package already exists:/, @response.body)

# create patchinfo
post "/source/BaseDistro?cmd=createpatchinfo&new_format=1"
post "/source/BaseDistro?cmd=createpatchinfo"
assert_response 403
post "/source/home:tom:branches:OBS_Maintained:pack2?cmd=createpatchinfo&new_format=1"
post "/source/home:tom:branches:OBS_Maintained:pack2?cmd=createpatchinfo"
assert_response :success
assert_tag( :tag => "data", :attributes => { :name => "targetpackage"}, :content => "patchinfo" )
assert_tag( :tag => "data", :attributes => { :name => "targetproject"}, :content => "home:tom:branches:OBS_Maintained:pack2" )
Expand Down

0 comments on commit 72dd892

Please sign in to comment.