PlanetLab API functions for slice autoconfig
smbaker [Tue, 15 Jan 2013 07:01:14 +0000 (23:01 -0800)]
apps/gacks/SliceAutoConfig.py [new file with mode: 0644]
apps/gacks/SliceGetConfig.py [new file with mode: 0644]
apps/gacks/bridgeconfig.py

diff --git a/apps/gacks/SliceAutoConfig.py b/apps/gacks/SliceAutoConfig.py
new file mode 100644 (file)
index 0000000..bffc442
--- /dev/null
@@ -0,0 +1,39 @@
+import functools
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Slices import Slice
+
+import PLC.bridgeconfig
+
+class _API:
+    def __init__(self, api, auth):
+        self.api = api
+        self.auth = auth
+
+    def __getattr__(self, name):
+        return functools.partial(self.api.callable(name), self.auth)
+
+class SliceAutoConfig(Method):
+    """
+    Auto-configures the slice.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        Parameter(dict, "options"),
+        ]
+
+    returns = Parameter(int, '1 if successful, 0 otherwise')
+
+    def call(self, auth, slice_id_or_name, options):
+        PLC.bridgeconfig.AutoConfig(_API(self.api, auth), slice_id_or_name, options)
+
+        return 1
+
diff --git a/apps/gacks/SliceGetConfig.py b/apps/gacks/SliceGetConfig.py
new file mode 100644 (file)
index 0000000..3e69a86
--- /dev/null
@@ -0,0 +1,36 @@
+import functools
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+from PLC.Slices import Slice
+
+import PLC.bridgeconfig
+
+class _API:
+    def __init__(self, api, auth):
+        self.api = api
+        self.auth = auth
+
+    def __getattr__(self, name):
+        return functools.partial(self.api.callable(name), self.auth)
+
+class SliceGetConfig(Method):
+    """
+    Get the auto-configuration settings for the slice.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed(Slice.fields['slice_id'],
+              Slice.fields['name']),
+        ]
+
+    returns = Parameter(dict, 'autoconfig settings')
+
+    def call(self, auth, slice_id_or_name):
+        return PLC.bridgeconfig.WizardHelper(_API(self.api, auth), slice_id_or_name).get_wizard_config()
+
index 7be00d0..9276782 100644 (file)
-class NetworkBuilder:
-    def __init__(self, plc):
+try:
+   # 2.6 and above
+   from ast import literal_eval
+except ImportError:
+   # below 2.6, unsafe
+   from __builtin__ import eval as literal_eval
+
+class WizardHelper:
+    """ Base class for wizards, defines several helper methods for getting
+        at slice tags, accessing the wizardconfig tag, etc.
+    """
+
+    def __init__(self, plc, slice):
         self.plc = plc
 
-    def set_tag(self, tagname, slice_id, node_id, value):
+        if isinstance(slice, dict):
+            # do nothing; caller passed us a slice record
+            pass
+        else:
+            slices = self.plc.GetSlices(slice, ["name", "slice_id", "node_ids", "slice_tag_ids"])
+            if not slices:
+                raise SliceNotFound(slice)
+            slice = slices[0]
+
+        self.slice = slice
+        self.slice_id = slice['slice_id']
+        self.wizard_config = None
+
+    def get_tag(self, tagname, node_id=None):
+        if node_id is not None:
+            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id, "node_id": node_id})
+        else:
+            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
+
+        if len(tags) == 0:
+            return None
+
+        return tags[0]
+
+    def get_tag_value(self, tagname, node_id=None):
+        tag = self.get_tag(tagname, node_id)
+        if tag is None:
+            return None
+        return tag["value"]
+
+    def set_tag(self, tagname, node_id, value):
         if node_id is not None:
-            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": slice_id, "node_id": node_id})
+            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id, "node_id": node_id})
         else:
-            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": slice_id})
+            tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
+
         if tags:
-            if tags[0]["value"] != value:
+            if (value == None):
+                self.plc.DeleteSliceTag(tags[0]["slice_tag_id"])
+            elif tags[0]["value"] != value:
                 self.plc.UpdateSliceTag(tags[0]['slice_tag_id'], value)\r
-        else:\r
-            self.plc.AddSliceTag(slice_id, tagname, value, node_id)
+        elif (value != None):\r
+            self.plc.AddSliceTag(self.slice_id, tagname, value, node_id)
+
+    def clear_tags(self, tagname):
+        tags = self.plc.GetSliceTags({"tagname": tagname, "slice_id": self.slice_id})
+        for tag in tags:
+            self.plc.DeleteSliceTag(tag["slice_tag_id"])
+
+    def get_wizard_config(self, name=None, default=None):
+        if self.wizard_config is not None:
+            return self.wizard_config
+
+        self.wizard_config = {}
+
+        tag = self.get_tag("slice_wizardconfig", None)
+        if tag is not None:
+            try:
+                self.wizard_config = literal_eval(tags[0]["value"])
+            except:
+                self.wizard_config = {}
+
+        if name:
+            return self.wizard_config.get("name", default)
+
+        return self.wizard_config
+
+    def update_wizard_config(self, name=None, value=None):
+        if self.wizard_config is None:
+            self.get_wizard_config()
+
+        if name:
+            self.wizard_config[name] = value
+
+        self.set_tag("slice_wizardconfig", None, str(self.wizard_config))
+
+    def get_master_node(self):
+        return self.get_wizard_config("master_node", None)
+
+    def set_master_node(self, master_node):
+        self.update_wizard_config("master_node", master_node)
+
+    def get_network_method(self):
+        return self.get_wizard_config("network_method", None)
+
+    def set_network_method(self, network_method):
+        self.update_wizard_config("network_method", network_method)
+
+    def pick_headnode(self, nodes):
+        """ Picks a headnode from the list of nodes, if a headnode hasn't
+            already been picked.
+
+            Orders the list of nodes so that the headnode is always at the
+            top.
+        """
+
+        if not nodes:
+            # there are no nodes, we can't pick anything
+            return (nodes, None)
+
+        headnode = None
+        headnode_hostname = self.get_master_node()
+        for node in nodes:
+            if headnode_hostname == node["hostname"]:
+                headnode = node
+
+        if headnode:
+            # move headnode up to the front
+            nodes = [headnode] + [x for x in nodes if x!=headnode]
+        else:
+            headnode = nodes[0]
+            self.set_master_node(headnode["hostname"])
+
+        return (nodes, headnode)
+
+class NetworkBuilder(WizardHelper):
+    def __init__(self, plc, slice):
+        WizardHelper.__init__(self, plc, slice)
+        self.want_default_interface = True
+
+    def add_host(self, hostmap, node, address):
+        hostmap.append("%s %s" % (address, node["hostname"]))
+
+    def configure_slice(self):
+        raise "Not Implemented"
 
 class NetworkBuilder_gre(NetworkBuilder):
     """ This sets up nodes using private bridges and GRE tunnels.
@@ -27,16 +153,11 @@ class NetworkBuilder_gre(NetworkBuilder):
         bottleneck.
     """
 
-    def __init__(self, plc):
-        NetworkBuilder.__init__(self, plc)
-
-    def configure_slice(self, slice_name_or_id):
-        slices = self.plc.GetSlices(slice_name_or_id, ["slice_id", "node_ids", "slice_tag_ids"])
-        if not slices:
-            # we couldn't find it
-            return
+    def __init__(self, plc, slice):
+        NetworkBuilder.__init__(self, plc, slice)
 
-        slice = slices[0]
+    def configure_slice(self):
+        slice = self.slice
         slice_id = slice["slice_id"]
 
         nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname"])
@@ -52,10 +173,10 @@ class NetworkBuilder_gre(NetworkBuilder):
         nodes = [x for x in nodes if node_ips.get(x["node_id"], None)]
 
         bridge_name = "br-slice-" + str(slice_id)
-        self.set_tag("slice_bridge_name", slice_id, None, bridge_name)
+        self.set_tag("slice_bridge_name", None, bridge_name)
 
         hostmap = []
-        for (node_id,neighbor_list) in topology.items():
+        for (node_id, neighbor_list) in topology.items():
             sliver_ip_addr = "10.2.%d.%d" % (node_id/254, node_id%254+1)
             bridge_ip_addr = "10.1.%d.%d" % (node_id/254, node_id%254+1)
 
@@ -63,22 +184,29 @@ class NetworkBuilder_gre(NetworkBuilder):
             for neighbor_id in neighbor_list:
                 neighbors.append("%d/%s" % (neighbor_id, node_ips[neighbor_id]))
 
-            self.set_tag("slice_bridge_addr", slice_id, node_id, bridge_ip_addr)
-            self.set_tag("slice_bridge_neighbors", slice_id, node_id, ",".join(neighbors))
-            self.set_tag("interface", slice_id, node_id, "{'bridge':'%s', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': '%s', 'NETMASK': '255.0.0.0'}" % (bridge_name, sliver_ip_addr))
+            interface_tag = []
+            interface_tag.append({'bridge':bridge_name, 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': sliver_ip_addr, 'NETMASK': '255.0.0.0'})
+            if self.want_default_interface:
+                interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
+
+            self.set_tag("slice_bridge_addr", node_id, bridge_ip_addr)
+            self.set_tag("slice_bridge_neighbors", node_id, ",".join(neighbors))
+            self.set_tag("interface", node_id, str(interface_tag))
+
+            node = [x for x in nodes if x["node_id"] == node_id][0]
 
-            hostmap.append("%s node%d" % (sliver_ip_addr, node_id))
+            self.add_host(hostmap, node, sliver_ip_addr)
 
-        self.set_tag("slice_hostmap", slice_id, None, "\n".join(hostmap))
+        self.set_tag("slice_hostmap", None, "\n".join(hostmap))
+        self.set_network_method("gre")
 
     def generate_star_topology(self, nodes):
-        if not nodes:
+        (nodes, headnode) = self.pick_headnode(nodes)
+        if not headnode:
             # if there are no nodes, then there is no topology
             return {}
 
         topology = {}
-
-        headnode = nodes[0]
         headnode_id = headnode["node_id"]
         for node in nodes[1:]:
             node_id = node["node_id"]
@@ -87,7 +215,7 @@ class NetworkBuilder_gre(NetworkBuilder):
 
         return topology
 
-class NetworkBuilder_10(NetworkBuilder):
+class NetworkBuilder_IPv4_shared(NetworkBuilder):
     """ This sets up each slice as a class A private network.
 
         10.<slice_id_hi>.<slice_id_lo>.<node_id>
@@ -98,19 +226,15 @@ class NetworkBuilder_10(NetworkBuilder):
         Nodes in one site cannot talk to nodes in another site
     """
 
-    def __init__(self, plc):
-        NetworkBuilder.__init__(self, plc)
+    def __init__(self, plc, slice):
+        NetworkBuilder.__init__(self, plc, slice)
 
-    def configure_slice(self, slice_name_or_id):
-        slices = self.plc.GetSlices(slice_name_or_id, ["slice_id", "node_ids", "slice_tag_ids"])
-        if not slices:
-            # we couldn't find it
-            return
-
-        slice = slices[0]
+    def configure_slice(self):
+        slice = self.slice
         slice_id = slice["slice_id"]
 
         nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname", "site_id"])
+        (nodes, headnode) = self.pick_headnode(nodes)
 
         site_ids = []
         for node in nodes:
@@ -123,10 +247,92 @@ class NetworkBuilder_10(NetworkBuilder):
             for (i, node_id) in enumerate(site["node_ids"]):
                 node_indices[node_id] = i
 
+        hostmap = []
         for node in nodes:
             node_id = node["node_id"]
             node_index = node_indices.get(node_id, 0)
-            ip_addr = "10.%d.%d.%d" % (slice_id>>8, slice_id&0xFF, node_index + 1)
-            self.set_tag("interface", slice_id, node_id, "{'bridge':'public0', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': '%s', 'NETMASK': '255.255.255.0'}" % ip_addr)
+            sliver_ip_addr = "10.%d.%d.%d" % (slice_id>>8, slice_id&0xFF, node_index + 1)
+            interface_tag = []
+            interface_tag.append({'bridge':'public0', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPADDR': sliver_ip_addr, 'NETMASK': '255.255.255.0'})
+            if self.want_default_interface:
+                interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
+            self.set_tag("interface", node_id, str(interface_tag))
+
+            self.add_host(hostmap, node, sliver_ip_addr)
+
+        self.set_tag("slice_hostmap", None, "\n".join(hostmap))
+        self.set_network_method("ipv4_shared")
+        self.clear_tags("slice_bridge_name")
+        self.clear_tags("slice_bridge_addr")
+        self.clear_tags("slice_bridge_neighbors")
+
+class NetworkBuilder_IPv6_shared(NetworkBuilder):
+    """ This sets up each slice as a class A private network.
+
+        10.<slice_id_hi>.<slice_id_lo>.<node_id>
+
+        There can be at most 65535 slice_ids. There can be at most 255 nodes
+        per site.
+
+        Nodes in one site cannot talk to nodes in another site
+    """
+
+    def __init__(self, plc, slice):
+        NetworkBuilder.__init__(self, plc, slice)
+
+    def configure_slice(self):
+        slice = self.slice
+        slice_id = slice["slice_id"]
+
+        nodes = self.plc.GetNodes(slice["node_ids"], ["node_id", "hostname", "site_id"])
+        (nodes, headnode) = self.pick_headnode(nodes)
+
+        hostmap = []
+        for node in nodes:
+            node_id = node["node_id"]
+            ipv6addr = "fec0::%04x:%04x" % (slice_id, node_id)
+            interface_tag = []
+            interface_tag.append({'bridge':'public0', 'DEVICE':'eth0', 'BOOTPROTO':'static', 'ONBOOT':'yes', 'PRIMARY':'yes', 'IPV6ADDR':ipv6addr, 'IPV6INIT': 'yes'})
+            if self.want_default_interface:
+                interface_tag.append({'DEVICE':'eth1', 'BOOTPROTO':'dhcp', 'ONBOOT':'yes'})
+            self.set_tag("interface", node_id, str(interface_tag))
+
+            self.add_host(hostmap, node, ipv6addr)
+
+        self.set_tag("slice_hostmap", None, "\n".join(hostmap))
+        self.set_network_method("ipv6_shared")
+        self.clear_tags("slice_bridge_name")
+        self.clear_tags("slice_bridge_addr")
+        self.clear_tags("slice_bridge_neighbors")
+
+class NetworkBuilder_default(NetworkBuilder):
+    def __init__(self, plc, slice):
+        NetworkBuilder.__init__(self, plc, slice)
+
+    def configure_slice(self):
+        self.set_network_method(None)
+        self.clear_tags("slice_hostmap")
+        self.clear_tags("slice_bridge_name")
+        self.clear_tags("slice_bridge_addr")
+        self.clear_tags("slice_bridge_neighbors")
+        self.clear_tags("interface")
+
+def AutoConfig(plc, slice, options={}):
+    wizard = WizardHelper(plc, slice)
+    slice = wizard.slice # wizardhelper will resolve this into a slice object
+
+    network_method = options.get("network_method", None)
+    if network_method is None:
+        network_method = wizard.get_network_method()
+
+    if network_method == "gre":
+        NetworkBuilder_gre(plc, slice).configure_slice()
+    elif network_method == "ipv4_shared":
+        NetworkBuilder_IPv4_shared(plc, slice).configure_slice()
+    elif network_method == "ipv6_shared":
+        NetworkBuilder_IPv6_shared(plc, slice).configure_slice()
+    else:
+        NetworkBuilder_default(plc, slice).configure_slice()
+