All Classes Functions
nfio.py
1 #!/usr/bin/env python
2 
3 from __future__ import with_statement
4 
5 import os
6 import sys
7 import errno
8 
9 import logging
10 
11 from fuse import FUSE, FuseOSError, Operations
12 from hypervisor import hypervisor_factory as hyp_factory
13 from vnfs_operations import VNFSOperations
14 
15 import getpass
16 import re
17 import importlib
18 import argparse
19 
20 logger = logging.getLogger(__name__)
21 
22 class Nfio(Operations):
23 
24  ##
25  # Instantiates a Nfio object.
26  #
27  # Args:
28  # root: The root directory of nfio file system. The root directory
29  # stores persistent state about the system.
30  # mountpoint: The mountpoint of nfio file system. The mountpoint is
31  # required to intercept the file system calls via fuse. All the
32  # file system calls for fuse mounted files/directories are
33  # intercepted by libfuse and our provided implementation is
34  # executed.
35  # hypervisor: The type of hypervisor to use for deploying VNFs. The
36  # default is to use Docker containers. However, we also plan to
37  # add support for Libvirt.
38  # module_root: Root directory of the middlebox modules. Each middlebox
39  # provides it's own implementation of certain system calls in a
40  # separate module. module_root points to the root of that module.
41  # If nothing is provided a default of 'middleboxes' will be
42  # assumed.
43  # Returns:
44  # Nothing. Mounts nf.io file system at the specified mountpoint and
45  # creates a loop to act upon different file system calls.
46  #
47  def __init__(
48  self,
49  root,
50  mountpoint,
51  hypervisor='Docker',
52  module_root='middleboxes'):
53  self.root = root
54  self.mountpoint = mountpoint
55  self.hypervisor = hypervisor
56  self.vnfs_ops = VNFSOperations(root)
57  self.module_root = module_root
58 
59  # Helpers
60  # =======
61 
62  ##
63  # Returns the absolute path of a partially specified path.
64  #
65  # Args:
66  # partial: The partially specified path. e.g., nf-types/firewall
67  # This partially specified path should be a relative path under
68  # the mounted directory of nfio"
69  # Returns:
70  # The absolute path for the partially specified path. The absolute
71  # path is in respect to nfio root directory. e.g., if partial is
72  # nf-types/firewall and nfio root is /vnfsroot, then the return value
73  # will be /vnfsroot/nf-types/firewall
74  #
75  def _full_path(self, partial):
76  if partial.startswith("/"):
77  partial = partial[1:]
78  path = os.path.join(self.root, partial)
79  return path
80 
81  # Filesystem methods
82  # ==================
83 
84  def access(self, path, mode):
85  full_path = self._full_path(path)
86  if not os.access(full_path, mode):
87  raise FuseOSError(errno.EACCES)
88 
89  def chmod(self, path, mode):
90  full_path = self._full_path(path)
91  return os.chmod(full_path, mode)
92 
93  def chown(self, path, uid, gid):
94  full_path = self._full_path(path)
95  return os.chown(full_path, uid, gid)
96 
97  ##
98  #
99  # Returns the file attributes of the file specified by path
100  # Args:
101  # path: Path of the file
102  # fh: Open file handle to the file
103  # Returns:
104  # A dictionary containing file attributes. The dictionary contains the
105  # following keys:
106  # st_atime: Last access time
107  # st_ctime: File creation time
108  # st_gid: Group id of the owner group
109  # st_mode: File access mode
110  # st_mtime: Last modification time
111  # st_nlink: Number of symbolic links to the file
112  # st_size: Size of the file in bytes
113  # st_uid: User id of the file owner
114  # Note:
115  # For special placeholder files for VNFs, st_size is set to a
116  # constant 1000. This is to make sure read utilities such as cat work
117  # for these special placeholder files.
118  #
119  def getattr(self, path, fh=None):
120  opcode = self.vnfs_ops.vnfs_get_opcode(path)
121  if opcode == VNFSOperations.OP_NF:
122  nf_type = self.vnfs_ops.vnfs_get_nf_type(path)
123  if len(nf_type) > 0:
124  try:
125  mbox_module = importlib.import_module(
126  self.module_root +
127  "." +
128  nf_type)
129  except ImportError:
130  logger.error('VNF module file missing. Add "' + nf_type
131  + '.py" under the directory ' + self.module_root)
132  ## TODO: raise an custom exception and handle it in a OS
133  ## specific way
134  raise OSError(errno.ENOSYS)
135  return mbox_module._getattr(self.root, path, fh)
136  full_path = self._full_path(path)
137  st = os.lstat(full_path)
138  file_name = self.vnfs_ops.vnfs_get_file_name(full_path)
139  return dict(
140  (key,
141  getattr(
142  st,
143  key)) for key in (
144  'st_atime',
145  'st_ctime',
146  'st_gid',
147  'st_mode',
148  'st_mtime',
149  'st_nlink',
150  'st_size',
151  'st_uid'))
152 
153  def readdir(self, path, fh):
154  full_path = self._full_path(path)
155  dirents = ['.', '..']
156  if os.path.isdir(full_path):
157  dirents.extend(os.listdir(full_path))
158  for entry in dirents:
159  yield entry
160 
161  def readlink(self, path):
162  pathname = os.readlink(self._full_path(path))
163  if pathname.startswith(self.root):
164  pathname = self.mountpoint + pathname[len(self.root):]
165  return pathname
166 
167  def mknod(self, path, mode, dev):
168  return os.mknod(self._full_path(path), mode, dev)
169 
170  def rmdir(self, path):
171  full_path = self._full_path(path)
172  return os.rmdir(full_path)
173 
174  ##
175  #
176  # The semantics have been redefined to create a new VNF instance when a
177  # directory is created under a specific type of VNF directory.
178  #
179  # Args:
180  # path: path of the directory to create. The path also represents the
181  # name of the new VNF instance to be created.
182  # mode: File access mode for the new directory.
183  # Returns:
184  # If path does not correspond to a directory under a specific VNF type
185  # directory then errno.EPERM is returned. Otherwise the return code is
186  # same as os.mkdir()'s return code.
187  #
188  def mkdir(self, path, mode):
189  opcode = self.vnfs_ops.vnfs_get_opcode(path)
190  if opcode == VNFSOperations.OP_NF:
191  nf_type = self.vnfs_ops.vnfs_get_nf_type(path)
192  # Check if this directory is an instance directory or a type
193  # directory
194  path_tokens = path.split("/")
195  if path_tokens.index("nf-types") == len(path_tokens) - 2:
196  return os.mkdir(self._full_path(path), mode)
197  mbox_module = importlib.import_module(
198  self.module_root +
199  "." +
200  nf_type)
201  result = mbox_module._mkdir(self.root, path, mode)
202  elif opcode == VNFSOperations.OP_UNDEFINED:
203  result = errno.EPERM
204  return result
205 
206  def statfs(self, path):
207  full_path = self._full_path(path)
208  stv = os.statvfs(full_path)
209  return dict(
210  (key,
211  getattr(
212  stv,
213  key)) for key in (
214  'f_bavail',
215  'f_bfree',
216  'f_blocks',
217  'f_bsize',
218  'f_favail',
219  'f_ffree',
220  'f_files',
221  'f_flag',
222  'f_frsize',
223  'f_namemax'))
224 
225  def unlink(self, path):
226  return os.unlink(self._full_path(path))
227 
228  def symlink(self, name, target):
229  return os.symlink(self._full_path(target), name)
230 
231  def rename(self, old, new):
232  return os.rename(self._full_path(old), self._full_path(new))
233 
234  def link(self, target, name):
235  return os.link(self._full_path(target), self._full_path(name))
236 
237  def utimens(self, path, times=None):
238  return os.utime(self._full_path(path), times)
239 
240  # File methods
241  # ============
242 
243  def open(self, path, flags):
244  full_path = self._full_path(path)
245  return os.open(full_path, flags)
246 
247  def create(self, path, mode, fi=None):
248  full_path = self._full_path(path)
249  return os.open(full_path, os.O_WRONLY | os.O_CREAT, mode)
250 
251  ##
252  #
253  # Reads an open file. This nfio specific implementation parses path to see
254  # if the read is from any VNF or not. In case the read is from a VNF, the
255  # corresponding VNF module is loaded and the module's _read function is
256  # invoked to complete the read system call.
257  #
258  # Args:
259  # path: path represents the path of the file to read from
260  # length: number of bytes to read from the file
261  # offset: byte offset indicating the starting byte to read from
262  # fh: file descriptor of the open file represented by path
263  #
264  # Returns:
265  # length bytes from offset byte of the file represented by fh and path
266  #
267  # Notes:
268  # VNFs can have special files which are placeholders for statistics
269  # such as number of received/sent bytes etc. VNFs provide their own
270  # implementation of read and handle reading of these special
271  # placeholder files.
272  #
273  def read(self, path, length, offset, fh):
274  full_path = self._full_path(path)
275  opcode = self.vnfs_ops.vnfs_get_opcode(full_path)
276  file_name = self.vnfs_ops.vnfs_get_file_name(full_path)
277  if opcode == self.vnfs_ops.OP_NF:
278  nf_type = self.vnfs_ops.vnfs_get_nf_type(path)
279  mbox_module = importlib.import_module(
280  self.module_root +
281  "." +
282  nf_type)
283  return mbox_module._read(self.root, path, length, offset, fh)
284  os.lseek(fh, offset, os.SEEK_SET)
285  return os.read(fh, length)
286 
287  ##
288  #
289  # Write to an open file. In this nfio specific implementation the path is
290  # parsed to see if the write is for any specific VNF or not. If the write
291  # is for any file under a VNF directory then the corresponding VNF module
292  # is loaded and the module's _write function is invoked.
293  #
294  # Args:
295  # path: path to the file to write
296  # buf: the data to write
297  # offset: the byte offset at which the write should begin
298  # fh: file descriptor of the open file represented by path
299  #
300  # Returns:
301  # Returns the number of bytes written to the file starting at offset
302  #
303  # Note:
304  # VNFs can have special files where writing specific strings trigger
305  # a specific function. For example, writing 'activate' to the 'action'
306  # file of a VNF will start the VNF. VNF specific modules handle such
307  # special cases of writing.
308  #
309  def write(self, path, buf, offset, fh):
310  opcode = self.vnfs_ops.vnfs_get_opcode(path)
311  full_path = self._full_path(path)
312  file_name = self.vnfs_ops.vnfs_get_file_name(path)
313  if opcode == VNFSOperations.OP_NF:
314  nf_type = self.vnfs_ops.vnfs_get_nf_type(full_path)
315  mbox_module = importlib.import_module(
316  self.module_root +
317  "." +
318  nf_type)
319  return mbox_module._write(self.root, path, buf, offset, fh)
320 
321  os.lseek(fh, offset, os.SEEK_SET)
322  return os.write(fh, buf)
323 
324  def truncate(self, path, length, fh=None):
325  full_path = self._full_path(path)
326  with open(full_path, 'r+') as f:
327  f.truncate(length)
328 
329  def flush(self, path, fh):
330  return os.fsync(fh)
331 
332  def release(self, path, fh):
333  return os.close(fh)
334 
335  def fsync(self, path, fdatasync, fh):
336  return self.flush(path, fh)
337 
338 
339 def nfio_main():
340  arg_parser = argparse.ArgumentParser(
341  description="nf.io File System for NFV Orchestration",
342  formatter_class=argparse.ArgumentDefaultsHelpFormatter)
343  arg_parser.add_argument(
344  '--nfio_root',
345  help='Absolute path of nf.io root',
346  required=True)
347  arg_parser.add_argument(
348  '--nfio_mount',
349  help='Absolute path of nf.io mount point',
350  required=True)
351  arg_parser.add_argument(
352  '--hypervisor',
353  help='Hypervisor to use for VNF deployment (DockerDriver/Libvirt)',
354  default="DockerDriver")
355  arg_parser.add_argument(
356  '--middlebox_module_root',
357  help='Module directory inside the source tree containing middlebox specific implementation of system calls',
358  default='middleboxes')
359  arg_parser.add_argument(
360  '--log_level',
361  help='[debug|info|warning|error]',
362  default='info')
363 
364  args = arg_parser.parse_args()
365  root = args.nfio_root
366  mountpoint = args.nfio_mount
367  hypervisor = args.hypervisor
368  hypervisor_factory = hyp_factory.HypervisorFactory(hypervisor)
369  module_root = args.middlebox_module_root
370 
371  # set the logging level
372  LOG_LEVELS = {'debug':logging.DEBUG,
373  'info':logging.INFO,
374  'warning':logging.WARNING,
375  'error':logging.ERROR,
376  'critical':logging.CRITICAL,
377  }
378  log_level = LOG_LEVELS.get(args.log_level, logging.INFO)
379  logging.basicConfig(level=log_level)
380  # suppress INFO log messages from the requests module
381  logging.getLogger("requests").setLevel(logging.WARNING)
382  logging.addLevelName( logging.WARNING, "\033[1;31m%s\033[1;0m" % logging.getLevelName(logging.WARNING))
383  logging.addLevelName( logging.ERROR, "\033[1;41m%s\033[1;0m" % logging.getLevelName(logging.ERROR))
384  FORMAT = "[%(filename)s:%(lineno)s - %(funcName)20s() ] %(message)s"
385  logging.basicConfig(format=FORMAT)
386 
387  FUSE(
388  Nfio(
389  root,
390  mountpoint,
391  hypervisor,
392  module_root),
393  mountpoint,
394  foreground=True)
395 
396 if __name__ == '__main__':
397  nfio_main()
def write
Write to an open file.
Definition: nfio.py:309
def flush
Definition: nfio.py:329
Provides a common set of operations for nfio.
def read
Reads an open file.
Definition: nfio.py:273
module_root
Definition: nfio.py:57
def _full_path
Returns the absolute path of a partially specified path.
Definition: nfio.py:75
def getattr
Returns the file attributes of the file specified by path Args: path: Path of the file fh: Open file ...
Definition: nfio.py:119
root
Definition: nfio.py:53
hypervisor
Definition: nfio.py:55
def __init__
Instantiates a Nfio object.
Definition: nfio.py:52
def mkdir
The semantics have been redefined to create a new VNF instance when a directory is created under a sp...
Definition: nfio.py:188
mountpoint
Definition: nfio.py:54
vnfs_ops
Definition: nfio.py:56