class TestTasks(unittest.TestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.task = data_setup.create_task(name=u'/fake/task/here') distro_tree = data_setup.create_distro_tree() self.user = data_setup.create_user() self.xmljob = XmlJob(xmltramp.parse(''' <job> <whiteboard>job with fake task</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="%s" /> </distroRequires> <hostRequires/> <task name="%s" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''' % (distro_tree.distro.name, self.task.name))) session.flush() def tearDown(self): session.rollback() session.close() def test_enable_task(self): self.task.valid=True session.flush() self.controller.process_xmljob(self.xmljob, self.user) def test_disable_task(self): self.task.valid=False session.flush() self.assertRaises(BX, lambda: self.controller.process_xmljob(self.xmljob, self.user))
class TestTasks(DatabaseTestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.task = data_setup.create_task(name=u'/fake/task/here') distro_tree = data_setup.create_distro_tree() self.user = data_setup.create_user() self.xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with fake task</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="%s" /> </distroRequires> <hostRequires/> <task name="%s" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''' % (distro_tree.distro.name, self.task.name)) session.flush() def tearDown(self): session.rollback() def test_enable_task(self): self.task.valid = True session.flush() self.controller.process_xmljob(self.xmljob, self.user) def test_disable_task(self): self.task.valid = False session.flush() self.assertRaises( BX, lambda: self.controller.process_xmljob(self.xmljob, self.user))
class TestUpdateStatus(DatabaseTestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') session.flush() def tearDown(self): session.commit() def test_abort_recipe_bubbles_status_to_job(self): xmljob = XmlJob(xmltramp.parse(''' <job> <whiteboard>job </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''')) job = self.controller.process_xmljob(xmljob, self.user) session.flush() for recipeset in job.recipesets: for recipe in recipeset.recipes: recipe.process() recipe.queue() recipe.schedule() recipe.waiting() # Abort the first recipe. job.recipesets[0].recipes[0].abort() job.update_status() # Verify that it and its children are aborted. self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) for task in job.recipesets[0].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.aborted) # Verify that the second recipe and its children are still waiting. self.assertEquals(job.recipesets[1].recipes[0].status, TaskStatus.waiting) for task in job.recipesets[1].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.waiting) # Verify that the job still shows waiting. self.assertEquals(job.status, TaskStatus.waiting) # Abort the second recipe now. job.recipesets[1].recipes[0].abort() job.update_status() # Verify that the whole job shows aborted now. self.assertEquals(job.status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=903935 def test_finished_recipe_with_unstarted_guests(self): # host completes, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) data_setup.mark_recipe_running(job.recipesets[0].recipes[0], only=True) job.recipesets[0].recipes[0].tasks[-1].stop() job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.completed) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # host aborts, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) job.recipesets[0].recipes[0].abort(msg='blorf') job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) def test_update_status_can_be_roundtripped_35508(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'job_35508.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.wrappedEl('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.wrappedEl('result')), job.result) for i, recipeset in enumerate(xmljob.iter_recipeSets()): for j, recipe in enumerate(recipeset.iter_recipes()): self.assertEquals(TaskStatus.from_string(recipe.wrappedEl('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.wrappedEl('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.iter_tasks()): self.assertEquals(TaskStatus.from_string(task.status), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals(TaskResult.from_string(task.result), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0) def test_update_status_can_be_roundtripped_40214(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'job_40214.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.wrappedEl('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.wrappedEl('result')), job.result) for i, recipeset in enumerate(xmljob.iter_recipeSets()): for j, recipe in enumerate(recipeset.iter_recipes()): self.assertEquals(TaskStatus.from_string(recipe.wrappedEl('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.wrappedEl('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.iter_tasks()): self.assertEquals(TaskStatus.from_string(task.status), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals(TaskResult.from_string(task.result), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0)
class TestJobsController(DatabaseTestCase): maxDiff = None @with_transaction def setUp(self): from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() group = data_setup.create_group(group_name='somegroup') self.user.groups.append(group) testutil.set_identity_user(self.user) data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') data_setup.create_product(product_name=u'the_product') def tearDown(self): testutil.set_identity_user(None) def test_uploading_job_without_recipeset_raises_exception(self): xmljob = XmlJob( xmltramp.parse(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''')) with session.begin(): self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): session.begin() try: xmljob = XmlJob( xmltramp.parse(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''')) self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) finally: session.rollback() def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) with session.begin(): job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = job.to_xml(clone=True).toprettyxml(indent=' ') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml)
class TestUpdateStatus(DatabaseTestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() session.flush() def tearDown(self): session.commit() def test_abort_recipe_bubbles_status_to_job(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) session.flush() for recipeset in job.recipesets: for recipe in recipeset.recipes: recipe.process() recipe.queue() recipe.schedule() recipe.waiting() # Abort the first recipe. job.recipesets[0].recipes[0].abort() job.update_status() # Verify that it and its children are aborted. self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) for task in job.recipesets[0].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.aborted) # Verify that the second recipe and its children are still waiting. self.assertEquals(job.recipesets[1].recipes[0].status, TaskStatus.waiting) for task in job.recipesets[1].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.waiting) # Verify that the job still shows waiting. self.assertEquals(job.status, TaskStatus.waiting) # Abort the second recipe now. job.recipesets[1].recipes[0].abort() job.update_status() # Verify that the whole job shows aborted now. self.assertEquals(job.status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=903935 def test_finished_recipe_with_unstarted_guests(self): # host completes, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) data_setup.mark_recipe_running(job.recipesets[0].recipes[0], only=True) job.recipesets[0].recipes[0].tasks[-1].stop() job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.completed) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # host aborts, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) job.recipesets[0].recipes[0].abort(msg='blorf') job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=1309530 def test_recipe_start_time_is_set_to_rebooted_timestamp(self): # For a normal recipe running on a system, update_status should set # recipe.start_time to the rebooted timestamp. job = data_setup.create_job() data_setup.mark_job_scheduled(job) recipe = job.recipesets[0].recipes[0] self.assertIsNone(recipe.start_time) recipe.provision() recipe.installation.rebooted = datetime.datetime(2016, 2, 18, 13, 0, 0) job.update_status() self.assertEqual(recipe.start_time, datetime.datetime(2016, 2, 18, 13, 0, 0)) self.assertEqual(recipe.status, TaskStatus.installing) # https://bugzilla.redhat.com/show_bug.cgi?id=1309530 def test_recipe_start_time_is_set_to_first_task_start_time(self): # For guest recipes, and systems without power control, there is no # rebooted timestamp. Instead the first task just gets started. job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_scheduled(job) guestrecipe = job.recipesets[0].recipes[0].guests[0] self.assertIsNone(guestrecipe.start_time) # /distribution/virt/install starts the first task before it starts # running the guest installation. guestrecipe.provision() guestrecipe.first_task.start() guestrecipe.first_task.start_time = datetime.datetime( 2016, 2, 18, 14, 0, 0) self.assertIsNone(guestrecipe.installation.rebooted) job.update_status() self.assertEqual(guestrecipe.start_time, datetime.datetime(2016, 2, 18, 14, 0, 0)) # https://bugzilla.redhat.com/show_bug.cgi?id=991245#c12 def test_status_is_Waiting_when_installation_is_finished_but_tasks_have_not_started( self): # Beah <= 0.7.9 will consider 'Installing' to mean that the recipe is # finished, so we want the status to go back to 'Waiting' once the # installation is finished. job = data_setup.create_job() recipe = job.recipesets[0].recipes[0] data_setup.mark_recipe_installing(recipe) data_setup.mark_recipe_installation_finished(recipe) self.assertEqual(recipe.tasks[0].status, TaskStatus.waiting) self.assertIsNone(recipe.tasks[0].start_time) self.assertEqual(recipe.status, TaskStatus.waiting) # https://bugzilla.redhat.com/show_bug.cgi?id=1558776 def test_scheduler_status_is_not_reset_on_already_released_systems(self): first_recipe = data_setup.create_recipe() second_recipe = data_setup.create_recipe() job = data_setup.create_job_for_recipesets([ data_setup.create_recipeset_for_recipes([first_recipe]), data_setup.create_recipeset_for_recipes([second_recipe]) ]) data_setup.mark_recipe_complete(first_recipe) first_system = first_recipe.resource.system self.assertEquals(first_system.scheduler_status, SystemSchedulerStatus.pending) # Pretend the scheduler has set the system back to idle first_system.scheduler_status = SystemSchedulerStatus.idle data_setup.mark_recipe_scheduled(second_recipe) job.update_status() # The bug was that job.update_status() would reset the *first* recipe's # system back to pending, even though it had already been released and # could potentially be reserved for another recipe already. self.assertEquals(first_system.scheduler_status, SystemSchedulerStatus.idle) def test_update_status_can_be_roundtripped_35508(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'job_35508.xml') xmljob = lxml.etree.fromstring(complete_job_xml) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.get('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.get('result')), job.result) for i, recipeset in enumerate(xmljob.xpath('recipeSet')): for j, recipe in enumerate(recipeset.xpath('recipe')): self.assertEquals(TaskStatus.from_string(recipe.get('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.get('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.xpath('task')): self.assertEquals( TaskStatus.from_string(task.get('status')), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals( TaskResult.from_string(task.get('result')), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0) def test_update_status_can_be_roundtripped_40214(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'job_40214.xml') xmljob = lxml.etree.fromstring(complete_job_xml) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.get('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.get('result')), job.result) for i, recipeset in enumerate(xmljob.xpath('recipeSet')): for j, recipe in enumerate(recipeset.xpath('recipes')): self.assertEquals(TaskStatus.from_string(recipe.get('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.get('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.xpath('task')): self.assertEquals( TaskStatus.from_string(task.get('status')), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals( TaskResult.from_string(task.get('result')), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0)
class TestUpdateStatus(DatabaseTestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') session.flush() def tearDown(self): session.commit() def test_abort_recipe_bubbles_status_to_job(self): xmljob = XmlJob( xmltramp.parse(''' <job> <whiteboard>job </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''')) job = self.controller.process_xmljob(xmljob, self.user) session.flush() for recipeset in job.recipesets: for recipe in recipeset.recipes: recipe.process() recipe.queue() recipe.schedule() recipe.waiting() # Abort the first recipe. job.recipesets[0].recipes[0].abort() job.update_status() # Verify that it and its children are aborted. self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) for task in job.recipesets[0].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.aborted) # Verify that the second recipe and its children are still waiting. self.assertEquals(job.recipesets[1].recipes[0].status, TaskStatus.waiting) for task in job.recipesets[1].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.waiting) # Verify that the job still shows waiting. self.assertEquals(job.status, TaskStatus.waiting) # Abort the second recipe now. job.recipesets[1].recipes[0].abort() job.update_status() # Verify that the whole job shows aborted now. self.assertEquals(job.status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=903935 def test_finished_recipe_with_unstarted_guests(self): # host completes, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) data_setup.mark_recipe_running(job.recipesets[0].recipes[0], only=True) job.recipesets[0].recipes[0].tasks[-1].stop() job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.completed) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # host aborts, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) job.recipesets[0].recipes[0].abort(msg='blorf') job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) def test_update_status_can_be_roundtripped_35508(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'job_35508.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.wrappedEl('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.wrappedEl('result')), job.result) for i, recipeset in enumerate(xmljob.iter_recipeSets()): for j, recipe in enumerate(recipeset.iter_recipes()): self.assertEquals( TaskStatus.from_string(recipe.wrappedEl('status')), job.recipesets[i].recipes[j].status) self.assertEquals( TaskResult.from_string(recipe.wrappedEl('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.iter_tasks()): self.assertEquals( TaskStatus.from_string(task.status), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals( TaskResult.from_string(task.result), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0) def test_update_status_can_be_roundtripped_40214(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'job_40214.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.wrappedEl('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.wrappedEl('result')), job.result) for i, recipeset in enumerate(xmljob.iter_recipeSets()): for j, recipe in enumerate(recipeset.iter_recipes()): self.assertEquals( TaskStatus.from_string(recipe.wrappedEl('status')), job.recipesets[i].recipes[j].status) self.assertEquals( TaskResult.from_string(recipe.wrappedEl('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.iter_tasks()): self.assertEquals( TaskStatus.from_string(task.status), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals( TaskResult.from_string(task.result), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0)
class TestJobsController(DatabaseTestCase): maxDiff = None def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user( user_name=u'test-job-owner', email_address=u'*****@*****.**') group = data_setup.create_group(group_name='somegroup') group.add_member(self.user) testutil.set_identity_user(self.user) data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') data_setup.create_product(product_name=u'the_product') session.flush() def tearDown(self): testutil.set_identity_user(None) session.rollback() def test_uploading_job_without_recipeset_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml) def test_complete_job_results(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) session.flush() # Complete the job, filling in values to match what's hardcoded in # complete-job-results.xml... recipe = job.recipesets[0].recipes[0] guestrecipe = recipe.guests[0] data_setup.mark_recipe_running( recipe, fqdn=u'system.test-complete-job-results', start_time=datetime.datetime(2016, 1, 31, 23, 0, 0), install_started=datetime.datetime(2016, 1, 31, 23, 0, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 0, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 0, 3), task_start_time=datetime.datetime(2016, 1, 31, 23, 0, 4)) data_setup.mark_recipe_complete( guestrecipe, fqdn=u'guest.test-complete-job-results', mac_address='ff:ff:ff:00:00:00', start_time=datetime.datetime(2016, 1, 31, 23, 30, 0), install_started=datetime.datetime(2016, 1, 31, 23, 30, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 30, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 30, 3), finish_time=datetime.datetime(2016, 1, 31, 23, 30, 4)) data_setup.mark_recipe_complete( recipe, only=True, start_time=datetime.datetime(2016, 1, 31, 23, 0, 4), finish_time=datetime.datetime(2016, 1, 31, 23, 59, 0)) recipe.installation.rendered_kickstart.url = u'http://example.com/recipe.ks' guestrecipe.installation.rendered_kickstart.url = u'http://example.com/guest.ks' session.flush() # Hack up the database ids... This will fail if it's flushed, but it's # the easiest way to make them match the expected values. job.id = 1 job.recipesets[0].id = 1 recipe.id = 1 guestrecipe.id = 2 recipe.tasks[0].id = 1 recipe.tasks[1].id = 2 guestrecipe.tasks[0].id = 3 guestrecipe.tasks[0].results[0].id = 1 recipe.tasks[0].results[0].id = 2 recipe.tasks[1].results[0].id = 3 expected_results_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job-results.xml') actual_results_xml = lxml.etree.tostring(job.to_xml(clone=False), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(expected_results_xml, actual_results_xml) def test_does_not_fail_when_whiteboard_empty(self): xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'') def test_creates_taskpackages_successfully(self): # Note: installPackage is deprecated but we still provide backwards compatibility xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <installPackage>libbeer</installPackage> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertListEqual( ['libbeer'], [x.package for x in job.recipesets[0].recipes[0].custom_packages]) def test_upload_xml_catches_invalid_xml(self): """We want that invalid Job XML is caught in the validation step.""" xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with arbitrary XML in namespaces</whriteboard> <recipeSet> <rawcipe> <distroRequires> <distro_name /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </rawcipe> </recipeSet> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) # https://bugzilla.redhat.com/show_bug.cgi?id=1112131 def test_preserves_arbitrary_XML_elements_in_namespace(self): complete_job_xml = pkg_resources.resource_filename( 'bkr.inttest', 'complete-job.xml') with open(complete_job_xml, 'r') as f: contents = f.read() xmljob = lxml.etree.fromstring(contents) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) tree = job.to_xml(clone=True) self.assertEqual(2, len(tree.xpath('*[namespace-uri()]'))) self.assertEqual( '<b:option xmlns:b="http://example.com/bar">--foobar arbitrary</b:option>', lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[0], encoding='utf8')) self.assertEqual( u'<f:test xmlns:f="http://example.com/foo">unicode text: heißer Шис</f:test>' .encode('utf8'), lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[1], encoding='utf8')) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual( job.recipesets[0].recipes[0].reservation_request.duration, 86400) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys_with_duration(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys with duration</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys duration="600"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual( job.recipesets[0].recipes[0].reservation_request.duration, 600) # https://bugzilla.redhat.com/show_bug.cgi?id=1302857 def test_strips_whitespace_from_whiteboard(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'so pretty')
class TestJobsController(unittest.TestCase): maxDiff = None @with_transaction def setUp(self): from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() group = data_setup.create_group(group_name='somegroup') self.user.groups.append(group) testutil.set_identity_user(self.user) if not Distro.by_name(u'BlueShoeLinux5-5'): data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') data_setup.create_product(product_name=u'the_product') def tearDown(self): testutil.set_identity_user(None) def test_uploading_job_without_recipeset_raises_exception(self): xmljob = XmlJob(xmltramp.parse(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''')) with session.begin(): self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): session.begin() try: xmljob = XmlJob(xmltramp.parse(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''')) self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) finally: session.rollback() session.close() def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job.xml') xmljob = XmlJob(xmltramp.parse(complete_job_xml)) with session.begin(): job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = job.to_xml(clone=True).toprettyxml(indent=' ') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml)
class TestJobsController(DatabaseTestCase): maxDiff = None def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user(user_name=u'test-job-owner', email_address=u'*****@*****.**') group = data_setup.create_group(group_name='somegroup') group.add_member(self.user) testutil.set_identity_user(self.user) data_setup.create_distro_tree(distro_name=u'BlueShoeLinux5-5') data_setup.create_product(product_name=u'the_product') session.flush() def tearDown(self): testutil.set_identity_user(None) session.rollback() def test_uploading_job_without_recipeset_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml) def test_complete_job_results(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) session.flush() # Complete the job, filling in values to match what's hardcoded in # complete-job-results.xml... recipe = job.recipesets[0].recipes[0] guestrecipe = recipe.guests[0] data_setup.mark_recipe_running(recipe, fqdn=u'system.test-complete-job-results', start_time=datetime.datetime(2016, 1, 31, 23, 0, 0), install_started=datetime.datetime(2016, 1, 31, 23, 0, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 0, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 0, 3), task_start_time=datetime.datetime(2016, 1, 31, 23, 0, 4)) data_setup.mark_recipe_complete(guestrecipe, fqdn=u'guest.test-complete-job-results', mac_address='ff:ff:ff:00:00:00', start_time=datetime.datetime(2016, 1, 31, 23, 30, 0), install_started=datetime.datetime(2016, 1, 31, 23, 30, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 30, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 30, 3), finish_time=datetime.datetime(2016, 1, 31, 23, 30, 4)) data_setup.mark_recipe_complete(recipe, only=True, start_time=datetime.datetime(2016, 1, 31, 23, 0, 4), finish_time=datetime.datetime(2016, 1, 31, 23, 59, 0)) recipe.installation.rendered_kickstart.url = u'http://example.com/recipe.ks' guestrecipe.installation.rendered_kickstart.url = u'http://example.com/guest.ks' session.flush() # Hack up the database ids... This will fail if it's flushed, but it's # the easiest way to make them match the expected values. job.id = 1 job.recipesets[0].id = 1 recipe.id = 1 guestrecipe.id = 2 recipe.tasks[0].id = 1 recipe.tasks[1].id = 2 guestrecipe.tasks[0].id = 3 guestrecipe.tasks[0].results[0].id = 1 recipe.tasks[0].results[0].id = 2 recipe.tasks[1].results[0].id = 3 expected_results_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job-results.xml') actual_results_xml = lxml.etree.tostring(job.to_xml(clone=False), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(expected_results_xml, actual_results_xml) def test_does_not_fail_when_whiteboard_empty(self): xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'') def test_creates_taskpackages_successfully(self): # Note: installPackage is deprecated but we still provide backwards compatibility xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <installPackage>libbeer</installPackage> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertListEqual(['libbeer'], [x.package for x in job.recipesets[0].recipes[0].custom_packages]) def test_upload_xml_catches_invalid_xml(self): """We want that invalid Job XML is caught in the validation step.""" xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with arbitrary XML in namespaces</whriteboard> <recipeSet> <rawcipe> <distroRequires> <distro_name /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </rawcipe> </recipeSet> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) # https://bugzilla.redhat.com/show_bug.cgi?id=1112131 def test_preserves_arbitrary_XML_elements_in_namespace(self): complete_job_xml = pkg_resources.resource_filename('bkr.inttest', 'complete-job.xml') with open(complete_job_xml, 'r') as f: contents = f.read() xmljob = lxml.etree.fromstring(contents) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) tree = job.to_xml(clone=True) self.assertEqual(2, len(tree.xpath('*[namespace-uri()]'))) self.assertEqual('<b:option xmlns:b="http://example.com/bar">--foobar arbitrary</b:option>', lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[0], encoding='utf8')) self.assertEqual(u'<f:test xmlns:f="http://example.com/foo">unicode text: heißer Шис</f:test>'.encode('utf8'), lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[1], encoding='utf8')) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.recipesets[0].recipes[0].reservation_request.duration, 86400) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys_with_duration(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys with duration</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys duration="600"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.recipesets[0].recipes[0].reservation_request.duration, 600) # https://bugzilla.redhat.com/show_bug.cgi?id=1302857 def test_strips_whitespace_from_whiteboard(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'so pretty')
class TestUpdateStatus(DatabaseTestCase): def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user() session.flush() def tearDown(self): session.commit() def test_abort_recipe_bubbles_status_to_job(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/check-install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/check-install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) session.flush() for recipeset in job.recipesets: for recipe in recipeset.recipes: recipe.process() recipe.queue() recipe.schedule() recipe.waiting() # Abort the first recipe. job.recipesets[0].recipes[0].abort() job.update_status() # Verify that it and its children are aborted. self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) for task in job.recipesets[0].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.aborted) # Verify that the second recipe and its children are still waiting. self.assertEquals(job.recipesets[1].recipes[0].status, TaskStatus.waiting) for task in job.recipesets[1].recipes[0].tasks: self.assertEquals(task.status, TaskStatus.waiting) # Verify that the job still shows waiting. self.assertEquals(job.status, TaskStatus.waiting) # Abort the second recipe now. job.recipesets[1].recipes[0].abort() job.update_status() # Verify that the whole job shows aborted now. self.assertEquals(job.status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=903935 def test_finished_recipe_with_unstarted_guests(self): # host completes, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) data_setup.mark_recipe_running(job.recipesets[0].recipes[0], only=True) job.recipesets[0].recipes[0].tasks[-1].stop() job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.completed) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # host aborts, but guest never started job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_waiting(job) job.recipesets[0].recipes[0].abort(msg='blorf') job.update_status() self.assertEquals(job.recipesets[0].recipes[0].status, TaskStatus.aborted) self.assertEquals(job.recipesets[0].recipes[0].guests[0].status, TaskStatus.aborted) # https://bugzilla.redhat.com/show_bug.cgi?id=1309530 def test_recipe_start_time_is_set_to_rebooted_timestamp(self): # For a normal recipe running on a system, update_status should set # recipe.start_time to the rebooted timestamp. job = data_setup.create_job() data_setup.mark_job_scheduled(job) recipe = job.recipesets[0].recipes[0] self.assertIsNone(recipe.start_time) recipe.provision() recipe.installation.rebooted = datetime.datetime(2016, 2, 18, 13, 0, 0) job.update_status() self.assertEqual(recipe.start_time, datetime.datetime(2016, 2, 18, 13, 0, 0)) self.assertEqual(recipe.status, TaskStatus.installing) # https://bugzilla.redhat.com/show_bug.cgi?id=1309530 def test_recipe_start_time_is_set_to_first_task_start_time(self): # For guest recipes, and systems without power control, there is no # rebooted timestamp. Instead the first task just gets started. job = data_setup.create_job(num_recipes=1, num_guestrecipes=1) data_setup.mark_job_scheduled(job) guestrecipe = job.recipesets[0].recipes[0].guests[0] self.assertIsNone(guestrecipe.start_time) # /distribution/virt/install starts the first task before it starts # running the guest installation. guestrecipe.provision() guestrecipe.first_task.start() guestrecipe.first_task.start_time = datetime.datetime(2016, 2, 18, 14, 0, 0) self.assertIsNone(guestrecipe.installation.rebooted) job.update_status() self.assertEqual(guestrecipe.start_time, datetime.datetime(2016, 2, 18, 14, 0, 0)) # https://bugzilla.redhat.com/show_bug.cgi?id=991245#c12 def test_status_is_Waiting_when_installation_is_finished_but_tasks_have_not_started(self): # Beah <= 0.7.9 will consider 'Installing' to mean that the recipe is # finished, so we want the status to go back to 'Waiting' once the # installation is finished. job = data_setup.create_job() recipe = job.recipesets[0].recipes[0] data_setup.mark_recipe_installing(recipe) data_setup.mark_recipe_installation_finished(recipe) self.assertEqual(recipe.tasks[0].status, TaskStatus.waiting) self.assertIsNone(recipe.tasks[0].start_time) self.assertEqual(recipe.status, TaskStatus.waiting) # https://bugzilla.redhat.com/show_bug.cgi?id=1558776 def test_scheduler_status_is_not_reset_on_already_released_systems(self): first_recipe = data_setup.create_recipe() second_recipe = data_setup.create_recipe() job = data_setup.create_job_for_recipesets([ data_setup.create_recipeset_for_recipes([first_recipe]), data_setup.create_recipeset_for_recipes([second_recipe])]) data_setup.mark_recipe_complete(first_recipe) first_system = first_recipe.resource.system self.assertEquals(first_system.scheduler_status, SystemSchedulerStatus.pending) # Pretend the scheduler has set the system back to idle first_system.scheduler_status = SystemSchedulerStatus.idle data_setup.mark_recipe_scheduled(second_recipe) job.update_status() # The bug was that job.update_status() would reset the *first* recipe's # system back to pending, even though it had already been released and # could potentially be reserved for another recipe already. self.assertEquals(first_system.scheduler_status, SystemSchedulerStatus.idle) def test_update_status_can_be_roundtripped_35508(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'job_35508.xml') xmljob = lxml.etree.fromstring(complete_job_xml) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.get('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.get('result')), job.result) for i, recipeset in enumerate(xmljob.xpath('recipeSet')): for j, recipe in enumerate(recipeset.xpath('recipe')): self.assertEquals(TaskStatus.from_string(recipe.get('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.get('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.xpath('task')): self.assertEquals(TaskStatus.from_string(task.get('status')), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals(TaskResult.from_string(task.get('result')), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0) def test_update_status_can_be_roundtripped_40214(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'job_40214.xml') xmljob = lxml.etree.fromstring(complete_job_xml) data_setup.create_tasks(xmljob) session.flush() # Import the job xml job = self.controller.process_xmljob(xmljob, self.user) session.flush() # Mark job waiting data_setup.mark_job_waiting(job) session.flush() # watchdog's should exist self.assertNotEqual(len(watchdogs_for_job(job)), 0) # Play back the original jobs results and status data_setup.playback_job_results(job, xmljob) session.flush() # Verify that the original status and results match self.assertEquals(TaskStatus.from_string(xmljob.get('status')), job.status) self.assertEquals(TaskResult.from_string(xmljob.get('result')), job.result) for i, recipeset in enumerate(xmljob.xpath('recipeSet')): for j, recipe in enumerate(recipeset.xpath('recipes')): self.assertEquals(TaskStatus.from_string(recipe.get('status')), job.recipesets[i].recipes[j].status) self.assertEquals(TaskResult.from_string(recipe.get('result')), job.recipesets[i].recipes[j].result) for k, task in enumerate(recipe.xpath('task')): self.assertEquals(TaskStatus.from_string(task.get('status')), job.recipesets[i].recipes[j].tasks[k].status) self.assertEquals(TaskResult.from_string(task.get('result')), job.recipesets[i].recipes[j].tasks[k].result) # No watchdog's should exist when the job is complete self.assertEquals(len(watchdogs_for_job(job)), 0)
class TestJobsController(DatabaseTestCase): maxDiff = None def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user( user_name=u'test-job-owner', email_address=u'*****@*****.**') group = data_setup.create_group(group_name='somegroup') group.add_member(self.user) testutil.set_identity_user(self.user) data_setup.create_product(product_name=u'the_product') session.flush() def tearDown(self): testutil.set_identity_user(None) session.rollback() def test_uploading_job_without_recipeset_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml) # https://bugzilla.redhat.com/show_bug.cgi?id=911515 def test_job_with_custom_distro_without_optional_attributes_can_be_roundtripped( self): complete_job_xml = ''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''' xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertIn( '<tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/>', roundtripped_xml) self.assertIn('<initrd url="pxeboot/initrd"/>', roundtripped_xml) self.assertIn('<kernel url="pxeboot/vmlinuz"/>', roundtripped_xml) self.assertIn('<arch value="i386"/>', roundtripped_xml) self.assertIn('<osversion major="RedHatEnterpriseLinux7" minor="0"/>', roundtripped_xml) def test_complete_job_results(self): complete_job_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) session.flush() # Complete the job, filling in values to match what's hardcoded in # complete-job-results.xml... recipe = job.recipesets[0].recipes[0] guestrecipe = recipe.guests[0] data_setup.mark_recipe_running( recipe, fqdn=u'system.test-complete-job-results', start_time=datetime.datetime(2016, 1, 31, 23, 0, 0), install_started=datetime.datetime(2016, 1, 31, 23, 0, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 0, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 0, 3), task_start_time=datetime.datetime(2016, 1, 31, 23, 0, 4)) data_setup.mark_recipe_complete( guestrecipe, fqdn=u'guest.test-complete-job-results', mac_address='ff:ff:ff:00:00:00', start_time=datetime.datetime(2016, 1, 31, 23, 30, 0), install_started=datetime.datetime(2016, 1, 31, 23, 30, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 30, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 30, 3), finish_time=datetime.datetime(2016, 1, 31, 23, 30, 4)) data_setup.mark_recipe_complete( recipe, only=True, start_time=datetime.datetime(2016, 1, 31, 23, 0, 4), finish_time=datetime.datetime(2016, 1, 31, 23, 59, 0)) recipe.installation.rendered_kickstart.url = u'http://example.com/recipe.ks' guestrecipe.installation.rendered_kickstart.url = u'http://example.com/guest.ks' session.flush() # Hack up the database ids... This will fail if it's flushed, but it's # the easiest way to make them match the expected values. job.id = 1 job.recipesets[0].id = 1 recipe.id = 1 guestrecipe.id = 2 recipe.tasks[0].id = 1 recipe.tasks[1].id = 2 guestrecipe.tasks[0].id = 3 guestrecipe.tasks[0].results[0].id = 1 recipe.tasks[0].results[0].id = 2 recipe.tasks[1].results[0].id = 3 expected_results_xml = pkg_resources.resource_string( 'bkr.inttest', 'complete-job-results.xml') expected_results_xml = expected_results_xml.replace( '${BEAKER_SERVER_BASE_URL}', get_server_base()) actual_results_xml = lxml.etree.tostring(job.to_xml(clone=False), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(expected_results_xml, actual_results_xml) def test_does_not_fail_when_whiteboard_empty(self): xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'') def test_creates_taskpackages_successfully(self): # Note: installPackage is deprecated but we still provide backwards compatibility xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <installPackage>libbeer</installPackage> <task name="/distribution/install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertListEqual( ['libbeer'], [x.package for x in job.recipesets[0].recipes[0].custom_packages]) def test_upload_xml_catches_invalid_xml(self): """We want that invalid Job XML is caught in the validation step.""" xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with arbitrary XML in namespaces</whriteboard> <recipeSet> <rawcipe> <distroRequires> <distro_name /> </distroRequires> <hostRequires/> <task name="/distribution/install" role="STANDALONE"/> </rawcipe> </recipeSet> </job> ''') self.assertRaises( BX, lambda: self.controller.process_xmljob(xmljob, self.user)) # https://bugzilla.redhat.com/show_bug.cgi?id=1112131 def test_preserves_arbitrary_XML_elements_in_namespace(self): complete_job_xml = pkg_resources.resource_filename( 'bkr.inttest', 'complete-job.xml') with open(complete_job_xml, 'r') as f: contents = f.read() xmljob = lxml.etree.fromstring(contents) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) tree = job.to_xml(clone=True) self.assertEqual(2, len(tree.xpath('*[namespace-uri()]'))) self.assertEqual( '<b:option xmlns:b="http://example.com/bar">--foobar arbitrary</b:option>', lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[0], encoding='utf8')) self.assertEqual( u'<f:test xmlns:f="http://example.com/foo">unicode text: heißer Шис</f:test>' .encode('utf8'), lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[1], encoding='utf8')) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual( job.recipesets[0].recipes[0].reservation_request.duration, 86400) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys_with_duration(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys with duration</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> <reservesys duration="600"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual( job.recipesets[0].recipes[0].reservation_request.duration, 600) # https://bugzilla.redhat.com/show_bug.cgi?id=1302857 def test_strips_whitespace_from_whiteboard(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'so pretty') # https://bugzilla.redhat.com/show_bug.cgi?id=911515 def test_distro_metadata_stored_at_job_submission_time_for_user_defined_distro( self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7" minor="4"/> <name value="MyCustomLinux1.0"/> <variant value="Server"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertEqual( recipe.installation.tree_url, "ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/" ) self.assertEqual(recipe.installation.initrd_path, "pxeboot/initrd") self.assertEqual(recipe.installation.kernel_path, "pxeboot/vmlinuz") self.assertEqual(recipe.installation.arch.arch, "i386") self.assertEqual(recipe.installation.distro_name, "MyCustomLinux1.0") self.assertEqual(recipe.installation.osmajor, "RedHatEnterpriseLinux7") self.assertEqual(recipe.installation.osminor, "4") self.assertEqual(recipe.installation.variant, "Server") self.assertEqual(recipe.distro_requires, None) def test_distro_metadata_stored_at_job_submission_time_for_traditional_distro( self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertIsNone(recipe.installation.tree_url) self.assertIsNone(recipe.installation.initrd_path) self.assertIsNone(recipe.installation.kernel_path) self.assertEqual(recipe.installation.arch.arch, u'i386') self.assertEqual(recipe.installation.distro_name, u'BlueShoeLinux5-5') self.assertEqual(recipe.installation.osmajor, u'BlueShoeLinux5') self.assertEqual(recipe.installation.osminor, u'9') self.assertEqual(recipe.installation.variant, u'Server') self.assertNotEqual(recipe.distro_requires, None) def test_unknown_arch_in_user_defined_distro_throws_exception(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="idontexist"/> <osversion major="RedHatEnterpriseLinux7" minor="4"/> <name value="MyCustomLinux1.0"/> <variant value="Server"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, 'No arch matches'): self.controller.process_xmljob(jobxml, self.user) def test_osminor_defaults_to_zero_when_not_provided_in_distro_metadata( self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7"/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertEqual(recipe.installation.osminor, "0") def test_required_attributes_throw_exception_when_empty(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="some/random/tree"/> <kernel url="some/kernel"/> <arch value="i386"/> <osversion major=""/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<initrd/> element is required'): self.controller.process_xmljob(jobxml, self.user) jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="some/random/tree"/> <initrd url="some/random/initrd"/> <arch value="i386"/> <osversion major=""/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<kernel/> element is required'): self.controller.process_xmljob(jobxml, self.user) jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="/some/random/tree"/> <initrd url="/some/random/initrd"/> <kernel url="/some/random/kernel"/> <arch value="i386"/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<osmajor/> element is required'): self.controller.process_xmljob(jobxml, self.user)
class TestJobsController(DatabaseTestCase): maxDiff = None def setUp(self): session.begin() from bkr.server.jobs import Jobs self.controller = Jobs() self.user = data_setup.create_user(user_name=u'test-job-owner', email_address=u'*****@*****.**') group = data_setup.create_group(group_name='somegroup') group.add_member(self.user) testutil.set_identity_user(self.user) data_setup.create_product(product_name=u'the_product') session.flush() def tearDown(self): testutil.set_identity_user(None) session.rollback() def test_uploading_job_without_recipeset_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with norecipesets</whiteboard> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_uploading_job_with_invalid_hostRequires_raises_exception(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard>job with invalid hostRequires</whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires> <memory op=">=" value="500MB" /> </hostRequires> <task name="/distribution/check-install" role="STANDALONE"> <params/> </task> </recipe> </recipeSet> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) def test_job_xml_can_be_roundtripped(self): # Ideally the logic for parsing job XML into a Job instance would live in model code, # so that this test doesn't have to go through the web layer... complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(roundtripped_xml, complete_job_xml) # https://bugzilla.redhat.com/show_bug.cgi?id=911515 def test_job_with_custom_distro_without_optional_attributes_can_be_roundtripped(self): complete_job_xml = ''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''' xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) roundtripped_xml = lxml.etree.tostring(job.to_xml(clone=True), pretty_print=True, encoding='utf8') self.assertIn('<tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/>', roundtripped_xml) self.assertIn('<initrd url="pxeboot/initrd"/>', roundtripped_xml) self.assertIn('<kernel url="pxeboot/vmlinuz"/>', roundtripped_xml) self.assertIn('<arch value="i386"/>', roundtripped_xml) self.assertIn('<osversion major="RedHatEnterpriseLinux7" minor="0"/>', roundtripped_xml) def test_complete_job_results(self): complete_job_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job.xml') xmljob = lxml.etree.fromstring(complete_job_xml) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) session.flush() # Complete the job, filling in values to match what's hardcoded in # complete-job-results.xml... recipe = job.recipesets[0].recipes[0] guestrecipe = recipe.guests[0] data_setup.mark_recipe_running(recipe, fqdn=u'system.test-complete-job-results', start_time=datetime.datetime(2016, 1, 31, 23, 0, 0), install_started=datetime.datetime(2016, 1, 31, 23, 0, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 0, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 0, 3), task_start_time=datetime.datetime(2016, 1, 31, 23, 0, 4)) data_setup.mark_recipe_complete(guestrecipe, fqdn=u'guest.test-complete-job-results', mac_address='ff:ff:ff:00:00:00', start_time=datetime.datetime(2016, 1, 31, 23, 30, 0), install_started=datetime.datetime(2016, 1, 31, 23, 30, 1), install_finished=datetime.datetime(2016, 1, 31, 23, 30, 2), postinstall_finished=datetime.datetime(2016, 1, 31, 23, 30, 3), finish_time=datetime.datetime(2016, 1, 31, 23, 30, 4)) data_setup.mark_recipe_complete(recipe, only=True, start_time=datetime.datetime(2016, 1, 31, 23, 0, 4), finish_time=datetime.datetime(2016, 1, 31, 23, 59, 0)) recipe.installation.rendered_kickstart.url = u'http://example.com/recipe.ks' guestrecipe.installation.rendered_kickstart.url = u'http://example.com/guest.ks' session.flush() # Hack up the database ids... This will fail if it's flushed, but it's # the easiest way to make them match the expected values. job.id = 1 job.recipesets[0].id = 1 recipe.id = 1 guestrecipe.id = 2 recipe.tasks[0].id = 1 recipe.tasks[1].id = 2 guestrecipe.tasks[0].id = 3 guestrecipe.tasks[0].results[0].id = 1 recipe.tasks[0].results[0].id = 2 recipe.tasks[1].results[0].id = 3 expected_results_xml = pkg_resources.resource_string('bkr.inttest', 'complete-job-results.xml') expected_results_xml = expected_results_xml.replace( '${BEAKER_SERVER_BASE_URL}', get_server_base()) actual_results_xml = lxml.etree.tostring(job.to_xml(clone=False), pretty_print=True, encoding='utf8') self.assertMultiLineEqual(expected_results_xml, actual_results_xml) def test_does_not_fail_when_whiteboard_empty(self): xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <task name="/distribution/check-install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'') def test_creates_taskpackages_successfully(self): # Note: installPackage is deprecated but we still provide backwards compatibility xml = """ <job> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5"/> </distroRequires> <hostRequires/> <installPackage>libbeer</installPackage> <task name="/distribution/check-install" role="STANDALONE"/> </recipe> </recipeSet> </job> """ xmljob = lxml.etree.fromstring(xml) job = self.controller.process_xmljob(xmljob, self.user) self.assertListEqual(['libbeer'], [x.package for x in job.recipesets[0].recipes[0].custom_packages]) def test_upload_xml_catches_invalid_xml(self): """We want that invalid Job XML is caught in the validation step.""" xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with arbitrary XML in namespaces</whriteboard> <recipeSet> <rawcipe> <distroRequires> <distro_name /> </distroRequires> <hostRequires/> <task name="/distribution/check-install" role="STANDALONE"/> </rawcipe> </recipeSet> </job> ''') self.assertRaises(BX, lambda: self.controller.process_xmljob(xmljob, self.user)) # https://bugzilla.redhat.com/show_bug.cgi?id=1112131 def test_preserves_arbitrary_XML_elements_in_namespace(self): complete_job_xml = pkg_resources.resource_filename('bkr.inttest', 'complete-job.xml') with open(complete_job_xml, 'r') as f: contents = f.read() xmljob = lxml.etree.fromstring(contents) job = testutil.call(self.controller.process_xmljob, xmljob, self.user) tree = job.to_xml(clone=True) self.assertEqual(2, len(tree.xpath('*[namespace-uri()]'))) self.assertEqual('<b:option xmlns:b="http://example.com/bar">--foobar arbitrary</b:option>', lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[0], encoding='utf8')) self.assertEqual(u'<f:test xmlns:f="http://example.com/foo">unicode text: heißer Шис</f:test>'.encode('utf8'), lxml.etree.tostring(tree.xpath('*[namespace-uri()]')[1], encoding='utf8')) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/check-install"/> <reservesys/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.recipesets[0].recipes[0].reservation_request.duration, 86400) # https://bugzilla.redhat.com/show_bug.cgi?id=1295642 def test_parses_reservesys_with_duration(self): xmljob = lxml.etree.fromstring(''' <job> <whriteboard>job with reservesys with duration</whriteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/check-install"/> <reservesys duration="600"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.recipesets[0].recipes[0].reservation_request.duration, 600) # https://bugzilla.redhat.com/show_bug.cgi?id=1302857 def test_strips_whitespace_from_whiteboard(self): xmljob = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires/> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(xmljob, self.user) self.assertEqual(job.whiteboard, u'so pretty') # https://bugzilla.redhat.com/show_bug.cgi?id=911515 def test_distro_metadata_stored_at_job_submission_time_for_user_defined_distro(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7" minor="4"/> <name value="MyCustomLinux1.0"/> <variant value="Server"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertEqual(recipe.installation.tree_url, "ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/") self.assertEqual(recipe.installation.initrd_path, "pxeboot/initrd") self.assertEqual(recipe.installation.kernel_path, "pxeboot/vmlinuz") self.assertEqual(recipe.installation.arch.arch, "i386") self.assertEqual(recipe.installation.distro_name, "MyCustomLinux1.0") self.assertEqual(recipe.installation.osmajor, "RedHatEnterpriseLinux7") self.assertEqual(recipe.installation.osminor, "4") self.assertEqual(recipe.installation.variant, "Server") self.assertEqual(recipe.distro_requires, None) def test_distro_metadata_stored_at_job_submission_time_for_traditional_distro(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distroRequires> <distro_name op="=" value="BlueShoeLinux5-5" /> </distroRequires> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertIsNone(recipe.installation.tree_url) self.assertIsNone(recipe.installation.initrd_path) self.assertIsNone(recipe.installation.kernel_path) self.assertEqual(recipe.installation.arch.arch, u'i386') self.assertEqual(recipe.installation.distro_name, u'BlueShoeLinux5-5') self.assertEqual(recipe.installation.osmajor, u'BlueShoeLinux5') self.assertEqual(recipe.installation.osminor, u'9') self.assertEqual(recipe.installation.variant, u'Server') self.assertNotEqual(recipe.distro_requires, None) def test_unknown_arch_in_user_defined_distro_throws_exception(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="idontexist"/> <osversion major="RedHatEnterpriseLinux7" minor="4"/> <name value="MyCustomLinux1.0"/> <variant value="Server"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, 'No arch matches'): self.controller.process_xmljob(jobxml, self.user) def test_osminor_defaults_to_zero_when_not_provided_in_distro_metadata(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="ftp://dummylab.example.com/distros/MyCustomLinux1.0/Server/i386/os/"/> <initrd url="pxeboot/initrd"/> <kernel url="pxeboot/vmlinuz"/> <arch value="i386"/> <osversion major="RedHatEnterpriseLinux7"/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') job = self.controller.process_xmljob(jobxml, self.user) recipe = job.recipesets[0].recipes[0] self.assertEqual(recipe.installation.osminor, "0") def test_required_attributes_throw_exception_when_empty(self): jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="some/random/tree"/> <kernel url="some/kernel"/> <arch value="i386"/> <osversion major=""/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<initrd/> element is required'): self.controller.process_xmljob(jobxml, self.user) jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="some/random/tree"/> <initrd url="some/random/initrd"/> <arch value="i386"/> <osversion major=""/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<kernel/> element is required'): self.controller.process_xmljob(jobxml, self.user) jobxml = lxml.etree.fromstring(''' <job> <whiteboard> so pretty </whiteboard> <recipeSet> <recipe> <distro> <tree url="/some/random/tree"/> <initrd url="/some/random/initrd"/> <kernel url="/some/random/kernel"/> <arch value="i386"/> <name value="MyCustomLinux1.0"/> </distro> <hostRequires/> <task name="/distribution/check-install"/> </recipe> </recipeSet> </job> ''') with self.assertRaisesRegexp(BX, '<osmajor/> element is required'): self.controller.process_xmljob(jobxml, self.user)