def negotiate_version(self, conn, resp): """Negotiate the server version Assumption: Called after receiving a 406 error when doing a request. param conn: A connection object param resp: The response object from http request """ if self.api_version_select_state not in API_VERSION_SELECTED_STATES: raise RuntimeError( 'Error: self.api_version_select_state should be one of the ' 'values in: "%(valid)s" but had the value: "%(value)s"' % {'valid': ', '.join(API_VERSION_SELECTED_STATES), 'value': self.api_version_select_state}) min_ver, max_ver = self._parse_version_headers(resp) # NOTE: servers before commit 32fb6e99 did not return version headers # on error, so we need to perform a GET to determine # the supported version range if not max_ver: LOG.debug('No version header in response, requesting from server') if self.os_ironic_api_version: base_version = ("/v%s" % str(self.os_ironic_api_version).split('.')[0]) else: base_version = API_VERSION resp = self._make_simple_request(conn, 'GET', base_version) min_ver, max_ver = self._parse_version_headers(resp) # If the user requested an explicit version or we have negotiated a # version and still failing then error now. The server could # support the version requested but the requested operation may not # be supported by the requested version. if self.api_version_select_state == 'user': raise exc.UnsupportedVersion(textwrap.fill( "Requested API version %(req)s is not supported by the " "server or the requested operation is not supported by the " "requested version. Supported version range is %(min)s to " "%(max)s" % {'req': self.os_ironic_api_version, 'min': min_ver, 'max': max_ver})) if self.api_version_select_state == 'negotiated': raise exc.UnsupportedVersion(textwrap.fill( "No API version was specified and the requested operation was " "not supported by the client's negotiated API version " "%(req)s. Supported version range is: %(min)s to %(max)s" % {'req': self.os_ironic_api_version, 'min': min_ver, 'max': max_ver})) # TODO(deva): cache the negotiated version for this server negotiated_ver = min(self.os_ironic_api_version, max_ver) if negotiated_ver < min_ver: negotiated_ver = min_ver # server handles microversions, but doesn't support # the requested version, so try a negotiated version self.api_version_select_state = 'negotiated' self.os_ironic_api_version = negotiated_ver LOG.debug('Negotiated API version is %s', negotiated_ver) return negotiated_ver
def negotiate_version(self, conn, resp): """Negotiate the server version Assumption: Called after receiving a 406 error when doing a request. :param conn: A connection object :param resp: The response object from http request """ def _query_server(conn): if (self.os_ironic_api_version and not isinstance(self.os_ironic_api_version, list) and self.os_ironic_api_version != 'latest'): base_version = ("/v%s" % str(self.os_ironic_api_version).split('.')[0]) else: base_version = API_VERSION return self._make_simple_request(conn, 'GET', base_version) version_overridden = False if (resp and hasattr(resp, 'request') and hasattr(resp.request, 'headers')): orig_hdr = resp.request.headers # Get the version of the client's last request and fallback # to the default for things like unit tests to not cause # migraines. req_api_ver = orig_hdr.get('X-OpenStack-Ironic-API-Version', self.os_ironic_api_version) else: req_api_ver = self.os_ironic_api_version if (resp and req_api_ver != self.os_ironic_api_version and self.api_version_select_state == 'negotiated'): # If we have a non-standard api version on the request, # but we think we've negotiated, then the call was overriden. # We should report the error with the called version requested_version = req_api_ver # And then we shouldn't save the newly negotiated # version of this negotiation because we have been # overridden a request. version_overridden = True else: requested_version = self.os_ironic_api_version if not resp: resp = _query_server(conn) if self.api_version_select_state not in API_VERSION_SELECTED_STATES: raise RuntimeError( _('Error: self.api_version_select_state should be one of the ' 'values in: "%(valid)s" but had the value: "%(value)s"') % { 'valid': ', '.join(API_VERSION_SELECTED_STATES), 'value': self.api_version_select_state }) min_ver, max_ver = self._parse_version_headers(resp) # NOTE: servers before commit 32fb6e99 did not return version headers # on error, so we need to perform a GET to determine # the supported version range if not max_ver: LOG.debug('No version header in response, requesting from server') resp = _query_server(conn) min_ver, max_ver = self._parse_version_headers(resp) # Reset the maximum version that we permit if StrictVersion(max_ver) > StrictVersion(LATEST_VERSION): LOG.debug( "Remote API version %(max_ver)s is greater than the " "version supported by ironicclient. Maximum available " "version is %(client_ver)s", { 'max_ver': max_ver, 'client_ver': LATEST_VERSION }) max_ver = LATEST_VERSION # If the user requested an explicit version or we have negotiated a # version and still failing then error now. The server could # support the version requested but the requested operation may not # be supported by the requested version. # TODO(TheJulia): We should break this method into several parts, # such as a sanity check/error method. if ((self.api_version_select_state == 'user' and not self._must_negotiate_version()) or (self.api_version_select_state == 'negotiated' and version_overridden)): raise exc.UnsupportedVersion( textwrap.fill( _("Requested API version %(req)s is not supported by the " "server, client, or the requested operation is not " "supported by the requested version. " "Supported version range is %(min)s to " "%(max)s") % { 'req': requested_version, 'min': min_ver, 'max': max_ver })) if (self.api_version_select_state == 'negotiated'): raise exc.UnsupportedVersion( textwrap.fill( _("No API version was specified or the requested operation " "was not supported by the client's negotiated API version " "%(req)s. Supported version range is: %(min)s to %(max)s" ) % { 'req': requested_version, 'min': min_ver, 'max': max_ver })) if isinstance(requested_version, six.string_types): if requested_version == 'latest': negotiated_ver = max_ver else: negotiated_ver = str( min(StrictVersion(requested_version), StrictVersion(max_ver))) elif isinstance(requested_version, list): if 'latest' in requested_version: raise ValueError( textwrap.fill( _("The 'latest' API version can not be requested " "in a list of versions. Please explicitly request " "'latest' or request only versios between " "%(min)s to %(max)s") % { 'min': min_ver, 'max': max_ver })) versions = [] for version in requested_version: if min_ver <= StrictVersion(version) <= max_ver: versions.append(StrictVersion(version)) if versions: negotiated_ver = str(max(versions)) else: raise exc.UnsupportedVersion( textwrap.fill( _("Requested API version specified and the requested " "operation was not supported by the client's " "requested API version %(req)s. Supported " "version range is: %(min)s to %(max)s") % { 'req': requested_version, 'min': min_ver, 'max': max_ver })) else: raise ValueError( textwrap.fill( _("Requested API version %(req)s type is unsupported. " "Valid types are Strings such as '1.1', 'latest' " "or a list of string values representing API versions.") % {'req': requested_version})) if StrictVersion(negotiated_ver) < StrictVersion(min_ver): negotiated_ver = min_ver # server handles microversions, but doesn't support # the requested version, so try a negotiated version self.api_version_select_state = 'negotiated' self.os_ironic_api_version = negotiated_ver LOG.debug('Negotiated API version is %s', negotiated_ver) # Cache the negotiated version for this server # TODO(vdrok): get rid of self.endpoint attribute in Stein endpoint_override = (getattr(self, 'endpoint_override', None) or getattr(self, 'endpoint', None)) host, port = get_server(endpoint_override) filecache.save_data(host=host, port=port, data=negotiated_ver) return negotiated_ver