=== modified file 'doc/changes.rst'
--- doc/changes.rst	2012-04-20 03:30:58 +0000
+++ doc/changes.rst	2012-05-23 21:43:36 +0000
@@ -1,6 +1,7 @@
 Version History
 ***************
 
+* Add support for looping of health care jobs
 
 Version 0.13
 ============

=== modified file 'lava_scheduler_app/models.py'
--- lava_scheduler_app/models.py	2012-04-20 03:22:44 +0000
+++ lava_scheduler_app/models.py	2012-05-23 11:42:47 +0000
@@ -74,11 +74,12 @@
     )
 
     # A device health shows a device is ready to test or not
-    HEALTH_UNKNOWN, HEALTH_PASS, HEALTH_FAIL = range(3)
+    HEALTH_UNKNOWN, HEALTH_PASS, HEALTH_FAIL, HEALTH_LOOPING = range(4)
     HEALTH_CHOICES = (
         (HEALTH_UNKNOWN, 'Unknown'),
         (HEALTH_PASS, 'Pass'),
         (HEALTH_FAIL, 'Fail'),
+        (HEALTH_LOOPING, 'Looping'),
     )
 
     hostname = models.CharField(
@@ -147,6 +148,8 @@
             created_by=user, device=self, old_state=self.status,
             new_state=new_status, message=reason, job=None).save()
         self.status = new_status
+        if self.health_status == Device.HEALTH_LOOPING:
+            self.health_status = Device.HEALTH_UNKNOWN
         self.save()
 
     def put_into_online_mode(self, user, reason):
@@ -160,6 +163,17 @@
         self.health_status = Device.HEALTH_UNKNOWN
         self.save()
 
+    def put_into_looping_mode(self, user):
+        if self.status not in [Device.OFFLINE, Device.OFFLINING]:
+            return
+        new_status = self.IDLE
+        DeviceStateTransition.objects.create(
+            created_by=user, device=self, old_state=self.status,
+            new_state=new_status, message="Looping mode", job=None).save()
+        self.status = new_status
+        self.health_status = Device.HEALTH_LOOPING
+        self.save()
+
     #@classmethod
     #def find_devices_by_type(cls, device_type):
     #    return device_type.device_set.all()

=== modified file 'lava_scheduler_app/templates/lava_scheduler_app/_device_base.html'
--- lava_scheduler_app/templates/lava_scheduler_app/_device_base.html	2012-03-09 02:49:54 +0000
+++ lava_scheduler_app/templates/lava_scheduler_app/_device_base.html	2012-05-23 11:42:47 +0000
@@ -54,6 +54,11 @@
   {% csrf_token %}
   <input name="reason" style="width: 100%"/>
 </form>
+<form style="display:inline " method="POST"
+      action="{% url lava.scheduler.device.looping device.pk %}">
+  {% csrf_token %}
+  <button id="looping-button">Put into looping mode</button>
+</form>
 {% endif %}
 
 <div id="columns">

=== modified file 'lava_scheduler_app/urls.py'
--- lava_scheduler_app/urls.py	2012-03-02 04:35:51 +0000
+++ lava_scheduler_app/urls.py	2012-05-23 11:42:47 +0000
@@ -3,33 +3,36 @@
 
 urlpatterns = patterns(
     'lava_scheduler_app.views',
-    url(r'^$', 
+    url(r'^$',
         'index',
         name='lava.scheduler'),
-    url(r'^active_jobs_json$', 
+    url(r'^active_jobs_json$',
         'index_active_jobs_json',
         name='lava.scheduler.active_jobs_json'),
-    url(r'^devices_json$', 
+    url(r'^devices_json$',
         'index_devices_json',
         name='lava.scheduler.index_devices_json'),
-    url(r'^alljobs$', 
-        'job_list', 
+    url(r'^alljobs$',
+        'job_list',
         name='lava.scheduler.job.list'),
     url(r'^alljobs_json$',
         'alljobs_json',
         name='lava.scheduler.job.list_json'),
-    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)$', 
-        'device_detail', 
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)$',
+        'device_detail',
         name='lava.scheduler.device.detail'),
-    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/recent_jobs_json$', 
-        'recent_jobs_json', 
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/recent_jobs_json$',
+        'recent_jobs_json',
         name='lava.scheduler.device.recent_jobs_json'),
-    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/transition_json$', 
-        'transition_json', 
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/transition_json$',
+        'transition_json',
         name='lava.scheduler.device.transition_json'),
-    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/maintenance$', 
-        'device_maintenance_mode', 
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/maintenance$',
+        'device_maintenance_mode',
         name='lava.scheduler.device.maintenance'),
+    url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/looping$',
+        'device_looping_mode',
+        name='lava.scheduler.device.looping'),
     url(r'^device/(?P<pk>[-_a-zA-Z0-9]+)/online$',
         'device_online',
         name='lava.scheduler.device.online'),
@@ -64,7 +67,7 @@
         'job_cancel',
         name='lava.scheduler.job.cancel'),
     url(r'^job/(?P<pk>[0-9]+)/json$',
-        'job_json', 
+        'job_json',
         name='lava.scheduler.job.json'),
     url(r'^job/(?P<pk>[0-9]+)/output$',
         'job_output',

=== modified file 'lava_scheduler_app/views.py'
--- lava_scheduler_app/views.py	2012-03-19 02:01:59 +0000
+++ lava_scheduler_app/views.py	2012-05-23 11:42:47 +0000
@@ -573,3 +573,14 @@
     else:
         return HttpResponseForbidden(
             "you cannot administer this device", content_type="text/plain")
+
+@post_only
+def device_looping_mode(request, pk):
+    device = Device.objects.get(pk=pk)
+    if device.can_admin(request.user):
+        device.put_into_looping_mode(request.user)
+        return redirect(device)
+    else:
+        return HttpResponseForbidden(
+            "you cannot administer this device", content_type="text/plain")
+

=== modified file 'lava_scheduler_daemon/dbjobsource.py'
--- lava_scheduler_daemon/dbjobsource.py	2012-04-05 04:34:13 +0000
+++ lava_scheduler_daemon/dbjobsource.py	2012-05-22 14:33:13 +0000
@@ -152,6 +152,8 @@
                 run_health_check = False
             elif device.health_status == Device.HEALTH_UNKNOWN:
                 run_health_check = True
+            elif device.health_status == Device.HEALTH_LOOPING:
+                run_health_check = True
             elif not device.last_health_report_job:
                 run_health_check = True
             else:
@@ -238,11 +240,12 @@
 
         if job.health_check:
             device.last_health_report_job = job
-            if job.status == TestJob.INCOMPLETE:
-                device.health_status = Device.HEALTH_FAIL
-                device.put_into_maintenance_mode(None, "Health Check Job Failed")
-            elif job.status == TestJob.COMPLETE:
-                device.health_status = Device.HEALTH_PASS
+            if device.health_status != Device.HEALTH_LOOPING:
+                if job.status == TestJob.INCOMPLETE:
+                    device.health_status = Device.HEALTH_FAIL
+                    device.put_into_maintenance_mode(None, "Health Check Job Failed")
+                elif job.status == TestJob.COMPLETE:
+                    device.health_status = Device.HEALTH_PASS
 
         job.end_time = datetime.datetime.utcnow()
         token = job.submit_token

