diff options
Diffstat (limited to 'ironic/common')
-rw-r--r-- | ironic/common/image_service.py | 35 | ||||
-rw-r--r-- | ironic/common/kickstart_utils.py | 4 | ||||
-rw-r--r-- | ironic/common/molds.py | 6 | ||||
-rw-r--r-- | ironic/common/rpc_service.py | 18 |
4 files changed, 38 insertions, 25 deletions
diff --git a/ironic/common/image_service.py b/ironic/common/image_service.py index 5280ee6bc..4b95b5152 100644 --- a/ironic/common/image_service.py +++ b/ironic/common/image_service.py @@ -34,10 +34,6 @@ from ironic.common import utils from ironic.conf import CONF IMAGE_CHUNK_SIZE = 1024 * 1024 # 1mb -# NOTE(kaifeng) Image will be truncated to 2GiB by sendfile, -# we use a large chunk size here for a better performance -# while keep the chunk size less than the size limit. -SENDFILE_CHUNK_SIZE = 1024 * 1024 * 1024 # 1Gb LOG = log.getLogger(__name__) @@ -264,26 +260,23 @@ class FileImageService(BaseImageService): """ source_image_path = self.validate_href(image_href) dest_image_path = image_file.name - local_device = os.stat(dest_image_path).st_dev try: - # We should have read and write access to source file to create - # hard link to it. - if (local_device == os.stat(source_image_path).st_dev - and os.access(source_image_path, os.R_OK | os.W_OK)): - image_file.close() - os.remove(dest_image_path) + image_file.close() + os.remove(dest_image_path) + + try: os.link(source_image_path, dest_image_path) + except OSError as exc: + LOG.debug('Could not create a link from %(src)s to %(dest)s, ' + 'will copy the content instead. Error: %(exc)s.', + {'src': source_image_path, 'dest': dest_image_path, + 'exc': exc}) else: - filesize = os.path.getsize(source_image_path) - offset = 0 - with open(source_image_path, 'rb') as input_img: - while offset < filesize: - count = min(SENDFILE_CHUNK_SIZE, filesize - offset) - nbytes_out = os.sendfile(image_file.fileno(), - input_img.fileno(), - offset, - count) - offset += nbytes_out + return + + # NOTE(dtantsur): starting with Python 3.8, copyfile() uses + # efficient copying (i.e. sendfile) under the hood. + shutil.copyfile(source_image_path, dest_image_path) except Exception as e: raise exception.ImageDownloadFailed(image_href=image_href, reason=str(e)) diff --git a/ironic/common/kickstart_utils.py b/ironic/common/kickstart_utils.py index 433cf2390..4e02e2ea7 100644 --- a/ironic/common/kickstart_utils.py +++ b/ironic/common/kickstart_utils.py @@ -23,6 +23,7 @@ import pycdlib import requests from ironic.common import exception +from ironic.conf import CONF LOG = logging.getLogger(__name__) @@ -107,7 +108,8 @@ def decode_and_extract_config_drive_iso(config_drive_iso_gz): def _fetch_config_drive_from_url(url): try: - config_drive = requests.get(url).content + config_drive = requests.get( + url, timeout=CONF.webserver_connection_timeout).content except requests.exceptions.RequestException as e: raise exception.InstanceDeployFailure( "Can't download the configdrive content from '%(url)s'. " diff --git a/ironic/common/molds.py b/ironic/common/molds.py index 234fcc6e3..a77e42a63 100644 --- a/ironic/common/molds.py +++ b/ironic/common/molds.py @@ -49,7 +49,8 @@ def save_configuration(task, url, data): ) def _request(url, data, auth_header): return requests.put( - url, data=json.dumps(data, indent=2), headers=auth_header) + url, data=json.dumps(data, indent=2), headers=auth_header, + timeout=CONF.webserver_connection_timeout) auth_header = _get_auth_header(task) response = _request(url, data, auth_header) @@ -76,7 +77,8 @@ def get_configuration(task, url): reraise=True ) def _request(url, auth_header): - return requests.get(url, headers=auth_header) + return requests.get(url, headers=auth_header, + timeout=CONF.webserver_connection_timeout) auth_header = _get_auth_header(task) response = _request(url, auth_header) diff --git a/ironic/common/rpc_service.py b/ironic/common/rpc_service.py index cb0f23c98..a74f6bab3 100644 --- a/ironic/common/rpc_service.py +++ b/ironic/common/rpc_service.py @@ -100,7 +100,8 @@ class RPCService(service.Service): seconds=CONF.hash_ring_reset_interval) try: - self.manager.del_host(deregister=self.deregister) + self.manager.del_host(deregister=self.deregister, + clear_node_reservations=False) except Exception as e: LOG.exception('Service error occurred when cleaning up ' 'the RPC manager. Error: %s', e) @@ -127,6 +128,21 @@ class RPCService(service.Service): LOG.info('Stopped RPC server for service %(service)s on host ' '%(host)s.', {'service': self.topic, 'host': self.host}) + + # Wait for reservation locks held by this conductor. + # The conductor process will end when: + # - All reservations for this conductor are released + # - CONF.graceful_shutdown_timeout has elapsed + # - The process manager (systemd, kubernetes) sends SIGKILL after the + # configured graceful period + graceful_time = initial_time + datetime.timedelta( + seconds=CONF.graceful_shutdown_timeout) + while (self.manager.has_reserved() + and graceful_time > timeutils.utcnow()): + LOG.info('Waiting for reserved nodes to clear on host %(host)s', + {'host': self.host}) + time.sleep(1) + rpc.set_global_manager(None) def _handle_signal(self, signo, frame): |