diff --git a/.travis.yml b/.travis.yml
index bafd753..077b50f 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -3,7 +3,7 @@ sudo: required
 language: python
 
 python:
-  - "2.7"
+  - "3.7"
 
 env:
   global:
diff --git a/deployment/ansible/development/group_vars/all.travis.yml b/deployment/ansible/development/group_vars/all.travis.yml
index 01051dc..f1addd6 100644
--- a/deployment/ansible/development/group_vars/all.travis.yml
+++ b/deployment/ansible/development/group_vars/all.travis.yml
@@ -16,7 +16,7 @@ interpreters:
 inasafe:
   repo: https://github.com/inasafe/inasafe.git
   remote: upstream
-  version: inasafe_4
+  version: maintenance-fix
   depth: 1
 
 inasafe_headless_worker:
diff --git a/deployment/docker-headless/Dockerfile b/deployment/docker-headless/Dockerfile
index 79faad2..030e5d2 100644
--- a/deployment/docker-headless/Dockerfile
+++ b/deployment/docker-headless/Dockerfile
@@ -1,5 +1,5 @@
 #--------- Generic stuff all our Dockerfiles should start with so we get caching ------------
-FROM kartoza/qgis-desktop:2.18
+FROM qgis/qgis:release-3_14
 
 RUN apt-get -y update; apt-get -y --force-yes install pwgen git inotify-tools
 
@@ -9,7 +9,10 @@ RUN apt-get -y update; apt-get -y --force-yes install pwgen git inotify-tools
 RUN apt-get update -y; apt-get install -y --force-yes openssh-server sudo
 RUN mkdir /var/run/sshd
 RUN echo 'root:docker' | chpasswd
-RUN sed -i 's/PermitRootLogin prohibit-password/PermitRootLogin yes/' /etc/ssh/sshd_config
+# Comment out PermitRootLogin setting, whatever it is
+RUN sed -i 's/^PermitRootLogin */#PermitRootLogin /' /etc/ssh/sshd_config
+# Write out PermitRootLogin setting at the end
+RUN echo "PermitRootLogin yes" >> /etc/ssh/sshd_config
 
 # SSH login fix. Otherwise user is kicked off after login
 RUN sed 's@session\s*required\s*pam_loginuid.so@session optional pam_loginuid.so@g' -i /etc/pam.d/sshd
@@ -21,8 +24,7 @@ RUN echo "export VISIBLE=now" >> /etc/profile
 
 #-------------Application Specific Stuff ----------------------------------------------------
 # Install git, xvfb
-RUN apt-get -y update; apt-get -y --force-yes install git xvfb python-setuptools python-dev libssl-dev libffi-dev python-scipy
-RUN easy_install pip==9.0.1
+RUN apt-get -y update; apt-get -y --force-yes install git xvfb python3-setuptools python3-dev libssl-dev libffi-dev python3-scipy
 # Copy ubuntu fonts
 RUN apt-get -y update; apt-get -y --force-yes install wget unzip
 ADD ubuntu-font-family-0.83.zip /ubuntu-font-family-0.83.zip
@@ -31,8 +33,9 @@ RUN mv ubuntu-font-family-0.83 /usr/share/fonts/truetype/ubuntu-font-family
 RUN fc-cache -f -v
 
 # This image instance uses dist-packages directory
-ADD sitecustomize.py /usr/local/lib/python2.7/dist-packages/sitecustomize.py
+ADD sitecustomize.py /usr/lib/python3/dist-packages/sitecustomize.py
 ADD REQUIREMENTS.txt /REQUIREMENTS.txt
+RUN ln -s /usr/bin/pip3 /usr/bin/pip
 RUN pip install -r REQUIREMENTS.txt
 
 ADD docker-entrypoint.sh /docker-entrypoint.sh
diff --git a/deployment/docker-headless/docker-entrypoint.sh b/deployment/docker-headless/docker-entrypoint.sh
index 8c461b0..46887da 100644
--- a/deployment/docker-headless/docker-entrypoint.sh
+++ b/deployment/docker-headless/docker-entrypoint.sh
@@ -2,7 +2,7 @@
 
 # Wait run xvfb
 while [ -z  "$(pidof /usr/bin/Xvfb)" ]; do
-  start-stop-daemon --start -b -x /usr/bin/Xvfb ${DISPLAY}
+  start-stop-daemon --start -b -x /usr/bin/Xvfb ${DISPLAY} -- -screen 0 1024x768x24 -ac +extension GLX +render -noreset -nolisten tcp
   sleep 5
 done
 
diff --git a/deployment/production/docker/headless/Dockerfile b/deployment/production/docker/headless/Dockerfile
index 53abbf5..bab377c 100644
--- a/deployment/production/docker/headless/Dockerfile
+++ b/deployment/production/docker/headless/Dockerfile
@@ -1,12 +1,11 @@
 #--------- Generic stuff all our Dockerfiles should start with so we get caching ------------
-FROM kartoza/qgis-desktop:2.18
+FROM qgis/qgis:release-3_14
 
 RUN apt-get -y update; apt-get -y --force-yes install pwgen git inotify-tools
 
 #-------------Application Specific Stuff ----------------------------------------------------
 # Install git, xvfb
-RUN apt-get -y update; apt-get -y --force-yes install git xvfb python-setuptools python-dev libssl-dev libffi-dev python-scipy
-RUN easy_install pip==9.0.1
+RUN apt-get -y update; apt-get -y --force-yes install git xvfb python3-setuptools python3-dev libssl-dev libffi-dev python3-scipy
 # Copy ubuntu fonts
 RUN apt-get -y update; apt-get -y --force-yes install wget unzip
 ADD ubuntu-font-family-0.83.zip /ubuntu-font-family-0.83.zip
@@ -15,6 +14,7 @@ RUN mv ubuntu-font-family-0.83 /usr/share/fonts/truetype/ubuntu-font-family
 RUN fc-cache -f -v
 
 ADD REQUIREMENTS.txt /REQUIREMENTS.txt
+RUN ln -s /usr/bin/pip3 /usr/bin/pip
 RUN pip install -r REQUIREMENTS.txt
 
 ADD docker-entrypoint.sh /docker-entrypoint.sh
@@ -23,7 +23,7 @@ RUN chmod +x /docker-entrypoint.sh
 # Install InaSAFE Core
 RUN mkdir -p /usr/share/qgis/python/plugins
 WORKDIR /usr/share/qgis/python/plugins
-ARG INASAFE_CORE_TAG=version-4_4_0
+ARG INASAFE_CORE_TAG=develop
 RUN git clone --branch ${INASAFE_CORE_TAG} --depth 1 --recursive https://github.com/inasafe/inasafe.git inasafe
 
 # Install InaSAFE Headless
diff --git a/deployment/production/docker/headless/docker-entrypoint.sh b/deployment/production/docker/headless/docker-entrypoint.sh
index c0fafb3..0ca7e37 100644
--- a/deployment/production/docker/headless/docker-entrypoint.sh
+++ b/deployment/production/docker/headless/docker-entrypoint.sh
@@ -2,11 +2,12 @@
 
 # Wait run xvfb
 while [ -z  "$(pidof /usr/bin/Xvfb)" ]; do
-  start-stop-daemon --start -b -x /usr/bin/Xvfb ${DISPLAY}
+  start-stop-daemon --start -b -x /usr/bin/Xvfb ${DISPLAY} -- -screen 0 1024x768x24 -ac +extension GLX +render -noreset -nolisten tcp
   sleep 5
 done
 
 cp -n /home/app/headless/celeryconfig_sample.py /home/app/headless/celeryconfig.py
+echo "Config file copied"
 
 if [ $# -eq 2 ] && [ $1 = "prod" ] && [ $2 = "inasafe-headless-worker" ]; then
     /usr/local/bin/celery -A headless.celery_app worker -l info -Q inasafe-headless -n inasafe-headless.%h
diff --git a/src/headless/REQUIREMENTS.txt b/src/headless/REQUIREMENTS.txt
index 76120b4..f0b5969 100644
--- a/src/headless/REQUIREMENTS.txt
+++ b/src/headless/REQUIREMENTS.txt
@@ -1,8 +1,11 @@
 requests>=2.6.2
 tzlocal==1.2
-celery==4.1.0
-nosexcover
+hammock==0.2.4
 nose2==0.6.5
 coverage==4.4.2
+threadpool==1.3.2
+pyinotify==0.9.6
+celery==4.1.1
 raven==5.29.0
 mock==2.0.0
+nose==1.3.7
diff --git a/src/headless/celery_app.py b/src/headless/celery_app.py
index d7ccb6f..1f5ce50 100644
--- a/src/headless/celery_app.py
+++ b/src/headless/celery_app.py
@@ -1,5 +1,6 @@
 # coding=utf-8
 import importlib
+from importlib import reload
 import json
 import os
 
diff --git a/src/headless/tasks/inasafe_analysis.py b/src/headless/tasks/inasafe_analysis.py
index 4ec67eb..0f1439f 100644
--- a/src/headless/tasks/inasafe_analysis.py
+++ b/src/headless/tasks/inasafe_analysis.py
@@ -5,10 +5,10 @@
 
 from copy import deepcopy
 from datetime import datetime
-from PyQt4.QtCore import QUrl
+from qgis.PyQt.QtCore import QUrl
 
 from qgis.core import (
-    QgsCoordinateReferenceSystem, QgsMapLayerRegistry, QgsProject)
+    QgsCoordinateReferenceSystem, QgsProject)
 
 from safe.definitions.constants import (
     PREPARE_SUCCESS, ANALYSIS_SUCCESS, MULTI_EXPOSURE_ANALYSIS_FLAG)
@@ -118,7 +118,7 @@ def inasafe_analysis(
     """
     # Clean up layer registry before using
     # In case previous task exited prematurely before cleanup
-    layer_registry = QgsMapLayerRegistry.instance()
+    layer_registry = QgsProject.instance()
     layer_registry.removeAllMapLayers()
 
     impact_function = ImpactFunction()
@@ -213,7 +213,7 @@ def inasafe_multi_exposure_analysis(
     """
     # Clean up layer registry before using
     # In case previous task exited prematurely before cleanup
-    layer_registry = QgsMapLayerRegistry.instance()
+    layer_registry = QgsProject.instance()
     layer_registry.removeAllMapLayers()
 
     multi_exposure_if = MultiExposureImpactFunction()
@@ -324,7 +324,7 @@ def generate_report(
     """
     # Clean up layer registry before using
     # In case previous task exited prematurely before cleanup
-    layer_registry = QgsMapLayerRegistry.instance()
+    layer_registry = QgsProject.instance()
     layer_registry.removeAllMapLayers()
 
     output_metadata = read_iso19115_metadata(impact_layer_uri)
@@ -344,14 +344,14 @@ def generate_report(
         root = QgsProject.instance().layerTreeRoot()
 
         group_analysis = root.insertGroup(0, impact_function.name)
-        group_analysis.setVisible(True)
+        group_analysis.setItemVisibilityChecked(True)
         group_analysis.setCustomProperty(
             MULTI_EXPOSURE_ANALYSIS_FLAG, True)
 
         for layer in impact_function.outputs:
-            QgsMapLayerRegistry.instance().addMapLayer(layer, False)
+            QgsProject.instance().addMapLayer(layer, False)
             layer_node = group_analysis.addLayer(layer)
-            layer_node.setVisible(False)
+            layer_node.setItemVisibilityChecked(False)
 
             # set layer title if any
             try:
@@ -362,7 +362,7 @@ def generate_report(
 
         for analysis in impact_function.impact_functions:
             detailed_group = group_analysis.insertGroup(0, analysis.name)
-            detailed_group.setVisible(True)
+            detailed_group.setItemVisibilityChecked(True)
             add_impact_layers_to_canvas(analysis, group=detailed_group)
     else:
         impact_function = (
diff --git a/src/headless/tasks/inasafe_wrapper.py b/src/headless/tasks/inasafe_wrapper.py
index 07ca7b3..b2c7176 100644
--- a/src/headless/tasks/inasafe_wrapper.py
+++ b/src/headless/tasks/inasafe_wrapper.py
@@ -1,6 +1,6 @@
 # coding=utf-8
 """Task for InaSAFE Headless."""
-
+from importlib import reload
 from headless.celery_app import app, start_inasafe
 from headless.tasks import inasafe_analysis
 from headless.utils import get_headless_logger
diff --git a/src/headless/tasks/test/data/input_layers/buildings.xml b/src/headless/tasks/test/data/input_layers/buildings.xml
index fd8ce41..ceaa723 100644
--- a/src/headless/tasks/test/data/input_layers/buildings.xml
+++ b/src/headless/tasks/test/data/input_layers/buildings.xml
@@ -1,4 +1,4 @@
-<?xml version="1.0" encoding="UTF-8"?>
+<?xml version="1.0" ?>
 <gmd:MD_Metadata xmlns:gco="http://www.isotc211.org/2005/gco" xmlns:gmd="http://www.isotc211.org/2005/gmd" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://www.isotc211.org/2005/gmd http://www.isotc211.org/2005/gmd/gmd.xsd">
   <gmd:fileIdentifier>
     <gco:CharacterString>None</gco:CharacterString>
@@ -171,57 +171,48 @@
       </gmd:extent>
       <gmd:supplementalInformation>
         <inasafe>
-          <classification>
-            <gco:CharacterString>generic_structure_classes</gco:CharacterString>
-          </classification>
-          <inasafe_fields>
-            <gco:Dictionary>{&quot;exposure_id_field&quot;: &quot;exposure_id&quot;, &quot;exposure_type_field&quot;: &quot;exposure_type&quot;}</gco:Dictionary>
-          </inasafe_fields>
-          <inasafe_default_values>
-            <gco:Dictionary/>
-          </inasafe_default_values>
+          <layer_purpose>
+            <gco:CharacterString>exposure</gco:CharacterString>
+          </layer_purpose>
+          <layer_mode>
+            <gco:CharacterString>classified</gco:CharacterString>
+          </layer_mode>
+          <layer_geometry>
+            <gco:CharacterString>polygon</gco:CharacterString>
+          </layer_geometry>
+          <keyword_version>
+            <gco:CharacterString>5.0</gco:CharacterString>
+          </keyword_version>
           <scale>
             <gco:CharacterString/>
           </scale>
-          <multipart_polygon>
-            <gco:Boolean/>
-          </multipart_polygon>
           <source>
             <gco:CharacterString>Test layer</gco:CharacterString>
           </source>
-          <value_map>
-            <gco:Dictionary>{&quot;commercial&quot;: [&quot;shop&quot;], &quot;education&quot;: [&quot;school&quot;], &quot;health&quot;: [&quot;hospital&quot;], &quot;government&quot;: [&quot;ministry&quot;]}</gco:Dictionary>
-          </value_map>
-          <layer_geometry>
-            <gco:CharacterString>polygon</gco:CharacterString>
-          </layer_geometry>
+          <inasafe_fields>
+            <gco:Dictionary>{&quot;exposure_id_field&quot;: &quot;exposure_id&quot;, &quot;exposure_type_field&quot;: &quot;exposure_type&quot;}</gco:Dictionary>
+          </inasafe_fields>
+          <inasafe_default_values>
+            <gco:Dictionary/>
+          </inasafe_default_values>
+          <extra_keywords>
+            <gco:Dictionary/>
+          </extra_keywords>
           <exposure>
             <gco:CharacterString>structure</gco:CharacterString>
           </exposure>
-          <report>
-            <gco:CharacterString/>
-          </report>
           <exposure_unit>
             <gco:CharacterString/>
           </exposure_unit>
-          <keyword_version>
-            <gco:CharacterString>4.0</gco:CharacterString>
-          </keyword_version>
-          <datatype>
-            <gco:CharacterString/>
-          </datatype>
-          <allow_resampling>
-            <gco:CharacterString/>
-          </allow_resampling>
-          <layer_purpose>
-            <gco:CharacterString>exposure</gco:CharacterString>
-          </layer_purpose>
-          <resolution>
-            <gco:FloatTuple/>
-          </resolution>
-          <layer_mode>
-            <gco:CharacterString>classified</gco:CharacterString>
-          </layer_mode>
+          <classification>
+            <gco:CharacterString>generic_structure_classes</gco:CharacterString>
+          </classification>
+          <value_map>
+            <gco:Dictionary>{&quot;commercial&quot;: [&quot;shop&quot;], &quot;education&quot;: [&quot;school&quot;], &quot;health&quot;: [&quot;hospital&quot;], &quot;government&quot;: [&quot;ministry&quot;]}</gco:Dictionary>
+          </value_map>
+          <active_band>
+            <gco:Integer/>
+          </active_band>
         </inasafe>
       </gmd:supplementalInformation>
     </gmd:MD_DataIdentification>
diff --git a/src/headless/tasks/test/data/input_layers/buildings_geojson.qlr b/src/headless/tasks/test/data/input_layers/buildings_geojson.qlr
new file mode 100644
index 0000000..8b0eb2b
--- /dev/null
+++ b/src/headless/tasks/test/data/input_layers/buildings_geojson.qlr
@@ -0,0 +1,216 @@
+<!DOCTYPE qgis-layer-definition>
+<qlr>
+  <layer-tree-group checked="Qt::Checked" expanded="1" name="">
+    <customproperties/>
+    <layer-tree-layer source="./buildings.geojson" providerKey="ogr" checked="Qt::Checked" patch_size="-1,-1" expanded="1" name="buildings" legend_exp="" id="buildings_25dafc4f_de52_4c06_94ab_14cd6a301be1" legend_split_behavior="0">
+      <customproperties/>
+    </layer-tree-layer>
+  </layer-tree-group>
+  <maplayers>
+    <maplayer wkbType="Polygon" geometry="Polygon" type="vector" simplifyDrawingTol="1" refreshOnNotifyMessage="" refreshOnNotifyEnabled="0" simplifyLocal="1" autoRefreshTime="0" hasScaleBasedVisibilityFlag="0" simplifyDrawingHints="1" maxScale="0" simplifyAlgorithm="0" labelsEnabled="0" simplifyMaxScale="1" autoRefreshEnabled="0" readOnly="0" styleCategories="AllStyleCategories" minScale="100000000">
+      <extent>
+        <xmin>106.6388189694232409</xmin>
+        <ymin>-6.29597524568388156</ymin>
+        <xmax>106.90657529121263281</xmax>
+        <ymax>-6.11387787165906804</ymax>
+      </extent>
+      <id>buildings_25dafc4f_de52_4c06_94ab_14cd6a301be1</id>
+      <datasource>./buildings.geojson</datasource>
+      <keywordList>
+        <value></value>
+      </keywordList>
+      <layername>buildings</layername>
+      <srs>
+        <spatialrefsys>
+          <wkt>GEOGCRS["WGS 84",DATUM["World Geodetic System 1984",ELLIPSOID["WGS 84",6378137,298.257223563,LENGTHUNIT["metre",1]]],PRIMEM["Greenwich",0,ANGLEUNIT["degree",0.0174532925199433]],CS[ellipsoidal,2],AXIS["geodetic latitude (Lat)",north,ORDER[1],ANGLEUNIT["degree",0.0174532925199433]],AXIS["geodetic longitude (Lon)",east,ORDER[2],ANGLEUNIT["degree",0.0174532925199433]],USAGE[SCOPE["unknown"],AREA["World"],BBOX[-90,-180,90,180]],ID["EPSG",4326]]</wkt>
+          <proj4>+proj=longlat +datum=WGS84 +no_defs</proj4>
+          <srsid>3452</srsid>
+          <srid>4326</srid>
+          <authid>EPSG:4326</authid>
+          <description>WGS 84</description>
+          <projectionacronym>longlat</projectionacronym>
+          <ellipsoidacronym>EPSG:7030</ellipsoidacronym>
+          <geographicflag>true</geographicflag>
+        </spatialrefsys>
+      </srs>
+      <resourceMetadata>
+        <identifier></identifier>
+        <parentidentifier></parentidentifier>
+        <language></language>
+        <type>dataset</type>
+        <title></title>
+        <abstract></abstract>
+        <links/>
+        <fees></fees>
+        <encoding></encoding>
+        <crs>
+          <spatialrefsys>
+            <wkt></wkt>
+            <proj4></proj4>
+            <srsid>0</srsid>
+            <srid>0</srid>
+            <authid></authid>
+            <description></description>
+            <projectionacronym></projectionacronym>
+            <ellipsoidacronym></ellipsoidacronym>
+            <geographicflag>false</geographicflag>
+          </spatialrefsys>
+        </crs>
+        <extent/>
+      </resourceMetadata>
+      <provider encoding="UTF-8">ogr</provider>
+      <vectorjoins/>
+      <layerDependencies/>
+      <dataDependencies/>
+      <legend type="default-vector"/>
+      <expressionfields/>
+      <map-layer-style-manager current="default">
+        <map-layer-style name="default"/>
+      </map-layer-style-manager>
+      <auxiliaryLayer/>
+      <flags>
+        <Identifiable>1</Identifiable>
+        <Removable>1</Removable>
+        <Searchable>1</Searchable>
+      </flags>
+      <temporal enabled="0" accumulate="0" startField="" fixedDuration="0" durationUnit="min" mode="0" endField="" durationField="" startExpression="" endExpression="">
+        <fixedRange>
+          <start></start>
+          <end></end>
+        </fixedRange>
+      </temporal>
+      <renderer-v2 symbollevels="0" enableorderby="0" type="singleSymbol" forceraster="0">
+        <symbols>
+          <symbol clip_to_extent="1" type="fill" name="0" alpha="1" force_rhr="0">
+            <layer locked="0" class="SimpleFill" enabled="1" pass="0">
+              <prop v="3x:0,0,0,0,0,0" k="border_width_map_unit_scale"/>
+              <prop v="133,182,111,255" k="color"/>
+              <prop v="bevel" k="joinstyle"/>
+              <prop v="0,0" k="offset"/>
+              <prop v="3x:0,0,0,0,0,0" k="offset_map_unit_scale"/>
+              <prop v="MM" k="offset_unit"/>
+              <prop v="35,35,35,255" k="outline_color"/>
+              <prop v="solid" k="outline_style"/>
+              <prop v="0.26" k="outline_width"/>
+              <prop v="MM" k="outline_width_unit"/>
+              <prop v="solid" k="style"/>
+              <data_defined_properties>
+                <Option type="Map">
+                  <Option value="" type="QString" name="name"/>
+                  <Option name="properties"/>
+                  <Option value="collection" type="QString" name="type"/>
+                </Option>
+              </data_defined_properties>
+            </layer>
+          </symbol>
+        </symbols>
+        <rotation/>
+        <sizescale/>
+      </renderer-v2>
+      <customproperties/>
+      <blendMode>0</blendMode>
+      <featureBlendMode>0</featureBlendMode>
+      <layerOpacity>1</layerOpacity>
+      <geometryOptions geometryPrecision="0" removeDuplicateNodes="0">
+        <activeChecks type="StringList">
+          <Option value="" type="QString"/>
+        </activeChecks>
+        <checkConfiguration/>
+      </geometryOptions>
+      <referencedLayers/>
+      <referencingLayers/>
+      <fieldConfiguration>
+        <field name="exposure_id">
+          <editWidget type="">
+            <config>
+              <Option/>
+            </config>
+          </editWidget>
+        </field>
+        <field name="exposure_type">
+          <editWidget type="">
+            <config>
+              <Option/>
+            </config>
+          </editWidget>
+        </field>
+        <field name="fake_field">
+          <editWidget type="">
+            <config>
+              <Option/>
+            </config>
+          </editWidget>
+        </field>
+        <field name="absolute">
+          <editWidget type="">
+            <config>
+              <Option/>
+            </config>
+          </editWidget>
+        </field>
+        <field name="ratio">
+          <editWidget type="">
+            <config>
+              <Option/>
+            </config>
+          </editWidget>
+        </field>
+      </fieldConfiguration>
+      <aliases>
+        <alias field="exposure_id" name="" index="0"/>
+        <alias field="exposure_type" name="" index="1"/>
+        <alias field="fake_field" name="" index="2"/>
+        <alias field="absolute" name="" index="3"/>
+        <alias field="ratio" name="" index="4"/>
+      </aliases>
+      <excludeAttributesWMS/>
+      <excludeAttributesWFS/>
+      <defaults>
+        <default field="exposure_id" expression="" applyOnUpdate="0"/>
+        <default field="exposure_type" expression="" applyOnUpdate="0"/>
+        <default field="fake_field" expression="" applyOnUpdate="0"/>
+        <default field="absolute" expression="" applyOnUpdate="0"/>
+        <default field="ratio" expression="" applyOnUpdate="0"/>
+      </defaults>
+      <constraints>
+        <constraint notnull_strength="0" constraints="0" field="exposure_id" unique_strength="0" exp_strength="0"/>
+        <constraint notnull_strength="0" constraints="0" field="exposure_type" unique_strength="0" exp_strength="0"/>
+        <constraint notnull_strength="0" constraints="0" field="fake_field" unique_strength="0" exp_strength="0"/>
+        <constraint notnull_strength="0" constraints="0" field="absolute" unique_strength="0" exp_strength="0"/>
+        <constraint notnull_strength="0" constraints="0" field="ratio" unique_strength="0" exp_strength="0"/>
+      </constraints>
+      <constraintExpressions>
+        <constraint desc="" field="exposure_id" exp=""/>
+        <constraint desc="" field="exposure_type" exp=""/>
+        <constraint desc="" field="fake_field" exp=""/>
+        <constraint desc="" field="absolute" exp=""/>
+        <constraint desc="" field="ratio" exp=""/>
+      </constraintExpressions>
+      <expressionfields/>
+      <attributeactions>
+        <defaultAction key="Canvas" value="{00000000-0000-0000-0000-000000000000}"/>
+      </attributeactions>
+      <attributetableconfig actionWidgetStyle="dropDown" sortExpression="" sortOrder="0">
+        <columns/>
+      </attributetableconfig>
+      <conditionalstyles>
+        <rowstyles/>
+        <fieldstyles/>
+      </conditionalstyles>
+      <storedexpressions/>
+      <editform tolerant="1"></editform>
+      <editforminit/>
+      <editforminitcodesource>0</editforminitcodesource>
+      <editforminitfilepath></editforminitfilepath>
+      <editforminitcode><![CDATA[]]></editforminitcode>
+      <featformsuppress>0</featformsuppress>
+      <editorlayout>generatedlayout</editorlayout>
+      <editable/>
+      <labelOnTop/>
+      <dataDefinedFieldProperties/>
+      <widgets/>
+      <previewExpression></previewExpression>
+      <mapTip></mapTip>
+    </maplayer>
+  </maplayers>
+</qlr>
diff --git a/src/headless/tasks/test/data/input_layers/buildings_geojson.xml b/src/headless/tasks/test/data/input_layers/buildings_geojson.xml
new file mode 120000
index 0000000..933f876
--- /dev/null
+++ b/src/headless/tasks/test/data/input_layers/buildings_geojson.xml
@@ -0,0 +1 @@
+buildings.xml
\ No newline at end of file
diff --git a/src/headless/tasks/test/data/input_layers/population_multi_fields.qml b/src/headless/tasks/test/data/input_layers/population_multi_fields.qml
index e34221b..f5a2eb3 100644
--- a/src/headless/tasks/test/data/input_layers/population_multi_fields.qml
+++ b/src/headless/tasks/test/data/input_layers/population_multi_fields.qml
@@ -420,7 +420,7 @@ Enter the name of the function in the "Python Init function"
 field.
 An example follows:
 """
-from PyQt4.QtGui import QWidget
+from qgis.PyQt.QtGui import QWidget
 
 def my_form_open(dialog, layer, feature):
 	geom = feature.geometry()
diff --git a/src/headless/tasks/test/data/input_layers/small_grid.qml b/src/headless/tasks/test/data/input_layers/small_grid.qml
index 23b3608..ee5b841 100644
--- a/src/headless/tasks/test/data/input_layers/small_grid.qml
+++ b/src/headless/tasks/test/data/input_layers/small_grid.qml
@@ -220,7 +220,7 @@ Enter the name of the function in the "Python Init function"
 field.
 An example follows:
 """
-from PyQt4.QtGui import QWidget
+from qgis.PyQt.QtGui import QWidget
 
 def my_form_open(dialog, layer, feature):
 	geom = feature.geometry()
diff --git a/src/headless/tasks/test/helpers.py b/src/headless/tasks/test/helpers.py
index 48b7224..e9f9b2d 100644
--- a/src/headless/tasks/test/helpers.py
+++ b/src/headless/tasks/test/helpers.py
@@ -24,7 +24,7 @@
 buildings_layer_uri = os.path.join(
     dir_path, 'data', 'input_layers', 'buildings.geojson')
 buildings_layer_qlr_uri = os.path.join(
-    dir_path, 'data', 'input_layers', 'buildings.qlr')
+    dir_path, 'data', 'input_layers', 'buildings_geojson.qlr')
 
 shapefile_layer_uri = standard_data_path('exposure', 'airports.shp')
 ascii_layer_uri = standard_data_path('gisv4', 'hazard', 'earthquake.asc')
diff --git a/src/headless/tasks/test/test_generate_report.py b/src/headless/tasks/test/test_generate_report.py
index ecf7ac8..366985e 100644
--- a/src/headless/tasks/test/test_generate_report.py
+++ b/src/headless/tasks/test/test_generate_report.py
@@ -3,7 +3,6 @@
 from past.builtins import basestring
 import os
 import unittest
-from distutils.util import strtobool
 
 from headless.settings import OUTPUT_DIRECTORY
 from headless.tasks.inasafe_analysis import (
@@ -38,9 +37,6 @@
 
 class TestGenerateReport(unittest.TestCase):
 
-    @unittest.skipIf(
-        strtobool(os.environ.get('ON_TRAVIS', 'False')),
-        """Skipped because we don't have remote service QLR anymore.""")
     @retry_on_worker_lost_error()
     def test_generate_report_qlr(self):
         """Test generating report with QLR files."""
diff --git a/src/headless/tasks/test/test_run_analysis.py b/src/headless/tasks/test/test_run_analysis.py
index acb94d9..1be76e4 100644
--- a/src/headless/tasks/test/test_run_analysis.py
+++ b/src/headless/tasks/test/test_run_analysis.py
@@ -2,7 +2,6 @@
 from past.builtins import basestring
 import os
 import unittest
-from distutils.util import strtobool
 
 from headless.settings import OUTPUT_DIRECTORY
 from headless.tasks.inasafe_wrapper import (
@@ -102,9 +101,6 @@ def test_run_multi_exposure_analysis(self):
         # of exposures
         self.assertEqual(num_exposure_output, len(exposure_layer_uris))
 
-    @unittest.skipIf(
-        strtobool(os.environ.get('ON_TRAVIS', 'False')),
-        """Skipped because we don't have remote service QLR anymore.""")
     @retry_on_worker_lost_error()
     def test_run_analysis_qlr(self):
         """Test running analysis with QLR files."""
diff --git a/src/headless/tasks/test/test_subsequent_run.py b/src/headless/tasks/test/test_subsequent_run.py
index 138f87b..33a2fa2 100644
--- a/src/headless/tasks/test/test_subsequent_run.py
+++ b/src/headless/tasks/test/test_subsequent_run.py
@@ -2,7 +2,7 @@
 import os
 import unittest
 
-from qgis.core import QgsMapLayerRegistry
+from qgis.core import QgsProject
 
 from headless.celeryconfig import task_always_eager
 from headless.tasks.inasafe_wrapper import (
@@ -32,7 +32,7 @@ class TestSubsequentRun(unittest.TestCase):
 
     def check_layer_registry_empty(self):
         # Layer registry should be empty between run
-        layer_registry = QgsMapLayerRegistry.instance()
+        layer_registry = QgsProject.instance()
         self.assertDictEqual(layer_registry.mapLayers(), {})
 
     @unittest.skipUnless(
diff --git a/src/headless/utils.py b/src/headless/utils.py
index 8ff94b7..8bd4153 100644
--- a/src/headless/utils.py
+++ b/src/headless/utils.py
@@ -2,7 +2,7 @@
 import logging
 import os
 
-from qgis.core import QgsMapLayer
+from qgis.core import QgsLayerDefinition
 
 from headless import settings as headless_settings
 from safe.common.exceptions import NoKeywordsFoundError
@@ -50,11 +50,12 @@ def load_layer(full_layer_uri_string, name=None, provider=None):
     base, ext = os.path.splitext(full_layer_uri_string)
 
     if ext.lower() == '.qlr':
-        layer = QgsMapLayer.fromLayerDefinitionFile(full_layer_uri_string)
-        if not layer:
+        layers = QgsLayerDefinition.loadLayerDefinitionLayers(
+            full_layer_uri_string)
+        if not layers:
             return None, None
 
-        layer = layer[0]
+        layer = layers[0]
         if layer.isValid():
             keyword_io = KeywordIO()