def call_inspector(data, failures): """Post data to inspector.""" data['error'] = failures.get_error() LOG.info('posting collected data to %s', CONF.inspection_callback_url) LOG.debug('collected data: %s', {k: v for k, v in data.items() if k not in _NO_LOGGING_FIELDS}) encoder = encoding.RESTJSONEncoder() data = encoder.encode(data) verify, cert = utils.get_ssl_client_options(CONF) @tenacity.retry(retry=tenacity.retry_if_exception_type( requests.exceptions.ConnectionError), stop=tenacity.stop_after_attempt(_RETRY_ATTEMPTS), wait=tenacity.wait_fixed(_RETRY_WAIT), reraise=True) def _post_to_inspector(): return requests.post(CONF.inspection_callback_url, data=data, verify=verify, cert=cert) resp = _post_to_inspector() if resp.status_code >= 400: LOG.error('inspector %s error %d: %s, proceeding with lookup', CONF.inspection_callback_url, resp.status_code, resp.content.decode('utf-8')) return return resp.json()
def _download_with_proxy(image_info, url, image_id): """Opens a download stream for the given URL. :param image_info: Image information dictionary. :param url: The URL string to request the image from. :param image_id: Image ID or URL for logging. :raises: ImageDownloadError if the download stream was not started properly. """ no_proxy = image_info.get('no_proxy') if no_proxy: os.environ['no_proxy'] = no_proxy proxies = image_info.get('proxies', {}) verify, cert = utils.get_ssl_client_options(CONF) resp = requests.get(url, stream=True, proxies=proxies, verify=verify, cert=cert) if resp.status_code != 200: msg = ('Received status code {} from {}, expected 200. Response ' 'body: {}').format(resp.status_code, url, resp.text) raise errors.ImageDownloadError(image_id, msg) return resp
def config_raid(data): # server_type = raid_utils.get_type_by_properties(data) # if server_type not in raid_utils.VALID_TYPE: # LOG.error('Unknown server type, and can not configure RAID default.') # return sn = data.get('inventory').get('system_vendor').serial_number verify, cert = utils.get_ssl_client_options(CONF) # retrieve raid configuration info from arobot server raid_get_url = CONF.arobot_callback_url + ('/raid_conf/%s' % sn) resp = requests.get(raid_get_url, cert=cert, verify=verify) config = resp.json() if resp.status_code >= 400: LOG.error('error fetching raid configuration') return if config is None: LOG.error('configuration should never be None') return if config['is_ok']: LOG.info('sn: %s has been already been configured. Pass' % config.get('sn', '')) return # start configuring if raid has not been configured properly LOG.info("Start configuring RAID") raid_config = raid_utils.config_raid() # call back and save raid configurations raid_post_url = CONF.arobot_callback_url + '/raid_conf' json = { 'sn': sn, 'config': raid_config } # update contents of inventory.disks # because hardware.GenericHardwareManager will always # be loaded before raid properly configured data['inventory']['disks'] = hardware.GenericHardwareManager().list_block_devices() # call back to ironic-inspector LOG.info("Posting RAID configuration back to %s", raid_post_url) while True: try: resp = requests.post(raid_post_url, json=json, cert=cert, verify=verify) if resp.status_code >= 400: LOG.error("arobot raid error %d: %s", resp.status_code, resp.content.decode('utf-8')) except Exception as e: LOG.error(e) time.sleep(5) continue LOG.info('request ok') break
def _download_with_proxy(image_info, url, image_id): """Opens a download stream for the given URL. :param image_info: Image information dictionary. :param url: The URL string to request the image from. :param image_id: Image ID or URL for logging. :raises: ImageDownloadError if the download stream was not started properly. """ no_proxy = image_info.get('no_proxy') if no_proxy: os.environ['no_proxy'] = no_proxy proxies = image_info.get('proxies', {}) verify, cert = utils.get_ssl_client_options(CONF) resp = None for attempt in range(CONF.image_download_connection_retries + 1): try: # NOTE(TheJulia) The get request below does the following: # * Performs dns lookups, if necessary # * Opens the TCP socket to the remote host # * Negotiates TLS, if applicable # * Checks cert validity, if necessary, which may be # more tcp socket connections. # * Issues the get request and then returns back to the caller the # handler which is used to stream the data into the agent. # While this all may be at risk of transitory interrupts, most of # these socket will have timeouts applied to them, although not # exactly just as the timeout value exists. The risk in transitory # failure is more so once we've started the download and we are # processing the incoming data. resp = requests.get(url, stream=True, proxies=proxies, verify=verify, cert=cert, timeout=CONF.image_download_connection_timeout) if resp.status_code != 200: msg = ('Received status code {} from {}, expected 200. ' 'Response body: {}').format(resp.status_code, url, resp.text) raise errors.ImageDownloadError(image_id, msg) except (errors.ImageDownloadError, requests.RequestException) as e: if (attempt == CONF.image_download_connection_retries # NOTE(dtantsur): do not retry 4xx status codes or (resp and resp.status_code < 500)): raise else: LOG.warning('Unable to connect to %s, retrying. Error: %s', url, e) time.sleep(CONF.image_download_connection_retry_interval) else: break return resp
def test_get_ssl_client_options(self): # defaults conf = mock.Mock(insecure=False, cafile=None, keyfile=None, certfile=None) self.assertEqual((True, None), utils.get_ssl_client_options(conf)) # insecure=True overrides cafile conf = mock.Mock(insecure=True, cafile='spam', keyfile=None, certfile=None) self.assertEqual((False, None), utils.get_ssl_client_options(conf)) # cafile returned as verify when not insecure conf = mock.Mock(insecure=False, cafile='spam', keyfile=None, certfile=None) self.assertEqual(('spam', None), utils.get_ssl_client_options(conf)) # only both certfile and keyfile produce non-None result conf = mock.Mock(insecure=False, cafile=None, keyfile=None, certfile='ham') self.assertEqual((True, None), utils.get_ssl_client_options(conf)) conf = mock.Mock(insecure=False, cafile=None, keyfile='ham', certfile=None) self.assertEqual((True, None), utils.get_ssl_client_options(conf)) conf = mock.Mock(insecure=False, cafile=None, keyfile='spam', certfile='ham') self.assertEqual((True, ('ham', 'spam')), utils.get_ssl_client_options(conf))
def tell_arobot_ipmi(sn): LOG.info('Tell arobot ipmi config successfully %s', CONF.arobot_callback_url) verify, cert = utils.get_ssl_client_options(CONF) # arobot_callback_url like http://172.23.4.111:9876/v1 ipmi_get_url = CONF.arobot_callback_url + '/ipmi_conf/' + sn while True: try: resp = requests.put(ipmi_get_url, verify=verify, cert=cert) except Exception as e: LOG.info('Got exception %s', e) continue else: LOG.info('Put ok') break
def _request(self, method, path, data=None, headers=None, **kwargs): request_url = '{api_url}{path}'.format(api_url=self.api_url, path=path) if data is not None: data = self.encoder.encode(data) headers = headers or {} headers.update({ 'Content-Type': 'application/json', 'Accept': 'application/json', }) verify, cert = utils.get_ssl_client_options(CONF) return self.session.request(method, request_url, headers=headers, data=data, verify=verify, cert=cert, **kwargs)
def call_inspector(data, failures): """Post data to inspector.""" data['error'] = failures.get_error() LOG.info('posting collected data to %s', CONF.inspection_callback_url) LOG.debug('collected data: %s', {k: v for k, v in data.items() if k not in _NO_LOGGING_FIELDS}) encoder = encoding.RESTJSONEncoder() data = encoder.encode(data) verify, cert = utils.get_ssl_client_options(CONF) resp = requests.post(CONF.inspection_callback_url, data=data, verify=verify, cert=cert) if resp.status_code >= 400: LOG.error('inspector error %d: %s, proceeding with lookup', resp.status_code, resp.content.decode('utf-8')) return return resp.json()
def call_arobot(sn): LOG.info('Getting ipmi conf from %s', CONF.arobot_callback_url) verify, cert = utils.get_ssl_client_options(CONF) # arobot_callback_url like http://172.23.4.111:9876/v1 ipmi_get_url = CONF.arobot_callback_url + '/ipmi_conf/' + sn resp = requests.get(ipmi_get_url, verify=verify, cert=cert) if resp.status_code >= 400: LOG.error('arobot ipmi error %d: %s', resp.status_code , resp.content.decode('utf-8')) return ret = resp.json() if ret.get('return_value') == 'NotFound' or \ ret.get('return_value') == 'UnknownERR': LOG.warning('arobot api call ok, but no valid ipmi conf. Loop go on!') return elif ret.get('return_value') == 'Success': LOG.warning('arobot api call ok, but already conf OK. Break loop!') return ret
def _download_with_proxy(image_info, url, image_id): """Opens a download stream for the given URL. :param image_info: Image information dictionary. :param url: The URL string to request the image from. :param image_id: Image ID or URL for logging. :raises: ImageDownloadError if the download stream was not started properly. """ no_proxy = image_info.get('no_proxy') if no_proxy: os.environ['no_proxy'] = no_proxy proxies = image_info.get('proxies', {}) verify, cert = utils.get_ssl_client_options(CONF) resp = None for attempt in range(CONF.image_download_connection_retries + 1): try: resp = requests.get(url, stream=True, proxies=proxies, verify=verify, cert=cert, timeout=CONF.image_download_connection_timeout) if resp.status_code != 200: msg = ('Received status code {} from {}, expected 200. ' 'Response body: {}').format(resp.status_code, url, resp.text) raise errors.ImageDownloadError(image_id, msg) except (errors.ImageDownloadError, requests.RequestException) as e: if (attempt == CONF.image_download_connection_retries # NOTE(dtantsur): do not retry 4xx status codes or (resp and resp.status_code < 500)): raise else: LOG.warning('Unable to connect to %s, retrying. Error: %s', url, e) time.sleep(CONF.image_download_connection_retry_interval) else: break return resp
def get_configdrive(configdrive, node_uuid, tempdir=None): """Get the information about size and location of the configdrive. :param configdrive: Base64 encoded Gzipped configdrive content or configdrive HTTP URL. :param node_uuid: Node's uuid. Used for logging. :param tempdir: temporary directory for the temporary configdrive file :raises: InstanceDeployFailure if it can't download or decode the config drive. :returns: A tuple with the size in MiB and path to the uncompressed configdrive file. """ # Check if the configdrive option is a HTTP URL or the content directly is_url = utils.is_http_url(configdrive) if is_url: verify, cert = ipa_utils.get_ssl_client_options(CONF) timeout = CONF.image_download_connection_timeout # TODO(dtantsur): support proxy parameters from instance_info try: resp = requests.get(configdrive, verify=verify, cert=cert, timeout=timeout) except requests.exceptions.RequestException as e: raise exception.InstanceDeployFailure( "Can't download the configdrive content for node %(node)s " "from '%(url)s'. Reason: %(reason)s" % {'node': node_uuid, 'url': configdrive, 'reason': e}) if resp.status_code >= 400: raise exception.InstanceDeployFailure( "Can't download the configdrive content for node %(node)s " "from '%(url)s'. Got status code %(code)s, response " "body %(body)s" % {'node': node_uuid, 'url': configdrive, 'code': resp.status_code, 'body': resp.text}) data = resp.content else: data = configdrive configdrive_file = tempfile.NamedTemporaryFile(delete=False, prefix='configdrive', dir=tempdir) try: data = io.BytesIO(base64.b64decode(data)) except Exception as exc: if isinstance(data, bytes): LOG.debug('Config drive for node %(node)s is not base64 encoded ' '(%(error)s), assuming binary', {'node': node_uuid, 'error': exc}) configdrive_mb = int(math.ceil(len(data) / units.Mi)) configdrive_file.write(data) configdrive_file.close() return (configdrive_mb, configdrive_file.name) else: configdrive_file.close() utils.unlink_without_raise(configdrive_file.name) error_msg = ('Config drive for node %(node)s is not base64 ' 'encoded or the content is malformed. ' '%(cls)s: %(err)s.' % {'node': node_uuid, 'err': exc, 'cls': type(exc).__name__}) if is_url: error_msg += ' Downloaded from "%s".' % configdrive raise exception.InstanceDeployFailure(error_msg) configdrive_mb = 0 with gzip.GzipFile('configdrive', 'rb', fileobj=data) as gunzipped: try: shutil.copyfileobj(gunzipped, configdrive_file) except EnvironmentError as e: # Delete the created file utils.unlink_without_raise(configdrive_file.name) raise exception.InstanceDeployFailure( 'Encountered error while decompressing and writing ' 'config drive for node %(node)s. Error: %(exc)s' % {'node': node_uuid, 'exc': e}) else: # Get the file size and convert to MiB configdrive_file.seek(0, os.SEEK_END) bytes_ = configdrive_file.tell() configdrive_mb = int(math.ceil(float(bytes_) / units.Mi)) finally: configdrive_file.close() return (configdrive_mb, configdrive_file.name)