1 2 3 4 5 6 7 8 9 10 11 12 13 14 15 16 17 18 19 20 21 22 23 24 25 26 27 28 29 30 31 32 33 34 35 36 37 38 39 40 41 42 43 44 45 46 47 48 49 50 51 52 53 54 55 56 57 58 59 60 61 62 63 64 65 66 67 68 69 70 71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92 93 94 95 96 97 98 99 100 101 102 103 104 105 106 107 108 109 110 111 112 113 114 115 116 117 118 119 120 121 122
|
"""
Copyright (C) 2023 Michael Ablassmeier <abi@grinser.de>
This program is free software: you can redistribute it and/or modify
it under the terms of the GNU General Public License as published by
the Free Software Foundation, either version 3 of the License, or
(at your option) any later version.
This program is distributed in the hope that it will be useful,
but WITHOUT ANY WARRANTY; without even the implied warranty of
MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
GNU General Public License for more details.
You should have received a copy of the GNU General Public License
along with this program. If not, see <https://www.gnu.org/licenses/>.
"""
import os
import logging
from time import sleep
import nbd
from libvirtnbdbackup.nbdcli import exceptions
log = logging.getLogger("nbd")
# pylint: disable=too-many-instance-attributes
class client:
"""Helper functions for NBD"""
def __init__(self, cType, no_sparse_detection: bool):
"""
Connect NBD backend
"""
self.cType = cType
self._exportName = cType.exportName
self._metaContext = ""
if cType.metaContext != "":
self._metaContext = cType.metaContext
else:
self._metaContext = nbd.CONTEXT_BASE_ALLOCATION
self.maxRequestSize = 33554432
self.minRequestSize = 65536
self.no_sparse_detection = no_sparse_detection
self.nbd = nbd.NBD()
def debug(func, args):
"""Write NBD debugging messages to logfile instead of
stderr"""
log.debug("%s: %s", func, args)
self.nbd.set_debug_callback(debug)
self.connection = None
def _getBlockInfo(self) -> None:
"""Read maximum request/block size as advertised by the nbd
server. This is the value which will then be used by default
"""
maxSize = self.nbd.get_block_size(nbd.SIZE_MAXIMUM)
if maxSize != 0:
self.maxRequestSize = maxSize
log.debug("Block size supported by NBD server: [%s]", maxSize)
def _connect(self) -> nbd.NBD:
"""Setup connection to NBD server endpoint, return
connection handle
"""
if self.cType.tls and not self.nbd.supports_tls():
raise exceptions.NbdConnectionError(
"Installed python nbd binding is missing required tls features."
)
try:
if self.cType.tls:
self.nbd.set_tls(nbd.TLS_REQUIRE)
if self.no_sparse_detection is False:
self.nbd.add_meta_context(nbd.CONTEXT_BASE_ALLOCATION)
if self._metaContext != "":
log.debug(
"Adding meta context to NBD connection: [%s]", self._metaContext
)
self.nbd.add_meta_context(self._metaContext)
self.nbd.set_export_name(self._exportName)
self.nbd.connect_uri(self.cType.uri)
except nbd.Error as e:
raise exceptions.NbdConnectionError(f"Unable to connect nbd server: {e}")
self._getBlockInfo()
return self.nbd
def connect(self) -> nbd.NBD:
"""Wait until NBD endpoint connection can be established. It can take
some time until qemu-nbd process is running and reachable. Attempt to
connect and fail if no connection can be established. In case of unix
domain socket, wait until socket file is created by qemu-nbd."""
log.info("Waiting until NBD server at [%s] is up.", self.cType.uri)
for retry in range(20):
sleep(1)
if self.cType.backupSocket and not os.path.exists(self.cType.backupSocket):
log.info("Waiting for NBD Server unix socket, Retry: %s", retry)
continue
try:
connection = self._connect()
except exceptions.NbdConnectionError as e:
self.nbd = nbd.NBD()
log.info("Waiting for NBD Server connection, Retry: %s [%s]", retry, e)
continue
log.info("Connection to NBD backend succeeded.")
self.connection = connection
return self
raise exceptions.NbdConnectionTimeout(
"Timeout during connection to NBD server backend."
)
def disconnect(self) -> None:
"""Close nbd connection handle"""
self.nbd.shutdown()
|