From 56c4d79fb2c30a3481c80693875f24c9a871bd51 Mon Sep 17 00:00:00 2001 From: volpino Date: Wed, 30 May 2012 22:54:55 +0200 Subject: [PATCH 01/37] euscanwww: starting implementing Celery tasks Basic tasks.py module, some fixes in the management commands Signed-off-by: volpino --- .../management/commands/scan_metadata.py | 8 +- .../management/commands/scan_portage.py | 21 ++--- .../management/commands/scan_upstream.py | 49 +++++------ euscanwww/djeuscan/tasks.py | 83 +++++++++++++++++++ euscanwww/euscanwww/settings.py | 9 ++ 5 files changed, 126 insertions(+), 44 deletions(-) create mode 100644 euscanwww/djeuscan/tasks.py diff --git a/euscanwww/djeuscan/management/commands/scan_metadata.py b/euscanwww/djeuscan/management/commands/scan_metadata.py index 8590412..d9c7e49 100644 --- a/euscanwww/djeuscan/management/commands/scan_metadata.py +++ b/euscanwww/djeuscan/management/commands/scan_metadata.py @@ -18,7 +18,7 @@ class ScanMetadata(object): self.style = color_style() @commit_on_success - def run(self, query=None, obj=None): + def scan(self, query=None, obj=None): matches = Query(query).find( include_masked=True, in_installed=False, @@ -158,10 +158,10 @@ class Command(BaseCommand): if options['all']: for pkg in Package.objects.all(): - scan_metadata.run('%s/%s' % (pkg.category, pkg.name), pkg) + scan_metadata.scan('%s/%s' % (pkg.category, pkg.name), pkg) elif len(args) > 0: for package in args: - scan_metadata.run(package) + scan_metadata.scan(package) else: for package in sys.stdin.readlines(): - scan_metadata.run(package[:-1]) + scan_metadata.scan(package[:-1]) diff --git a/euscanwww/djeuscan/management/commands/scan_portage.py b/euscanwww/djeuscan/management/commands/scan_portage.py index 4227501..9533730 100644 --- a/euscanwww/djeuscan/management/commands/scan_portage.py +++ b/euscanwww/djeuscan/management/commands/scan_portage.py @@ -3,8 +3,8 @@ import portage import sys import os import re - from optparse import make_option +from collections import defaultdict from django.db.transaction import commit_on_success from django.core.management.base import BaseCommand @@ -14,13 +14,10 @@ from djeuscan.models import Package, Version, VersionLog class ScanPortage(object): - def __init__(self, stdout=None, **options): - if stdout is None: - self.stdout = sys.stdout - else: - self.stdout = stdout + def __init__(self, stdout=None, options=None): + self.stdout = sys.stdout if stdout is None else stdout + self.options = defaultdict(None) if options is None else options - self.options = options self.style = color_style() self._cache = {'packages': {}, 'versions': {}} self._overlays = None @@ -84,7 +81,7 @@ class ScanPortage(object): return self._overlays @commit_on_success - def run(self, query=None): + def scan(self, query=None): env = os.environ env['MY'] = "/-: []\n" @@ -312,7 +309,7 @@ class Command(BaseCommand): help = 'Scans portage tree and fills database' def handle(self, *args, **options): - scan_portage = ScanPortage(stdout=self.stdout, **options) + scan_portage = ScanPortage(stdout=self.stdout, options=options) if not options['quiet']: self.stdout.write('Scanning portage tree...\n') @@ -329,13 +326,13 @@ class Command(BaseCommand): self.stdout.write('done\n') if options['all']: - scan_portage.run() + scan_portage.scan() elif len(args): for package in args: - scan_portage.run(package) + scan_portage.scan(package) else: for package in sys.stdin.readlines(): - scan_portage.run(package[:-1]) + scan_portage.scan(package[:-1]) if options['purge-versions']: purge_versions(options) diff --git a/euscanwww/djeuscan/management/commands/scan_upstream.py b/euscanwww/djeuscan/management/commands/scan_upstream.py index f6e78c3..2e2d9c2 100644 --- a/euscanwww/djeuscan/management/commands/scan_upstream.py +++ b/euscanwww/djeuscan/management/commands/scan_upstream.py @@ -4,7 +4,6 @@ import sys import re from StringIO import StringIO from optparse import make_option -from collections import defaultdict from django.utils import timezone from django.db.transaction import commit_on_success @@ -14,21 +13,17 @@ from djeuscan.models import Package, Version, EuscanResult, VersionLog class ScanUpstream(object): - def __init__(self, options=None): - if options is None: - self.options = defaultdict(None) - else: - self.options = options + def __init__(self, quiet=False): + self.quiet = quiet - def run(self, packages=None): - for package in packages: - cmd = ['euscan', package] + def scan(self, package): + cmd = ['euscan', package] - fp = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = StringIO(fp.communicate()[0]) + fp = subprocess.Popen(cmd, stdout=subprocess.PIPE, + stderr=subprocess.PIPE) + output = StringIO(fp.communicate()[0]) - self.parse_output(output) + self.parse_output(output) def store_result(self, package, log): # Remove previous logs @@ -45,7 +40,7 @@ class ScanUpstream(object): obj, created = Package.objects.get_or_create(category=cat, name=pkg) - if created and not self.options['quiet']: + if created and not self.quiet: sys.stdout.write('+ [p] %s/%s\n' % (cat, pkg)) # Set all versions dead, then set found versions alive and @@ -69,7 +64,7 @@ class ScanUpstream(object): if not created: return - if not self.options['quiet']: + if not self.quiet: sys.stdout.write('+ [u] %s %s\n' % (obj, url)) VersionLog.objects.create( @@ -125,7 +120,7 @@ class ScanUpstream(object): @commit_on_success -def purge_versions(options): +def purge_versions(quiet=False): # For each dead versions for version in Version.objects.filter(packaged=False, alive=False): VersionLog.objects.create( @@ -140,7 +135,7 @@ def purge_versions(options): version.package.n_versions -= 1 version.package.save() - if not options['quiet']: + if not quiet: sys.stdout.write('- [u] %s %s\n' % (version, version.urls)) Version.objects.filter(packaged=False, alive=False).delete() @@ -174,31 +169,29 @@ class Command(BaseCommand): help = 'Scans metadata and fills database' def handle(self, *args, **options): - scan_upstream = ScanUpstream(options) + scan_upstream = ScanUpstream(options["quiet"]) if options['feed']: - scan_upstream.parse_output(options, sys.stdin) + scan_upstream.parse_output(sys.stdin) if options['purge-versions']: - purge_versions(options) + purge_versions(options["quiet"]) return if not options['quiet']: self.stdout.write('Scanning upstream...\n') - packages = [] - if options['all']: for pkg in Package.objects.all(): - packages.append('%s/%s' % (pkg.category, pkg.name)) + scan_upstream.scan('%s/%s' % (pkg.category, pkg.name)) elif args: - packages = list(args) + for arg in args: + scan_upstream.scan(arg) else: - packages = [package[:-1] for package in sys.stdin.readlines()] - - scan_upstream.run(packages) + for package in sys.stdin.readlines(): + scan_upstream.scan(package[:-1]) if options['purge-versions']: - purge_versions(options) + purge_versions(options["quiet"]) if not options['quiet']: self.stdout.write('Done.\n') diff --git a/euscanwww/djeuscan/tasks.py b/euscanwww/djeuscan/tasks.py new file mode 100644 index 0000000..fbc216f --- /dev/null +++ b/euscanwww/djeuscan/tasks.py @@ -0,0 +1,83 @@ +from celery.task import task +from celery.task.sets import TaskSet + +from djeuscan.models import Package + +from djeuscan.management.commands.regen_rrds import regen_rrds +from djeuscan.management.commands.update_counters import update_counters +from djeuscan.management.commands.scan_metadata import ScanMetadata +from djeuscan.management.commands.scan_portage import ScanPortage, \ + purge_versions as scan_portage_purge +from djeuscan.management.commands.scan_upstream import ScanUpstream, \ + purge_versions as scan_upstream_purge + + +@task +def regen_rrds_task(): + regen_rrds() + + +@task +def update_counters_task(): + update_counters() + + +@task +def scan_metadata_task(query, obj=None): + scan_metadata = ScanMetadata() + scan_metadata.scan(query) + + +@task +def scan_metadata_all_task(): + job = TaskSet(tasks=[ + scan_metadata_task.subtask(('%s/%s' % (pkg.category, pkg.name), pkg)) + for pkg in Package.objects.all() + ]) + job.apply_async() + + +@task +def scan_portage_all_task(purge=False): + scan_portage = ScanPortage() + scan_portage.scan() + + if purge: + scan_portage_purge() + + +@task +def scan_portage_task(query, purge=False): + scan_portage = ScanPortage() + scan_portage.scan(query) + + if purge: + scan_portage_purge() + + +@task +def scan_portage_purge_task(): + scan_portage_purge() + + +@task +def scan_upstream_all_task(purge=False): + tasks = [scan_upstream_task.subtask(('%s/%s' % (pkg.category, pkg.name))) + for pkg in Package.objects.all()] + + if purge: + tasks.append(scan_upstream_purge_task.subtask()) + + job = TaskSet(tasks=tasks) + job.apply_async() + + +@task +def scan_upstream_task(query): + scan_upstream = ScanUpstream() + scan_upstream.scan(query) + + +@task +def scan_upstream_purge_task(): + scan_upstream_purge() diff --git a/euscanwww/euscanwww/settings.py b/euscanwww/euscanwww/settings.py index 0365bc7..0f2b483 100644 --- a/euscanwww/euscanwww/settings.py +++ b/euscanwww/euscanwww/settings.py @@ -166,6 +166,7 @@ INSTALLED_APPS = ( # Uncomment the next line to enable admin documentation: # 'django.contrib.admindocs', 'south', + 'djcelery', 'euscanwww', 'djeuscan', ) @@ -199,6 +200,14 @@ LOGGING = { } } + +# Celery config +import djcelery +djcelery.setup_loader() +BROKER_URL = "amqp://guest:guest@localhost:5672//" +CELERY_RESULT_BACKEND = "amqp" + + try: from local_settings import * except ImportError, ex: From e8718748b4b499fb37516df90d093c615e9433b3 Mon Sep 17 00:00:00 2001 From: volpino Date: Thu, 31 May 2012 09:11:40 +0200 Subject: [PATCH 02/37] euscanwww: Celery fixes and little logging Signed-off-by: volpino --- euscanwww/djeuscan/tasks.py | 16 +++++++++++++++- 1 file changed, 15 insertions(+), 1 deletion(-) diff --git a/euscanwww/djeuscan/tasks.py b/euscanwww/djeuscan/tasks.py index fbc216f..e614a70 100644 --- a/euscanwww/djeuscan/tasks.py +++ b/euscanwww/djeuscan/tasks.py @@ -24,6 +24,9 @@ def update_counters_task(): @task def scan_metadata_task(query, obj=None): + logger = scan_metadata_task.get_logger() + logger.info("Starting metadata scanning for package %s ...", query) + scan_metadata = ScanMetadata() scan_metadata.scan(query) @@ -39,19 +42,27 @@ def scan_metadata_all_task(): @task def scan_portage_all_task(purge=False): + logger = scan_portage_all_task.get_logger() + logger.info("Starting Portage scanning...") + scan_portage = ScanPortage() scan_portage.scan() if purge: + logger.info("Purging") scan_portage_purge() @task def scan_portage_task(query, purge=False): + logger = scan_portage_task.get_logger() + logger.info("Starting Portage package scanning: %s ...", query) + scan_portage = ScanPortage() scan_portage.scan(query) if purge: + logger.info("Purging") scan_portage_purge() @@ -62,7 +73,7 @@ def scan_portage_purge_task(): @task def scan_upstream_all_task(purge=False): - tasks = [scan_upstream_task.subtask(('%s/%s' % (pkg.category, pkg.name))) + tasks = [scan_upstream_task.subtask(('%s/%s' % (pkg.category, pkg.name), )) for pkg in Package.objects.all()] if purge: @@ -74,6 +85,9 @@ def scan_upstream_all_task(purge=False): @task def scan_upstream_task(query): + logger = scan_upstream_task.get_logger() + logger.info("Starting upstream scanning for package %s ...", query) + scan_upstream = ScanUpstream() scan_upstream.scan(query) From 47ec539e1da48276fcd1b647414d6be52c5dda0e Mon Sep 17 00:00:00 2001 From: volpino Date: Thu, 31 May 2012 18:42:37 +0200 Subject: [PATCH 03/37] euscanwww: scan_upstream imports euscan.scan scan_upstream command imports euscan.scan and simply calls a function, it doesn't call a subprocess Signed-off-by: volpino --- .../management/commands/scan_upstream.py | 26 ++++++++++++++----- pym/euscan/__init__.py | 2 +- pym/euscan/out.py | 9 ++++--- 3 files changed, 26 insertions(+), 11 deletions(-) diff --git a/euscanwww/djeuscan/management/commands/scan_upstream.py b/euscanwww/djeuscan/management/commands/scan_upstream.py index 2e2d9c2..131ff1a 100644 --- a/euscanwww/djeuscan/management/commands/scan_upstream.py +++ b/euscanwww/djeuscan/management/commands/scan_upstream.py @@ -1,14 +1,16 @@ -import subprocess import portage import sys import re -from StringIO import StringIO + from optparse import make_option from django.utils import timezone from django.db.transaction import commit_on_success from django.core.management.base import BaseCommand +from euscan import CONFIG, output +from euscan.scan import scan_upstream + from djeuscan.models import Package, Version, EuscanResult, VersionLog @@ -17,13 +19,23 @@ class ScanUpstream(object): self.quiet = quiet def scan(self, package): - cmd = ['euscan', package] + CONFIG["format"] = "dict" + output.set_query(package) + scan_upstream(package) + out = output.get_formatted_output() + out_json = output.get_formatted_output("json") - fp = subprocess.Popen(cmd, stdout=subprocess.PIPE, - stderr=subprocess.PIPE) - output = StringIO(fp.communicate()[0]) + try: + cp = out["metadata"]["cp"] + except KeyError: + return {} - self.parse_output(output) + for out in out["result"]: + self.store_version(cp, out["version"], " ".join(out["urls"])) + + self.store_result(cp, out_json) + + return out def store_result(self, package, log): # Remove previous logs diff --git a/pym/euscan/__init__.py b/pym/euscan/__init__.py index 01255f4..84754ee 100644 --- a/pym/euscan/__init__.py +++ b/pym/euscan/__init__.py @@ -67,4 +67,4 @@ ROBOTS_TXT_BLACKLIST_DOMAINS = [ ] from out import EuscanOutput -output = out.EuscanOutput(CONFIG) +output = EuscanOutput(CONFIG) diff --git a/pym/euscan/out.py b/pym/euscan/out.py index c569a89..6a98336 100644 --- a/pym/euscan/out.py +++ b/pym/euscan/out.py @@ -95,7 +95,7 @@ class EuscanOutput(object): "metadata": {}, } - def get_formatted_output(self): + def get_formatted_output(self, format_=None): data = {} for query in self.queries: @@ -105,8 +105,11 @@ class EuscanOutput(object): "messages": self.queries[query]["output"].getvalue(), } - if self.config["format"].lower() == "json": + format_ = format_ or self.config["format"] + if format_.lower() == "json": return json.dumps(data, indent=self.config["indent"]) + elif format_.lower() == "dict": + return data else: raise TypeError("Invalid output format") @@ -133,7 +136,7 @@ class EuscanOutput(object): print "%s: %s" % (key.capitalize(), value) def __getattr__(self, key): - if not self.config["quiet"]: + if not self.config["quiet"] and self.current_query is not None: output = self.queries[self.current_query]["output"] return getattr(output, key) else: From 5970b4df3727e90053dfb9470570010e756ca069 Mon Sep 17 00:00:00 2001 From: volpino Date: Sun, 3 Jun 2012 09:43:49 +0200 Subject: [PATCH 04/37] euscanwww: Admin improved and simple task launcher added Signed-off-by: volpino --- .../management/commands/scan_metadata.py | 2 +- .../management/commands/scan_portage.py | 8 +- euscanwww/djeuscan/models.py | 5 ++ euscanwww/djeuscan/tasks.py | 16 +++- .../admin/djcelery/taskstate/change_list.html | 77 +++++++++++++++++++ euscanwww/djeuscan/urls.py | 17 ++++ euscanwww/djeuscan/views.py | 22 +++++- euscanwww/euscanwww/settings.py | 5 +- 8 files changed, 143 insertions(+), 9 deletions(-) create mode 100644 euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html diff --git a/euscanwww/djeuscan/management/commands/scan_metadata.py b/euscanwww/djeuscan/management/commands/scan_metadata.py index d9c7e49..c3f4c4b 100644 --- a/euscanwww/djeuscan/management/commands/scan_metadata.py +++ b/euscanwww/djeuscan/management/commands/scan_metadata.py @@ -13,7 +13,7 @@ from djeuscan.models import Package, Herd, Maintainer class ScanMetadata(object): - def __init__(self, quiet): + def __init__(self, quiet=False): self.quiet = quiet self.style = color_style() diff --git a/euscanwww/djeuscan/management/commands/scan_portage.py b/euscanwww/djeuscan/management/commands/scan_portage.py index 9533730..4471fac 100644 --- a/euscanwww/djeuscan/management/commands/scan_portage.py +++ b/euscanwww/djeuscan/management/commands/scan_portage.py @@ -241,7 +241,7 @@ class ScanPortage(object): @commit_on_success -def purge_versions(options): +def purge_versions(quiet=False, nolog=False): # For each dead versions for version in Version.objects.filter(packaged=True, alive=False): if version.overlay == 'gentoo': @@ -251,10 +251,10 @@ def purge_versions(options): version.package.n_versions -= 1 version.package.save() - if not options['quiet']: + if not quiet: sys.stdout.write('- [v] %s\n' % (version)) - if options['no-log']: + if nolog: continue VersionLog.objects.create( @@ -335,7 +335,7 @@ class Command(BaseCommand): scan_portage.scan(package[:-1]) if options['purge-versions']: - purge_versions(options) + purge_versions(options["quiet"], options["no-log"]) if not options['quiet']: self.stdout.write('Done.\n') diff --git a/euscanwww/djeuscan/models.py b/euscanwww/djeuscan/models.py index 727deef..fee8dc4 100644 --- a/euscanwww/djeuscan/models.py +++ b/euscanwww/djeuscan/models.py @@ -190,6 +190,11 @@ class EuscanResult(models.Model): self.full_clean() super(EuscanResult, self).save(*args, **kwargs) + def __unicode__(self): + return '[%s] %s/%s' % ( + self.datetime, self.package.category, self.package.name + ) + class Log(models.Model): """ diff --git a/euscanwww/djeuscan/tasks.py b/euscanwww/djeuscan/tasks.py index e614a70..89a90dc 100644 --- a/euscanwww/djeuscan/tasks.py +++ b/euscanwww/djeuscan/tasks.py @@ -28,7 +28,7 @@ def scan_metadata_task(query, obj=None): logger.info("Starting metadata scanning for package %s ...", query) scan_metadata = ScanMetadata() - scan_metadata.scan(query) + scan_metadata.scan(query, obj) @task @@ -95,3 +95,17 @@ def scan_upstream_task(query): @task def scan_upstream_purge_task(): scan_upstream_purge() + + +launchable_tasks = [ + regen_rrds_task, + update_counters_task, + scan_metadata_task, + scan_metadata_all_task, + scan_portage_all_task, + scan_portage_task, + scan_portage_purge_task, + scan_upstream_all_task, + scan_upstream_task, + scan_upstream_purge_task, +] diff --git a/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html b/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html new file mode 100644 index 0000000..693214a --- /dev/null +++ b/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html @@ -0,0 +1,77 @@ +{% extends "admin/change_list.html" %} + +{% load url from future %} + +{% block object-tools %} +
+ +
+ Launch task +
+ + +{% endblock %} diff --git a/euscanwww/djeuscan/urls.py b/euscanwww/djeuscan/urls.py index 019f99a..721fad5 100644 --- a/euscanwww/djeuscan/urls.py +++ b/euscanwww/djeuscan/urls.py @@ -1,7 +1,16 @@ from django.conf.urls.defaults import url, patterns, include +from django.contrib.auth.decorators import user_passes_test + +from djcelery.views import apply +from djeuscan.views import registered_tasks + from djeuscan.feeds import PackageFeed, CategoryFeed, HerdFeed, \ MaintainerFeed, GlobalFeed + +admin_required = user_passes_test(lambda u: u.is_superuser) + + package_patterns = patterns('djeuscan.views', url(r'^(?P[\w+][\w+.-]*)/(?P[\w+][\w+.-]*)/feed/$', PackageFeed(), name='package_feed'), @@ -41,6 +50,13 @@ overlays_patterns = patterns('djeuscan.views', url(r'^$', 'overlays', name="overlays"), ) +tasks_patterns = patterns('djcelery.views', + url(r'^registered_tasks/$', admin_required(registered_tasks), + name="registered_tasks"), + url(r'^apply/(?P.*)/$', admin_required(apply), + name="apply_task"), +) + urlpatterns = patterns('djeuscan.views', # Global stuff url(r'^api/', include('djeuscan.api.urls')), @@ -60,4 +76,5 @@ urlpatterns = patterns('djeuscan.views', url(r'^maintainers/', include(maintainers_patterns)), url(r'^overlays/', include(overlays_patterns)), url(r'^package/', include(package_patterns)), + url(r'^tasks/', include(tasks_patterns)), ) diff --git a/euscanwww/djeuscan/views.py b/euscanwww/djeuscan/views.py index 68836af..d84c39d 100644 --- a/euscanwww/djeuscan/views.py +++ b/euscanwww/djeuscan/views.py @@ -1,6 +1,8 @@ """ Views """ -from annoying.decorators import render_to +import inspect +from annoying.decorators import render_to, ajax_request + from django.http import Http404 from django.shortcuts import get_object_or_404 @@ -8,6 +10,7 @@ from djeuscan.helpers import version_key, packages_from_names from djeuscan.models import Version, Package, Herd, Maintainer, EuscanResult, \ VersionLog from djeuscan.forms import WorldForm, PackagesForm +from djeuscan.tasks import launchable_tasks from djeuscan import charts @@ -254,3 +257,20 @@ def chart_herd(request, **kwargs): def chart_category(request, **kwargs): return chart(request, **kwargs) + + +@ajax_request +def registered_tasks(request): + data = {} + for task in launchable_tasks: + argspec = inspect.getargspec(task.run) + data[task.name] = { + "args": argspec.args, + "defaults": argspec.defaults, + "default_types": None + } + if argspec.defaults is not None: + data[task.name].update({ + "defaults_types": [type(x).__name__ for x in argspec.defaults] + }) + return {"tasks": data} diff --git a/euscanwww/euscanwww/settings.py b/euscanwww/euscanwww/settings.py index 0f2b483..d84e340 100644 --- a/euscanwww/euscanwww/settings.py +++ b/euscanwww/euscanwww/settings.py @@ -157,6 +157,9 @@ TEMPLATE_CONTEXT_PROCESSORS = ( ) INSTALLED_APPS = ( + 'euscanwww', + 'djeuscan', + 'django.contrib.auth', 'django.contrib.contenttypes', 'django.contrib.sessions', @@ -167,8 +170,6 @@ INSTALLED_APPS = ( # 'django.contrib.admindocs', 'south', 'djcelery', - 'euscanwww', - 'djeuscan', ) # A sample logging configuration. The only tangible logging From 2edb220d3edf43b7e48d1d0152bd91f32eaa5edb Mon Sep 17 00:00:00 2001 From: volpino Date: Sun, 3 Jun 2012 17:38:51 +0200 Subject: [PATCH 05/37] euscanwww: use jQuery each instead of for Signed-off-by: volpino --- .../admin/djcelery/taskstate/change_list.html | 19 ++++++++++--------- 1 file changed, 10 insertions(+), 9 deletions(-) diff --git a/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html b/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html index 693214a..a4780cd 100644 --- a/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html +++ b/euscanwww/djeuscan/templates/admin/djcelery/taskstate/change_list.html @@ -18,21 +18,22 @@ selector.append( $("