# Copyright (c) 2014-2016  Barnstormer Softworks, Ltd.
# This Source Code Form is subject to the terms of the Mozilla Public
# License, v. 2.0. If a copy of the MPL was not distributed with this
# file, You can obtain one at http://mozilla.org/MPL/2.0/.
from __future__ import absolute_import
import re
import sys
import inspect
from lxml import etree as ET
from .. import namespaces as GNS
from .pg import Namespaces as PGNS
from .pg import Node
from .pg import Resource
from . import pg
from .. import urn
[docs]class OFController(object):
  """OpenFlow controller specification to be used on a PG VLAN.
Add to link objects using the Link.addChild() method.
.. note::
  This will have no effect if a trivial link is created by the aggregate.
  You need to make sure that a VLAN will be provisioned (typically by making sure
  that at least two interfaces on the link are on different physical hosts)."""
  def __init__ (self, host, port=6633):
    self.host = host
    self.port = port
  def _write (self, element):
    eof = ET.SubElement(element, "{%s}openflow_controller" % (PGNS.EMULAB))
    eof.attrib["url"] = "tcp:%s:%d" % (self.host, self.port)
    return eof 
[docs]class XenVM(Node):
  """Xen-based Virtual Machine resource
  
  Args:
    client_id (str): Your name for this VM.  This must be unique within a single `Request` object.
    component_id (Optional[str]): The `component_id` of the site node you want to bind this VM to
    exclusive (Optional[bool]): Request this VM on an isolated host used only by your sliver.
  Attributes:
    cores (int): Number of CPU cores
    ram (int): Amount of memory in megabytes
    disk (int): Amount of disk space in gigabytes
    xen_ptype (str): Physical node type on which to instantiate the VM. Types are AM-specific.
  """
  def __init__ (self, client_id, component_id = None, exclusive = False):
    super(XenVM, self).__init__(client_id, "emulab-xen", component_id = component_id, exclusive = exclusive)
    self.cores = 1
    self.ram = 512 
    self.disk = 0
    self.xen_ptype = None
  def _write (self, root):
    nd = super(XenVM, self)._write(root)
    st = nd.find("{%s}sliver_type" % (GNS.REQUEST.name))
    if self.cores or self.ram or self.disk:
      xen = ET.SubElement(st, "{%s}xen" % (PGNS.EMULAB.name))
      if self.cores:
        xen.attrib["cores"] = str(self.cores)
      if self.ram:
        xen.attrib["ram"] = str(self.ram)
      if self.disk:
        xen.attrib["disk"] = str(self.disk)
    if self.xen_ptype is not None:
      pt = ET.SubElement(st, "{%s}xen_ptype" % (PGNS.EMULAB.name))
      pt.attrib["name"] = self.xen_ptype
    return nd 
pg.Request.EXTENSIONS.append(("XenVM", XenVM))
[docs]class AddressPool(Resource):
  """A pool of public dynamic IP addresses belonging to a slice."""
  def __init__(self, name, count=1, type="any"):
    super(AddressPool, self).__init__()
    self.client_id = name
    self.count = count
    self.type = type
    self.component_manager_id = None
  @property
  def name (self):
    return self.client_id
  def _write (self, root):
    pl = ET.SubElement(root, "{%s}routable_pool" % (PGNS.EMULAB.name))
    pl.attrib["client_id"] = self.client_id
    if self.component_manager_id:
      pl.attrib["component_manager_id"] = self.component_manager_id
    pl.attrib["count"] = str(self.count)
    pl.attrib["type"] = self.type
    return pl 
pg.Request.EXTENSIONS.append(("AddressPool", AddressPool))
[docs]class Blockstore(object):
  def __init__ (self, name, mount):
    """Creates a BlockStore object with the given name (arbitrary) and mountpoint."""
    self.name = name
    self.mount = mount
    self.size = None
    self.where = "local"    # local|remote
    self.readonly = False
    self.placement = "any"  # any|sysvol|nonsysvol
    self.dataset = None
    self.rwclone = False    # Only for remote blockstores.
  def _write (self, element):
    bse = ET.SubElement(element, "{%s}blockstore" % (PGNS.EMULAB))
    bse.attrib["name"] = self.name
    bse.attrib["mountpoint"] = self.mount
    bse.attrib["class"] = self.where
    if self.size:
      if re.match(r"^\d+$", self.size):
        self.size = str(self.size) + "GB"
      bse.attrib["size"] = self.size
    bse.attrib["placement"] = self.placement
    if self.readonly:
      bse.attrib["readonly"] = "true"
    if self.rwclone:
      bse.attrib["rwclone"] = "true"
    if self.dataset:
      if isinstance(self.dataset, (str, unicode)):
        bse.attrib["dataset"] = self.dataset
      elif isinstance(self.dataset, urn.Base):
        bse.attrib["dataset"] = str(self.dataset)
    return bse 
pg.Node.EXTENSIONS.append(("Blockstore", Blockstore))
[docs]class RemoteBlockstore(pg.Node):
  def __init__ (self, name, mount, ifacename = "if0"):
    super(RemoteBlockstore, self).__init__(name, "emulab-blockstore")
    bs = Blockstore("%s-bs" % (self.name), mount)
    bs.where = "remote"
    self._bs = bs
    self._interface = self.addInterface(ifacename)
  def _write (self, element):
    return self._bs._write(super(RemoteBlockstore, self)._write(element));
  @property
  def interface (self):
    return self._interface
  @property
  def size (self):
    return self._bs.size
  @size.setter
  def size (self, val):
    self._bs.size = val
  @property
  def mountpoint (self):
    return self._bs.mount
  @mountpoint.setter
  def mountpoint (self, val):
    self._bs.mount = val
  @property
  def readonly (self):
    return self._bs.readonly
  @readonly.setter
  def readonly (self, val):
    self._bs.readonly = val
  @property
  def rwclone (self):
    return self._bs.rwclone
  @rwclone.setter
  def rwclone (self, val):
    self._bs.rwclone = val
  @property
  def placement (self):
    return self._bs.placement
  @placement.setter
  def placement (self, val):
    self._bs.placement = val
  @property
  def dataset (self):
    return self._bs.dataset
  @dataset.setter
  def dataset (self, val):
    self._bs.dataset = val 
pg.Request.EXTENSIONS.append(("RemoteBlockstore", RemoteBlockstore))
[docs]class Bridge(pg.Node):
[docs]  class Pipe(object):
    def __init__ (self):
      self.bandwidth = 0
      self.latency   = 0
      self.lossrate  = 0.0 
  
  def __init__ (self, name, if0name = "if0", if1name = "if1"):
    super(Bridge, self).__init__(name, "delay")
    self.addNamespace(PGNS.DELAY)
    
    self.iface0 = self.addInterface(if0name)
    self.pipe0  = self.Pipe();
    self.iface1 = self.addInterface(if1name)
    self.pipe1  = self.Pipe();
  def _write (self, root):
    nd = super(Bridge, self)._write(root)
    st = nd.find("{%s}sliver_type" % (GNS.REQUEST.name))
    delay = ET.SubElement(st, "{%s}sliver_type_shaping" % (PGNS.DELAY.name))
    pipe0 = ET.SubElement(delay, "{%s}pipe" % (PGNS.DELAY.name))
    pipe0.attrib["source"]    = self.iface0.name
    pipe0.attrib["dest"]      = self.iface1.name
    pipe0.attrib["capacity"]  = str(self.pipe0.bandwidth)
    pipe0.attrib["latency"]   = str(self.pipe0.latency)
    pipe0.attrib["lossrate"]  = str(self.pipe0.lossrate)
    pipe1 = ET.SubElement(delay, "{%s}pipe" % (PGNS.DELAY.name))
    pipe1.attrib["source"]    = self.iface1.name
    pipe1.attrib["dest"]      = self.iface0.name
    pipe1.attrib["capacity"]  = str(self.pipe1.bandwidth)
    pipe1.attrib["latency"]   = str(self.pipe1.latency)
    pipe1.attrib["lossrate"]  = str(self.pipe1.lossrate)
    return nd;
  # pipe0 goes with iface0, and pipe1 goes with iface1
[docs]  def pipe (self, which):
    which = self.client_id + ":" + which
    if self.iface0.name == which:
      return self.pipe0
    else:
      return self.pipe1 
  
[docs]  def interface (self, which):
    which = self.client_id + ":" + which
    if self.iface0.name == which:
      return self.iface0
    else:
      return self.iface1  
pg.Request.EXTENSIONS.append(("Bridge", Bridge))
[docs]class Firewall(object):
[docs]  class Style(object):
    OPEN     = "open"
    CLOSED   = "closed"
    BASIC    = "basic" 
[docs]  class Direction(object):
    INCOMING = "incoming"
    OUTGOING = "outgoing" 
  def __init__ (self, style):
    self.style = style
    self.exceptions = []
[docs]  def addException(self, port, direction, ip = None):
    self.exceptions.append({"port" : port, "direction" : direction, "ip" : ip}) 
  def _write (self, node):
    fw = ET.SubElement(node, "{%s}firewall" % (PGNS.EMULAB))
    fw.attrib["style"] = self.style
    for excep in self.exceptions:
      ex = ET.SubElement(fw, "exception")
      ex.attrib["port"]      = str(excep["port"])
      ex.attrib["direction"] = excep["direction"]
      if excep["ip"]:
        ex.attrib["ip"] = excep["ip"]
    return fw 
XenVM.EXTENSIONS.append(("Firewall", Firewall))
[docs]class Tour(object):
  TEXT = "text"
  MARKDOWN = "markdown"
  # One or more blank lines, followed by "Instructions:" on it's own line, then
  # zero or more blank lines. Eats the blank lines.
  SPLIT_REGEX = re.compile(r"\n+^\w*instructions\w*:?\w*$\n+",
                           re.IGNORECASE | re.MULTILINE)
  def __init__ (self):
    self.description = None
    # Type can markdown
    self.description_type = Tour.TEXT
    self.instructions = None
    # Type can markdown
    self.instructions_type = Tour.TEXT
[docs]  def Description(self, type, desc):
    self.description_type = type
    self.description = desc 
[docs]  def Instructions(self, type, inst):
    self.instructions_type = type
    self.instructions = inst 
[docs]  def useDocstring(self, module = None):
    if module is None:
      module = sys.modules["__main__"]
    if not self.description and module.__doc__:
      docstr = inspect.getdoc(module)
      docparts = Tour.SPLIT_REGEX.split(docstr,2)
      self.Description(Tour.MARKDOWN,docparts[0])
      if len(docparts) == 2 and not self.instructions:
        self.Instructions(Tour.MARKDOWN,docparts[1])
      return True
    else:
      return False 
  def _write (self, root):
    #
    # Please do it this way, until some of our JS code is fixed.
    #
    td = ET.SubElement(root, "rspec_tour",
                       nsmap={None : PGNS.TOUR.name})
    if self.description:
      desc = ET.SubElement(td, "description")
      desc.text = self.description
      desc.attrib["type"] = self.description_type
    if self.instructions:
      inst = ET.SubElement(td, "instructions")
      inst.text = self.instructions
      inst.attrib["type"] = self.instructions_type
    return td 
[docs]class ParameterData(object):
  def __init__ (self, parameters):
    self.parameters = parameters
  def _write (self, root):
    td = ET.SubElement(root, "data_set",
                       nsmap={None : PGNS.PARAMS.name})
    for param in self.parameters:
      that = self.parameters[param]
      if 'hide' in that and that['hide'] is False:
        desc = ET.SubElement(td, "data_item")
        desc.attrib["name"] = that['prefix'] + param
        if isinstance(that['value'], ET._Element):
          desc.append(that['value'])
        else:
          desc.text = str(that['value']) 
pg.Request.EXTENSIONS.append(("ParameterData", ParameterData))
[docs]class Site(object):
  def __init__ (self, id):
    self.id = id
  def _write (self, node):
    site = ET.SubElement(node, "{%s}site" % (PGNS.JACKS))
    site.attrib["id"] = self.id
    return site 
pg.Node.EXTENSIONS.append(("Site", Site))
[docs]class Desire(object):
  def __init__ (self, name, weight):
    self.name = name
    self.weight = weight
  def _write (self, node):
    fd = ET.SubElement(node, "{%s}fd" % (PGNS.EMULAB))
    fd.attrib["name"] = self.name
    fd.attrib["weight"] = str(self.weight)
    return fd 
pg.Node.EXTENSIONS.append(("Desire", Desire))
[docs]class Password(Resource):
  """A declaration for a randomly generated password.
The portal will generate the password, encrypt it, and pass on the
encrypted value to the AM(s) and therefore the node(s)."""
  def __init__(self, name=None):
    super(Password, self).__init__()
    self.name = name
  def _write (self, root):
    pl = ET.SubElement(root, "{%s}password" % (PGNS.EMULAB.name))
    if self.name:
      pl.attrib["name"] = self.name
    return pl