xos plugin sits on top of ml2 plugin, for Icehouse
Scott Baker [Tue, 24 Feb 2015 08:11:51 +0000 (00:11 -0800)]
ml2_plugin/README [new file with mode: 0644]
ml2_plugin/__init__.py [new file with mode: 0644]
ml2_plugin/nat.py [new file with mode: 0644]
ml2_plugin/upload.sh [new file with mode: 0755]
ml2_plugin/xos_db_v2.py [new file with mode: 0644]
ml2_plugin/xos_models_v2.py [new file with mode: 0644]
ml2_plugin/xos_neutron_plugin.py [new file with mode: 0644]

diff --git a/ml2_plugin/README b/ml2_plugin/README
new file mode 100644 (file)
index 0000000..778916c
--- /dev/null
@@ -0,0 +1,8 @@
+See upload.sh for a list of files to upload and which directories to put them
+
+On the neutron controller node, edit /etc/neutron/neutron.conf and set:
+    core_plugin = neutron.plugins.xos.xos_neutron_plugin.XOSPluginV2
+
+Then run 
+   stop neutron_server
+   start neutron_server
diff --git a/ml2_plugin/__init__.py b/ml2_plugin/__init__.py
new file mode 100644 (file)
index 0000000..8b13789
--- /dev/null
@@ -0,0 +1 @@
+
diff --git a/ml2_plugin/nat.py b/ml2_plugin/nat.py
new file mode 100644 (file)
index 0000000..04e39f1
--- /dev/null
@@ -0,0 +1,50 @@
+from neutron.api.v2 import attributes
+
+FORWARD_PORTS = 'nat:forward_ports'
+
+EXTENDED_ATTRIBUTES_2_0 = {
+    'ports': {
+        FORWARD_PORTS: {'allow_post': True, 'allow_put': True,
+                       'default': attributes.ATTR_NOT_SPECIFIED,
+                       'is_visible': True},
+    }
+}
+
+
+class Nat(object):
+    """Extension class supporting OpenCloud NAT networking
+
+    This class is used by Quantum's extension framework to make
+    metadata about the OpenCloud Port extension available to
+    clients. No new resources are defined by this extension. Instead,
+    the existing Port resource's request and response messages are
+    extended with attributes in the OpenCloud namespace.
+    """
+
+    @classmethod
+    def get_name(cls):
+        return "OpenCloud NAT Networking Extension"
+
+    @classmethod
+    def get_alias(cls):
+        return "nat"
+
+    @classmethod
+    def get_description(cls):
+        return "Add TCP/UDP port forwarding through NAT to Quantum Port objects"
+
+    @classmethod
+    def get_namespace(cls):
+        # return "http://docs.openstack.org/ext/provider/api/v1.0"
+        # Nothing there right now
+        return "http://www.vicci.org/ext/opencloud/nat/api/v0.1"
+
+    @classmethod
+    def get_updated(cls):
+        return "2013-09-12T10:00:00-00:00"
+
+    def get_extended_resources(self, version):
+        if version == "2.0":
+            return EXTENDED_ATTRIBUTES_2_0
+        else:
+            return {}
diff --git a/ml2_plugin/upload.sh b/ml2_plugin/upload.sh
new file mode 100755 (executable)
index 0000000..dd6d911
--- /dev/null
@@ -0,0 +1,4 @@
+#! /bin/bash
+# head node
+scp xos_db_v2.py xos_models_v2.py xos_neutron_plugin.py __init__.py neutron-washington-beta:/usr/lib/python2.7/dist-packages/neutron/plugins/xos/
+scp nat.py neutron-washington-beta:/usr/lib/python2.7/dist-packages/neutron/extensions/
diff --git a/ml2_plugin/xos_db_v2.py b/ml2_plugin/xos_db_v2.py
new file mode 100644 (file)
index 0000000..6342779
--- /dev/null
@@ -0,0 +1,31 @@
+from sqlalchemy import func
+from sqlalchemy.orm import exc
+
+import neutron.db.api as db
+import xos_models_v2
+
+def get_port_forwarding(session, port_id):
+    session = session or db.get_session()
+    try:
+        forward = (session.query(xos_models_v2.PortForwarding).
+                   filter_by(port_id=port_id).one())
+        return forward['forward_ports']
+    except exc.NoResultFound:
+        return
+
+def clear_port_forwarding(session, port_id):
+    with session.begin(subtransactions=True):
+        try:
+            # Get rid of old port bindings
+            forward = (session.query(xos_models_v2.PortForwarding).
+                       filter_by(port_id=port_id).one())
+            if forward:
+                session.delete(forward)
+        except exc.NoResultFound:
+            pass
+
+def add_port_forwarding(session, port_id, forward_ports):
+    with session.begin(subtransactions=True):
+        forward = xos_models_v2.PortForwarding(port_id, forward_ports)
+        session.add(forward)
+
diff --git a/ml2_plugin/xos_models_v2.py b/ml2_plugin/xos_models_v2.py
new file mode 100644 (file)
index 0000000..c786a19
--- /dev/null
@@ -0,0 +1,21 @@
+from sqlalchemy import Boolean, Column, ForeignKey, Integer, String, PickleType
+from sqlalchemy.schema import UniqueConstraint
+
+from neutron.db.models_v2 import model_base
+
+class PortForwarding(model_base.BASEV2):
+    """Ports to be forwarded through NAT """
+    __tablename__ = 'xos_port_forwarding'
+
+    port_id = Column(String(36),
+                     ForeignKey('ports.id', ondelete="CASCADE"),
+                     primary_key=True)
+    forward_ports = Column(PickleType)
+
+    def __init__(self, port_id, forward_ports):
+        self.port_id = port_id
+        self.forward_ports = forward_ports
+
+    def __repr__(self):
+        return "<PortForwarding(%s,%s)>" % (self.port_id, self.forward_ports)
+
diff --git a/ml2_plugin/xos_neutron_plugin.py b/ml2_plugin/xos_neutron_plugin.py
new file mode 100644 (file)
index 0000000..8dcbd7a
--- /dev/null
@@ -0,0 +1,84 @@
+from neutron.plugins.ml2.plugin import *
+from neutron.extensions import nat
+import xos_db_v2
+
+"""
+mkdir /usr/lib/python2.7/dist-packages/neutron/plugins/xos
+"""
+
+class XOSPluginV2(Ml2Plugin):
+    _supported_extension_aliases = Ml2Plugin._supported_extension_aliases + ["nat"]
+
+    def _extend_port_dict_nat(self, context, port):
+        forward = xos_db_v2.get_port_forwarding(context.session, port['id'])
+        if forward:
+            port[nat.FORWARD_PORTS] = forward
+        else:
+            port[nat.FORWARD_PORTS] = None
+
+    def _process_nat_update(self, context, attrs, id):
+        forward_ports = attrs.get(nat.FORWARD_PORTS)
+        forward_ports_set = attributes.is_attr_set(forward_ports)
+
+        if not forward_ports_set:
+            return None
+
+        # LOG.info("forward ports %s" % forward_ports)
+        valid_protocols = ["tcp", "udp"]
+        for entry in forward_ports:
+            if not isinstance(entry, dict):
+                msg = _("nat:forward_ports: must specify a list of dicts (ex: 'l4_protocol=tcp,l4_port=80')")
+                raise q_exc.InvalidInput(error_message=msg)
+            if not ("l4_protocol" in entry and "l4_port" in entry):
+                msg = _("nat:forward_ports: dict is missing l4_protocol and l4_port (ex: 'l4_protocol=tcp,l4_port=80')")
+                raise q_exc.InvalidInput(error_message=msg)
+            if entry['l4_protocol'] not in valid_protocols:
+                msg = _("nat:forward_ports: invalid protocol (only tcp and udp allowed)")
+                raise q_exc.InvalidInput(error_message=msg)
+
+            l4_port = entry['l4_port']
+            if ":" in l4_port:
+                try:
+                    (first, last) = l4_port.split(":")
+                    first = int(first)
+                    last = int(last)
+                except:
+                    msg = _("nat:forward_ports: l4_port range must be integer:integer")
+                    raise q_exc.InvalidInput(error_message=msg)
+            else:
+                try:
+                    l4_port = int(l4_port)
+                except:
+                    msg = _("nat:forward_ports: l4_port must be an integer")
+                    raise q_exc.InvalidInput(error_message=msg)
+
+        return forward_ports
+
+    def get_port(self, context, id, fields=None):
+        session = context.session
+        with session.begin(subtransactions=True):
+            port = super(XOSPluginV2, self).get_port(context, id, None)
+            self._extend_port_dict_nat(context, port)
+        return self._fields(port, fields)
+
+    def get_ports(self, context, filters=None, fields=None):
+        session = context.session
+        with session.begin(subtransactions=True):
+            ports = super(XOSPluginV2, self).get_ports(context, filters,
+                                                          None)
+            for port in ports:
+                self._extend_port_dict_nat(context, port)
+
+        return [self._fields(port, fields) for port in ports]
+
+    def update_port(self, context, id, port):
+        forward_ports = self._process_nat_update(context, port['port'], id)
+        session = context.session
+        with session.begin(subtransactions=True):
+            updated_port = super(XOSPluginV2, self).update_port(context, id, port)
+            if forward_ports:
+                xos_db_v2.clear_port_forwarding(session, updated_port['id'])\r
+                xos_db_v2.add_port_forwarding(session, updated_port['id'], forward_ports)\r
+                self._extend_port_dict_nat(context, updated_port)
+
+        return updated_port