Note: This change is to support a project that I am working on. You should see no change in the behavior of your current Autotest installations.
----- Implement the models and set up the RPC framework for the Test Planner Signed-off-by: James Ren <[email protected]> --- autotest/frontend/afe/frontend_test_utils.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/afe/frontend_test_utils.py 2009-12-18 00:27:34.000000000 -0800 @@ -89,11 +89,12 @@ thread_local.set_user(self.user) - def _frontend_common_setup(self): + def _frontend_common_setup(self, fill_data=True): self.god = mock.mock_god() self._open_test_db() self._setup_dummy_user() - self._fill_in_test_data() + if fill_data: + self._fill_in_test_data() def _frontend_common_teardown(self): --- autotest/frontend/afe/model_logic.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/afe/model_logic.py 2009-12-18 00:27:34.000000000 -0800 @@ -943,3 +943,50 @@ self.delete_attribute(attribute) else: self.set_attribute(attribute, value) + + +class ModelWithHashManager(dbmodels.Manager): + """Manager for use with the ModelWithHash abstract model class""" + + def create(self, **kwargs): + raise Exception('ModelWithHash manager should use get_or_create() ' + 'instead of create()') + + + def get_or_create(self, **kwargs): + kwargs['the_hash'] = self.model._compute_hash(**kwargs) + return super(ModelWithHashManager, self).get_or_create(**kwargs) + + +class ModelWithHash(dbmodels.Model): + """Superclass with methods for dealing with a hash column""" + + the_hash = dbmodels.CharField(max_length=40, unique=True) + + objects = ModelWithHashManager() + + class Meta: + abstract = True + + + @classmethod + def _compute_hash(cls, **kwargs): + raise NotImplementedError('Subclasses must override _compute_hash()') + + + def save(self, force_insert=False, **kwargs): + """Prevents saving the model in most cases + + We want these models to be immutable, so the generic save() operation + will not work. These models should be instantiated through their the + model.objects.get_or_create() method instead. + + The exception is that save(force_insert=True) will be allowed, since + that creates a new row. However, the preferred way to make instances of + these models is through the get_or_create() method. + """ + if not force_insert: + # Allow a forced insert to happen; if it's a duplicate, the unique + # constraint will catch it later anyways + raise Exception('ModelWithHash is immutable') + super(ModelWithHash, self).save(force_insert=force_insert, **kwargs) --- autotest/frontend/afe/rpc_utils.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/afe/rpc_utils.py 2009-12-18 00:27:34.000000000 -0800 @@ -5,7 +5,7 @@ __author__ = '[email protected] (Steve Howard)' -import datetime, os +import datetime, os, sys import django.http from autotest_lib.frontend.afe import models, model_logic @@ -623,3 +623,19 @@ special_task_index += 1 return interleaved_entries + + +def get_sha1_hash(source): + """Gets the SHA-1 hash of the source string + + @param source The string to hash + """ + if sys.version_info < (2,5): + import sha + digest = sha.new() + else: + import hashlib + digest = hashlib.sha1() + + digest.update(source) + return digest.hexdigest() --- autotest/frontend/afe/urls.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/afe/urls.py 2009-12-18 00:27:34.000000000 -0800 @@ -1,34 +1,19 @@ from django.conf.urls.defaults import * -import os -from autotest_lib.frontend import settings +import common +from autotest_lib.frontend import settings, urls_common from autotest_lib.frontend.afe.feeds import feed feeds = { 'jobs' : feed.JobFeed } -pattern_list = [ - (r'^(?:|noauth/)rpc/', 'frontend.afe.views.handle_rpc'), - (r'^rpc_doc', 'frontend.afe.views.rpc_documentation'), - ] +pattern_list, debug_pattern_list = ( + urls_common.generate_pattern_lists('frontend.afe', 'AfeClient')) -debug_pattern_list = [ - (r'^model_doc/', 'frontend.afe.views.model_documentation'), - # for GWT hosted mode - (r'^(?P<forward_addr>autotest.*)', 'frontend.afe.views.gwt_forward'), - # for GWT compiled files - (r'^client/(?P<path>.*)$', 'django.views.static.serve', - {'document_root': os.path.join(os.path.dirname(__file__), '..', 'client', - 'www')}), - # redirect / to compiled client - (r'^$', 'django.views.generic.simple.redirect_to', - {'url': 'client/autotest.AfeClient/AfeClient.html'}), - - # Job feeds - (r'^feeds/(?P<url>.*)/$', 'frontend.afe.feeds.feed.feed_view', - {'feed_dict': feeds}) - -] +# Job feeds +debug_pattern_list.append(( + r'^feeds/(?P<url>.*)/$', 'frontend.afe.feeds.feed.feed_view', + {'feed_dict': feeds})) if settings.DEBUG: pattern_list += debug_pattern_list --- autotest/frontend/afe/views.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/afe/views.py 2009-12-18 00:27:34.000000000 -0800 @@ -4,6 +4,7 @@ from django.http import HttpResponseServerError from django.template import Context, loader from autotest_lib.client.common_lib import utils +from autotest_lib.frontend import views_common from autotest_lib.frontend.afe import models, rpc_handler, rpc_interface from autotest_lib.frontend.afe import rpc_utils @@ -26,13 +27,9 @@ def model_documentation(request): - doc = '<h2>Models</h2>\n' - for model_name in ('Label', 'Host', 'Test', 'User', 'AclGroup', 'Job', - 'AtomicGroup'): - model_class = getattr(models, model_name) - doc += '<h3>%s</h3>\n' % model_name - doc += '<pre>\n%s</pre>\n' % model_class.__doc__ - return HttpResponse(doc) + model_names = ('Label', 'Host', 'Test', 'User', 'AclGroup', 'Job', + 'AtomicGroup') + return views_common.model_documentation(models, model_names) def redirect_with_extra_data(request, url, **kwargs): --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/migrations/045_test_planner_framework.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,242 @@ +UP_SQL = """\ +BEGIN; + +SET storage_engine = InnoDB; + +CREATE TABLE `planner_plans` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `name` varchar(255) NOT NULL UNIQUE, + `label_override` varchar(255) NULL, + `support` longtext NOT NULL, + `complete` bool NOT NULL, + `dirty` bool NOT NULL, + `initialized` bool NOT NULL +) +; + + +CREATE TABLE `planner_hosts` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `host_id` integer NOT NULL, + `complete` bool NOT NULL, + `blocked` bool NOT NULL +) +; +ALTER TABLE `planner_hosts` ADD CONSTRAINT hosts_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_hosts` ADD CONSTRAINT hosts_host_id_fk FOREIGN KEY (`host_id`) REFERENCES `afe_hosts` (`id`); + + +CREATE TABLE `planner_test_control_files` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `the_hash` varchar(40) NOT NULL UNIQUE, + `contents` longtext NOT NULL +) +; + + +CREATE TABLE `planner_tests` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `control_file_id` integer NOT NULL, + `execution_order` integer NOT NULL +) +; +ALTER TABLE `planner_tests` ADD CONSTRAINT tests_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_tests` ADD CONSTRAINT tests_control_file_id_fk FOREIGN KEY (`control_file_id`) REFERENCES `planner_test_control_files` (`id`); + + +CREATE TABLE `planner_test_jobs` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `test_id` integer NOT NULL, + `afe_job_id` integer NOT NULL +) +; +ALTER TABLE `planner_test_jobs` ADD CONSTRAINT test_jobs_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_test_jobs` ADD CONSTRAINT test_jobs_test_id_fk FOREIGN KEY (`test_id`) REFERENCES `planner_tests` (`id`); +ALTER TABLE `planner_test_jobs` ADD CONSTRAINT test_jobs_afe_job_id_fk FOREIGN KEY (`afe_job_id`) REFERENCES `afe_jobs` (`id`); +CREATE TABLE `planner_bugs` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `external_uid` varchar(255) NOT NULL UNIQUE +) +; + + +CREATE TABLE `planner_test_runs` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `test_job_id` integer NOT NULL, + `tko_test_id` integer(10) UNSIGNED NOT NULL, + `status` varchar(16) NOT NULL, + `finalized` bool NOT NULL, + `seen` bool NOT NULL, + `triaged` bool NOT NULL +) +; +ALTER TABLE `planner_test_runs` ADD CONSTRAINT test_runs_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_test_runs` ADD CONSTRAINT test_runs_test_job_id_fk FOREIGN KEY (`test_job_id`) REFERENCES `planner_test_jobs` (`id`); +ALTER TABLE `planner_test_runs` ADD CONSTRAINT test_runs_tko_test_id_fk FOREIGN KEY (`tko_test_id`) REFERENCES `tko.tko_tests` (`test_idx`); + + +CREATE TABLE `planner_data_types` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `name` varchar(255) NOT NULL, + `db_table` varchar(255) NOT NULL +) +; + + +CREATE TABLE `planner_history` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `action_id` integer NOT NULL, + `user_id` integer NOT NULL, + `data_type_id` integer NOT NULL, + `object_id` integer NOT NULL, + `old_object_repr` longtext NOT NULL, + `new_object_repr` longtext NOT NULL, + `time` datetime NOT NULL +) +; +ALTER TABLE `planner_history` ADD CONSTRAINT history_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_history` ADD CONSTRAINT history_user_id_fk FOREIGN KEY (`user_id`) REFERENCES `afe_users` (`id`); +ALTER TABLE `planner_history` ADD CONSTRAINT history_data_type_id_fk FOREIGN KEY (`data_type_id`) REFERENCES `planner_data_types` (`id`); + + +CREATE TABLE `planner_saved_objects` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `user_id` integer NOT NULL, + `type` varchar(16) NOT NULL, + `name` varchar(255) NOT NULL, + `encoded_object` longtext NOT NULL, + UNIQUE (`user_id`, `type`, `name`) +) +; +ALTER TABLE `planner_saved_objects` ADD CONSTRAINT saved_objects_user_id_fk FOREIGN KEY (`user_id`) REFERENCES `afe_users` (`id`); + + +CREATE TABLE `planner_custom_queries` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `query` longtext NOT NULL +) +; +ALTER TABLE `planner_custom_queries` ADD CONSTRAINT custom_queries_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); + + +CREATE TABLE `planner_keyvals` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `the_hash` varchar(40) NOT NULL UNIQUE, + `key` varchar(1024) NOT NULL, + `value` varchar(1024) NOT NULL +) +; + + +CREATE TABLE `planner_autoprocess` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `condition` longtext NOT NULL, + `enabled` bool NOT NULL, + `reason_override` varchar(255) NULL +) +; +ALTER TABLE `planner_autoprocess` ADD CONSTRAINT autoprocess_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); + + +CREATE TABLE `planner_plan_owners` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `plan_id` integer NOT NULL, + `user_id` integer NOT NULL, + UNIQUE (`plan_id`, `user_id`) +) +; +ALTER TABLE `planner_plan_owners` ADD CONSTRAINT plan_owners_plan_id_fk FOREIGN KEY (`plan_id`) REFERENCES `planner_plans` (`id`); +ALTER TABLE `planner_plan_owners` ADD CONSTRAINT plan_owners_user_id_fk FOREIGN KEY (`user_id`) REFERENCES `afe_users` (`id`); + + +CREATE TABLE `planner_test_run_bugs` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `testrun_id` integer NOT NULL, + `bug_id` integer NOT NULL, + UNIQUE (`testrun_id`, `bug_id`) +) +; +ALTER TABLE `planner_test_run_bugs` ADD CONSTRAINT test_run_bugs_testrun_id_fk FOREIGN KEY (`testrun_id`) REFERENCES `planner_test_runs` (`id`); +ALTER TABLE `planner_test_run_bugs` ADD CONSTRAINT test_run_bugs_bug_id_fk FOREIGN KEY (`bug_id`) REFERENCES `planner_bugs` (`id`); + + +CREATE TABLE `planner_autoprocess_labels` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `autoprocess_id` integer NOT NULL, + `testlabel_id` integer NOT NULL, + UNIQUE (`autoprocess_id`, `testlabel_id`) +) +; +ALTER TABLE `planner_autoprocess_labels` ADD CONSTRAINT autoprocess_labels_autoprocess_id_fk FOREIGN KEY (`autoprocess_id`) REFERENCES `planner_autoprocess` (`id`); +ALTER TABLE `planner_autoprocess_labels` ADD CONSTRAINT autoprocess_labels_testlabel_id_fk FOREIGN KEY (`testlabel_id`) REFERENCES `tko.tko_test_labels` (`id`); + + +CREATE TABLE `planner_autoprocess_keyvals` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `autoprocess_id` integer NOT NULL, + `keyval_id` integer NOT NULL, + UNIQUE (`autoprocess_id`, `keyval_id`) +) +; +ALTER TABLE `planner_autoprocess_keyvals` ADD CONSTRAINT autoprocess_keyvals_autoprocess_id_fk FOREIGN KEY (`autoprocess_id`) REFERENCES `planner_autoprocess` (`id`); +ALTER TABLE `planner_autoprocess_keyvals` ADD CONSTRAINT autoprocess_keyvals_keyval_id_fk FOREIGN KEY (`keyval_id`) REFERENCES `planner_keyvals` (`id`); + + +CREATE TABLE `planner_autoprocess_bugs` ( + `id` integer AUTO_INCREMENT NOT NULL PRIMARY KEY, + `autoprocess_id` integer NOT NULL, + `bug_id` integer NOT NULL, + UNIQUE (`autoprocess_id`, `bug_id`) +) +; +ALTER TABLE `planner_autoprocess_bugs` ADD CONSTRAINT autoprocess_bugs_autoprocess_id_fk FOREIGN KEY (`autoprocess_id`) REFERENCES `planner_autoprocess` (`id`); +ALTER TABLE `planner_autoprocess_bugs` ADD CONSTRAINT autoprocess_bugs_bug_id_fk FOREIGN KEY (`bug_id`) REFERENCES `planner_bugs` (`id`); + + +CREATE INDEX `planner_hosts_plan_id` ON `planner_hosts` (`plan_id`); +CREATE INDEX `planner_hosts_host_id` ON `planner_hosts` (`host_id`); +CREATE INDEX `planner_tests_plan_id` ON `planner_tests` (`plan_id`); +CREATE INDEX `planner_tests_control_file_id` ON `planner_tests` (`control_file_id`); +CREATE INDEX `planner_test_jobs_plan_id` ON `planner_test_jobs` (`plan_id`); +CREATE INDEX `planner_test_jobs_test_id` ON `planner_test_jobs` (`test_id`); +CREATE INDEX `planner_test_jobs_afe_job_id` ON `planner_test_jobs` (`afe_job_id`); +CREATE INDEX `planner_test_runs_plan_id` ON `planner_test_runs` (`plan_id`); +CREATE INDEX `planner_test_runs_test_job_id` ON `planner_test_runs` (`test_job_id`); +CREATE INDEX `planner_test_runs_tko_test_id` ON `planner_test_runs` (`tko_test_id`); +CREATE INDEX `planner_history_plan_id` ON `planner_history` (`plan_id`); +CREATE INDEX `planner_history_user_id` ON `planner_history` (`user_id`); +CREATE INDEX `planner_history_data_type_id` ON `planner_history` (`data_type_id`); +CREATE INDEX `planner_saved_objects_user_id` ON `planner_saved_objects` (`user_id`); +CREATE INDEX `planner_custom_queries_plan_id` ON `planner_custom_queries` (`plan_id`); +CREATE INDEX `planner_autoprocess_plan_id` ON `planner_autoprocess` (`plan_id`); + +COMMIT; +""" + +DOWN_SQL = """\ +DROP TABLE IF EXISTS planner_autoprocess_labels; +DROP TABLE IF EXISTS planner_autoprocess_bugs; +DROP TABLE IF EXISTS planner_autoprocess_keyvals; +DROP TABLE IF EXISTS planner_autoprocess; +DROP TABLE IF EXISTS planner_custom_queries; +DROP TABLE IF EXISTS planner_saved_objects; +DROP TABLE IF EXISTS planner_history; +DROP TABLE IF EXISTS planner_data_types; +DROP TABLE IF EXISTS planner_hosts; +DROP TABLE IF EXISTS planner_keyvals; +DROP TABLE IF EXISTS planner_plan_owners; +DROP TABLE IF EXISTS planner_test_run_bugs; +DROP TABLE IF EXISTS planner_test_runs; +DROP TABLE IF EXISTS planner_test_jobs; +DROP TABLE IF EXISTS planner_tests; +DROP TABLE IF EXISTS planner_test_control_files; +DROP TABLE IF EXISTS planner_bugs; +DROP TABLE IF EXISTS planner_plans; +""" --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/common.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,8 @@ +import os, sys +dirname = os.path.dirname(sys.modules[__name__].__file__) +autotest_dir = os.path.abspath(os.path.join(dirname, '..', '..')) +client_dir = os.path.join(autotest_dir, "client") +sys.path.insert(0, client_dir) +import setup_modules +sys.path.pop(0) +setup_modules.setup(base_path=autotest_dir, root_module_name="autotest_lib") --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/models.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,363 @@ +from django.db import models as dbmodels +import common +from autotest_lib.frontend.afe import models as afe_models +from autotest_lib.frontend.afe import model_logic, rpc_utils +from autotest_lib.new_tko.tko import models as tko_models +from autotest_lib.client.common_lib import enum + + +class Plan(dbmodels.Model): + """A test plan + + Required: + name: Plan name, unique + complete: True if the plan is completed + dirty: True if the plan has been changed since the execution engine has + last seen it + initialized: True if the plan has started + + Optional: + label_override: A label to apply to each Autotest job. + support: The global support object to apply to this plan + """ + name = dbmodels.CharField(max_length=255, unique=True) + label_override = dbmodels.CharField(max_length=255, null=True, blank=True) + support = dbmodels.TextField(blank=True) + complete = dbmodels.BooleanField(default=False) + dirty = dbmodels.BooleanField(default=False) + initialized = dbmodels.BooleanField(default=False) + + owners = dbmodels.ManyToManyField(afe_models.User, + db_table='planner_plan_owners') + hosts = dbmodels.ManyToManyField(afe_models.Host, through='Host') + + class Meta: + db_table = 'planner_plans' + + + def __unicode__(self): + return unicode(self.name) + + +class ModelWithPlan(dbmodels.Model): + """Superclass for models that have a plan_id + + Required: + plan: The associated test plan + """ + plan = dbmodels.ForeignKey(Plan) + + class Meta: + abstract = True + + + def __unicode__(self): + return u'%s (%s)' % (self._get_details_unicode(), self.plan.name) + + + def _get_details_unicode(self): + """Gets the first part of the unicode string + + subclasses must override this method + """ + raise NotImplementedError( + 'Subclasses must override _get_details_unicode()') + + +class Host(ModelWithPlan): + """A plan host + + Required: + host: The AFE host + complete: True if and only if this host is finished in the test plan + blocked: True if and only if the host is blocked (not executing tests) + """ + host = dbmodels.ForeignKey(afe_models.Host) + complete = dbmodels.BooleanField(default=False) + blocked = dbmodels.BooleanField(default=False) + + class Meta: + db_table = 'planner_hosts' + + + def _get_details_unicode(self): + return 'Host: %s' % host.hostname + + +class ControlFile(model_logic.ModelWithHash): + """A control file. Immutable once added to the table + + Required: + contents: The text of the control file + + Others: + control_hash: The SHA1 hash of the control file, for duplicate detection + and fast search + """ + contents = dbmodels.TextField() + + class Meta: + db_table = 'planner_test_control_files' + + + @classmethod + def _compute_hash(cls, **kwargs): + return rpc_utils.get_sha1_hash(kwargs['contents']) + + + def __unicode__(self): + return u'Control file id %s (SHA1: %s)' % (self.id, self.control_hash) + + +class Test(ModelWithPlan): + """A planned test + + Required: + test_control_file: The control file to run + execution_order: An integer describing when this test should be run in + the test plan + """ + control_file = dbmodels.ForeignKey(ControlFile) + execution_order = dbmodels.IntegerField(blank=True) + + class Meta: + db_table = 'planner_tests' + ordering = ('execution_order',) + + + def _get_details_unicode(self): + return 'Planned test - Control file id %s' % test_control_file.id + + +class Job(ModelWithPlan): + """Represents an Autotest job initiated for a test plan + + Required: + test: The Test associated with this Job + afe_job: The Autotest job + """ + test = dbmodels.ForeignKey(Test) + afe_job = dbmodels.ForeignKey(afe_models.Job) + + class Meta: + db_table = 'planner_test_jobs' + + + def _get_details_unicode(self): + return 'AFE job %s' % afe_job.id + + +class Bug(dbmodels.Model): + """Represents a bug ID + + Required: + external_uid: External unique ID for the bug + """ + external_uid = dbmodels.CharField(max_length=255, unique=True) + + class Meta: + db_table = 'planner_bugs' + + + def __unicode__(self): + return u'Bug external ID %s' % self.external_uid + + +class TestRun(ModelWithPlan): + """An individual test run from an Autotest job for the test plan. + + Each Job object may have multiple TestRun objects associated with it. + + Required: + test_job: The Job object associated with this TestRun + tko_test: The TKO Test associated with this TestRun + status: One of 'Active', 'Passed', 'Failed' + finalized: True if and only if the TestRun is ready to be shown in + triage + invalidated: True if and only if a user has decided to invalidate this + TestRun's results + seen: True if and only if a user has marked this TestRun as "seen" + triaged: True if and only if the TestRun no longer requires any user + intervention + + Optional: + bugs: Bugs filed that a relevant to this run + """ + Status = enum.Enum('Active', 'Passed', 'Failed', string_values=True) + + test_job = dbmodels.ForeignKey(Job) + tko_test = dbmodels.ForeignKey(tko_models.Test) + status = dbmodels.CharField(max_length=16, choices=Status.choices()) + finalized = dbmodels.BooleanField(default=False) + seen = dbmodels.BooleanField(default=False) + triaged = dbmodels.BooleanField(default=False) + + bugs = dbmodels.ManyToManyField(Bug, null=True, + db_table='planner_test_run_bugs') + + class Meta: + db_table = 'planner_test_runs' + + + def _get_details_unicode(self): + return 'Test Run: %s' % self.id + + +class DataType(dbmodels.Model): + """Encodes the data model types + + For use in the history table, to identify the type of object that was + changed. + + Required: + name: The name of the data type + db_table: The name of the database table that stores this type + """ + name = dbmodels.CharField(max_length=255) + db_table = dbmodels.CharField(max_length=255) + + class Meta: + db_table = 'planner_data_types' + + + def __unicode__(self): + return u'Data type %s (stored in table %s)' % (self.name, self.db_table) + + +class History(ModelWithPlan): + """Represents a history action + + Required: + action_id: An arbitrary ID that uniquely identifies the user action + related to the history entry. One user action may result in + multiple history entries + user: The user who initiated the change + data_type: The type of object that was changed + object_id: Value of the primary key field for the changed object + old_object_repr: A string representation of the object before the change + new_object_repr: A string representation of the object after the change + + Others: + time: A timestamp. Automatically generated. + """ + action_id = dbmodels.IntegerField() + user = dbmodels.ForeignKey(afe_models.User) + data_type = dbmodels.ForeignKey(DataType) + object_id = dbmodels.IntegerField() + old_object_repr = dbmodels.TextField(blank=True) + new_object_repr = dbmodels.TextField(blank=True) + + time = dbmodels.DateTimeField(auto_now_add=True) + + class Meta: + db_table = 'planner_history' + + + def _get_details_unicode(self): + return 'History entry: %s => %s' % (self.old_object_repr, + self.new_object_repr) + + +class SavedObject(dbmodels.Model): + """A saved object that can be recalled at certain points in the UI + + Required: + user: The creator of the object + object_type: One of 'support', 'triage', 'autoprocess', 'custom_query' + name: The name given to the object + encoded_object: The actual object + """ + Type = enum.Enum('support', 'triage', 'autoprocess', 'custom_query', + string_values=True) + + user = dbmodels.ForeignKey(afe_models.User) + object_type = dbmodels.CharField(max_length=16, + choices=Type.choices(), db_column='type') + name = dbmodels.CharField(max_length=255) + encoded_object = dbmodels.TextField() + + class Meta: + db_table = 'planner_saved_objects' + unique_together = ('user', 'object_type', 'name') + + + def __unicode__(self): + return u'Saved %s object: %s, by %s' % (self.object_type, self.name, + self.user.login) + + +class CustomQuery(ModelWithPlan): + """A custom SQL query for the triage page + + Required: + query: the SQL WHERE clause to attach to the main query + """ + query = dbmodels.TextField() + + class Meta: + db_table = 'planner_custom_queries' + + + def _get_details_unicode(self): + return 'Custom Query: %s' % self.query + + +class KeyVal(model_logic.ModelWithHash): + """Represents a keyval. Immutable once added to the table. + + Required: + key: The key + value: The value + + Others: + keyval_hash: The result of SHA1(SHA1(key) ++ value), for duplicate + detection and fast search. + """ + key = dbmodels.CharField(max_length=1024) + value = dbmodels.CharField(max_length=1024) + + class Meta: + db_table = 'planner_keyvals' + + + @classmethod + def _compute_hash(cls, **kwargs): + round1 = rpc_utils.get_sha1_hash(kwargs['key']) + return rpc_utils.get_sha1_hash(round1 + kwargs['value']) + + + def __unicode__(self): + return u'Keyval: %s = %s' % (self.key, self.value) + + +class AutoProcess(ModelWithPlan): + """An autoprocessing directive to perform on test runs that enter triage + + Required: + condition: A SQL WHERE clause. The autoprocessing will be applied if the + test run matches this condition + enabled: If this is False, this autoprocessing entry will not be applied + + Optional: + labels: Labels to apply to the TKO test + keyvals: Keyval overrides to apply to the TKO test + bugs: Bugs filed that a relevant to this run + reason_override: Override for the AFE reason + """ + condition = dbmodels.TextField() + enabled = dbmodels.BooleanField(default=False) + + labels = dbmodels.ManyToManyField(tko_models.TestLabel, null=True, + db_table='planner_autoprocess_labels') + keyvals = dbmodels.ManyToManyField(KeyVal, null=True, + db_table='planner_autoprocess_keyvals') + bugs = dbmodels.ManyToManyField(Bug, null=True, + db_table='planner_autoprocess_bugs') + reason_override = dbmodels.CharField(max_length=255, null=True, blank=True) + + class Meta: + db_table = 'planner_autoprocess' + + + def _get_details_unicode(self): + return 'Autoprocessing condition: %s' % self.condition --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/models_test.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,65 @@ +#!/usr/bin/python + +import unittest +import common +from autotest_lib.frontend import setup_django_environment +from autotest_lib.frontend.afe import frontend_test_utils, rpc_utils +from autotest_lib.frontend.planner import models + + +class ModelWithHashTestBase(frontend_test_utils.FrontendTestMixin): + def setUp(self): + self._frontend_common_setup(fill_data=False) + + + def tearDown(self): + self._frontend_common_teardown() + + + def _model_class(self): + raise NotImplementedError('Subclasses must override _model_class()') + + + def _test_data(self): + raise NotImplementedError('Subclasses must override _test_data()') + + + def test_disallowed_operations(self): + def _call_create(): + self._model_class().objects.create(**self._test_data()) + self.assertRaises(Exception, _call_create) + + model = self._model_class().objects.get_or_create( + **self._test_data())[0] + self.assertRaises(Exception, model.save) + + + def test_hash_field(self): + model = self._model_class().objects.get_or_create( + **self._test_data())[0] + self.assertNotEqual(model.id, None) + self.assertEqual(self._model_class()._compute_hash(**self._test_data()), + model.the_hash) + + +class ControlFileTest(ModelWithHashTestBase, unittest.TestCase): + def _model_class(self): + return models.ControlFile + + + def _test_data(self): + return {'contents' : 'test_control'} + + +class KeyValTest(ModelWithHashTestBase, unittest.TestCase): + def _model_class(self): + return models.KeyVal + + + def _test_data(self): + return {'key' : 'test_key', + 'value' : 'test_value'} + + +if __name__ == '__main__': + unittest.main() --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/rpc_interface.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,8 @@ +"""\ +Functions to expose over the RPC interface. +""" + +__author__ = '[email protected] (James Ren)' + + +# Nothing yet --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/urls.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,12 @@ +from django.conf.urls.defaults import * +import common +from autotest_lib.frontend import settings, urls_common + +pattern_list, debug_pattern_list = ( + urls_common.generate_pattern_lists('frontend.planner', + 'TestPlannerClient')) + +if settings.DEBUG: + pattern_list += debug_pattern_list + +urlpatterns = patterns('', *pattern_list) --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/planner/views.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,24 @@ +import urllib2, sys, traceback, cgi + +import common +from autotest_lib.frontend import views_common +from autotest_lib.frontend.afe import rpc_handler +from autotest_lib.frontend.planner import models, rpc_interface + +rpc_handler_obj = rpc_handler.RpcHandler((rpc_interface,), + document_module=rpc_interface) + + +def handle_rpc(request): + return rpc_handler_obj.handle_rpc_request(request) + + +def rpc_documentation(request): + return rpc_handler_obj.get_rpc_documentation() + + +def model_documentation(request): + model_names = ('Plan', 'Host', 'ControlFile', 'Test', 'Job', 'KeyVal', + 'Bug', 'TestRun', 'DataType', 'History', 'SavedObject', + 'CustomQuery', 'AutoProcess') + return views_common.model_documentation(models, model_names) --- autotest/frontend/settings.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/settings.py 2009-12-18 00:27:34.000000000 -0800 @@ -43,6 +43,7 @@ # prefix applied to all URLs - useful if requests are coming through apache, # and you need this app to coexist with others URL_PREFIX = 'afe/server/' +PLANNER_URL_PREFIX = 'planner/server/' # Local time zone for this installation. Choices can be found here: # http://www.postgresql.org/docs/8.1/static/datetime-keywords.html#DATETIME-TIMEZONE-SET-TABLE @@ -105,6 +106,7 @@ INSTALLED_APPS = ( 'frontend.afe', + 'frontend.planner', 'django.contrib.admin', 'django.contrib.auth', 'django.contrib.contenttypes', --- autotest/frontend/urls.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/frontend/urls.py 2009-12-18 00:27:34.000000000 -0800 @@ -6,12 +6,14 @@ admin.autodiscover() RE_PREFIX = '^' + settings.URL_PREFIX +PLANNER_RE_PREFIX = '^' + settings.PLANNER_URL_PREFIX handler500 = 'frontend.afe.views.handler500' pattern_list = ( (RE_PREFIX + r'admin/(.*)', admin.site.root), (RE_PREFIX, include('frontend.afe.urls')), + (PLANNER_RE_PREFIX, include('frontend.planner.urls')), ) debug_pattern_list = ( --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/urls_common.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,36 @@ +import os + + +def generate_pattern_lists(django_name, gwt_name): + """ + Generates the common URL patterns for the given names + + @param django_name the full name of the Django application + (e.g., frontend.afe) + @param gwt_name the name of the GWT project (e.g., AfeClient) + @return the common standard and the debug pattern lists, as a tuple + """ + + pattern_list = [ + (r'^(?:|noauth/)rpc/', '%s.views.handle_rpc' % django_name), + (r'^rpc_doc', '%s.views.rpc_documentation' % django_name) + ] + + debug_pattern_list = [ + (r'^model_doc/', '%s.views.model_documentation' % django_name), + + # for GWT hosted mode + (r'^(?P<forward_addr>autotest.*)', + 'autotest_lib.frontend.afe.views.gwt_forward'), + + # for GWT compiled files + (r'^client/(?P<path>.*)$', 'django.views.static.serve', + {'document_root': os.path.join(os.path.dirname(__file__), '..', + 'frontend', 'client', 'www')}), + # redirect / to compiled client + (r'^$', 'django.views.generic.simple.redirect_to', + {'url': + 'client/autotest.%(name)s/%(name)s.html' % dict(name=gwt_name)}), + ] + + return (pattern_list, debug_pattern_list) --- /dev/null 2009-12-17 12:29:38.000000000 -0800 +++ autotest/frontend/views_common.py 2009-12-18 00:27:34.000000000 -0800 @@ -0,0 +1,9 @@ +from django.http import HttpResponse + +def model_documentation(models_module, model_names): + doc = '<h2>Models</h2>\n' + for model_name in model_names: + model_class = getattr(models_module, model_name) + doc += '<h3>%s</h3>\n' % model_name + doc += '<pre>\n%s</pre>\n' % model_class.__doc__ + return HttpResponse(doc) --- autotest/new_tko/tko/urls.py 2009-12-18 00:27:34.000000000 -0800 +++ autotest/new_tko/tko/urls.py 2009-12-18 00:27:34.000000000 -0800 @@ -1,30 +1,15 @@ from django.conf.urls.defaults import * -from django.conf import settings -import os +import common +from autotest_lib.frontend import settings, urls_common -pattern_list = [(r'^(?:|noauth/)rpc/', 'new_tko.tko.views.handle_rpc'), - (r'^(?:|noauth/)jsonp_rpc/', - 'new_tko.tko.views.handle_jsonp_rpc'), - (r'^(?:|noauth/)csv/', 'new_tko.tko.views.handle_csv'), - (r'^rpc_doc', 'new_tko.tko.views.rpc_documentation'), - (r'^(?:|noauth/)plot/', 'new_tko.tko.views.handle_plot')] - -debug_pattern_list = [ - (r'^model_doc/', 'new_tko.tko.views.model_documentation'), - - # for GWT hosted mode - (r'^(?P<forward_addr>autotest.*)', - 'autotest_lib.frontend.afe.views.gwt_forward'), - - # for GWT compiled files - (r'^client/(?P<path>.*)$', 'django.views.static.serve', - {'document_root': os.path.join(os.path.dirname(__file__), '..', '..', - 'frontend', 'client', 'www')}), - # redirect / to compiled client - (r'^$', 'django.views.generic.simple.redirect_to', - {'url': 'client/autotest.TkoClient/TkoClient.html'}), - -] +pattern_list, debug_pattern_list = ( + urls_common.generate_pattern_lists(django_name='new_tko.tko', + gwt_name='TkoClient')) + +pattern_list += [(r'^(?:|noauth/)jsonp_rpc/', + 'new_tko.tko.views.handle_jsonp_rpc'), + (r'^(?:|noauth/)csv/', 'new_tko.tko.views.handle_csv'), + (r'^(?:|noauth/)plot/', 'new_tko.tko.views.handle_plot')] if settings.DEBUG: pattern_list += debug_pattern_list _______________________________________________ Autotest mailing list [email protected] http://test.kernel.org/cgi-bin/mailman/listinfo/autotest
