Merge lp:~abentley/ci-director/remove-ci-director into lp:ci-director

Proposed by Aaron Bentley
Status: Merged
Merged at revision: 191
Proposed branch: lp:~abentley/ci-director/remove-ci-director
Merge into: lp:ci-director
Diff against target: 430 lines (+0/-347)
2 files modified
cidirector/cidirector.py (+0/-91)
cidirector/tests/test_cidirector.py (+0/-256)
To merge this branch: bzr merge lp:~abentley/ci-director/remove-ci-director
Reviewer Review Type Date Requested Status
Curtis Hovey (community) code Approve
Review via email: mp+316256@code.launchpad.net

Commit message

Start removing obsolete ci-director functionality.

Description of the change

This branch begins removing obsolete functionality from CI Director.

Now that cidirector.py is no longer being run, only functionality imported by start_builds.py or update_outcomes.py is still needed.

This branch removes the execute bit, the shebang, and the symlink, since ci-director will no longer be executible.

It removes main(), build_revision(), schedule_builds(), and their associated tests.

To post a comment you must log in.
Revision history for this message
Curtis Hovey (sinzui) wrote :

Thank you.

review: Approve (code)

Preview Diff

[H/L] Next/Prev Comment, [J/K] Next/Prev File, [N/P] Next/Prev Hunk
=== removed symlink 'ci-director'
=== target was u'cidirector/cidirector.py'
=== modified file 'cidirector/cidirector.py' (properties changed: +x to -x)
--- cidirector/cidirector.py 2017-01-23 15:37:35 +0000
+++ cidirector/cidirector.py 2017-02-02 17:57:29 +0000
@@ -1,4 +1,3 @@
1#!/usr/bin/env python
2from argparse import ArgumentParser1from argparse import ArgumentParser
3from collections import namedtuple2from collections import namedtuple
4from contextlib import contextmanager3from contextlib import contextmanager
@@ -15,8 +14,6 @@
15import re14import re
16import shlex15import shlex
17from smtplib import SMTP16from smtplib import SMTP
18from socket import setdefaulttimeout
19import sys
20from time import sleep17from time import sleep
21import urllib218import urllib2
2219
@@ -47,11 +44,9 @@
47 PENDING,44 PENDING,
48 SUCCEEDED,45 SUCCEEDED,
49 StateFile,46 StateFile,
50 StateFileInUse,
51 TERMINAL_STATUS,47 TERMINAL_STATUS,
52)48)
53from utility import (49from utility import (
54 log_exceptions,
55 S3Storage,50 S3Storage,
56 )51 )
5752
@@ -865,61 +860,6 @@
865 active.extend(item['task']['name'] for item in queued)860 active.extend(item['task']['name'] for item in queued)
866 return active861 return active
867862
868 def schedule_builds(self, server_info):
869 """Request builds for appropriate jobs.
870
871 Can schedule BuildRevisionJob, ResourcefulJob, RecordResultJob.
872 :param server_info: ServerInfo for the current Jenkins.
873 """
874 self.adopt_builds(server_info)
875 active_jobs = set(self.get_active_jobs(server_info))
876 self.logger.info('Active jobs: %s', ', '.join(sorted(active_jobs)))
877 candidate_jobs = []
878 self.update_builds()
879 judge = ResultJudge(self.logger, self.state_file, self.jenkins)
880 all_results_ready = judge.finalize(self.mailer)
881 job = RecordResultJob(
882 self.logger, self.state_file, self.jenkins, self.mailer)
883 judged = job.maybe_build()
884 if all_results_ready or judged:
885 self.state_file.finish_revision(datetime.utcnow())
886 # Disabled in favour of update-outcomes.
887 # self.state_file.build_info_to_s3()
888 resourcefuljobs = ResourcefulJob.get_available_jobs(
889 server_info, self.jenkins, self.state_file, self.logger)
890 for r_job in resourcefuljobs:
891 candidate_jobs.append(r_job)
892 candidate_jobs.append(BuildRevisionJob(
893 self.repo_path, self.branches, self.ignore_branches, self.logger,
894 self.state_file, self.jenkins))
895 for job in candidate_jobs:
896 if job.maybe_build(active_jobs):
897 # We create a new set so that tests that access the parameters
898 # of maybe_build don't get mutated results.
899 active_jobs = active_jobs.union({job.job_id})
900 if should_update_health(self.jenkins, active_jobs):
901 jobs = list_cloud_health(server_info, self.jenkins, self.logger)
902 self.update_cloud_health(jobs)
903
904 def adopt_builds(self, server_info):
905 """Adopt any builds for the build-revision not already recorded."""
906 # We happen to know that branch will not be used here.
907 job_instances = [BuildRevisionJob(self.repo_path, None, None,
908 self.logger, self.state_file, self.jenkins)]
909 job_instances.extend(
910 Job.make_subclass(si_job['name'], self.logger, self.state_file,
911 self.jenkins)
912 for si_job in server_info.info['jobs'])
913 for job in job_instances:
914 if job is None:
915 continue
916 job_info = JobInfo.from_jenkins(self.jenkins, job.job_id)
917 if job_info.current_build_number is None:
918 continue
919 build_info = self.jenkins.get_build_info(
920 job.job_id, job_info.current_build_number)
921 job.adopt_build(build_info)
922
923 def update_builds(self):863 def update_builds(self):
924 """Record the status of jobs that are not currently building."""864 """Record the status of jobs that are not currently building."""
925 self.logger.info('Updating job outcomes.')865 self.logger.info('Updating job outcomes.')
@@ -958,19 +898,6 @@
958 version, job, status, info['timestamp'], info['duration'])898 version, job, status, info['timestamp'], info['duration'])
959899
960900
961def build_revision(branches, ignore_branches):
962 """Top-level logic for building jobs.
963
964 For a supplied list of branches, it
965 - Uses the config to access Jenkins, and loads the current state.
966 - Runs jobs using CIDirector.
967 - Saves the updated state.
968 """
969 with CIDirector.stateful(branches, ignore_branches) as director:
970 server_info = ServerInfo(director.jenkins.get_info())
971 director.schedule_builds(server_info)
972
973
974def list_cloud_health(si, jenkins, logger):901def list_cloud_health(si, jenkins, logger):
975 """Iterate through CloudHealthJobs for every relevent job on Jenkins."""902 """Iterate through CloudHealthJobs for every relevent job on Jenkins."""
976 for job in si.info['jobs']:903 for job in si.info['jobs']:
@@ -1028,21 +955,3 @@
1028 root_logger.addHandler(s_handler)955 root_logger.addHandler(s_handler)
1029 if verbose:956 if verbose:
1030 root_logger.setLevel(logging.INFO)957 root_logger.setLevel(logging.INFO)
1031
1032
1033def main(argv=None):
1034 setdefaulttimeout(30)
1035 args = get_arg_parser().parse_args(argv)
1036 setup_logging(args.log_path, args.log_count, args.verbose)
1037 logger = logging.getLogger('cidirector')
1038 with log_exceptions(logger) as result:
1039 try:
1040 build_revision(args.branch, args.ignore)
1041 except StateFileInUse:
1042 logger.warning('State file already in use.')
1043 return 1
1044 return result.exit_status
1045
1046
1047if __name__ == '__main__':
1048 sys.exit(main())
1049958
=== modified file 'cidirector/tests/test_cidirector.py'
--- cidirector/tests/test_cidirector.py 2017-01-23 15:37:35 +0000
+++ cidirector/tests/test_cidirector.py 2017-02-02 17:57:29 +0000
@@ -6,7 +6,6 @@
6 )6 )
7import logging7import logging
8import os8import os
9import sys
10from StringIO import StringIO9from StringIO import StringIO
11from textwrap import dedent10from textwrap import dedent
12from unittest import TestCase11from unittest import TestCase
@@ -30,7 +29,6 @@
30 get_buildvars_file,29 get_buildvars_file,
31 Job,30 Job,
32 JobInfo,31 JobInfo,
33 main,
34 Mailer,32 Mailer,
35 NoSuchBranch,33 NoSuchBranch,
36 list_cloud_health,34 list_cloud_health,
@@ -306,154 +304,6 @@
306 self.assertEqual(['foo', 'bar', 'baz', 'qux'],304 self.assertEqual(['foo', 'bar', 'baz', 'qux'],
307 director.get_active_jobs(si))305 director.get_active_jobs(si))
308306
309 def make_schedule_builds_director(self, branches, active=True,
310 published=True, server_info=None):
311 state_file = StateFile()
312 state_file.start_revision('a', 1, 'b', '1.6', 2)
313 if published:
314 state_file.publication_job().update_from_build_result('SUCCESS')
315 version = 2 if active else 3
316 jenkins_dict = {
317 PUBLISH_REVISION: {
318 'build_number': 5,
319 'description': '[ci-director]\n requires: build-revision'},
320 REVISION_RESULTS: {'build_number': 1},
321 'aws-deploy': {
322 'build_number': 5,
323 'description': '[ci-director]\n requires: build-revision'},
324 }
325 if server_info is not None:
326 for entry in server_info.info['jobs']:
327 jenkins_dict.setdefault(entry['name'], {
328 'build_number': 277,
329 })
330 jenkins = FakeJenkins(jenkins_dict, current_version=version)
331 if not active:
332 jenkins.set_build_result(BUILD_REVISION, 2, 'SUCCESS')
333 return CIDirector(jenkins, state_file, branches,
334 repo_path='hubgit.com/mumu/mumu')
335
336 def test_schedule_builds_uses_get_active_jobs(self):
337 director = CIDirector(FakeJenkins({
338 REVISION_RESULTS: {'build_number': 2}
339 }, current_version=5), StateFile())
340 server_info = make_server_info('idle')
341 director.state_file.start_revision('a', 54, 'b', '1.25', 5)
342 # get_determine_result calls get_available_jobs, but doesn't count
343 # because it doesn't run those jobs.
344 with patch.object(
345 ResultJudge, 'get_candidate_determine_result_jobs',
346 return_value=[]):
347 with patch.object(
348 director, 'get_active_jobs',
349 return_value=[BUILD_REVISION]) as gaj_mock:
350 with patch.object(director.state_file, 'build_info_to_s3'):
351 director.schedule_builds(server_info)
352 gaj_mock.assert_called_with(server_info)
353
354 def test_schedule_builds_respects_branch_order(self):
355 bar_url = 'gitbranch:bar:hubgit.com/mumu/mumu'
356 foo_url = 'gitbranch:foo:hubgit.com/mumu/mumu'
357 director = self.make_schedule_builds_director([bar_url, foo_url])
358 ls_remote_out = dedent("""\
359 rev-1\trefs/heads/bar
360 rev-2\trefs/heads/foo
361 """)
362 with patch('subprocess.check_output', return_value=ls_remote_out):
363 director.schedule_builds(make_server_info('idle'))
364 self.assertEqual(
365 director.jenkins.calls['build_job'],
366 [((BUILD_REVISION, {
367 'branch': bar_url, 'revision': 'rev-1'
368 }), {})])
369 director = self.make_schedule_builds_director([foo_url, bar_url])
370 with patch('subprocess.check_output', return_value=ls_remote_out):
371 director.schedule_builds(make_server_info('idle'))
372 self.assertEqual(
373 director.jenkins.calls['build_job'],
374 [((BUILD_REVISION, {
375 'branch': foo_url, 'revision': 'rev-2'
376 }), {})])
377
378 def test_schedule_builds_updates_builds(self):
379 director = self.make_schedule_builds_director([])
380 with patch.object(director, 'update_builds') as ub_mock:
381 with patch('subprocess.check_output'):
382 director.schedule_builds(make_server_info('idle'))
383 ub_mock.assert_called_with()
384
385 def test_schedule_builds_records_results(self):
386 director = self.make_schedule_builds_director([])
387 with patch.object(RecordResultJob, 'maybe_build') as mb_mock:
388 with patch.object(director.state_file, 'build_info_to_s3'):
389 with patch('subprocess.check_output'):
390 director.schedule_builds(make_server_info('idle'))
391 mb_mock.assert_called_with()
392
393 def test_schedule_builds_publishes(self):
394 director = self.make_schedule_builds_director([], active=True)
395 with patch.object(ResourcefulJob, 'maybe_build') as mb_mock:
396 si = make_server_info('idle', buildable=[PUBLISH_REVISION])
397 director.schedule_builds(si)
398 mb_mock.assert_called_with(set())
399
400 def test_schedule_builds_inactive_not_publishes(self):
401 director = self.make_schedule_builds_director([], active=False)
402 with patch.object(ResourcefulJob, 'maybe_build') as mb_mock:
403 with patch('subprocess.check_output'):
404 director.schedule_builds(make_server_info('idle'))
405 self.assertEqual(mb_mock.call_count, 0)
406
407 def test_schedule_builds_schedules_resourceful_tests_unique(self):
408 server_info = make_server_info('idle', buildable=['package-foo'])
409 director = self.make_schedule_builds_director(
410 [], server_info=server_info)
411 description = "[ci-director]\n requires: build-revision"
412 director.jenkins.job_info['package-foo']['description'] = description
413 with patch.object(ResourcefulJob, 'maybe_build') as mb_mock:
414 director.schedule_builds(server_info)
415 mb_mock.assert_called_once_with(set())
416
417 def test_schedule_builds_adopts_builds(self):
418 director = self.make_schedule_builds_director([])
419 server_info = make_server_info('idle')
420 with patch.object(director, 'adopt_builds') as ab_mock:
421 with patch('subprocess.check_output'):
422 director.schedule_builds(server_info)
423 ab_mock.assert_called_with(server_info)
424
425 def test_schedule_builds_calls_send_final_mail(self):
426 director = self.make_schedule_builds_director([])
427 server_info = make_server_info('idle')
428 with patch.object(ResultJudge, 'finalize') as finalize_mock:
429 with patch.object(director.state_file, 'build_info_to_s3'):
430 with patch('subprocess.check_output'):
431 director.schedule_builds(server_info)
432 finalize_mock.assert_called_with(director.mailer)
433
434 def test_schedule_builds_no_write_results_to_s3_without_final_data(self):
435 director = self.make_schedule_builds_director([])
436 with patch.object(ResultJudge, 'finalize', return_value=False):
437 with patch.object(RecordResultJob, 'maybe_build',
438 return_value=False):
439 with patch.object(director.state_file,
440 'build_info_to_s3') as s3_mock:
441 with patch('subprocess.check_output'):
442 director.schedule_builds(make_server_info('idle'))
443 self.assertEqual(0, s3_mock.call_count)
444
445 def test_schedule_builds_completes_unfinished_tests_with_new_rev(self):
446 # A test suite is finished even when a new revision is available.
447 server_info = make_server_info(
448 'idle', buildable=['aws-deploy'])
449 director = self.make_schedule_builds_director(
450 ['branch-url'], server_info=server_info, published=True)
451 director.schedule_builds(server_info)
452 # build_job is not called for the build-revision job.
453 self.assertEqual(
454 [(('aws-deploy', {'revision_build': 2}), {})],
455 director.jenkins.calls['build_job'])
456
457 def test_from_config(self):307 def test_from_config(self):
458 director = CIDirector.from_config({308 director = CIDirector.from_config({
459 'jenkins_url': 'http://192.168.1.1:8080',309 'jenkins_url': 'http://192.168.1.1:8080',
@@ -646,57 +496,6 @@
646 director.update_builds()496 director.update_builds()
647 self.assertEqual(sfj.get_status(), SUCCEEDED)497 self.assertEqual(sfj.get_status(), SUCCEEDED)
648498
649 def test_adopt_builds(self):
650 jenkins = FakeJenkins({
651 'foo-deploy': {
652 'build_number': 5,
653 'description': '[ci-director]\n requires: build-revision'},
654 BuildRevisionJob.job_id: {'build_number': 2},
655 })
656 sf = self.get_state_file()
657 director = CIDirector(jenkins, sf)
658 with patch.object(ResourcefulJob, 'adopt_build') as ab_mock:
659 director.adopt_builds(make_server_info('idle'))
660 self.assertEqual(ab_mock.call_count, 0)
661 director.adopt_builds(
662 make_server_info(
663 'idle', buildable=['foo-deploy']))
664 ab_mock.assert_called_with({
665 'result': 'SUCCESS',
666 'number': 5,
667 'building': False,
668 'artifacts': [],
669 'timestamp': 0,
670 'url': 'fakettp://fake.fake/job/foo-deploy/5'
671 })
672
673 def test_adopt_builds_unbuilt(self):
674 info = ServerInfo({'jobs': [{
675 'name': 'aws-deploy',
676 }]})
677 jenkins = FakeJenkins({
678 BuildRevisionJob.job_id: {'build_number': 2},
679 'aws-deploy': {
680 'build_number': 2,
681 'description': '[ci-director]\n requires: build-revision',
682 'lastBuild': None}
683 })
684 jenkins.job_info['aws-deploy'] = {'lastBuild': None}
685 sf = StateFile()
686 director = CIDirector(jenkins, sf)
687 director.adopt_builds(info)
688 self.assertEqual(
689 [c[0][0] for c in jenkins.calls['get_build_info']],
690 ['build-revision'])
691 jenkins.job_info['aws-deploy'] = {
692 'lastBuild': {'number': 1},
693 'description': '[ci-director]\n requires: build-revision'}
694 jenkins.build_info['aws-deploy'] = {1: {}}
695 director.adopt_builds(info)
696 self.assertEqual(
697 [c[0][0] for c in jenkins.calls['get_build_info']],
698 ['build-revision', 'build-revision', 'aws-deploy'])
699
700 def update_cloud_health_check(self, result, status):499 def update_cloud_health_check(self, result, status):
701 foo = CloudHealthJob(name='foo', substrate='bar', last_completed=4)500 foo = CloudHealthJob(name='foo', substrate='bar', last_completed=4)
702 jenkins = make_cloud_health_jenkins([foo])501 jenkins = make_cloud_health_jenkins([foo])
@@ -2295,61 +2094,6 @@
2295 })2094 })
22962095
22972096
2298class TestMain(TestCase):
2299
2300 def setUp(self):
2301 self.logger = logging.getLogger()
2302 self.orig_handlers = self.logger.handlers
2303 self.logger.handlers = []
2304 self.orig_level = self.logger.level
2305
2306 def tearDown(self):
2307 self.logger.handlers = self.orig_handlers
2308 self.logger.level = self.orig_level
2309
2310 def test_main_default(self):
2311 with patch('cidirector.cidirector.build_revision',
2312 autospec=True) as br_mock:
2313 with patch('cidirector.cidirector.RotatingFileHandler'
2314 ) as rfh_mock:
2315 ret = main(['b1', 'b2'])
2316 self.assertEqual(ret, 0)
2317 br_mock.assert_called_with(['b1', 'b2'], [])
2318 self.assertEqual(1, br_mock.call_count)
2319 root_logger = logging.getLogger()
2320 self.assertEqual(logging.WARNING, root_logger.level)
2321 rfh_mock.assert_called_once_with(
2322 'ci-director.log', backupCount=2, maxBytes=1024 * 1024)
2323 self.assertIs(sys.stderr, root_logger.handlers[1].stream)
2324
2325 def test_main_logging_config(self):
2326 with patch('cidirector.cidirector.build_revision'):
2327 with temp_dir() as log_dir:
2328 log_path = '{}/ci-director.log'.format(log_dir)
2329 ret = main(['-v', '--log-path', log_path, '--log-count',
2330 '5', 'b1'])
2331 self.assertEqual(ret, 0)
2332 root_logger = logging.getLogger()
2333 self.assertEqual(logging.INFO, root_logger.level)
2334 self.assertEqual(5, root_logger.handlers[0].backupCount)
2335 self.assertEqual(log_path, root_logger.handlers[0].baseFilename)
2336
2337 def test_main_logs_exception(self):
2338 with test_logger() as (log, log_stream):
2339 with patch('cidirector.cidirector.setup_logging', autospec=True):
2340 with patch('cidirector.cidirector.build_revision',
2341 side_effect=ValueError("mishap")):
2342 ret = main(["branch"])
2343 self.assertEqual(ret, 1)
2344 log_pattern = (
2345 "(?s)\\Aexception during build revision:\n"
2346 "Traceback \\(most recent call last\\):\n"
2347 ".*\n"
2348 "ValueError: mishap\n\\Z"
2349 )
2350 self.assertRegexpMatches(log_stream.getvalue(), log_pattern)
2351
2352
2353def make_description(tags=None, section='ci-director'):2097def make_description(tags=None, section='ci-director'):
2354 lines = ['[{}]\n'.format(section)]2098 lines = ['[{}]\n'.format(section)]
2355 if tags is not None:2099 if tags is not None:

Subscribers

People subscribed via source and target branches