Repository: Azure/WALinuxAgent
Pull request #505 (2016-12-06 01:33:23)

Host plugin updates
These changes allow the agent to function normally even while outbound connectivity to the public internet is blocked, via NSG for example. 

- enables host plugin downloads for extension and agent manifests and packages
- fixes an issue uploading the extension health via the host plugin
- fixes issues whereby the agent manifest and package are downloaded multiple times
- refactor and cleanup 
- adds unit tests

/cc @jinhyunr @brendandixon 

brendandixon: @hglkrijger LGTM. Nice.

Patch:
diff --git a/azurelinuxagent/common/event.py b/azurelinuxagent/common/event.py
index 957b686f..40376229 100644
--- a/azurelinuxagent/common/event.py
+++ b/azurelinuxagent/common/event.py
@@ -41,6 +41,7 @@ class WALAEventOperation:
     Disable = "Disable"
     Download = "Download"
     Enable = "Enable"
+    HealthCheck = "HealthCheck"
     HeartBeat="HeartBeat"
     Install = "Install"
     Provision = "Provision"
diff --git a/azurelinuxagent/common/protocol/hostplugin.py b/azurelinuxagent/common/protocol/hostplugin.py
index c3179239..e83dd4b1 100644
--- a/azurelinuxagent/common/protocol/hostplugin.py
+++ b/azurelinuxagent/common/protocol/hostplugin.py
@@ -29,7 +29,8 @@ API_VERSION = "2015-09-01"
 HEADER_CONTAINER_ID = "x-ms-containerid"
 HEADER_VERSION = "x-ms-version"
 HEADER_HOST_CONFIG_NAME = "x-ms-host-config-name"
-
+HEADER_ARTIFACT_LOCATION = "x-ms-artifact-location"
+HEADER_ARTIFACT_MANIFEST_LOCATION = "x-ms-artifact-manifest-location"
 
 class HostPluginProtocol(object):
     def __init__(self, endpoint, container_id, role_config_name):
@@ -41,6 +42,7 @@ class HostPluginProtocol(object):
         self.endpoint = endpoint
         self.container_id = container_id
         self.role_config_name = role_config_name
+        self.manifest_uri = None
 
     def ensure_initialized(self):
         if not self.is_initialized:
@@ -52,21 +54,24 @@ class HostPluginProtocol(object):
     def get_api_versions(self):
         url = URI_FORMAT_GET_API_VERSIONS.format(self.endpoint,
                                                  HOST_PLUGIN_PORT)
-        logger.info("getting API versions at [{0}]".format(url))
+        logger.verbose("getting API versions at [{0}]".format(url))
+        return_val = []
         try:
-            headers = { HEADER_CONTAINER_ID: self.container_id }
+            headers = {HEADER_CONTAINER_ID: self.container_id}
             response = restutil.http_get(url, headers)
             if response.status != httpclient.OK:
                 logger.error(
                     "get API versions returned status code [{0}]".format(
                         response.status))
-                return []
-            return response.read()
+            else:
+                return_val = ustr(remove_bom(response.read()), encoding='utf-8')
+
         except HttpError as e:
             logger.error("get API versions failed with [{0}]".format(e))
-            return []
 
-    def get_extension_artifact_url_and_headers(self, artifact_url, artifact_manifest_url = None):
+        return return_val
+
+    def get_artifact_request(self, artifact_url, artifact_manifest_url=None):
         if not self.ensure_initialized():
             logger.error("host plugin channel is not available")
             return
@@ -74,20 +79,23 @@ class HostPluginProtocol(object):
             logger.error("no extension artifact url was provided")
             return
 
-        url = URI_FORMAT_GET_EXTENSION_ARTIFACT.format(self.endpoint, HOST_PLUGIN_PORT)
+        url = URI_FORMAT_GET_EXTENSION_ARTIFACT.format(self.endpoint,
+                                                       HOST_PLUGIN_PORT)
         headers = {HEADER_VERSION: API_VERSION,
                    HEADER_CONTAINER_ID: self.container_id,
                    HEADER_HOST_CONFIG_NAME: self.role_config_name,
-                   "x-ms-artifact-location": artifact_url}
+                   HEADER_ARTIFACT_LOCATION: artifact_url}
+
         if artifact_manifest_url is not None:
-            headers["x-ms-artifact-manifest-location"] = artifact_manifest_url
+            headers[HEADER_ARTIFACT_MANIFEST_LOCATION] = artifact_manifest_url
 
         return url, headers
 
-    def put_vm_status(self, status_blob, sas_url):
+    def put_vm_status(self, status_blob, sas_url, config_blob_type=None):
         """
         Try to upload the VM status via the host plugin /status channel
         :param sas_url: the blob SAS url to pass to the host plugin
+        :param config_blob_type: the blob type from the extension config
         :type status_blob: StatusBlob
         """
         if not self.ensure_initialized():
@@ -96,26 +104,28 @@ class HostPluginProtocol(object):
         if status_blob is None or status_blob.vm_status is None:
             logger.error("no status data was provided")
             return
-        url = URI_FORMAT_PUT_VM_STATUS.format(self.endpoint, HOST_PLUGIN_PORT)
-        logger.verbose("Posting VM status to host channel [{0}]".format(url))
-        status = textutil.b64encode(status_blob.data)
-        headers = {HEADER_VERSION: API_VERSION,
-                   "Content-type": "application/json",
-                   HEADER_CONTAINER_ID: self.container_id,
-                   HEADER_HOST_CONFIG_NAME: self.role_config_name}
-        blob_headers = [{'headerName': 'x-ms-version',
-                         'headerValue': status_blob.__storage_version__},
-                        {'headerName': 'x-ms-blob-type',
-                         'headerValue': status_blob.type}]
-        data = json.dumps({'requestUri': sas_url, 'headers': blob_headers,
-                           'content': status}, sort_keys=True)
         try:
-            response = restutil.http_put(url, data, headers)
+            url = URI_FORMAT_PUT_VM_STATUS.format(self.endpoint, HOST_PLUGIN_PORT)
+            logger.verbose("Posting VM status to host plugin")
+            status = textutil.b64encode(status_blob.data)
+            blob_type = status_blob.type if status_blob.type else config_blob_type
+            headers = {HEADER_VERSION: API_VERSION,
+                       "Content-type": "application/json",
+                       HEADER_CONTAINER_ID: self.container_id,
+                       HEADER_HOST_CONFIG_NAME: self.role_config_name}
+            blob_headers = [{'headerName': 'x-ms-version',
+                             'headerValue': status_blob.__storage_version__},
+                            {'headerName': 'x-ms-blob-type',
+                             'headerValue': blob_type}]
+            data = json.dumps({'requestUri': sas_url, 'headers': blob_headers,
+                               'content': status}, sort_keys=True)
+            response = restutil.http_put(url, data=data, headers=headers)
             if response.status != httpclient.OK:
-                logger.error("put VM status returned status code [{0}]".format(
-                    response.status))
-        except HttpError as e:
-            logger.error("put VM status failed with [{0}]".format(e))
+                logger.error("PUT failed [{0}]", response.status)
+            else:
+                logger.verbose("Successfully uploaded status to host plugin")
+        except Exception as e:
+            logger.error("Put VM status failed [{0}]", e)
 
     def put_vm_log(self, content):
         """
diff --git a/azurelinuxagent/common/protocol/restapi.py b/azurelinuxagent/common/protocol/restapi.py
index d9ade02f..5f71cf25 100644
--- a/azurelinuxagent/common/protocol/restapi.py
+++ b/azurelinuxagent/common/protocol/restapi.py
@@ -282,16 +282,16 @@ class Protocol(DataContract):
     def get_ext_handler_pkgs(self, extension):
         raise NotImplementedError()
 
-    def get_in_vm_artifacts_profile(self):
+    def get_artifacts_profile(self):
         raise NotImplementedError()
 
-    def download_ext_handler_pkg(self, uri):
+    def download_ext_handler_pkg(self, uri, headers=None):
         try:
-            resp = restutil.http_get(uri, chk_proxy=True)
+            resp = restutil.http_get(uri, chk_proxy=True, headers=headers)
             if resp.status == restutil.httpclient.OK:
                 return resp.read()
-        except HttpError as e:
-            raise ProtocolError("Failed to download from: {0}".format(uri), e)
+        except Exception as e:
+            logger.warn("Failed to download from: {0}".format(uri), e)
 
     def report_provision_status(self, provision_status):
         raise NotImplementedError()
diff --git a/azurelinuxagent/common/protocol/wire.py b/azurelinuxagent/common/protocol/wire.py
index 1eab6760..9f634e9a 100644
--- a/azurelinuxagent/common/protocol/wire.py
+++ b/azurelinuxagent/common/protocol/wire.py
@@ -140,9 +140,21 @@ class WireProtocol(Protocol):
         man = self.client.get_ext_manifest(ext_handler, goal_state)
         return man.pkg_list
 
-    def get_in_vm_artifacts_profile(self):
+    def get_artifacts_profile(self):
         logger.verbose("Get In-VM Artifacts Profile")
-        return self.client.get_in_vm_artifacts_profile()
+        return self.client.get_artifacts_profile()
+
+    def download_ext_handler_pkg(self, uri, headers=None):
+        package = super(WireProtocol, self).download_ext_handler_pkg(uri)
+
+        if package is not None:
+            return package
+        else:
+            logger.warn("Download did not succeed, falling back to host plugin")
+            host = self.client.get_host_plugin()
+            uri, headers = host.get_artifact_request(uri, host.manifest_uri)
+            package = super(WireProtocol, self).download_ext_handler_pkg(uri, headers=headers)
+        return package
 
     def report_provision_status(self, provision_status):
         validate_param("provision_status", provision_status, ProvisionStatus)
@@ -359,8 +371,8 @@ class StatusBlob(object):
         # TODO upload extension only if content has changed
         logger.verbose("Upload status blob")
         upload_successful = False
-        self.type = self.get_blob_type(url)
         self.data = self.to_json()
+        self.type = self.get_blob_type(url)
         try:
             if self.type == "BlockBlob":
                 self.put_block_blob(url, self.data)
@@ -376,8 +388,7 @@ class StatusBlob(object):
         return upload_successful
 
     def get_blob_type(self, url):
-        # Check blob type
-        logger.verbose("Check blob type.")
+        logger.verbose("Get blob type")
         timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
         try:
             resp = self.client.call_storage_service(
@@ -388,18 +399,17 @@ class StatusBlob(object):
                     "x-ms-version": self.__class__.__storage_version__
                 })
         except HttpError as e:
-            raise ProtocolError((u"Failed to get status blob type: {0}"
-                                 u"").format(e))
+            raise ProtocolError("Failed to get status blob type: {0}", e)
+
         if resp is None or resp.status != httpclient.OK:
-            raise ProtocolError(("Failed to get status blob type: {0}"
-                                 "").format(resp.status))
+            raise ProtocolError("Failed to get status blob type")
 
         blob_type = resp.getheader("x-ms-blob-type")
-        logger.verbose("Blob type={0}".format(blob_type))
+        logger.verbose("Blob type: [{0}]", blob_type)
         return blob_type
 
     def put_block_blob(self, url, data):
-        logger.verbose("Upload block blob")
+        logger.verbose("Put block blob")
         timestamp = time.strftime("%Y-%m-%dT%H:%M:%SZ", time.gmtime())
         resp = self.client.call_storage_service(
             restutil.http_put,
@@ -416,7 +426,7 @@ class StatusBlob(object):
                 "Failed to upload block blob: {0}".format(resp.status))
 
     def put_page_blob(self, url, data):
-        logger.verbose("Replace old page blob")
+        logger.verbose("Put page blob")
 
         # Convert string into bytes
         data = bytearray(data, encoding='utf-8')
@@ -516,6 +526,7 @@ class WireClient(object):
         self.req_count = 0
         self.host_plugin = None
         self.status_blob = StatusBlob(self)
+        self.status_blob_type_reported = False
 
     def prevent_throttling(self):
         """
@@ -618,25 +629,42 @@ class WireClient(object):
                              "{0}").format(resp.status))
 
     def fetch_manifest(self, version_uris):
-        for version_uri in version_uris:
-            logger.verbose("Fetch ext handler manifest: {0}", version_uri.uri)
-            try:
-                resp = self.call_storage_service(
-                    restutil.http_get,
-                    version_uri.uri,
-                    None)
-            except HttpError as e:
-                raise ProtocolError(ustr(e))
-
+        logger.verbose("Fetch manifest")
+        for version in version_uris:
+            response = self.fetch(version.uri)
+            if not response:
+                logger.verbose("Manifest could not be downloaded, falling back to host plugin")
+                host = self.get_host_plugin()
+                uri, headers = host.get_artifact_request(version.uri)
+                response = self.fetch(uri, headers)
+                if not response:
+                    logger.info("Retry fetch in {0} seconds",
+                                LONG_WAITING_INTERVAL)
+                    time.sleep(LONG_WAITING_INTERVAL)
+                else:
+                    host.manifest_uri = version.uri
+                    logger.verbose("Manifest downloaded successfully from host plugin")
+            if response:
+                return response
+        raise ProtocolError("Failed to fetch manifest from all sources")
+
+    def fetch(self, uri, headers=None):
+        logger.verbose("Fetch [{0}] with headers [{1}]", uri, headers)
+        return_value = None
+        try:
+            resp = self.call_storage_service(
+                restutil.http_get,
+                uri,
+                headers)
             if resp.status == httpclient.OK:
-                return self.decode_config(resp.read())
-            logger.warn("Failed to fetch ExtensionManifest: {0}, {1}",
-                        resp.status, version_uri.uri)
-            logger.info("Will retry later, in {0} seconds",
-                        LONG_WAITING_INTERVAL)
-            time.sleep(LONG_WAITING_INTERVAL)
-        raise ProtocolError(("Failed to fetch ExtensionManifest from "
-                             "all sources"))
+                return_value = self.decode_config(resp.read())
+            else:
+                logger.warn("Could not fetch {0} [{1}]",
+                            uri,
+                            resp.status)
+        except (HttpError, ProtocolError) as e:
+            logger.verbose("Fetch failed from [{0}]", uri)
+        return return_value
 
     def update_hosting_env(self, goal_state):
         if goal_state.hosting_env_uri is None:
@@ -676,6 +704,7 @@ class WireClient(object):
         xml_text = self.fetch_config(goal_state.ext_uri, self.get_header())
         self.save_cache(local_file, xml_text)
         self.ext_conf = ExtensionsConfig(xml_text)
+        self.status_blob_type_reported = False
 
     def update_goal_state(self, forced=False, max_retry=3):
         uri = GOAL_STATE_URI.format(self.endpoint)
@@ -709,7 +738,7 @@ class WireClient(object):
                 self.update_ext_conf(goal_state)
                 if self.host_plugin is not None:
                     self.host_plugin.container_id = goal_state.container_id
-                    self.host_plugin.role_config_name = goal_state.role_instance_config_name
+                    self.host_plugin.role_config_name = goal_state.role_config_name
                 return
             except WireProtocolResourceGone:
                 logger.info("Incarnation is out of date. Update goalstate.")
@@ -719,7 +748,7 @@ class WireClient(object):
         raise ProtocolError("Exceeded max retry updating goal state")
 
     def get_goal_state(self):
-        if (self.goal_state is None):
+        if self.goal_state is None:
             incarnation_file = os.path.join(conf.get_lib_dir(),
                                             INCARNATION_FILE_NAME)
             incarnation = self.fetch_cache(incarnation_file)
@@ -756,7 +785,7 @@ class WireClient(object):
         return self.certs
 
     def get_ext_conf(self):
-        if (self.ext_conf is None):
+        if self.ext_conf is None:
             goal_state = self.get_goal_state()
             if goal_state.ext_uri is None:
                 self.ext_conf = ExtensionsConfig(None)
@@ -765,6 +794,7 @@ class WireClient(object):
                 local_file = os.path.join(conf.get_lib_dir(), local_file)
                 xml_text = self.fetch_cache(local_file)
                 self.ext_conf = ExtensionsConfig(xml_text)
+                self.status_blob_type_reported = False
         return self.ext_conf
 
     def get_ext_manifest(self, ext_handler, goal_state):
@@ -802,8 +832,42 @@ class WireClient(object):
     def upload_status_blob(self):
         ext_conf = self.get_ext_conf()
         if ext_conf.status_upload_blob is not None:
-            if not self.status_blob.upload(ext_conf.status_upload_blob):
-                self.get_host_plugin().put_vm_status(self.status_blob, ext_conf.status_upload_blob)
+            uploaded = False
+            try:
+                uploaded = self.status_blob.upload(ext_conf.status_upload_blob)
+                self.report_blob_type(self.status_blob.type,
+                                      ext_conf.status_upload_blob_type)
+            except (HttpError, ProtocolError) as e:
+                # errors have already been logged
+                pass
+            if not uploaded:
+                host = self.get_host_plugin()
+                host.put_vm_status(self.status_blob,
+                                   ext_conf.status_upload_blob,
+                                   ext_conf.status_upload_blob_type)
+
+    """
+    Emit an event to determine if the type in the extension config
+    matches the actual type from the HTTP HEAD request.
+    """
+    def report_blob_type(self, head_type, config_type):
+        if head_type and config_type:
+            is_match = head_type == config_type
+            if self.status_blob_type_reported is False:
+                message = \
+                    'Blob type match [{0}]'.format(head_type) if is_match else \
+                    'Blob type mismatch [HEAD {0}], [CONFIG {1}]'.format(
+                        head_type,
+                        config_type)
+
+                from azurelinuxagent.common.event import add_event, WALAEventOperation
+                from azurelinuxagent.common.version import AGENT_NAME, CURRENT_VERSION
+                add_event(AGENT_NAME,
+                          version=CURRENT_VERSION,
+                          is_success=is_match,
+                          message=message,
+                          op=WALAEventOperation.HealthCheck)
+                self.status_blob_type_reported = True
 
     def report_role_prop(self, thumbprint):
         goal_state = self.get_goal_state()
@@ -923,52 +987,31 @@ class WireClient(object):
             goal_state = self.get_goal_state()
             self.host_plugin = HostPluginProtocol(self.endpoint,
                                                   goal_state.container_id,
-                                                  goal_state.role_instance_config_name)
+                                                  goal_state.role_config_name)
         return self.host_plugin
 
-    def get_in_vm_artifacts_profile(self):
-        ext_conf = self.ext_conf
-        if ext_conf and not textutil.is_str_none_or_whitespace(ext_conf.in_vm_artifacts_profile_blob):
-            # try with the default protocol
-            in_vm_artifacts_profile_byte_arr = \
-                self._get_in_vm_artifacts_profile(ext_conf.in_vm_artifacts_profile_blob)
-
-            # try with host GA plugin
-            if in_vm_artifacts_profile_byte_arr is None:
-                logger.warn(
-                    "Failed to get extension artifacts profile using the default protocol. Try with HostGAPlugin")
-                host_plugin_endpoint, headers = \
-                    self.get_host_plugin().get_extension_artifact_url_and_headers(ext_conf.in_vm_artifacts_profile_blob)
-                in_vm_artifacts_profile_byte_arr = self._get_in_vm_artifacts_profile(host_plugin_endpoint, headers)
-
-            in_vm_artifacts_profile_json = self.decode_config(in_vm_artifacts_profile_byte_arr)
-            if not textutil.is_str_none_or_whitespace(in_vm_artifacts_profile_json):
-                return InVMArtifactsProfile(in_vm_artifacts_profile_json)
-
-    def _get_in_vm_artifacts_profile(self, blob_url, headers=None):
-        result = None
-        try:
-            resp = self.call_storage_service(restutil.http_get, blob_url, headers=headers)
-            response_body = resp.read()
-            if resp.status < 400:
-                decoded = self.decode_config(response_body)
-                if not textutil.is_str_none_or_whitespace(decoded):
-                    result = InVMArtifactsProfile(decoded)
-            else:
-                logger.warn("Unexpected http status '{0}' is returned with the message '{1}'",
-                            resp.status,
-                            response_body)
+    def has_artifacts_profile_blob(self):
+        return self.ext_conf and not \
+               textutil.is_str_none_or_whitespace(self.ext_conf.artifacts_profile_blob)
 
-        except HttpError as e:
-            logger.warn("Encountered HttpError while getting InVMArtifactsProfile from '{0}' "
-                        "using the default protocol: [{1}]", blob_url, e)
-        else:
-            if resp.status == httpclient.OK:
-                result = resp.read()
-            else:
-                logger.warn("Failed to get InVMArtifactsProfile with status [{0}] from '{1}' "
-                            "using the default protocol", resp.status, blob_url)
-        return result
+    def get_artifacts_profile(self):
+        artifacts_profile = None
+        if self.has_artifacts_profile_blob():
+            blob = self.ext_conf.artifacts_profile_blob
+            logger.info("Getting the artifacts profile")
+            profile = self.fetch(blob)
+
+            if profile is None:
+                logger.warn("Download failed, falling back to host plugin")
+                host = self.get_host_plugin()
+                uri, headers = host.get_artifact_request(blob)
+                profile = self.decode_config(self.fetch(uri, headers))
+
+            if not textutil.is_str_none_or_whitespace(profile):
+                    logger.info("Artifacts profile downloaded successfully")
+                    artifacts_profile = InVMArtifactsProfile(profile)
+
+        return artifacts_profile
 
 
 class VersionInfo(object):
@@ -1015,9 +1058,10 @@ class GoalState(object):
         self.certs_uri = None
         self.ext_uri = None
         self.role_instance_id = None
-        self.role_instance_config_name = None
+        self.role_config_name = None
         self.container_id = None
         self.load_balancer_probe_port = None
+        self.xml_text = None
         self.parse(xml_text)
 
     def parse(self, xml_text):
@@ -1035,7 +1079,7 @@ class GoalState(object):
         role_instance = find(xml_doc, "RoleInstance")
         self.role_instance_id = findtext(role_instance, "InstanceId")
         role_config = find(role_instance, "Configuration")
-        self.role_instance_config_name = findtext(role_config, "ConfigName")
+        self.role_config_name = findtext(role_config, "ConfigName")
         container = find(xml_doc, "Container")
         self.container_id = findtext(container, "ContainerId")
         lbprobe_ports = find(xml_doc, "LBProbePorts")
@@ -1056,6 +1100,7 @@ class HostingEnv(object):
         self.vm_name = None
         self.role_name = None
         self.deployment_name = None
+        self.xml_text = None
         self.parse(xml_text)
 
     def parse(self, xml_text):
@@ -1199,7 +1244,8 @@ class ExtensionsConfig(object):
         self.ext_handlers = ExtHandlerList()
         self.vmagent_manifests = VMAgentManifestList()
         self.status_upload_blob = None
-        self.in_vm_artifacts_profile_blob = None
+        self.status_upload_blob_type = None
+        self.artifacts_profile_blob = None
         if xml_text is not None:
             self.parse(xml_text)
 
@@ -1234,7 +1280,13 @@ class ExtensionsConfig(object):
             self.parse_plugin_settings(ext_handler, plugin_settings)
 
         self.status_upload_blob = findtext(xml_doc, "StatusUploadBlob")
-        self.in_vm_artifacts_profile_blob = findtext(xml_doc, "InVMArtifactsProfileBlob")
+        self.artifacts_profile_blob = findtext(xml_doc, "InVMArtifactsProfileBlob")
+
+        status_upload_node = find(xml_doc, "StatusUploadBlob")
+        self.status_upload_blob_type = getattrib(status_upload_node,
+                                                 "statusBlobType")
+        logger.verbose("Extension config shows status blob type as [{0}]",
+                       self.status_upload_blob_type)
 
     def parse_plugin(self, plugin):
         ext_handler = ExtHandler()
@@ -1340,7 +1392,7 @@ class ExtensionManifest(object):
 
 # Do not extend this class
 class InVMArtifactsProfile(object):
-    '''
+    """
     deserialized json string of InVMArtifactsProfile.
     It is expected to contain the following fields:
     * inVMArtifactsProfileBlobSeqNo
@@ -1349,13 +1401,13 @@ class InVMArtifactsProfile(object):
     * certificateThumbprint (optional)
     * encryptedHealthChecks (optional)
     * encryptedApplicationProfile (optional)
-    '''
+    """
 
-    def __init__(self, in_vm_artifacts_profile_json):
-        if not textutil.is_str_none_or_whitespace(in_vm_artifacts_profile_json):
-            self.__dict__.update(parse_json(in_vm_artifacts_profile_json))
+    def __init__(self, artifacts_profile):
+        if not textutil.is_str_none_or_whitespace(artifacts_profile):
+            self.__dict__.update(parse_json(artifacts_profile))
 
-    def is_extension_handlers_handling_on_hold(self):
+    def is_on_hold(self):
         # hasattr() is not available in Python 2.6
         if 'onHold' in self.__dict__:
             return self.onHold.lower() == 'true'
diff --git a/azurelinuxagent/common/utils/restutil.py b/azurelinuxagent/common/utils/restutil.py
index 115d437f..7c9ee175 100644
--- a/azurelinuxagent/common/utils/restutil.py
+++ b/azurelinuxagent/common/utils/restutil.py
@@ -131,7 +131,10 @@ def http_request(method, url, data, headers=None, max_retry=3,
     logger.verbose("HTTP headers: [{0}]", headers)
     logger.verbose("HTTP proxy: [{0}:{1}]", proxy_host, proxy_port)
 
+    retry_msg = ''
+    log_msg = "HTTP {0}".format(method)
     for retry in range(0, max_retry):
+        retry_interval = RETRY_WAITING_INTERVAL
         try:
             resp = _http_request(method,
                                  host,
@@ -145,21 +148,22 @@ def http_request(method, url, data, headers=None, max_retry=3,
             logger.verbose("HTTP response status: [{0}]", resp.status)
             return resp
         except httpclient.HTTPException as e:
-            logger.warn('HTTPException: [{0}]', e)
+            retry_msg = 'HTTP exception: {0} {1}'.format(log_msg, e)
+            retry_interval = 5
         except IOError as e:
-            logger.warn('IOError: [{0}]', e)
-
-        if retry < max_retry - 1:
-            logger.info("Retry {0}", retry)
-            time.sleep(RETRY_WAITING_INTERVAL)
-        else:
-            logger.error("All retries failed")
-
-    if url is not None and len(url) > 100:
-        url_log = url[0: 100]  # In case the url is too long
-    else:
-        url_log = url
-    raise HttpError("HTTPError: {0} {1}".format(method, url_log))
+            retry_msg = 'IO error: {0} {1}'.format(log_msg, e)
+            retry_interval = 0
+            max_retry = 0
+
+        if retry < max_retry:
+            logger.info("Retry [{0}/{1} - {3}]",
+                        retry+1,
+                        max_retry,
+                        retry_interval,
+                        retry_msg)
+            time.sleep(retry_interval)
+
+    raise HttpError("{0} failed".format(log_msg))
 
 
 def http_get(url, headers=None, max_retry=3, chk_proxy=False):
diff --git a/azurelinuxagent/ga/exthandlers.py b/azurelinuxagent/ga/exthandlers.py
index c9a1382d..c9e6b5f2 100644
--- a/azurelinuxagent/ga/exthandlers.py
+++ b/azurelinuxagent/ga/exthandlers.py
@@ -192,18 +192,17 @@ class ExtHandlersHandler(object):
     def handle_ext_handlers(self, etag=None):
         if self.ext_handlers.extHandlers is None or \
                 len(self.ext_handlers.extHandlers) == 0:
-            logger.verbose("No ext handler config found")
+            logger.verbose("No extension handler config found")
             return
 
-        in_vm_artifacts_profile = self.protocol.get_in_vm_artifacts_profile()
-        if conf.get_enable_overprovisioning() and \
-                in_vm_artifacts_profile and \
-                in_vm_artifacts_profile.is_extension_handlers_handling_on_hold():
-            logger.info("Handling Extension handlers is on hold")
-            return
+        if conf.get_enable_overprovisioning():
+            artifacts_profile = self.protocol.get_artifacts_profile()
+            if artifacts_profile and artifacts_profile.is_on_hold():
+                logger.info("Extension handling is on hold")
+                return
 
         for ext_handler in self.ext_handlers.extHandlers:
-            #TODO handle install in sequence, enable in parallel
+            # TODO: handle install in sequence, enable in parallel
             self.handle_ext_handler(ext_handler, etag)
     
     def handle_ext_handler(self, ext_handler, etag):
@@ -522,8 +521,10 @@ class ExtHandlerInstance(object):
         for uri in self.pkg.uris:
             try:
                 package = self.protocol.download_ext_handler_pkg(uri.uri)
-            except ProtocolError as e: 
-                logger.warn("Failed download extension: {0}", e)
+                if package is not None:
+                    break
+            except Exception as e:
+                logger.warn("Error while downloading extension: {0}", e)
         
         if package is None:
             raise ExtensionError("Failed to download extension")
diff --git a/azurelinuxagent/ga/update.py b/azurelinuxagent/ga/update.py
index 850315d3..996484b2 100644
--- a/azurelinuxagent/ga/update.py
+++ b/azurelinuxagent/ga/update.py
@@ -375,7 +375,10 @@ class UpdateHandler(object):
         #  The code leaves on disk available, but blacklisted, agents so as to
         #  preserve the state. Otherwise, those agents could be again
         #  downloaded and inappropriately retried.
-        self._set_agents([GuestAgent(pkg=pkg) for pkg in pkg_list.versions])
+        host = None
+        if protocol and protocol.client:
+            host = protocol.client.get_host_plugin()
+        self._set_agents([GuestAgent(pkg=pkg, host=host) for pkg in pkg_list.versions])
         self._purge_agents()
         self._filter_blacklisted_agents()
 
@@ -575,8 +578,9 @@ class UpdateHandler(object):
 
 
 class GuestAgent(object):
-    def __init__(self, path=None, pkg=None):
+    def __init__(self, path=None, pkg=None, host=None):
         self.pkg = pkg
+        self.host = host
         version = None
         if path is not None:
             m = AGENT_DIR_PATTERN.match(path)
@@ -693,18 +697,15 @@ class GuestAgent(object):
         return
 
     def _download(self):
-        package = None
-
         for uri in self.pkg.uris:
-            try:
-                resp = restutil.http_get(uri.uri, chk_proxy=True)
-                if resp.status == restutil.httpclient.OK:
-                    package = resp.read()
-                    fileutil.write_file(self.get_agent_pkg_path(), bytearray(package), asbin=True)
-                    logger.info(u"Agent {0} downloaded from {1}", self.name, uri.uri)
-                    break
-            except restutil.HttpError as e:
-                logger.warn(u"Agent {0} download from {1} failed", self.name, uri.uri)
+            if self._fetch(uri.uri):
+                break
+            else:
+                if self.host is not None:
+                    logger.info("Download unsuccessful, falling back to host plugin")
+                    uri, headers = self.host.get_artifact_request(uri.uri, self.host.manifest_uri)
+                    if self._fetch(uri, headers=headers):
+                        break
 
         if not os.path.isfile(self.get_agent_pkg_path()):
             msg = u"Unable to download Agent {0} from any URI".format(self.name)
@@ -717,6 +718,23 @@ class GuestAgent(object):
             raise UpdateError(msg)
         return
 
+    def _fetch(self, uri, headers=None):
+        package = None
+        try:
+            resp = restutil.http_get(uri, chk_proxy=True, headers=headers)
+            if resp.status == restutil.httpclient.OK:
+                package = resp.read()
+                fileutil.write_file(self.get_agent_pkg_path(),
+                                    bytearray(package),
+                                    asbin=True)
+                logger.info(u"Agent {0} downloaded from {1}", self.name, uri)
+        except restutil.HttpError as http_error:
+            logger.verbose(u"Agent {0} download from {1} failed [{2}]",
+                        self.name,
+                        uri,
+                        http_error)
+        return package is not None
+
     def _load_error(self):
         try:
             if self.error is None:


Test patch:
diff --git a/tests/data/wire/ext_conf.xml b/tests/data/wire/ext_conf.xml
index 0b7c528d..508fb982 100644
--- a/tests/data/wire/ext_conf.xml
+++ b/tests/data/wire/ext_conf.xml
@@ -22,5 +22,5 @@
     <RuntimeSettings seqNo="0">{"runtimeSettings":[{"handlerSettings":{"protectedSettingsCertThumbprint":"4037FBF5F1F3014F99B5D6C7799E9B20E6871CB3","protectedSettings":"MIICWgYJK","publicSettings":{"foo":"bar"}}}]}</RuntimeSettings>
   </Plugin>
 </PluginSettings>
-<StatusUploadBlob>https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions>
+<StatusUploadBlob statusBlobType="BlockBlob">https://yuezhatest.blob.core.windows.net/vhds/test-cs12.test-cs12.test-cs12.status?sr=b&amp;sp=rw&amp;se=9999-01-01&amp;sk=key1&amp;sv=2014-02-14&amp;sig=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D</StatusUploadBlob></Extensions>
 
diff --git a/tests/ga/test_extension.py b/tests/ga/test_extension.py
index 346d94c0..1df0a043 100644
--- a/tests/ga/test_extension.py
+++ b/tests/ga/test_extension.py
@@ -290,7 +290,7 @@ class TestExtension(AgentTestCase):
         test_data = WireProtocolData(DATA_FILE)
         exthandlers_handler, protocol = self._create_mock(test_data, *args)
         exthandlers_handler.ext_handlers, exthandlers_handler.last_etag = protocol.get_ext_handlers()
-        protocol.get_in_vm_artifacts_profile = MagicMock()
+        protocol.get_artifacts_profile = MagicMock()
         exthandlers_handler.protocol = protocol
 
         # Disable extension handling blocking
@@ -315,17 +315,17 @@ class TestExtension(AgentTestCase):
         # enable extension handling blocking
         conf.get_enable_overprovisioning = Mock(return_value=True)
 
-        #Test when is_extension_handlers_handling_on_hold returns False
+        #Test when is_on_hold returns False
         from azurelinuxagent.common.protocol.wire import InVMArtifactsProfile
         mock_in_vm_artifacts_profile = InVMArtifactsProfile(MagicMock())
-        mock_in_vm_artifacts_profile.is_extension_handlers_handling_on_hold = Mock(return_value=False)
-        protocol.get_in_vm_artifacts_profile = Mock(return_value=mock_in_vm_artifacts_profile)
+        mock_in_vm_artifacts_profile.is_on_hold = Mock(return_value=False)
+        protocol.get_artifacts_profile = Mock(return_value=mock_in_vm_artifacts_profile)
         with patch.object(ExtHandlersHandler, 'handle_ext_handler') as patch_handle_ext_handler:
             exthandlers_handler.handle_ext_handlers()
             patch_handle_ext_handler.assert_called_once()
 
         #Test when in_vm_artifacts_profile is not available
-        protocol.get_in_vm_artifacts_profile = Mock(return_value=None)
+        protocol.get_artifacts_profile = Mock(return_value=None)
         with patch.object(ExtHandlersHandler, 'handle_ext_handler') as patch_handle_ext_handler:
             exthandlers_handler.handle_ext_handlers()
             patch_handle_ext_handler.assert_called_once()
diff --git a/tests/ga/test_update.py b/tests/ga/test_update.py
index 3c2d8502..a431a9b6 100644
--- a/tests/ga/test_update.py
+++ b/tests/ga/test_update.py
@@ -535,6 +535,34 @@ class TestGuestAgent(UpdateTestCase):
         self.assertFalse(agent.is_downloaded)
         return
 
+    @patch("azurelinuxagent.ga.update.GuestAgent._ensure_downloaded")
+    @patch("azurelinuxagent.ga.update.restutil.http_get")
+    def test_download_fallback(self, mock_http_get, mock_ensure):
+        self.remove_agents()
+        self.assertFalse(os.path.isdir(self.agent_path))
+
+        mock_http_get.return_value = ResponseMock(
+            status=restutil.httpclient.SERVICE_UNAVAILABLE)
+
+        ext_uri = 'ext_uri'
+        host_uri = 'host_uri'
+        mock_host = HostPluginProtocol(host_uri,
+                                       'container_id',
+                                       'role_config')
+
+        pkg = ExtHandlerPackage(version=str(get_agent_version()))
+        pkg.uris.append(ExtHandlerPackageUri(uri=ext_uri))
+        agent = GuestAgent(pkg=pkg)
+        agent.host = mock_host
+
+        with patch.object(HostPluginProtocol,
+                          "get_artifact_request",
+                          return_value=[host_uri, {}]):
+            self.assertRaises(UpdateError, agent._download)
+            self.assertEqual(mock_http_get.call_count, 2)
+            self.assertEqual(mock_http_get.call_args_list[0][0][0], ext_uri)
+            self.assertEqual(mock_http_get.call_args_list[1][0][0], host_uri)
+
     @patch("azurelinuxagent.ga.update.restutil.http_get")
     def test_ensure_downloaded(self, mock_http_get):
         self.remove_agents()
@@ -1299,8 +1327,9 @@ class ChildMock(Mock):
 
 
 class ProtocolMock(object):
-    def __init__(self, family="TestAgent", etag=42, versions=None):
+    def __init__(self, family="TestAgent", etag=42, versions=None, client=None):
         self.family = family
+        self.client = client
         self.etag = etag
         self.versions = versions if versions is not None else []
         self.create_manifests()
diff --git a/tests/protocol/test_hostplugin.py b/tests/protocol/test_hostplugin.py
index ceabd2de..3b990502 100644
--- a/tests/protocol/test_hostplugin.py
+++ b/tests/protocol/test_hostplugin.py
@@ -25,6 +25,7 @@ from tests.tools import *
 
 wireserver_url = "168.63.129.16"
 sas_url = "http://sas_url"
+testtype = 'BlockBlob'
 api_versions = '["2015-09-01"]'
 
 
@@ -55,7 +56,7 @@ class TestHostPlugin(AgentTestCase):
         exp_url = 'http://{0}:32526/status'.format(wireserver_url)
         exp_data = '{"content": "eyJkdW1teSI6ICJkYXRhIn0=", "headers": [{"headerName": ' \
                    '"x-ms-version", "headerValue": "2014-02-14"}, ' \
-                   '{"headerName": "x-ms-blob-type", "headerValue": null}], ' \
+                   '{"headerName": "x-ms-blob-type", "headerValue": "BlockBlob"}], ' \
                    '"requestUri": "http://sas_url"}'
         test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state)
 
@@ -68,7 +69,7 @@ class TestHostPlugin(AgentTestCase):
             blob.data = '{"dummy": "data"}'
             with patch.object(plugin, 'get_api_versions') as patch_api:
                 patch_api.return_value = API_VERSION
-                plugin.put_vm_status(blob, sas_url)
+                plugin.put_vm_status(blob, sas_url, testtype)
                 self.assertTrue(patch_http.call_count == 1)
                 self.assertTrue(patch_http.call_args[0][0] == exp_method)
                 self.assertTrue(patch_http.call_args[0][1] == exp_url)
@@ -77,7 +78,7 @@ class TestHostPlugin(AgentTestCase):
                 # Assert headers
                 headers = patch_http.call_args[1]['headers']
                 self.assertEqual(headers['x-ms-containerid'], test_goal_state.container_id)
-                self.assertEqual(headers['x-ms-host-config-name'], test_goal_state.role_instance_config_name)
+                self.assertEqual(headers['x-ms-host-config-name'], test_goal_state.role_config_name)
 
     def test_no_fallback(self):
         """
@@ -102,7 +103,7 @@ class TestHostPlugin(AgentTestCase):
         expected_headers = {'x-ms-version': '2015-09-01',
                             "Content-type": "application/json",
                             "x-ms-containerid": test_goal_state.container_id,
-                            "x-ms-host-config-name": test_goal_state.role_instance_config_name}
+                            "x-ms-host-config-name": test_goal_state.role_config_name}
         expected_content = '{"content": "eyJkdW1teSI6ICJkYXRhIn0=", ' \
                            '"headers": [{"headerName": "x-ms-version", ' \
                            '"headerValue": "2014-02-14"}, ' \
@@ -112,7 +113,7 @@ class TestHostPlugin(AgentTestCase):
 
         host_client = wire.HostPluginProtocol(wireserver_url,
                                               test_goal_state.container_id,
-                                              test_goal_state.role_instance_config_name)
+                                              test_goal_state.role_config_name)
         self.assertFalse(host_client.is_initialized)
         self.assertTrue(host_client.api_versions is None)
         status_blob = wire.StatusBlob(None)
@@ -129,25 +130,25 @@ class TestHostPlugin(AgentTestCase):
                 self.assertFalse(host_client.api_versions is None)
                 self.assertTrue(patch_put.call_count == 1)
                 self.assertTrue(patch_put.call_args[0][0] == expected_url)
-                self.assertTrue(patch_put.call_args[0][1] == expected_content)
-                self.assertTrue(patch_put.call_args[0][2] == expected_headers)
+                self.assertTrue(patch_put.call_args[1]['data'] == expected_content)
+                self.assertTrue(patch_put.call_args[1]['headers'] == expected_headers)
 
     def test_validate_get_extension_artifacts(self):
         test_goal_state = wire.GoalState(WireProtocolData(DATA_FILE).goal_state)
         expected_url = hostplugin.URI_FORMAT_GET_EXTENSION_ARTIFACT.format(wireserver_url, hostplugin.HOST_PLUGIN_PORT)
         expected_headers = {'x-ms-version': '2015-09-01',
                             "x-ms-containerid": test_goal_state.container_id,
-                            "x-ms-host-config-name": test_goal_state.role_instance_config_name,
+                            "x-ms-host-config-name": test_goal_state.role_config_name,
                             "x-ms-artifact-location": sas_url}
 
         host_client = wire.HostPluginProtocol(wireserver_url,
                                               test_goal_state.container_id,
-                                              test_goal_state.role_instance_config_name)
+                                              test_goal_state.role_config_name)
         self.assertFalse(host_client.is_initialized)
         self.assertTrue(host_client.api_versions is None)
 
         with patch.object(wire.HostPluginProtocol, "get_api_versions", return_value=api_versions) as patch_get:
-            actual_url, actual_headers = host_client.get_extension_artifact_url_and_headers(sas_url)
+            actual_url, actual_headers = host_client.get_artifact_request(sas_url)
             self.assertTrue(host_client.is_initialized)
             self.assertFalse(host_client.api_versions is None)
             self.assertEqual(expected_url, actual_url)
diff --git a/tests/protocol/test_wire.py b/tests/protocol/test_wire.py
index bc2f4964..8c9cc02f 100644
--- a/tests/protocol/test_wire.py
+++ b/tests/protocol/test_wire.py
@@ -20,6 +20,7 @@ from tests.protocol.mockwiredata import *
 
 data_with_bom = b'\xef\xbb\xbfhehe'
 testurl = 'http://foo'
+testtype = 'BlockBlob'
 wireserver_url = '168.63.129.16'
 
 @patch("time.sleep")
@@ -100,6 +101,18 @@ class TestWireProtocolGetters(AgentTestCase):
             for c in http_patch.call_args_list:
                 self.assertTrue(c[-1]['chk_proxy'] == True)
 
+    def test_status_blob_parsing(self, *args):
+        wire_protocol_client = WireProtocol(wireserver_url).client
+        wire_protocol_client.ext_conf = ExtensionsConfig(WireProtocolData(DATA_FILE).ext_conf)
+        self.assertEqual(wire_protocol_client.ext_conf.status_upload_blob,
+                         u'https://yuezhatest.blob.core.windows.net/vhds/test'
+                         u'-cs12.test-cs12.test-cs12.status?sr=b&sp=rw&se'
+                         u'=9999-01-01&sk=key1&sv=2014-02-14&sig'
+                         u'=hfRh7gzUE7sUtYwke78IOlZOrTRCYvkec4hGZ9zZzXo%3D')
+        self.assertEqual(wire_protocol_client.ext_conf.status_upload_blob_type,
+                         u'BlockBlob')
+        pass
+
     def test_get_host_ga_plugin(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         goal_state = GoalState(WireProtocolData(DATA_FILE).goal_state)
@@ -107,9 +120,33 @@ class TestWireProtocolGetters(AgentTestCase):
         with patch.object(WireClient, "get_goal_state", return_value = goal_state) as patch_get_goal_state:
             host_plugin = wire_protocol_client.get_host_plugin()
             self.assertEqual(goal_state.container_id, host_plugin.container_id)
-            self.assertEqual(goal_state.role_instance_config_name, host_plugin.role_config_name)
+            self.assertEqual(goal_state.role_config_name, host_plugin.role_config_name)
             patch_get_goal_state.assert_called_once()
 
+    def test_download_ext_handler_pkg_fallback(self, *args):
+        ext_uri = 'extension_uri'
+        host_uri = 'host_uri'
+        mock_host = HostPluginProtocol(host_uri, 'container_id', 'role_config')
+        with patch.object(restutil,
+                          "http_request",
+                          side_effect=IOError) as patch_http:
+            with patch.object(WireClient,
+                              "get_host_plugin",
+                              return_value=mock_host):
+                with patch.object(HostPluginProtocol,
+                                  "get_artifact_request",
+                                  return_value=[host_uri, {}]) as patch_request:
+
+                    WireProtocol(wireserver_url).download_ext_handler_pkg(ext_uri)
+
+                    self.assertEqual(patch_http.call_count, 2)
+                    self.assertEqual(patch_request.call_count, 1)
+
+                    self.assertEqual(patch_http.call_args_list[0][0][1],
+                                     ext_uri)
+                    self.assertEqual(patch_http.call_args_list[1][0][1],
+                                     host_uri)
+
     def test_upload_status_blob_default(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
@@ -128,6 +165,7 @@ class TestWireProtocolGetters(AgentTestCase):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
         wire_protocol_client.ext_conf.status_upload_blob = testurl
+        wire_protocol_client.ext_conf.status_upload_blob_type = testtype
         goal_state = GoalState(WireProtocolData(DATA_FILE).goal_state)
 
         with patch.object(HostPluginProtocol, "put_vm_status") as patch_host_ga_plugin_upload:
@@ -137,43 +175,43 @@ class TestWireProtocolGetters(AgentTestCase):
 
                 patch_default_upload.assert_called_once_with(testurl)
                 wire_protocol_client.get_goal_state.assert_called_once()
-                patch_host_ga_plugin_upload.assert_called_once_with(wire_protocol_client.status_blob, testurl)
+                patch_host_ga_plugin_upload.assert_called_once_with(wire_protocol_client.status_blob, testurl, testtype)
 
     def test_get_in_vm_artifacts_profile_blob_not_available(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
 
-        # Test when in_vm_artifacts_profile_blob is null/None
-        self.assertEqual(None, wire_protocol_client.get_in_vm_artifacts_profile())
+        # Test when artifacts_profile_blob is null/None
+        self.assertEqual(None, wire_protocol_client.get_artifacts_profile())
 
-        #Test when in_vm_artifacts_profile_blob is whitespace
-        wire_protocol_client.ext_conf.in_vm_artifacts_profile_blob = "  "
-        self.assertEqual(None, wire_protocol_client.get_in_vm_artifacts_profile())
+        #Test when artifacts_profile_blob is whitespace
+        wire_protocol_client.ext_conf.artifacts_profile_blob = "  "
+        self.assertEqual(None, wire_protocol_client.get_artifacts_profile())
 
     def test_get_in_vm_artifacts_profile_response_body_not_valid(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
-        wire_protocol_client.ext_conf.in_vm_artifacts_profile_blob = testurl
+        wire_protocol_client.ext_conf.artifacts_profile_blob = testurl
         goal_state = GoalState(WireProtocolData(DATA_FILE).goal_state)
         wire_protocol_client.get_goal_state = Mock(return_value=goal_state)
 
-        with patch.object(HostPluginProtocol, "get_extension_artifact_url_and_headers",
+        with patch.object(HostPluginProtocol, "get_artifact_request",
                           return_value = ['dummy_url', {}]) as host_plugin_get_artifact_url_and_headers:
             #Test when response body is None
             wire_protocol_client.call_storage_service = Mock(return_value=MockResponse(None, 200))
-            in_vm_artifacts_profile = wire_protocol_client.get_in_vm_artifacts_profile()
+            in_vm_artifacts_profile = wire_protocol_client.get_artifacts_profile()
             self.assertTrue(in_vm_artifacts_profile is None)
 
             #Test when response body is None
             wire_protocol_client.call_storage_service = Mock(return_value=MockResponse('   '.encode('utf-8'), 200))
-            in_vm_artifacts_profile = wire_protocol_client.get_in_vm_artifacts_profile()
+            in_vm_artifacts_profile = wire_protocol_client.get_artifacts_profile()
             self.assertTrue(in_vm_artifacts_profile is None)
 
             #Test when response body is None
             wire_protocol_client.call_storage_service = Mock(return_value=MockResponse('{ }'.encode('utf-8'), 200))
-            in_vm_artifacts_profile = wire_protocol_client.get_in_vm_artifacts_profile()
+            in_vm_artifacts_profile = wire_protocol_client.get_artifacts_profile()
             self.assertEqual(dict(), in_vm_artifacts_profile.__dict__,
-                             'If in_vm_artifacts_profile_blob has empty json dictionary, in_vm_artifacts_profile '
+                             'If artifacts_profile_blob has empty json dictionary, in_vm_artifacts_profile '
                              'should contain nothing')
 
             host_plugin_get_artifact_url_and_headers.assert_called_with(testurl)
@@ -182,30 +220,55 @@ class TestWireProtocolGetters(AgentTestCase):
     def test_get_in_vm_artifacts_profile_default(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
-        wire_protocol_client.ext_conf.in_vm_artifacts_profile_blob = testurl
+        wire_protocol_client.ext_conf.artifacts_profile_blob = testurl
         goal_state = GoalState(WireProtocolData(DATA_FILE).goal_state)
         wire_protocol_client.get_goal_state = Mock(return_value=goal_state)
 
         wire_protocol_client.call_storage_service = Mock(return_value=MockResponse('{"onHold": "true"}'.encode('utf-8'), 200))
-        in_vm_artifacts_profile = wire_protocol_client.get_in_vm_artifacts_profile()
+        in_vm_artifacts_profile = wire_protocol_client.get_artifacts_profile()
         self.assertEqual(dict(onHold='true'), in_vm_artifacts_profile.__dict__)
-        self.assertTrue(in_vm_artifacts_profile.is_extension_handlers_handling_on_hold())
+        self.assertTrue(in_vm_artifacts_profile.is_on_hold())
+
+    @patch("time.sleep")
+    def test_fetch_manifest_fallback(self, patch_sleep,  *args):
+        uri1 = ExtHandlerVersionUri()
+        uri1.uri = 'ext_uri'
+        uris = DataContractList(ExtHandlerVersionUri)
+        uris.append(uri1)
+        host_uri = 'host_uri'
+        mock_host = HostPluginProtocol(host_uri,
+                                       'container_id',
+                                       'role_config')
+        client = WireProtocol(wireserver_url).client
+        with patch.object(WireClient,
+                          "fetch",
+                          return_value=None) as patch_fetch:
+            with patch.object(WireClient,
+                              "get_host_plugin",
+                              return_value=mock_host):
+                with patch.object(HostPluginProtocol,
+                                  "get_artifact_request",
+                                  return_value=[host_uri, {}]):
+                    self.assertRaises(ProtocolError, client.fetch_manifest, uris)
+                    self.assertEqual(patch_fetch.call_count, 2)
+                    self.assertEqual(patch_fetch.call_args_list[0][0][0], uri1.uri)
+                    self.assertEqual(patch_fetch.call_args_list[1][0][0], host_uri)
 
     def test_get_in_vm_artifacts_profile_host_ga_plugin(self, *args):
         wire_protocol_client = WireProtocol(wireserver_url).client
         wire_protocol_client.ext_conf = ExtensionsConfig(None)
-        wire_protocol_client.ext_conf.in_vm_artifacts_profile_blob = testurl
+        wire_protocol_client.ext_conf.artifacts_profile_blob = testurl
         goal_state = GoalState(WireProtocolData(DATA_FILE).goal_state)
         wire_protocol_client.get_goal_state = Mock(return_value=goal_state)
-
-        wire_protocol_client._get_in_vm_artifacts_profile = Mock(side_effect=[None, '{"onHold": "true"}'.encode('utf-8')])
-
-        with patch.object(HostPluginProtocol, "get_extension_artifact_url_and_headers",
-                          return_value = ['dummy_url', {}]) as host_plugin_get_artifact_url_and_headers:
-            in_vm_artifacts_profile = wire_protocol_client.get_in_vm_artifacts_profile()
+        wire_protocol_client.fetch = Mock(side_effect=[None, '{"onHold": "true"}'.encode('utf-8')])
+        with patch.object(HostPluginProtocol,
+                          "get_artifact_request",
+                          return_value=['dummy_url', {}]) as artifact_request:
+            in_vm_artifacts_profile = wire_protocol_client.get_artifacts_profile()
+            self.assertTrue(in_vm_artifacts_profile is not None)
             self.assertEqual(dict(onHold='true'), in_vm_artifacts_profile.__dict__)
-            self.assertTrue(in_vm_artifacts_profile.is_extension_handlers_handling_on_hold())
-            host_plugin_get_artifact_url_and_headers.assert_called_once_with(testurl)
+            self.assertTrue(in_vm_artifacts_profile.is_on_hold())
+            artifact_request.assert_called_once_with(testurl)
 
 
 class MockResponse: