Welcome to mirror list, hosted at ThFree Co, Russian Federation.

github.com/bareos/python-bareos.git - Unnamed repository; edit this file 'description' to name the repository.
summaryrefslogtreecommitdiff
diff options
context:
space:
mode:
authorJoerg Steffens <joerg.steffens@bareos.com>2016-06-01 17:28:53 +0300
committerJoerg Steffens <joerg.steffens@bareos.com>2016-06-24 18:34:13 +0300
commit52708427992ef1a516e5f394a37b2b29cf3c3274 (patch)
treea68c92f6dbaaf19eee4a301979fcdb5b939dcd4c
parent907f1c1c2a4cd2104026cf31dd8eb4c1233f6457 (diff)
bareos-fd-connect: improved integration
bareos-fd-connect now works with all FD commands, can cope with mutliple command arguments (spaces have to be replaced by 0x1) and offers an interactive mode. Under the hood, there is now a cleaner separation between DirectorConsole and FileDaemon connections. The classes BSock and BSockJson are deprecated. You should use the new classes * bareos.bsock.DirectorConsole * bareos.bsock.DirectorConsoleJson * bareos.bsock.FileDaemon
-rw-r--r--README.rst12
-rw-r--r--bareos/bsock/__init__.py14
-rw-r--r--bareos/bsock/bsock.py69
-rwxr-xr-xbareos/bsock/bsockjson.py85
-rw-r--r--bareos/bsock/constants.py2
-rw-r--r--bareos/bsock/directorconsole.py29
-rwxr-xr-xbareos/bsock/directorconsolejson.py57
-rw-r--r--bareos/bsock/filedaemon.py30
-rw-r--r--bareos/bsock/lowlevel.py129
-rwxr-xr-xbin/bareos-fd-connect.py6
-rwxr-xr-xbin/bconsole-json.py2
-rwxr-xr-xbin/bconsole.py2
12 files changed, 256 insertions, 181 deletions
diff --git a/README.rst b/README.rst
index b2dd929..025f161 100644
--- a/README.rst
+++ b/README.rst
@@ -14,8 +14,8 @@ calling bareos-director user agent commands
import bareos.bsock
password=bareos.bsock.Password("secret")
- bsock=bareos.bsock.BSock(address="localhost", port=9101, password=password)
- print bsock.call("help")
+ directorconsole=bareos.bsock.DirectorConsole(address="localhost", port=9101, password=password)
+ print directorconsole.call("help")
...
@@ -26,8 +26,8 @@ simple version of the bconsole in Python
import bareos.bsock
password=bareos.bsock.Password("secret")
- bconsole=bareos.BSock(address="localhost", port=9101, password=password)
- bconsole.interactive()
+ directorconsole=bareos.bsock.DirectorConsole(address="localhost", port=9101, password=password)
+ directorconsole.interactive()
...
use JSON objects of API mode 2
@@ -39,7 +39,7 @@ Requires: bareos >= 15.2
import bareos.bsock
password=bareos.bsock.Password("secret")
- bconsole=bareos.bsock.BSockJson(address="localhost", port=9101, password=password)
- bconsole.call("list pools")
+ directorconsole=bareos.bsock.DirectorConsoleJson(address="localhost", port=9101, password=password)
+ directorconsole.call("list pools")
...
diff --git a/bareos/bsock/__init__.py b/bareos/bsock/__init__.py
index f1a845d..8984d21 100644
--- a/bareos/bsock/__init__.py
+++ b/bareos/bsock/__init__.py
@@ -1,6 +1,10 @@
#__all__ = [ "bconsole" ]
-from bareos.exceptions import *
-from bareos.util.password import Password
-from bareos.bsock.connectiontype import ConnectionType
-from bareos.bsock.bsock import BSock
-from bareos.bsock.bsockjson import BSockJson
+from bareos.exceptions import *
+from bareos.util.password import Password
+from bareos.bsock.connectiontype import ConnectionType
+from bareos.bsock.directorconsole import DirectorConsole
+from bareos.bsock.directorconsolejson import DirectorConsoleJson
+from bareos.bsock.filedaemon import FileDaemon
+# compat
+from bareos.bsock.bsock import BSock
+from bareos.bsock.bsockjson import BSockJson
diff --git a/bareos/bsock/bsock.py b/bareos/bsock/bsock.py
index e2357b8..51dd59d 100644
--- a/bareos/bsock/bsock.py
+++ b/bareos/bsock/bsock.py
@@ -1,69 +1,12 @@
"""
Communicates with the bareos-director
-"""
-
-from bareos.bsock.connectiontype import ConnectionType
-from bareos.bsock.lowlevel import LowLevel
-from bareos.exceptions import *
-import sys
-
-class BSock(LowLevel):
- '''use to send and receive the response from director'''
-
- def __init__(self,
- address="localhost",
- port=9101,
- dirname=None,
- name="*UserAgent*",
- password=None,
- type=ConnectionType.DIRECTOR):
- super(BSock, self).__init__()
- self.connect(address, port, dirname, type)
- self.auth(name=name, password=password)
- if type == ConnectionType.DIRECTOR:
- self._set_state_director_prompt()
+Legacy, use DirectorConsole instead.
+"""
- def call(self, command):
- '''
- call a bareos-director user agent command
- '''
- return self.__call(command, 0)
-
- def __call(self, command, count):
- '''
- Call a bareos-director user agent command.
- If connection is lost, try to reconnect.
- '''
- result = b''
- try:
- self.send(bytearray(command, 'utf-8'))
- result = self.recv_msg()
- except (SocketEmptyHeader, ConnectionLostError) as e:
- self.logger.error("connection problem (%s): %s" % (type(e).__name__, str(e)))
- if count == 0:
- if self.reconnect():
- return self.__call(command, count+1)
- return result
-
- def send_command(self, commamd):
- return self.call(command)
+from bareos.bsock.directorconsole import DirectorConsole
+class BSock(DirectorConsole):
- def interactive(self):
- """
- Enter the interactive mode.
- Exit via typing "exit" or "quit".
- """
- self._set_state_director_prompt()
- command = ""
- while command != "exit" and command != "quit":
- # Python2: raw_input, Python3: input
- try:
- myinput = raw_input
- except NameError:
- myinput = input
- command = myinput(">>")
- resultmsg = self.call(command)
- sys.stdout.write(resultmsg.decode('utf-8'))
- return True
+ def __init__(self, *args, **kwargs):
+ super(BSock, self).__init__(*args, **kwargs)
diff --git a/bareos/bsock/bsockjson.py b/bareos/bsock/bsockjson.py
index d0b5898..17b2934 100755
--- a/bareos/bsock/bsockjson.py
+++ b/bareos/bsock/bsockjson.py
@@ -1,83 +1,12 @@
"""
-Reimplementation of the bconsole program in python.
-"""
-
-from bareos.bsock.bsock import BSock
-from pprint import pformat, pprint
-import json
-
-class BSockJson(BSock):
- """
- use to send and receive the response from director
- """
-
- def __init__(self,
- address="localhost",
- port=9101,
- dirname=None,
- name="*UserAgent*",
- password=None):
- super(BSockJson, self).__init__(
- address, port, dirname, name,
- password)
-
-
- def call(self, command):
- json = self.call_fullresult(command)
- if json == None:
- return
- if 'result' in json:
- result = json['result']
- else:
- # TODO: or raise an exception?
- result = json
- return result
-
-
- def call_fullresult(self, command):
- resultstring = super(BSockJson, self).call(command)
- data = None
- if resultstring:
- #print(resultstring.decode('utf-8'))
- try:
- data = json.loads(resultstring.decode('utf-8'))
- except ValueError as e:
- # in case result is not valid json,
- # create a JSON-RPC wrapper
- data = {
- 'error': {
- 'code': 2,
- 'message': str(e),
- 'data': resultstring
- },
- }
- return data
+Communicates with the bareos-director using JSON results
+Legacy, use DirectorConsoleJson instead.
+"""
- def interactive(self):
- """
- Enter the interactive mode.
- """
- self._set_state_director_prompt()
- command = ""
- while command != "exit" and command != "quit":
- try:
- myinput = raw_input
- except NameError:
- myinput = input
- command = myinput(">>")
- if command:
- pprint(self.call(command))
- return True
+from bareos.bsock.directorconsolejson import DirectorConsoleJson
+class BSockJson(DirectorConsoleJson):
- def _set_state_director_prompt(self):
- result = False
- if super(BSockJson, self)._set_state_director_prompt():
- # older version did not support compact mode,
- # therfore first set api mode to json (which should always work in bareos >= 15.2.0)
- # and then set api mode json compact (which should work with bareos >= 15.2.2)
- self.call(".api json")
- self.call(".api json compact=yes")
- result = True
- return result
+ def __init__(self, *args, **kwargs):
+ super(BSockJson, self).__init__(*args, **kwargs)
diff --git a/bareos/bsock/constants.py b/bareos/bsock/constants.py
index f51cd54..b4c0ba4 100644
--- a/bareos/bsock/constants.py
+++ b/bareos/bsock/constants.py
@@ -39,7 +39,7 @@ class Constants:
BNET_EOD: "End of data stream, new data may follow",
BNET_EOD_POLL: "End of data and poll all in one",
BNET_STATUS: "Send full status",
- BNET_TERMINATE: "Conversation terminated, doing close()",
+ BNET_TERMINATE: "Conversation terminated",
BNET_POLL: "Poll request, I'm hanging on a read",
BNET_HEARTBEAT: "Heartbeat Response requested",
BNET_HB_RESPONSE: "Only response permited to HB",
diff --git a/bareos/bsock/directorconsole.py b/bareos/bsock/directorconsole.py
new file mode 100644
index 0000000..81e4f6c
--- /dev/null
+++ b/bareos/bsock/directorconsole.py
@@ -0,0 +1,29 @@
+"""
+Communicates with the bareos-dir console
+"""
+
+from bareos.bsock.connectiontype import ConnectionType
+from bareos.bsock.lowlevel import LowLevel
+
+class DirectorConsole(LowLevel):
+ '''use to send and receive the response to Bareos File Daemon'''
+
+ def __init__(self,
+ address="localhost",
+ port=9101,
+ dirname=None,
+ name="*UserAgent*",
+ password=None):
+ super(DirectorConsole, self).__init__()
+ self.connect(address, port, dirname, ConnectionType.DIRECTOR)
+ self.auth(name=name, password=password, auth_success_regex=b'^1000 OK.*$')
+ self._init_connection()
+
+
+ def _init_connection(self):
+ self.call("autodisplay off")
+
+
+ def get_to_prompt(self):
+ self.send(b".")
+ return super(DirectorConsole, self).get_to_prompt()
diff --git a/bareos/bsock/directorconsolejson.py b/bareos/bsock/directorconsolejson.py
new file mode 100755
index 0000000..2dfa952
--- /dev/null
+++ b/bareos/bsock/directorconsolejson.py
@@ -0,0 +1,57 @@
+"""
+Reimplementation of the bconsole program in python.
+"""
+
+from bareos.bsock.directorconsole import DirectorConsole
+from pprint import pformat, pprint
+import json
+
+class DirectorConsoleJson(DirectorConsole):
+ """
+ use to send and receive the response from director
+ """
+
+ def __init__(self, *args, **kwargs):
+ super(DirectorConsoleJson, self).__init__(*args, **kwargs)
+
+ def _init_connection(self):
+ # older version did not support compact mode,
+ # therfore first set api mode to json (which should always work in bareos >= 15.2.0)
+ # and then set api mode json compact (which should work with bareos >= 15.2.2)
+ self.logger.debug(self.call(".api json"))
+ self.logger.debug(self.call(".api json compact=yes"))
+
+
+ def call(self, command):
+ json = self.call_fullresult(command)
+ if json == None:
+ return
+ if 'result' in json:
+ result = json['result']
+ else:
+ # TODO: or raise an exception?
+ result = json
+ return result
+
+
+ def call_fullresult(self, command):
+ resultstring = super(DirectorConsoleJson, self).call(command)
+ data = None
+ if resultstring:
+ try:
+ data = json.loads(resultstring.decode('utf-8'))
+ except ValueError as e:
+ # in case result is not valid json,
+ # create a JSON-RPC wrapper
+ data = {
+ 'error': {
+ 'code': 2,
+ 'message': str(e),
+ 'data': resultstring
+ },
+ }
+ return data
+
+
+ def _show_result(self, msg):
+ pprint(msg)
diff --git a/bareos/bsock/filedaemon.py b/bareos/bsock/filedaemon.py
index a5845d7..93a06e1 100644
--- a/bareos/bsock/filedaemon.py
+++ b/bareos/bsock/filedaemon.py
@@ -2,10 +2,12 @@
Communicates with the bareos-fd
"""
-from bareos.bsock.connectiontype import ConnectionType
-from bareos.bsock import BSock
+from bareos.bsock.connectiontype import ConnectionType
+from bareos.bsock.lowlevel import LowLevel
+import shlex
-class FileDaemon(BSock):
+
+class FileDaemon(LowLevel):
'''use to send and receive the response to Bareos File Daemon'''
def __init__(self,
@@ -13,6 +15,22 @@ class FileDaemon(BSock):
port=9102,
dirname=None,
name=None,
- password=None,
- type=ConnectionType.FILEDAEMON):
- super(FileDaemon, self).__init__(address, port, dirname, name, password, type)
+ password=None):
+ super(FileDaemon, self).__init__()
+ self.connect(address, port, dirname, ConnectionType.FILEDAEMON)
+ self.auth(name=name, password=password, auth_success_regex=b'^2000 OK Hello.*$')
+ self._init_connection()
+
+ def call(self, command):
+ '''
+ Replace spaces by char(1) in quoted arguments
+ and then call the original function.
+ '''
+ if isinstance(command, list):
+ cmdlist=command
+ else:
+ cmdlist=shlex.split(command)
+ command0 = []
+ for arg in cmdlist:
+ command0.append(arg.replace(" ", "\x01"))
+ return super(FileDaemon, self).call(command0)
diff --git a/bareos/bsock/lowlevel.py b/bareos/bsock/lowlevel.py
index 644c3de..5e5fd38 100644
--- a/bareos/bsock/lowlevel.py
+++ b/bareos/bsock/lowlevel.py
@@ -14,8 +14,11 @@ from bareos.bsock.protocolmessages import ProtocolMessages
import hmac
import logging
import random
+import re
+from select import select
import socket
import struct
+import sys
import time
class LowLevel(object):
@@ -34,12 +37,9 @@ class LowLevel(object):
self.socket = None
self.auth_credentials_valid = False
self.connection_type = None
+ self.receive_buffer = b''
-
- def connect(self, address="localhost", port=9101, dirname=None, type=ConnectionType.DIRECTOR):
- '''
- connect to bareos-director
- '''
+ def connect(self, address, port, dirname, type):
self.address = address
self.port = port
if dirname:
@@ -62,17 +62,18 @@ class LowLevel(object):
return True
- def auth(self, password, name="*UserAgent*"):
+ def auth(self, name, password, auth_success_regex):
'''
login to the bareos-director
if the authenticate success return True else False
dir: the director location
- name: own name. Default is *UserAgent*
+ name: own name.
'''
if not isinstance(password, Password):
raise AuthenticationError("password must by of type bareos.Password() not %s" % (type(password)))
self.password = password
self.name = name
+ self.auth_success_regex = auth_success_regex
return self.__auth()
@@ -86,10 +87,15 @@ class LowLevel(object):
raise AuthenticationError("failed (in response)")
if not self._cram_md5_challenge(clientname=self.name, password=self.password.md5(), tls_local_need=0, compatible=True):
raise AuthenticationError("failed (in challenge)")
+ self.recv_msg(self.auth_success_regex)
self.auth_credentials_valid = True
return True
+ def _init_connection(self):
+ pass
+
+
def disconnect(self):
''' disconnect '''
# TODO
@@ -100,13 +106,43 @@ class LowLevel(object):
result = False
if self.auth_credentials_valid:
try:
- if self.__connect() and self.__auth() and self._set_state_director_prompt():
+ if self.__connect() and self.__auth() and self._init_connection():
result = True
except socket.error:
self.logger.warning("failed to reconnect")
return result
+ def call(self, command):
+ '''
+ call a bareos-director user agent command
+ '''
+ if isinstance(command, list):
+ command = " ".join(command)
+ return self.__call(command, 0)
+
+
+ def __call(self, command, count):
+ '''
+ Send a command and receive the result.
+ If connection is lost, try to reconnect.
+ '''
+ result = b''
+ try:
+ self.send(bytearray(command, 'utf-8'))
+ result = self.recv_msg()
+ except (SocketEmptyHeader, ConnectionLostError) as e:
+ self.logger.error("connection problem (%s): %s" % (type(e).__name__, str(e)))
+ if count == 0:
+ if self.reconnect():
+ return self.__call(command, count+1)
+ return result
+
+
+ def send_command(self, commamd):
+ return self.call(command)
+
+
def send(self, msg=None):
'''use socket to send request to director'''
self.__check_socket_connection()
@@ -133,10 +169,9 @@ class LowLevel(object):
return msg
- def recv_msg(self):
+ def recv_msg(self, regex = b'^\d\d\d\d OK.*$', timeout = None):
'''will receive data from director '''
self.__check_socket_connection()
- msg = b""
try:
timeouts = 0
while True:
@@ -154,11 +189,22 @@ class LowLevel(object):
# header is a signal
self.__set_status(header)
if self.is_end_of_message(header):
- return msg
+ result = self.receive_buffer
+ self.receive_buffer = b''
+ return result
else:
# header is the length of the next message
length = header
- msg += self.recv_submsg(length)
+ self.receive_buffer += self.recv_submsg(length)
+ # Bareos indicates end of command result by line starting with 4 digits
+ match = re.search(regex, self.receive_buffer, re.MULTILINE)
+ if match:
+ self.logger.debug("msg \"{0}\" matches regex \"{1}\"".format(self.receive_buffer.strip(), regex))
+ result = self.receive_buffer[0:match.end()]
+ self.receive_buffer = self.receive_buffer[match.end()+1:]
+ return result
+ #elif re.search("^\d\d\d\d .*$", msg, re.MULTILINE):
+ #return msg
except socket.error as e:
self._handleSocketError(e)
return msg
@@ -166,7 +212,7 @@ class LowLevel(object):
def recv_submsg(self, length):
# get the message
- msg = b""
+ msg = b''
while length > 0:
self.logger.debug(" submsg len: " + str(length))
# TODO
@@ -179,9 +225,41 @@ class LowLevel(object):
msg = bytearray(msg.decode('utf-8'), 'utf-8')
if (type(msg) is bytes):
msg = bytearray(msg)
+ #self.logger.debug(str(msg))
return msg
+ def interactive(self):
+ """
+ Enter the interactive mode.
+ Exit via typing "exit" or "quit".
+ """
+ command = ""
+ while command != "exit" and command != "quit" and self.is_connected():
+ command = self._get_input()
+ resultmsg = self.call(command)
+ self._show_result(resultmsg)
+ return True
+
+
+ def _get_input(self):
+ # Python2: raw_input, Python3: input
+ try:
+ myinput = raw_input
+ except NameError:
+ myinput = input
+ data = myinput(">>")
+ return data
+
+
+ def _show_result(self, msg):
+ #print(msg.decode('utf-8'))
+ sys.stdout.write(msg.decode('utf-8'))
+ # add a linefeed, if there isn't one already
+ if msg[-2] != ord(b'\n'):
+ sys.stdout.write(b'\n')
+
+
def __get_header(self):
self.__check_socket_connection()
header = self.socket.recv(4)
@@ -201,11 +279,17 @@ class LowLevel(object):
def is_end_of_message(self, data):
- return (data == Constants.BNET_EOD or
+ return ((not self.is_connected()) or
+ data == Constants.BNET_EOD or
+ data == Constants.BNET_TERMINATE or
data == Constants.BNET_MAIN_PROMPT or
data == Constants.BNET_SUB_PROMPT)
+ def is_connected(self):
+ return (self.status != Constants.BNET_TERMINATE)
+
+
def _cram_md5_challenge(self, clientname, password, tls_local_need=0, compatible=True):
'''
client launch the challenge,
@@ -246,6 +330,7 @@ class LowLevel(object):
# check the response is equal to base64
return is_correct
+
def _cram_md5_respond(self, password, tls_remote_need=0, compatible=True):
'''
client connect to dir,
@@ -291,10 +376,18 @@ class LowLevel(object):
self.logger.debug(str(status_text) + " (" + str(status) + ")")
- def _set_state_director_prompt(self):
- self.send(b".")
- msg = self.recv_msg()
- self.logger.debug("received message: " + str(msg))
+ def has_data(self):
+ self.__check_socket_connection()
+ timeout = 0.1
+ readable, writable, exceptional = select([self.socket], [], [], timeout)
+ return readable
+
+
+ def get_to_prompt(self):
+ time.sleep(0.1)
+ if self.has_data():
+ msg = self.recv_msg()
+ self.logger.debug("received message: " + str(msg))
# TODO: check prompt
return True
diff --git a/bin/bareos-fd-connect.py b/bin/bareos-fd-connect.py
index 37ac402..7998638 100755
--- a/bin/bareos-fd-connect.py
+++ b/bin/bareos-fd-connect.py
@@ -13,7 +13,7 @@ def getArguments():
parser.add_argument('-p', '--password', help="password to authenticate to a Bareos File Daemon", required=True)
parser.add_argument('--port', default=9102, help="Bareos File Daemon network port")
parser.add_argument('address', nargs='?', default="localhost", help="Bareos File Daemon network address")
- parser.add_argument('command', nargs='?', help="Command to send to the Bareos File Daemon")
+ parser.add_argument('command', nargs='*', help="Command to send to the Bareos File Daemon")
args = parser.parse_args()
return args
@@ -43,4 +43,6 @@ if __name__ == '__main__':
sys.exit(1)
logger.debug( "authentication successful" )
if args.command:
- print(bsock.call(args.command).decode('utf-8'))
+ bsock.call(args.command)
+ else:
+ bsock.interactive()
diff --git a/bin/bconsole-json.py b/bin/bconsole-json.py
index 3cca0f2..d04c7ab 100755
--- a/bin/bconsole-json.py
+++ b/bin/bconsole-json.py
@@ -37,7 +37,7 @@ if __name__ == '__main__':
logger.debug('options: %s' % (parameter))
password = bareos.bsock.Password(args.password)
parameter['password']=password
- director = bareos.bsock.BSockJson(**parameter)
+ director = bareos.bsock.DirectorConsoleJson(**parameter)
except RuntimeError as e:
print(str(e))
sys.exit(1)
diff --git a/bin/bconsole.py b/bin/bconsole.py
index 1c8f0c8..64afb48 100755
--- a/bin/bconsole.py
+++ b/bin/bconsole.py
@@ -38,7 +38,7 @@ if __name__ == '__main__':
logger.debug('options: %s' % (parameter))
password = bareos.bsock.Password(args.password)
parameter['password']=password
- director = bareos.bsock.BSock(**parameter)
+ director = bareos.bsock.DirectorConsole(**parameter)
except RuntimeError as e:
print(str(e))
sys.exit(1)