diff --git a/nailgun/manage.py b/nailgun/manage.py index 0e2074304..4ae4a240e 100644 --- a/nailgun/manage.py +++ b/nailgun/manage.py @@ -3,9 +3,9 @@ import os import sys if __name__ == "__main__": - # sys.path.insert(0, os.getcwd()) - os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nailgun.settings") + # sys.path.insert(0, os.getcwd()) + os.environ.setdefault("DJANGO_SETTINGS_MODULE", "nailgun.settings") - from django.core.management import execute_from_command_line + from django.core.management import execute_from_command_line - execute_from_command_line(sys.argv) + execute_from_command_line(sys.argv) diff --git a/nailgun/monitor.py b/nailgun/monitor.py index f11514ac7..ec0b09aab 100644 --- a/nailgun/monitor.py +++ b/nailgun/monitor.py @@ -14,101 +14,107 @@ _running = False _queue = Queue.Queue() _lock = threading.Lock() + def _restart(path): - _queue.put(True) - prefix = 'monitor (pid=%d):' % os.getpid() - print >> sys.stderr, '%s Change detected to \'%s\'.' % (prefix, path) - print >> sys.stderr, '%s Triggering process restart.' % prefix - os.kill(os.getpid(), signal.SIGINT) + _queue.put(True) + prefix = 'monitor (pid=%d):' % os.getpid() + print >> sys.stderr, '%s Change detected to \'%s\'.' % (prefix, path) + print >> sys.stderr, '%s Triggering process restart.' % prefix + os.kill(os.getpid(), signal.SIGINT) + def _modified(path): - try: - # If path doesn't denote a file and were previously - # tracking it, then it has been removed or the file type - # has changed so force a restart. If not previously - # tracking the file then we can ignore it as probably - # pseudo reference such as when file extracted from a - # collection of modules contained in a zip file. + try: + # If path doesn't denote a file and were previously + # tracking it, then it has been removed or the file type + # has changed so force a restart. If not previously + # tracking the file then we can ignore it as probably + # pseudo reference such as when file extracted from a + # collection of modules contained in a zip file. - if not os.path.isfile(path): - return path in _times + if not os.path.isfile(path): + return path in _times - # Check for when file last modified. + # Check for when file last modified. - mtime = os.stat(path).st_mtime - if path not in _times: - _times[path] = mtime + mtime = os.stat(path).st_mtime + if path not in _times: + _times[path] = mtime - # Force restart when modification time has changed, even - # if time now older, as that could indicate older file - # has been restored. + # Force restart when modification time has changed, even + # if time now older, as that could indicate older file + # has been restored. - if mtime != _times[path]: - return True - except: - # If any exception occured, likely that file has been - # been removed just before stat(), so force a restart. + if mtime != _times[path]: + return True + except: + # If any exception occured, likely that file has been + # been removed just before stat(), so force a restart. - return True + return True + + return False - return False def _monitor(): - while 1: - # Check modification times on all files in sys.modules. + while 1: + # Check modification times on all files in sys.modules. - for module in sys.modules.values(): - if not hasattr(module, '__file__'): - continue - path = getattr(module, '__file__') - if not path: - continue - if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']: - path = path[:-1] - if _modified(path): - return _restart(path) + for module in sys.modules.values(): + if not hasattr(module, '__file__'): + continue + path = getattr(module, '__file__') + if not path: + continue + if os.path.splitext(path)[1] in ['.pyc', '.pyo', '.pyd']: + path = path[:-1] + if _modified(path): + return _restart(path) - # Check modification times on files which have - # specifically been registered for monitoring. + # Check modification times on files which have + # specifically been registered for monitoring. - for path in _files: - if _modified(path): - return _restart(path) + for path in _files: + if _modified(path): + return _restart(path) - # Go to sleep for specified interval. + # Go to sleep for specified interval. + + try: + return _queue.get(timeout=_interval) + except: + pass - try: - return _queue.get(timeout=_interval) - except: - pass _thread = threading.Thread(target=_monitor) _thread.setDaemon(True) + def _exiting(): - try: - _queue.put(True) - except: - pass - _thread.join() + try: + _queue.put(True) + except: + pass + _thread.join() atexit.register(_exiting) + def track(path): - if not path in _files: - _files.append(path) + if not path in _files: + _files.append(path) + def start(interval=1.0): - global _interval - if interval < _interval: - _interval = interval - - global _running - _lock.acquire() - if not _running: - prefix = 'monitor (pid=%d):' % os.getpid() - print >> sys.stderr, '%s Starting change monitor.' % prefix - _running = True - _thread.start() - _lock.release()\ + global _interval + if interval < _interval: + _interval = interval + global _running + _lock.acquire() + if not _running: + prefix = 'monitor (pid=%d):' % os.getpid() + print >> sys.stderr, '%s Starting change monitor.' % prefix + _running = True + _thread.start() + _lock.release() diff --git a/nailgun/nailgun/api/client.py b/nailgun/nailgun/api/client.py index b16e74d3c..e659020d1 100644 --- a/nailgun/nailgun/api/client.py +++ b/nailgun/nailgun/api/client.py @@ -3,30 +3,29 @@ import urllib import httplib from urlparse import urlparse + def query_api(url, method='GET', params={}): if method not in ('GET', 'POST', 'PUT', 'DELETE'): raise ValueError("Invalid method %s" % method) - + parsed_url = urlparse(url) - + body = None path = parsed_url.path if method in ('POST', 'PUT'): body = urllib.urlencode(params) elif params: path = "%s?%s" % (path, urllib.urlencode(params)) - + conn = httplib.HTTPConnection(parsed_url.netloc) conn.request(method, path, body) response = conn.getresponse() raw_data = response.read() - + data = None try: data = json.loads(raw_data) except ValueError: pass - + return (response.status, data) - - \ No newline at end of file diff --git a/nailgun/nailgun/api/forms.py b/nailgun/nailgun/api/forms.py index 14a775e0d..ce3c380a6 100644 --- a/nailgun/nailgun/api/forms.py +++ b/nailgun/nailgun/api/forms.py @@ -3,19 +3,23 @@ from django import forms from django.forms.fields import Field, CharField, ChoiceField from nailgun.models import Environment, Node, Role + class EnvironmentForm(forms.ModelForm): class Meta: model = Environment + def validate_node_metadata(value): if value is not None: if isinstance(value, dict): for field in ('block_device', 'interfaces', 'cpu', 'memory'): if not field in value: - raise ValidationError('Node metadata \'%s\' field is required' % field) + raise ValidationError("Node metadata '%s' \ + field is required" % field) else: raise ValidationError('Node metadata must be a dictionary') + class NodeForm(forms.Form): metadata = Field(required=False, validators=[validate_node_metadata]) status = ChoiceField(required=False, choices=Node.NODE_STATUSES) diff --git a/nailgun/nailgun/api/handlers.py b/nailgun/nailgun/api/handlers.py index 5f98b3aad..0d13115d5 100644 --- a/nailgun/nailgun/api/handlers.py +++ b/nailgun/nailgun/api/handlers.py @@ -12,11 +12,11 @@ from forms import EnvironmentForm, NodeForm class EnvironmentHandler(BaseHandler): - + allowed_methods = ('GET', 'POST', 'PUT') model = Environment fields = ('id', 'name', ('nodes', ())) - + def read(self, request, environment_id=None): if environment_id: try: @@ -25,7 +25,7 @@ class EnvironmentHandler(BaseHandler): return rc.NOT_FOUND else: return Environment.objects.all() - + @validate_json(EnvironmentForm) def create(self, request): environment = Environment() @@ -38,7 +38,7 @@ class ConfigHandler(BaseHandler): allowed_methods = ('POST',) - """ Creates JSON files for chef-solo. This should be moved to the queue. """ + """ Creates JSON files for chef-solo. This should be moved to the queue """ def create(self, request, environment_id): env_id = environment_id nodes = Node.objects.filter(environment__id=env_id) @@ -63,33 +63,37 @@ class ConfigHandler(BaseHandler): ["role[" + x.name + "]" for x in n.roles.all()] solo_json['all_roles'] = nodes_per_role - filepath = os.path.join(settings.CHEF_CONF_FOLDER, n.name + '.json') + filepath = os.path.join(settings.CHEF_CONF_FOLDER, + n.name + '.json') f = open(filepath, 'w') f.write(json.dumps(solo_json)) f.close() class NodeHandler(BaseHandler): - + allowed_methods = ('GET', 'PUT') model = Node fields = ('name', 'metadata', 'status', ('roles', ())) - + def read(self, request, environment_id, node_name=None): try: if node_name: - return Node.objects.get(name=node_name, environment__id=environment_id) + return Node.objects.get(name=node_name, + environment__id=environment_id) else: return Node.objects.filter(environment__id=environment_id) except ObjectDoesNotExist: return rc.NOT_FOUND - + @validate_json(NodeForm) def update(self, request, environment_id, node_name): try: - node = Node.objects.get(name=node_name, environment__id=environment_id) + node = Node.objects.get(name=node_name, + environment__id=environment_id) for key, value in request.form.cleaned_data.items(): - if key in request.form.data: # check if parameter is really passed by client + # check if parameter is really passed by client + if key in request.form.data: setattr(node, key, value) node.save() return node @@ -106,29 +110,34 @@ class RoleHandler(BaseHandler): def read(self, request, environment_id, node_name, role_name=None): try: if role_name: - return Role.objects.get(nodes__environment__id=environment_id, nodes__name=node_name, name=role_name) + return Role.objects.get(nodes__environment__id=environment_id, + nodes__name=node_name, name=role_name) else: - return Role.objects.filter(nodes__environment__id=environment_id, nodes__name=node_name) + return Role.objects.filter( + nodes__environment__id=environment_id, + nodes__name=node_name) except ObjectDoesNotExist: return rc.NOT_FOUND def create(self, request, environment_id, node_name, role_name): try: print environment_id, node_name, role_name - node = Node.objects.get(environment__id=environment_id, name=node_name) + node = Node.objects.get(environment__id=environment_id, + name=node_name) role = Role.objects.get(name=role_name) - + if role in node.roles.all(): return rc.DUPLICATE_ENTRY - + node.roles.add(role) return role except ObjectDoesNotExist: return rc.NOT_FOUND - + def delete(self, request, environment_id, node_name, role_name): try: - node = Node.objects.get(environment__id=environment_id, name=node_name) + node = Node.objects.get(environment__id=environment_id, + name=node_name) role = Role.objects.get(name=role_name) node.roles.remove(role) return rc.DELETED diff --git a/nailgun/nailgun/api/urls.py b/nailgun/nailgun/api/urls.py index 80a0cfcbf..cca412ddc 100644 --- a/nailgun/nailgun/api/urls.py +++ b/nailgun/nailgun/api/urls.py @@ -1,11 +1,15 @@ from django.conf.urls import patterns, include, url from piston.resource import Resource -from handlers import EnvironmentHandler, NodeHandler, RoleHandler, ConfigHandler + +from handlers import EnvironmentHandler, NodeHandler +from handlers import RoleHandler, ConfigHandler + class JsonResource(Resource): def determine_emitter(self, request, *args, **kwargs): return 'json' + environment_handler = JsonResource(EnvironmentHandler) node_handler = JsonResource(NodeHandler) role_handler = JsonResource(RoleHandler) @@ -14,9 +18,13 @@ config_handler = JsonResource(ConfigHandler) urlpatterns = patterns('', url(r'^environments/?$', environment_handler), url(r'^environments/(?P\d+)/?$', environment_handler), - url(r'^environments/(?P\d+)/chef-config/?$', config_handler), + url(r'^environments/(?P\d+)/chef-config/?$', + config_handler), url(r'^environments/(?P\d+)/nodes/?$', node_handler), - url(r'^environments/(?P\d+)/nodes/(?P[\w\.\-]+)/?$', node_handler), - url(r'^environments/(?P\d+)/nodes/(?P[\w\.\-]+)/roles/?$', role_handler), - url(r'^environments/(?P\d+)/nodes/(?P[\w\.\-]+)/roles/(?P\w+)/?$', role_handler), + url(r'^environments/(?P\d+)/nodes/' + r'(?P[\w\.\-]+)/?$', node_handler), + url(r'^environments/(?P\d+)/nodes/' + r'(?P[\w\.\-]+)/roles/?$', role_handler), + url(r'^environments/(?P\d+)/nodes/' + r'(?P[\w\.\-]+)/roles/(?P\w+)/?$', role_handler), ) diff --git a/nailgun/nailgun/api/validators.py b/nailgun/nailgun/api/validators.py index 9ab178f32..f0ec410a1 100644 --- a/nailgun/nailgun/api/validators.py +++ b/nailgun/nailgun/api/validators.py @@ -2,6 +2,7 @@ import json from piston.utils import FormValidationError, HttpStatusCode, rc from piston.decorator import decorator + def validate_json(v_form): @decorator def wrap(f, self, request, *a, **kwa): @@ -10,9 +11,9 @@ def validate_json(v_form): response = rc.BAD_REQUEST response.content = "Invalid content type, must be application/json" raise HttpStatusCode(response) - + form = v_form(json.loads(request.body), request.FILES) - + if form.is_valid(): setattr(request, 'form', form) return f(self, request, *a, **kwa) diff --git a/nailgun/nailgun/middleware.py b/nailgun/nailgun/middleware.py index 752cdfd32..65714559a 100644 --- a/nailgun/nailgun/middleware.py +++ b/nailgun/nailgun/middleware.py @@ -1,5 +1,6 @@ import traceback + class ExceptionLoggingMiddleware(object): def process_exception(self, request, exception): print traceback.format_exc() diff --git a/nailgun/nailgun/models.py b/nailgun/nailgun/models.py index 5b2d6ac67..6c493e54b 100644 --- a/nailgun/nailgun/models.py +++ b/nailgun/nailgun/models.py @@ -20,7 +20,7 @@ class Node(models.Model): ) environment = models.ForeignKey(Environment, related_name='nodes') name = models.CharField(max_length=100, primary_key=True) - status = models.CharField(max_length=30, choices=NODE_STATUSES, default='online') + status = models.CharField(max_length=30, choices=NODE_STATUSES, + default='online') metadata = JSONField() roles = models.ManyToManyField(Role, related_name='nodes') - diff --git a/nailgun/nailgun/settings.py b/nailgun/nailgun/settings.py index cb8abe99d..71db069a2 100644 --- a/nailgun/nailgun/settings.py +++ b/nailgun/nailgun/settings.py @@ -15,12 +15,12 @@ MANAGERS = ADMINS DATABASES = { 'default': { - 'ENGINE': 'django.db.backends.sqlite3', # Add 'postgresql_psycopg2', 'mysql', 'sqlite3' or 'oracle'. - 'NAME': os.path.join(PROJECT_ROOT, 'nailgun.sqlite'), # Or path to database file if using sqlite3. + 'ENGINE': 'django.db.backends.sqlite3', + 'NAME': os.path.join(PROJECT_ROOT, 'nailgun.sqlite'), 'USER': '', # Not used with sqlite3. 'PASSWORD': '', # Not used with sqlite3. - 'HOST': '', # Set to empty string for localhost. Not used with sqlite3. - 'PORT': '', # Set to empty string for default. Not used with sqlite3. + 'HOST': '', + 'PORT': '', } } @@ -98,7 +98,8 @@ MIDDLEWARE_CLASSES = ( 'django.contrib.messages.middleware.MessageMiddleware', ) -if DEBUG: MIDDLEWARE_CLASSES += ('nailgun.middleware.ExceptionLoggingMiddleware',) +if DEBUG: + MIDDLEWARE_CLASSES += ('nailgun.middleware.ExceptionLoggingMiddleware',) ROOT_URLCONF = 'nailgun.urls' diff --git a/nailgun/nailgun/tasks.py b/nailgun/nailgun/tasks.py index 0a023500b..85b8a19d4 100644 --- a/nailgun/nailgun/tasks.py +++ b/nailgun/nailgun/tasks.py @@ -1,9 +1,9 @@ from celery.task import task + @task def add(x, y): - for i in xrange(1, 10): - time.sleep(1) - add.update_state(state="PROGRESS", meta={"current": i, "total": 10}) - return x + y - + for i in xrange(1, 10): + time.sleep(1) + add.update_state(state="PROGRESS", meta={"current": i, "total": 10}) + return x + y diff --git a/nailgun/nailgun/templatetags/jst.py b/nailgun/nailgun/templatetags/jst.py index 3c730acba..0bb83898f 100644 --- a/nailgun/nailgun/templatetags/jst.py +++ b/nailgun/nailgun/templatetags/jst.py @@ -1,23 +1,26 @@ import os + from django import template from django.contrib.staticfiles.finders import FileSystemFinder + TEMPLATE_EXTENSION = '.html' register = template.Library() finder = FileSystemFinder() + @register.simple_tag def jst(template_name): template_filename = os.path.join('jst', template_name + TEMPLATE_EXTENSION) - + match = finder.find(template_filename) if not match: raise Exception("JS template '%s' not found" % template_filename) - + f = open(match, 'r') - + template_content = f.read() - + return "" % \ (template_name, template_content) diff --git a/nailgun/nailgun/tests/test_handlers.py b/nailgun/nailgun/tests/test_handlers.py index 3833040f4..b3715bec6 100644 --- a/nailgun/nailgun/tests/test_handlers.py +++ b/nailgun/nailgun/tests/test_handlers.py @@ -20,15 +20,15 @@ class TestHandlers(TestCase): name=self.node_name, metadata=self.old_meta) self.node.save() - + self.role = Role() self.role.name = "myrole" self.role.save() - + self.another_role = Role() self.another_role.name = "myrole2" self.another_role.save() - + self.node.roles = [self.role] self.node.save() self.node_url = '/api/environments/1/nodes/' + self.node_name @@ -134,7 +134,6 @@ class TestHandlers(TestCase): name=self.node_name) self.assertEquals(nodes_from_db[0].roles.all()[0].name, "myrole") - # Tests for RoleHandler def test_can_get_list_of_roles_for_node(self): resp = self.client.get(self.node_url + '/roles') @@ -147,12 +146,14 @@ class TestHandlers(TestCase): resp = self.client.post(url, '', "plain/text") self.assertEquals(resp.status_code, 409) - + roles_from_db = Role.objects.all() nodes_from_db = Node.objects.filter(environment_id=1, name=self.node_name) - self.assertEquals(nodes_from_db[0].roles.all()[0].name, self.role.name) - self.assertEquals(nodes_from_db[0].roles.all()[1].name, self.another_role.name) + self.assertEquals(nodes_from_db[0].roles.all()[0].name, + self.role.name) + self.assertEquals(nodes_from_db[0].roles.all()[1].name, + self.another_role.name) #def test_jsons_created_for_chef_solo(self): #resp = self.client.post('/api/environments/1/chef-config/') diff --git a/nailgun/nailgun/tests/test_models.py b/nailgun/nailgun/tests/test_models.py index b215e0d71..8c38b002b 100644 --- a/nailgun/nailgun/tests/test_models.py +++ b/nailgun/nailgun/tests/test_models.py @@ -45,6 +45,6 @@ class TestRolesNodesAssociation(TestCase): node1.roles.add(role2) self.assertEquals(len(node1.roles.all()), 2) - self.assertEquals(Node.objects.filter(roles__name__startswith="myr")[0].name, + self.assertEquals(Node.objects.filter( + roles__name__startswith="myr")[0].name, "test.example.com") - diff --git a/nailgun/nailgun/webui/views.py b/nailgun/nailgun/webui/views.py index 41e860e3e..791021b5d 100644 --- a/nailgun/nailgun/webui/views.py +++ b/nailgun/nailgun/webui/views.py @@ -1,5 +1,6 @@ from django.http import HttpResponse from django.shortcuts import render + def index(request): return render(request, 'index.html', {})