first draft for ilinks : typed and valued links between interfaces
authorThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Tue, 27 May 2008 15:18:55 +0000 (15:18 +0000)
committerThierry Parmentelat <thierry.parmentelat@sophia.inria.fr>
Tue, 27 May 2008 15:18:55 +0000 (15:18 +0000)
e.g.
AddIlinkType ('dummynet')
AddIlink (interface_id_src, interface_id_dst, 'dummynet', 'a value if that makes sense')

TODO: clean up ilinks when nodes or interfaces get deleted

16 files changed:
Makefile
PLC/IlinkTypes.py [new file with mode: 0644]
PLC/Ilinks.py [new file with mode: 0644]
PLC/Methods/AddIlink.py [new file with mode: 0644]
PLC/Methods/AddIlinkType.py [new file with mode: 0644]
PLC/Methods/AddNodeTag.py
PLC/Methods/DeleteIlink.py [new file with mode: 0644]
PLC/Methods/DeleteIlinkType.py [new file with mode: 0644]
PLC/Methods/GetIlinkTypes.py [new file with mode: 0644]
PLC/Methods/GetIlinks.py [new file with mode: 0644]
PLC/Methods/UpdateIlink.py [new file with mode: 0644]
PLC/Methods/UpdateIlinkType.py [new file with mode: 0644]
PLC/Methods/__init__.py
PLC/__init__.py
migrations/migrate-v4-to-v5.sql
planetlab5.sql

index 276cc05..d15af83 100644 (file)
--- a/Makefile
+++ b/Makefile
@@ -51,7 +51,7 @@ index: $(init)
 index-clean:
        rm $(init)
 
-####################
+#################### regenerate indexes - not used by the build, as both files are svn added - please update as appropriate
 # All .py files in PLC/
 
 # the current content of __init__.py
diff --git a/PLC/IlinkTypes.py b/PLC/IlinkTypes.py
new file mode 100644 (file)
index 0000000..bb11979
--- /dev/null
@@ -0,0 +1,77 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from types import StringTypes
+
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Roles import Role, Roles
+
+class IlinkType (Row):
+
+    """
+    Representation of a row in the ilink_types table.
+    """
+
+    table_name = 'ilink_types'
+    primary_key = 'ilink_type_id'
+    join_tables = ['ilink']
+    fields = {
+        'ilink_type_id': Parameter(int, "ilink type identifier"),
+        'name': Parameter(str, "ilink type name", max = 100),
+        'description': Parameter(str, "ilink type description", max = 254),
+        'min_role_id': Parameter(int, "Minimum (least powerful) role that can set or change this attribute"),
+        }
+
+    def validate_name(self, name):
+        if not len(name):
+            raise PLCInvalidArgument, "ilink type name must be set"
+
+        conflicts = IlinkTypes(self.api, [name])
+        for tag_type in conflicts:
+            if 'ilink_type_id' not in self or \
+                   self['ilink_type_id'] != tag_type['ilink_type_id']:
+                raise PLCInvalidArgument, "ilink type name already in use"
+
+        return name
+
+    def validate_min_role_id(self, role_id):
+        roles = [row['role_id'] for row in Roles(self.api)]
+        if role_id not in roles:
+            raise PLCInvalidArgument, "Invalid role"
+
+        return role_id
+
+class IlinkTypes(Table):
+    """
+    Representation of row(s) from the ilink_types table
+    in the database.
+    """
+
+    def __init__(self, api, ilink_type_filter = None, columns = None):
+        Table.__init__(self, api, IlinkType, columns)
+
+        sql = "SELECT %s FROM ilink_types WHERE True" % \
+              ", ".join(self.columns)
+
+        if ilink_type_filter is not None:
+            if isinstance(ilink_type_filter, (list, tuple, set)):
+                # Separate the list into integers and strings
+                ints = filter(lambda x: isinstance(x, (int, long)), ilink_type_filter)
+                strs = filter(lambda x: isinstance(x, StringTypes), ilink_type_filter)
+                ilink_type_filter = Filter(IlinkType.fields, {'ilink_type_id': ints, 'name': strs})
+                sql += " AND (%s) %s" % ilink_type_filter.sql(api, "OR")
+            elif isinstance(ilink_type_filter, dict):
+                ilink_type_filter = Filter(IlinkType.fields, ilink_type_filter)
+                sql += " AND (%s) %s" % ilink_type_filter.sql(api, "AND")
+            elif isinstance (ilink_type_filter, StringTypes):
+                ilink_type_filter = Filter(IlinkType.fields, {'name':[ilink_type_filter]})
+                sql += " AND (%s) %s" % ilink_type_filter.sql(api, "AND")
+            else:
+                raise PLCInvalidArgument, "Wrong ilink type filter %r"%ilink_type_filter
+
+        self.selectall(sql)
diff --git a/PLC/Ilinks.py b/PLC/Ilinks.py
new file mode 100644 (file)
index 0000000..f6c9586
--- /dev/null
@@ -0,0 +1,53 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Faults import *
+from PLC.Parameter import Parameter
+from PLC.Filter import Filter
+from PLC.Table import Row, Table
+from PLC.Interfaces import Interface, Interfaces
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+
+class Ilink(Row):
+    """
+    Representation of a row in the ilink table.
+    To use, instantiate with a dict of values.
+    """
+
+    table_name = 'ilink'
+    primary_key = 'ilink_id'
+    fields = {
+        'ilink_id': Parameter(int, "ilink identifier"),
+        'ilink_type_id': IlinkType.fields['ilink_type_id'],
+        'src_interface_id': Parameter(int, "source interface identifier"),
+        'dst_interface_id': Parameter(int, "destination interface identifier"),
+        'value': Parameter( str, "optional ilink value"),
+        }
+
+class Ilinks(Table):
+    """
+    Representation of row(s) from the ilink table in the
+    database.
+    """
+
+    def __init__(self, api, ilink_filter = None, columns = None):
+        Table.__init__(self, api, Ilink, columns)
+
+        sql = "SELECT %s FROM view_ilinks WHERE True" % \
+              ", ".join(self.columns)
+
+        if ilink_filter is not None:
+            if isinstance(ilink_filter, (list, tuple, set)):
+                ilink_filter = Filter(Ilink.fields, {'ilink_id': ilink_filter})
+            elif isinstance(ilink_filter, dict):
+                ilink_filter = Filter(Ilink.fields, ilink_filter)
+            elif isinstance(ilink_filter, int):
+                ilink_filter = Filter(Ilink.fields, {'ilink_id': [ilink_filter]})
+            else:
+                raise PLCInvalidArgument, "Wrong ilink filter %r"%ilink_filter
+            sql += " AND (%s) %s" % ilink_filter.sql(api)
+
+
+        self.selectall(sql)
diff --git a/PLC/Methods/AddIlink.py b/PLC/Methods/AddIlink.py
new file mode 100644 (file)
index 0000000..c2c22c5
--- /dev/null
@@ -0,0 +1,92 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+from PLC.Ilinks import Ilink, Ilinks
+from PLC.Interfaces import Interface, Interfaces
+
+from PLC.Sites import Sites
+
+class AddIlink(Method):
+    """
+    Create a link between two interfaces
+    The link has a type, that needs be created beforehand
+    and an optional value. 
+
+    Returns the new ilink_id (> 0) if successful, faults
+    otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        # refer to either the id or the type name
+        Ilink.fields['src_interface_id'],
+        Ilink.fields['dst_interface_id'],
+        Mixed(IlinkType.fields['ilink_type_id'],
+              IlinkType.fields['name']),
+        Ilink.fields['value'],
+        ]
+
+    returns = Parameter(int, 'New ilink_id (> 0) if successful')
+
+    def call(self, auth,  src_if_id, dst_if_id, ilink_type_id_or_name, value):
+
+        src_if = Interfaces (self.api, [src_if_id],[interface_id])
+        if not src_if:
+            raise PLCInvalidArgument, "No such source interface %r"%src_if_id
+        dst_if = Interfaces (self.api, [dst_if_id],[interface_id])
+        if not dst_if:
+            raise PLCInvalidArgument, "No such destination interface %r"%dst_if_id
+
+        ilink_types = IlinkTypes(self.api, [ilink_type_id_or_name])
+        if not ilink_types:
+            raise PLCInvalidArgument, "No such ilink type %r"%ilink_type_id_or_name
+        ilink_type = ilink_types[0]
+
+       # checks for existence - with the same type
+        conflicts = Ilinks(self.api,
+                           {'ilink_type_id':ilink_type['ilink_type_id'],
+                            'src_interface_id':src_if_id,
+                            'dst_interface_id':dst_if_id,})
+
+        if len(conflicts) :
+            ilink=conflicts[0]
+            raise PLCInvalidArgument, "Ilink (%s,%d,%d) already exists and has value %r"\
+                %(ilink_type['name'],src_if_id,dst_if_id,ilink['value'])
+
+       if 'admin' not in self.caller['roles']:
+#      # check permission : it not admin, is the user affiliated with the right site(s) ????
+#          # locate node
+#          node = Nodes (self.api,[node['node_id']])[0]
+#          # locate site
+#          site = Sites (self.api, [node['site_id']])[0]
+#          # check caller is affiliated with this site
+#          if self.caller['person_id'] not in site['person_ids']:
+#              raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = ilink_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified ilink, requires role %d",required_min_role
+
+        ilink = Ilink(self.api)
+        ilink['ilink_type_id'] = ilink_type['ilink_type_id']
+        ilink['src_interface_id'] = src_if_id
+        ilink['dst_interface_id'] = dst_if_id
+        ilink['value'] = value
+
+        ilink.sync()
+
+        self.object_type = 'Interface'
+       self.object_ids = [src_if_id,dst_if_id]
+
+        return ilink['ilink_id']
diff --git a/PLC/Methods/AddIlinkType.py b/PLC/Methods/AddIlinkType.py
new file mode 100644 (file)
index 0000000..e4460fe
--- /dev/null
@@ -0,0 +1,45 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+             ['name', 'description', 'category', 'min_role_id']
+
+class AddIlinkType(Method):
+    """
+    Adds a new type of ilink.
+    Any fields specified are used, otherwise defaults are used.
+
+    Returns the new ilink_id (> 0) if successful,
+    faults otherwise.
+    """
+
+    roles = ['admin']
+
+    ilink_type_fields = dict(filter(can_update, IlinkType.fields.items()))
+
+    accepts = [
+        Auth(),
+        ilink_type_fields
+        ]
+
+    returns = Parameter(int, 'New ilink_id (> 0) if successful')
+
+
+    def call(self, auth, ilink_type_fields):
+        ilink_type_fields = dict(filter(can_update, ilink_type_fields.items()))
+        ilink_type = IlinkType(self.api, ilink_type_fields)
+        ilink_type.sync()
+
+       self.object_ids = [ilink_type['ilink_type_id']]
+
+        return ilink_type['ilink_type_id']
index 04dae7f..1584e02 100644 (file)
@@ -12,7 +12,6 @@ from PLC.NodeTagTypes import NodeTagType, NodeTagTypes
 from PLC.NodeTags import NodeTag, NodeTags
 from PLC.Nodes import Node, Nodes
 
-from PLC.Nodes import Nodes
 from PLC.Sites import Sites
 
 class AddNodeTag(Method):
diff --git a/PLC/Methods/DeleteIlink.py b/PLC/Methods/DeleteIlink.py
new file mode 100644 (file)
index 0000000..685a9ae
--- /dev/null
@@ -0,0 +1,73 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Ilinks import Ilink, Ilinks
+from PLC.Nodes import Node, Nodes
+
+from PLC.Nodes import Node, Nodes
+from PLC.Sites import Site, Sites
+
+class DeleteIlink(Method):
+    """
+    Deletes the specified ilink
+
+    Attributes may require the caller to have a particular role in order
+    to be deleted, depending on the related ilink type.
+    Admins may delete attributes of any slice or sliver.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'user']
+
+    accepts = [
+        Auth(),
+        Ilink.fields['ilink_id']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Node'
+
+
+    def call(self, auth, ilink_id):
+        ilinks = Ilinks(self.api, [ilink_id])
+        if not ilinks:
+            raise PLCInvalidArgument, "No such ilink %r"%ilink_id
+        ilink = ilinks[0]
+
+        ### reproducing a check from UpdateSliceAttribute, looks dumb though
+        nodes = Nodes(self.api, [ilink['node_id']])
+        if not nodes:
+            raise PLCInvalidArgument, "No such node %r"%ilink['node_id']
+        node = nodes[0]
+
+        assert ilink['ilink_id'] in node['tag_ids']
+
+       # check permission : it not admin, is the user affiliated with the right site
+       if 'admin' not in self.caller['roles']:
+           # locate node
+           node = Nodes (self.api,[node['node_id']])[0]
+           # locate site
+           site = Sites (self.api, [node['site_id']])[0]
+           # check caller is affiliated with this site
+           if self.caller['person_id'] not in site['person_ids']:
+               raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = ilink_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified ilink, requires role %d",required_min_role
+
+        ilink.delete()
+       self.object_ids = [ilink['ilink_id']]
+
+        return 1
diff --git a/PLC/Methods/DeleteIlinkType.py b/PLC/Methods/DeleteIlinkType.py
new file mode 100644 (file)
index 0000000..5a533ad
--- /dev/null
@@ -0,0 +1,39 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+from PLC.Auth import Auth
+
+class DeleteIlinkType(Method):
+    """
+    Deletes the specified ilink type.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    accepts = [
+        Auth(),
+        Mixed(IlinkType.fields['ilink_type_id'],
+              IlinkType.fields['name']),
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+
+    def call(self, auth, ilink_type_id_or_name):
+        ilink_types = IlinkTypes(self.api, [ilink_type_id_or_name])
+        if not ilink_types:
+            raise PLCInvalidArgument, "No such ilink type"
+        ilink_type = ilink_types[0]
+
+        ilink_type.delete()
+       self.object_ids = [ilink_type['ilink_type_id']]
+
+        return 1
diff --git a/PLC/Methods/GetIlinkTypes.py b/PLC/Methods/GetIlinkTypes.py
new file mode 100644 (file)
index 0000000..a76807d
--- /dev/null
@@ -0,0 +1,33 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+
+class GetIlinkTypes(Method):
+    """
+    Returns an array of structs containing details about
+    ilink types.
+
+    The usual filtering scheme applies on this method.
+    """
+
+    roles = ['admin', 'pi', 'user', 'tech', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed([Mixed(IlinkType.fields['ilink_type_id'],
+                     IlinkType.fields['name'])],
+              Filter(IlinkType.fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [IlinkType.fields]
+
+    def call(self, auth, ilink_type_filter = None, return_fields = None):
+        return IlinkTypes(self.api, ilink_type_filter, return_fields)
diff --git a/PLC/Methods/GetIlinks.py b/PLC/Methods/GetIlinks.py
new file mode 100644 (file)
index 0000000..c711e9d
--- /dev/null
@@ -0,0 +1,44 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Filter import Filter
+from PLC.Auth import Auth
+
+from PLC.Ilinks import Ilink, Ilinks
+from PLC.Sites import Site, Sites
+from PLC.Nodes import Node, Nodes
+
+class GetIlinks(Method):
+    """
+    Returns an array of structs containing details about
+    nodes and related tags.
+
+    If ilink_filter is specified and is an array of
+    ilink identifiers, only ilinks matching
+    the filter will be returned. If return_fields is specified, only
+    the specified details will be returned.
+    """
+
+    roles = ['admin', 'pi', 'user', 'node']
+
+    accepts = [
+        Auth(),
+        Mixed([Ilink.fields['ilink_id']],
+              Parameter(int,"ilink id"),
+              Filter(Ilink.fields)),
+        Parameter([str], "List of fields to return", nullok = True)
+        ]
+
+    returns = [Ilink.fields]
+    
+
+    def call(self, auth, ilink_filter = None, return_fields = None):
+
+        ilinks = Ilinks(self.api, ilink_filter, return_fields)
+
+        return ilinks
diff --git a/PLC/Methods/UpdateIlink.py b/PLC/Methods/UpdateIlink.py
new file mode 100644 (file)
index 0000000..a0578ba
--- /dev/null
@@ -0,0 +1,63 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.Auth import Auth
+
+from PLC.Ilinks import Ilink, Ilinks
+from PLC.Interfaces import Interface, Interfaces
+
+from PLC.Sites import Sites
+
+class UpdateIlink(Method):
+    """
+    Updates the value of an existing ilink
+
+    Access rights depend on the ilink type.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin', 'pi', 'tech', 'user']
+
+    accepts = [
+        Auth(),
+        Ilink.fields['ilink_id'],
+        Ilink.fields['value']
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    object_type = 'Interface'
+
+    def call(self, auth, ilink_id, value):
+        ilinks = Ilinks(self.api, [ilink_id])
+        if not ilinks:
+            raise PLCInvalidArgument, "No such ilink %r"%ilink_id
+        ilink = ilinks[0]
+
+       if 'admin' not in self.caller['roles']:
+#      # check permission : it not admin, is the user affiliated with the right site
+#          # locate node
+#          node = Nodes (self.api,[node['node_id']])[0]
+#          # locate site
+#          site = Sites (self.api, [node['site_id']])[0]
+#          # check caller is affiliated with this site
+#          if self.caller['person_id'] not in site['person_ids']:
+#              raise PLCPermissionDenied, "Not a member of the hosting site %s"%site['abbreviated_site']
+           
+           required_min_role = ilink_type ['min_role_id']
+           if required_min_role is not None and \
+                   min(self.caller['role_ids']) > required_min_role:
+               raise PLCPermissionDenied, "Not allowed to modify the specified ilink, requires role %d",required_min_role
+
+        ilink['value'] = value
+        ilink.sync()
+
+       self.object_ids = [ilink['src_interface_id'],ilink['dst_interface_id']]
+        return 1
diff --git a/PLC/Methods/UpdateIlinkType.py b/PLC/Methods/UpdateIlinkType.py
new file mode 100644 (file)
index 0000000..38644e6
--- /dev/null
@@ -0,0 +1,48 @@
+#
+# Thierry Parmentelat - INRIA
+#
+# $Revision: 9423 $
+#
+from PLC.Faults import *
+from PLC.Method import Method
+from PLC.Parameter import Parameter, Mixed
+from PLC.IlinkTypes import IlinkType, IlinkTypes
+from PLC.Auth import Auth
+
+can_update = lambda (field, value): field in \
+             ['name', 'description', 'category', 'min_role_id']
+
+class UpdateIlinkType(Method):
+    """
+    Updates the parameters of an existing tag type
+    with the values in ilink_type_fields.
+
+    Returns 1 if successful, faults otherwise.
+    """
+
+    roles = ['admin']
+
+    ilink_type_fields = dict(filter(can_update, IlinkType.fields.items()))
+
+    accepts = [
+        Auth(),
+        Mixed(IlinkType.fields['ilink_type_id'],
+              IlinkType.fields['name']),
+        ilink_type_fields
+        ]
+
+    returns = Parameter(int, '1 if successful')
+
+    def call(self, auth, ilink_type_id_or_name, ilink_type_fields):
+        ilink_type_fields = dict(filter(can_update, ilink_type_fields.items()))
+
+        ilink_types = IlinkTypes(self.api, [ilink_type_id_or_name])
+        if not ilink_types:
+            raise PLCInvalidArgument, "No such tag type"
+        ilink_type = ilink_types[0]
+
+        ilink_type.update(ilink_type_fields)
+        ilink_type.sync()
+       self.object_ids = [ilink_type['ilink_type_id']]
+
+        return 1
index 8ed5c3e..6ebe48d 100644 (file)
@@ -5,6 +5,8 @@ AddBootState
 AddConfFile
 AddConfFileToNode
 AddConfFileToNodeGroup
+AddIlink
+AddIlinkType
 AddInitScript
 AddInterface
 AddInterfaceSetting
@@ -50,6 +52,8 @@ DeleteBootState
 DeleteConfFile
 DeleteConfFileFromNode
 DeleteConfFileFromNodeGroup
+DeleteIlink
+DeleteIlinkType
 DeleteInitScript
 DeleteInterface
 DeleteInterfaceSetting
@@ -89,6 +93,8 @@ GetBootStates
 GetConfFiles
 GetEventObjects
 GetEvents
+GetIlinkTypes
+GetIlinks
 GetInitScripts
 GetInterfaceSettingTypes
 GetInterfaceSettings
@@ -148,6 +154,8 @@ SliceUsersList
 UpdateAddress
 UpdateAddressType
 UpdateConfFile
+UpdateIlink
+UpdateIlinkType
 UpdateInitScript
 UpdateInterface
 UpdateInterfaceSetting
index 96c4ec4..a632df0 100644 (file)
@@ -13,6 +13,8 @@ Events
 Faults
 Filter
 GPG
+IlinkTypes
+Ilinks
 InitScripts
 InterfaceSettingTypes
 InterfaceSettings
index d5ada42..285a01f 100644 (file)
@@ -42,42 +42,12 @@ ALTER TABLE view_nodes RENAME COLUMN nodenetwork_ids TO interface_ids;
 ----------------------------------------
 -- node tags
 ----------------------------------------
-CREATE TABLE node_tag_types (
-
-    node_tag_type_id serial PRIMARY KEY,       -- ID
-    name text UNIQUE NOT NULL,                 -- Tag Name
-    description text,                          -- Optional Description
-    category text NOT NULL,                    -- Free text for grouping tags together
-    min_role_id integer REFERENCES roles       -- set minimal role required
-) WITH OIDS;
-
-CREATE TABLE node_tag (
-    node_tag_id serial PRIMARY KEY,            -- ID
-    node_id integer REFERENCES nodes NOT NULL, -- node id
-    node_tag_type_id integer REFERENCES node_tag_types,
-                                               -- tag type id
-    value text                                 -- value attached
-) WITH OIDS;
+CREATE TABLE node_tag_types ...
+CREATE TABLE node_tag ...
 
 ---------- related views
-CREATE OR REPLACE VIEW node_tags AS
-SELECT node_id,
-array_accum(node_tag_id) AS tag_ids
-FROM node_tag
-GROUP BY node_id;
-
-CREATE OR REPLACE VIEW view_node_tags AS
-SELECT
-node_tag.node_tag_id,
-node_tag.node_id,
-node_tag_types.node_tag_type_id,
-node_tag_types.name,
-node_tag_types.description,
-node_tag_types.category,
-node_tag_types.min_role_id,
-node_tag.value
-FROM node_tag 
-INNER JOIN node_tag_types USING (node_tag_type_id);
+CREATE OR REPLACE VIEW node_tags AS ...
+CREATE OR REPLACE VIEW view_node_tags AS ... 
 
 ----------------------------------------
 -- nodegroups
@@ -87,72 +57,27 @@ INNER JOIN node_tag_types USING (node_tag_type_id);
 DROP TABLE IF EXISTS nodegroups CASCADE;
 
 -- Node groups
-CREATE TABLE nodegroups (
-    nodegroup_id serial PRIMARY KEY,                   -- Group identifier
-    groupname text UNIQUE NOT NULL,                    -- Group name
-    node_tag_type_id integer REFERENCES node_tag_types,        -- node is in nodegroup if it has this tag defined
-    value text                                         -- with value 'value'
-) WITH OIDS;
-
-CREATE OR REPLACE VIEW nodegroup_node AS
-SELECT nodegroup_id, node_id 
-FROM node_tag_types 
-JOIN node_tag 
-USING (node_tag_type_id) 
-JOIN nodegroups 
-USING (node_tag_type_id,value);
-
-CREATE OR REPLACE VIEW nodegroup_nodes AS
-SELECT nodegroup_id,
-array_accum(node_id) AS node_ids
-FROM nodegroup_node
-GROUP BY nodegroup_id;
-
--- Node groups that each node is a member of
-CREATE OR REPLACE VIEW node_nodegroups AS
-SELECT node_id,
-array_accum(nodegroup_id) AS nodegroup_ids
-FROM nodegroup_node
-GROUP BY node_id;
-
+CREATE TABLE nodegroups ... 
+CREATE OR REPLACE VIEW nodegroup_node AS ...
+CREATE OR REPLACE VIEW nodegroup_nodes AS ...
+CREATE OR REPLACE VIEW node_nodegroups AS ...
 CREATE OR REPLACE VIEW view_nodegroups AS
-SELECT
-nodegroups.*,
-COALESCE((SELECT conf_file_ids FROM nodegroup_conf_files WHERE nodegroup_conf_files.nodegroup_id = nodegroups.nodegroup_id), '{}') AS conf_file_ids
-FROM nodegroups;
+
 ----------------------------------------
 -- the nodes view
 ----------------------------------------
 DROP VIEW view_nodes;
-CREATE OR REPLACE VIEW view_nodes AS
-SELECT
-nodes.node_id,
-nodes.hostname,
-nodes.site_id,
-nodes.boot_state,
-nodes.deleted,
-nodes.model,
-nodes.boot_nonce,
-nodes.version,
-nodes.ssh_rsa_key,
-nodes.key,
-CAST(date_part('epoch', nodes.date_created) AS bigint) AS date_created,
-CAST(date_part('epoch', nodes.last_updated) AS bigint) AS last_updated,
-CAST(date_part('epoch', nodes.last_contact) AS bigint) AS last_contact,  
-peer_node.peer_id,
-peer_node.peer_node_id,
-COALESCE((SELECT interface_ids FROM node_interfaces WHERE node_interfaces.node_id = nodes.node_id), '{}') AS interface_ids,
-COALESCE((SELECT nodegroup_ids FROM node_nodegroups WHERE node_nodegroups.node_id = nodes.node_id), '{}') AS nodegroup_ids,
-COALESCE((SELECT slice_ids FROM node_slices WHERE node_slices.node_id = nodes.node_id), '{}') AS slice_ids,
-COALESCE((SELECT slice_ids_whitelist FROM node_slices_whitelist WHERE node_slices_whitelist.node_id = nodes.node_id), '{}') AS slice_ids_whitelist,
-COALESCE((SELECT pcu_ids FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS pcu_ids,
-COALESCE((SELECT ports FROM node_pcus WHERE node_pcus.node_id = nodes.node_id), '{}') AS ports,
-COALESCE((SELECT conf_file_ids FROM node_conf_files WHERE node_conf_files.node_id = nodes.node_id), '{}') AS conf_file_ids,
-COALESCE((SELECT tag_ids FROM node_tags WHERE node_tags.node_id = nodes.node_id), '{}') AS tag_ids,
-node_session.session_id AS session
-FROM nodes
-LEFT JOIN peer_node USING (node_id)
-LEFT JOIN node_session USING (node_id);
+CREATE OR REPLACE VIEW view_nodes AS ...
+
+----------------------------------------
+-- ilinks
+----------------------------------------
+CREATE TABLE ilink_types ...
+CREATE TABLE ilink ...
+
+CREATE OR REPLACE VIEW ilinks AS ...
+CREATE OR REPLACE VIEW ilink_src_node AS ...
+CREATE OR REPLACE VIEW ilink_nodes AS ...
 
 ----------------------------------------
 -- update versioning
index 0fd11aa..828d76f 100644 (file)
@@ -328,105 +328,7 @@ INNER JOIN node_tag_types USING (node_tag_type_id)
 INNER JOIN nodes USING (node_id);
 
 --------------------------------------------------------------------------------
--- Node groups
---------------------------------------------------------------------------------
-
--- Node groups
-CREATE TABLE nodegroups (
-    nodegroup_id serial PRIMARY KEY,                   -- Group identifier
-    groupname text UNIQUE NOT NULL,                    -- Group name 
-    node_tag_type_id integer REFERENCES node_tag_types,        -- node is in nodegroup if it has this tag defined
-    tagvalue text NOT NULL                             -- with this value attached
-) WITH OIDS;
-
--- xxx - first rough implem
-CREATE OR REPLACE VIEW nodegroup_node AS
-SELECT nodegroup_id, node_id 
-FROM node_tag_types 
-JOIN node_tag 
-USING (node_tag_type_id) 
-JOIN nodegroups 
-USING (node_tag_type_id,tagvalue);
-
-CREATE OR REPLACE VIEW nodegroup_nodes AS
-SELECT nodegroup_id,
-array_accum(node_id) AS node_ids
-FROM nodegroup_node
-GROUP BY nodegroup_id;
-
--- Node groups that each node is a member of
-CREATE OR REPLACE VIEW node_nodegroups AS
-SELECT node_id,
-array_accum(nodegroup_id) AS nodegroup_ids
-FROM nodegroup_node
-GROUP BY node_id;
-
---------------------------------------------------------------------------------
--- Node configuration files
---------------------------------------------------------------------------------
-
-CREATE TABLE conf_files (
-    conf_file_id serial PRIMARY KEY,                   -- Configuration file identifier
-    enabled bool NOT NULL DEFAULT true,                        -- Configuration file is active
-    source text NOT NULL,                              -- Relative path on the boot server
-                                                       -- where file can be downloaded
-    dest text NOT NULL,                                        -- Absolute path where file should be installed
-    file_permissions text NOT NULL DEFAULT '0644',     -- chmod(1) permissions
-    file_owner text NOT NULL DEFAULT 'root',           -- chown(1) owner
-    file_group text NOT NULL DEFAULT 'root',           -- chgrp(1) owner
-    preinstall_cmd text,                               -- Shell command to execute prior to installing
-    postinstall_cmd text,                              -- Shell command to execute after installing
-    error_cmd text,                                    -- Shell command to execute if any error occurs
-    ignore_cmd_errors bool NOT NULL DEFAULT false,     -- Install file anyway even if an error occurs
-    always_update bool NOT NULL DEFAULT false          -- Always attempt to install file even if unchanged
-) WITH OIDS;
-
-CREATE TABLE conf_file_node (
-    conf_file_id integer REFERENCES conf_files NOT NULL,       -- Configuration file identifier
-    node_id integer REFERENCES nodes NOT NULL,                 -- Node identifier
-    PRIMARY KEY (conf_file_id, node_id)
-);
-CREATE INDEX conf_file_node_conf_file_id_idx ON conf_file_node (conf_file_id);
-CREATE INDEX conf_file_node_node_id_idx ON conf_file_node (node_id);
-
--- Nodes linked to each configuration file
-CREATE OR REPLACE VIEW conf_file_nodes AS
-SELECT conf_file_id,
-array_accum(node_id) AS node_ids
-FROM conf_file_node
-GROUP BY conf_file_id;
-
--- Configuration files linked to each node
-CREATE OR REPLACE VIEW node_conf_files AS
-SELECT node_id,
-array_accum(conf_file_id) AS conf_file_ids
-FROM conf_file_node
-GROUP BY node_id;
-
-CREATE TABLE conf_file_nodegroup (
-    conf_file_id integer REFERENCES conf_files NOT NULL,       -- Configuration file identifier
-    nodegroup_id integer REFERENCES nodegroups NOT NULL,       -- Node group identifier
-    PRIMARY KEY (conf_file_id, nodegroup_id)
-);
-CREATE INDEX conf_file_nodegroup_conf_file_id_idx ON conf_file_nodegroup (conf_file_id);
-CREATE INDEX conf_file_nodegroup_nodegroup_id_idx ON conf_file_nodegroup (nodegroup_id);
-
--- Node groups linked to each configuration file
-CREATE OR REPLACE VIEW conf_file_nodegroups AS
-SELECT conf_file_id,
-array_accum(nodegroup_id) AS nodegroup_ids
-FROM conf_file_nodegroup
-GROUP BY conf_file_id;
-
--- Configuration files linked to each node group
-CREATE OR REPLACE VIEW nodegroup_conf_files AS
-SELECT nodegroup_id,
-array_accum(conf_file_id) AS conf_file_ids
-FROM conf_file_nodegroup
-GROUP BY nodegroup_id;
-
---------------------------------------------------------------------------------
--- Node network interfaces
+-- (network) interfaces
 --------------------------------------------------------------------------------
 
 -- Valid network addressing schemes
@@ -487,7 +389,7 @@ CREATE TABLE interface_setting_types (
     name text UNIQUE NOT NULL,                         -- Setting Name    
     description text,                                  -- Optional Description
     category text NOT NULL DEFAULT 'general',          -- Free text for grouping, e.g. Wifi, or whatever
-    min_role_id integer REFERENCES roles DEFAULT 10    -- If set, minimal role required
+    min_role_id integer REFERENCES roles DEFAULT 10    -- minimal role required
 ) WITH OIDS;
 
 CREATE TABLE interface_setting (
@@ -537,6 +439,144 @@ interfaces.hostname,
 COALESCE((SELECT interface_setting_ids FROM interface_settings WHERE interface_settings.interface_id = interfaces.interface_id), '{}') AS interface_setting_ids
 FROM interfaces;
 
+--------------------------------------------------------------------------------
+-- ilinks : links between interfaces
+--------------------------------------------------------------------------------
+CREATE TABLE ilink_types (
+       ilink_type_id serial PRIMARY KEY,               -- id
+       name text UNIQUE NOT NULL,                      -- link name
+       description text,                               -- optional description
+       min_role_id integer REFERENCES roles DEFAULT 10         -- minimal role required
+) WITH OIDS;
+
+CREATE TABLE ilink (
+       ilink_id serial PRIMARY KEY,                            -- id
+       ilink_type_id integer REFERENCES ilink_types,           -- id of the ilink type
+       src_interface_id integer REFERENCES interfaces not NULL,        -- id of src interface
+       dst_interface_id integer REFERENCES interfaces NOT NULL, -- id of dst interface
+       value text                                              -- optional value on the link
+) WITH OIDS;
+
+CREATE OR REPLACE VIEW view_ilinks AS
+SELECT * FROM ilink_types 
+INNER JOIN ilink USING (ilink_type_id);
+
+-- expose node_ids ???
+-- -- cannot mention the same table twice in a join ?
+-- -- CREATE OR REPLACE VIEW ilink_src_node AS 
+-- SELECT 
+-- ilink.ilink_type_id,
+-- ilink.src_interface_id,
+-- interfaces.node_id AS src_node_id,
+-- ilink.dst_interface_id
+-- FROM ilink 
+-- INNER JOIN interfaces ON ilink.src_interface_id = interfaces.interface_id;
+-- 
+-- CREATE OR REPLACE VIEW ilink_nodes AS
+-- SELECT 
+-- ilink_src_node.*,
+-- interfaces.node_id as dst_node_id
+-- FROM ilink_src_node
+-- INNER JOIN interfaces ON ilink_src_node.dst_interface_id = interfaces.interface_id;
+
+--------------------------------------------------------------------------------
+-- Node groups
+--------------------------------------------------------------------------------
+
+-- Node groups
+CREATE TABLE nodegroups (
+    nodegroup_id serial PRIMARY KEY,                   -- Group identifier
+    groupname text UNIQUE NOT NULL,                    -- Group name 
+    node_tag_type_id integer REFERENCES node_tag_types,        -- node is in nodegroup if it has this tag defined
+    tagvalue text NOT NULL                             -- with this value attached
+) WITH OIDS;
+
+-- xxx - first rough implem. similar to former semantics but might be slow
+CREATE OR REPLACE VIEW nodegroup_node AS
+SELECT nodegroup_id, node_id 
+FROM node_tag_types 
+JOIN node_tag 
+USING (node_tag_type_id) 
+JOIN nodegroups 
+USING (node_tag_type_id,tagvalue);
+
+CREATE OR REPLACE VIEW nodegroup_nodes AS
+SELECT nodegroup_id,
+array_accum(node_id) AS node_ids
+FROM nodegroup_node
+GROUP BY nodegroup_id;
+
+-- Node groups that each node is a member of
+CREATE OR REPLACE VIEW node_nodegroups AS
+SELECT node_id,
+array_accum(nodegroup_id) AS nodegroup_ids
+FROM nodegroup_node
+GROUP BY node_id;
+
+--------------------------------------------------------------------------------
+-- Node configuration files
+--------------------------------------------------------------------------------
+
+CREATE TABLE conf_files (
+    conf_file_id serial PRIMARY KEY,                   -- Configuration file identifier
+    enabled bool NOT NULL DEFAULT true,                        -- Configuration file is active
+    source text NOT NULL,                              -- Relative path on the boot server
+                                                       -- where file can be downloaded
+    dest text NOT NULL,                                        -- Absolute path where file should be installed
+    file_permissions text NOT NULL DEFAULT '0644',     -- chmod(1) permissions
+    file_owner text NOT NULL DEFAULT 'root',           -- chown(1) owner
+    file_group text NOT NULL DEFAULT 'root',           -- chgrp(1) owner
+    preinstall_cmd text,                               -- Shell command to execute prior to installing
+    postinstall_cmd text,                              -- Shell command to execute after installing
+    error_cmd text,                                    -- Shell command to execute if any error occurs
+    ignore_cmd_errors bool NOT NULL DEFAULT false,     -- Install file anyway even if an error occurs
+    always_update bool NOT NULL DEFAULT false          -- Always attempt to install file even if unchanged
+) WITH OIDS;
+
+CREATE TABLE conf_file_node (
+    conf_file_id integer REFERENCES conf_files NOT NULL,       -- Configuration file identifier
+    node_id integer REFERENCES nodes NOT NULL,                 -- Node identifier
+    PRIMARY KEY (conf_file_id, node_id)
+);
+CREATE INDEX conf_file_node_conf_file_id_idx ON conf_file_node (conf_file_id);
+CREATE INDEX conf_file_node_node_id_idx ON conf_file_node (node_id);
+
+-- Nodes linked to each configuration file
+CREATE OR REPLACE VIEW conf_file_nodes AS
+SELECT conf_file_id,
+array_accum(node_id) AS node_ids
+FROM conf_file_node
+GROUP BY conf_file_id;
+
+-- Configuration files linked to each node
+CREATE OR REPLACE VIEW node_conf_files AS
+SELECT node_id,
+array_accum(conf_file_id) AS conf_file_ids
+FROM conf_file_node
+GROUP BY node_id;
+
+CREATE TABLE conf_file_nodegroup (
+    conf_file_id integer REFERENCES conf_files NOT NULL,       -- Configuration file identifier
+    nodegroup_id integer REFERENCES nodegroups NOT NULL,       -- Node group identifier
+    PRIMARY KEY (conf_file_id, nodegroup_id)
+);
+CREATE INDEX conf_file_nodegroup_conf_file_id_idx ON conf_file_nodegroup (conf_file_id);
+CREATE INDEX conf_file_nodegroup_nodegroup_id_idx ON conf_file_nodegroup (nodegroup_id);
+
+-- Node groups linked to each configuration file
+CREATE OR REPLACE VIEW conf_file_nodegroups AS
+SELECT conf_file_id,
+array_accum(nodegroup_id) AS nodegroup_ids
+FROM conf_file_nodegroup
+GROUP BY conf_file_id;
+
+-- Configuration files linked to each node group
+CREATE OR REPLACE VIEW nodegroup_conf_files AS
+SELECT nodegroup_id,
+array_accum(conf_file_id) AS conf_file_ids
+FROM conf_file_nodegroup
+GROUP BY nodegroup_id;
+
 --------------------------------------------------------------------------------
 -- Power control units (PCUs)
 --------------------------------------------------------------------------------