summaryrefslogtreecommitdiff
path: root/cxmanage_api/tftp.py
diff options
context:
space:
mode:
Diffstat (limited to 'cxmanage_api/tftp.py')
-rw-r--r--cxmanage_api/tftp.py297
1 files changed, 297 insertions, 0 deletions
diff --git a/cxmanage_api/tftp.py b/cxmanage_api/tftp.py
new file mode 100644
index 0000000..02b7c49
--- /dev/null
+++ b/cxmanage_api/tftp.py
@@ -0,0 +1,297 @@
+# Copyright (c) 2012, Calxeda Inc.
+#
+# All rights reserved.
+#
+# Redistribution and use in source and binary forms, with or without
+# modification, are permitted provided that the following conditions are
+# met:
+#
+# * Redistributions of source code must retain the above copyright
+# notice, this list of conditions and the following disclaimer.
+# * Redistributions in binary form must reproduce the above copyright
+# notice, this list of conditions and the following disclaimer in the
+# documentation and/or other materials provided with the distribution.
+# * Neither the name of Calxeda Inc. nor the names of its contributors
+# may be used to endorse or promote products derived from this software
+# without specific prior written permission.
+#
+# THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
+# "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
+# LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
+# FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
+# COPYRIGHT HOLDERS OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
+# INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
+# BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS
+# OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND
+# ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR
+# TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF
+# THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH
+# DAMAGE.
+
+
+import os
+import sys
+import atexit
+import shutil
+import socket
+import logging
+import traceback
+
+from tftpy import TftpClient, TftpServer, setLogLevel
+from threading import Thread
+from cxmanage_api import temp_dir
+from tftpy.TftpShared import TftpException
+
+
+class InternalTftp(object):
+ """Internally serves files using the `Trivial File Transfer Protocol <http://en.wikipedia.org/wiki/Trivial_File_Transfer_Protocol>`_.
+
+ >>> # Typical instantiation ...
+ >>> from cxmanage_api.tftp import InternalTftp
+ >>> i_tftp = InternalTftp()
+ >>> # Alternatively, you can specify an address or hostname ...
+ >>> i_tftp = InternalTftp(ip_address='localhost')
+
+ :param ip_address: Ip address for the Internal TFTP server to use.
+ :type ip_address: string
+ :param port: Port for the internal TFTP server.
+ :type port: integer
+ :param verbose: Flag to turn on additional messaging.
+ :type verbose: boolean
+
+ """
+
+ def __init__(self, ip_address=None, port=0, verbose=False):
+ """Default constructor for the InternalTftp class."""
+ self.tftp_dir = temp_dir()
+ self.verbose = verbose
+ pipe = os.pipe()
+ pid = os.fork()
+ if (not pid):
+ # Force tftpy to use sys.stdout and sys.stderr
+ try:
+ os.dup2(sys.stdout.fileno(), 1)
+ os.dup2(sys.stderr.fileno(), 2)
+
+ except AttributeError, err_msg:
+ if (self.verbose):
+ print ('Passing on exception: %s' % err_msg)
+ pass
+
+ # Create a PortThread class only if needed ...
+ class PortThread(Thread):
+ """Thread that sends the port number through the pipe."""
+ def run(self):
+ """Run function override."""
+ # Need to wait for the server to open its socket
+ while not server.sock:
+ pass
+ with os.fdopen(pipe[1], "w") as a_file:
+ a_file.write("%i\n" % server.sock.getsockname()[1])
+ #
+ # Create an Internal TFTP server thread
+ #
+ server = TftpServer(tftproot=self.tftp_dir)
+ thread = PortThread()
+ thread.start()
+ try:
+ if not self.verbose:
+ setLogLevel(logging.CRITICAL)
+ # Start accepting connections ...
+ server.listen(listenport=port)
+ except KeyboardInterrupt:
+ # User @ keyboard cancelled server ...
+ if (self.verbose):
+ traceback.format_exc()
+ sys.exit(0)
+
+ self.server = pid
+ self.ip_address = ip_address
+ with os.fdopen(pipe[0]) as a_fd:
+ self.port = int(a_fd.readline())
+ atexit.register(self.kill)
+
+ def get_address(self, relative_host=None):
+ """Returns the ipv4 address of this server.
+ If a relative_host is specified, then we discover our address to them.
+
+ >>> i_tftp.get_address(relative_host='10.10.14.150')
+ 'localhost'
+
+ :param relative_host: Ip address to the relative host.
+ :type relative_host: string
+
+ :return: The ipv4 address of this InternalTftpServer.
+ :rtype: string
+
+ """
+ if (self.ip_address != None):
+ return self.ip_address
+ elif (relative_host == None):
+ return "localhost"
+ else:
+ sock = socket.socket(socket.AF_INET, socket.SOCK_DGRAM)
+ sock.connect((relative_host, self.port))
+ ipv4 = sock.getsockname()[0]
+ sock.close()
+ return ipv4
+
+ def kill(self):
+ """Kills the InternalTftpServer.
+
+ >>> i_tftp.kill()
+
+ """
+ if (self.server):
+ os.kill(self.server, 15)
+ self.server = None
+
+ def get_file(self, src, dest):
+ """Download a file from the tftp server to local_path.
+
+ >>> i_tftp.get_file(src='remote_file_i_want.txt', dest='/local/path')
+
+ :param src: Source file path on the tftp_server.
+ :type src: string
+ :param dest: Destination path (on your machine) to copy the TFTP file to.
+ :type dest: string
+
+ """
+ src = "%s/%s" % (self.tftp_dir, src)
+ if (src != dest):
+ try:
+ # Ensure the file exists ...
+ with open(src) as a_file:
+ a_file.close()
+ shutil.copy(src, dest)
+
+ except Exception:
+ traceback.format_exc()
+ raise
+
+ def put_file(self, src, dest):
+ """Upload a file from src to dest on the tftp server (path).
+
+ >>> i_tftp.put_file(src='/local/file.txt', dest='remote_file_name.txt')
+
+ :param src: Path to the local file to send to the TFTP server.
+ :type src: string
+ :param dest: Path to put the file to on the TFTP Server.
+ :type dest: string
+
+ """
+ dest = "%s/%s" % (self.tftp_dir, dest)
+ if (src != dest):
+ try:
+ # Ensure that the local file exists ...
+ with open(src) as a_file:
+ a_file.close()
+ shutil.copy(src, dest)
+ except Exception:
+ traceback.format_exc()
+ raise
+
+
+class ExternalTftp(object):
+ """Defines a ExternalTftp object, which is actually TFTP client.
+
+ >>> from cxmanage_api.tftp import ExternalTftp
+ >>> e_tftp = ExternalTftp(ip_address='1.2.3.4')
+
+ :param ip_address: Ip address of the TFTP server.
+ :type ip_address: string
+ :param port: Port to the External TFTP server.
+ :type port: integer
+ :param verbose: Flag to turn on verbose output (cmd/response).
+ :type verbose: boolean
+
+ """
+
+ def __init__(self, ip_address, port=69, verbose=False):
+ """Default constructor for this the ExternalTftp class."""
+ self.ip_address = ip_address
+ self.port = port
+ self.verbose = verbose
+
+ if not self.verbose:
+ setLogLevel(logging.CRITICAL)
+
+ def get_address(self, relative_host=None):
+ """Return the ip address of the ExternalTftp server.
+
+ >>> e_tftp.get_address()
+ '1.2.3.4'
+
+ :param relative_host: Unused parameter present only for function signature.
+ :type relative_host: None
+
+ :returns: The ip address of the external TFTP server.
+ :rtype: string
+
+ """
+ del relative_host # Needed only for function signature.
+ return self.ip_address
+
+ def get_file(self, src, dest):
+ """Download a file from the ExternalTftp Server.
+
+ .. note::
+ * TftpClient is not threadsafe, so we create a unique instance for
+ each transfer.
+
+ >>> e_tftp.get_file(src='remote_file_i_want.txt', dest='/local/path')
+
+ :param src: The path to the file on the Tftp server.
+ :type src: string
+ :param dest: The local destination to copy the file to.
+ :type dest: string
+
+ :raises TftpException: If the file does not exist or cannot be obtained
+ from the TFTP server.
+ :raises TftpException: If a TypeError is received from tftpy.
+
+ """
+ try:
+ client = TftpClient(self.ip_address, self.port)
+ client.download(output=dest, filename=src)
+ except TftpException:
+ if (self.verbose):
+ traceback.format_exc()
+ raise
+ except TypeError:
+ if (self.verbose):
+ traceback.format_exc()
+ raise TftpException("Failed download file from TFTP server")
+
+ def put_file(self, src, dest):
+ """Uploads a file to the tftp server.
+
+ .. note::
+ * TftpClient is not threadsafe, so we create a unique instance for
+ each transfer.
+
+ >>> e_tftp.put_file(src='local_file.txt', dest='remote_name.txt')
+
+ :param src: Source file path (on your local machine).
+ :type src: string
+ :param dest: Destination path (on the TFTP server).
+ :type dest: string
+
+ :raises TftpException: If the file cannot be written to the TFTP server.
+ :raises TftpException: If a TypeError is received from tftpy.
+
+ """
+ try:
+ client = TftpClient(self.ip_address, self.port)
+ client.upload(input=src, filename=dest)
+ except TftpException:
+ if (self.verbose):
+ traceback.format_exc()
+ raise
+ except TypeError:
+ if (self.verbose):
+ traceback.format_exc()
+ raise TftpException("Failed to upload file to TFTP server")
+
+
+# End of file: ./tftp.py