nodes tags might be exposed through GetNodes (implicit initializations still missing)
cannot filter on tags yet & very ilghtly tested
-# $Id#
+# $Id$
from types import StringTypes
try:
set
# Declare ourselves as a type of parameter that can take
# either a value or a list of values for each of the specified
# fields.
- self.fields = {}
-
- for field, expected in fields.iteritems():
- # Cannot filter on sequences
- if python_type(expected) in (list, tuple, set):
- continue
-
- # Accept either a value or a list of values of the specified type
- self.fields[field] = Mixed(expected, [expected])
+ self.fields = dict ( [ ( field, Mixed (expected, [expected]))
+ for (field,expected) in fields.iteritems()
+ if python_type(expected) not in (list, tuple, set) ] )
# Null filter means no filter
Parameter.__init__(self, self.fields, doc = doc, nullok = True)
- # this code is not used anymore
- # at some point the select in the DB for event objects was done on
- # the events table directly, that is stored as a timestamp, thus comparisons
- # needed to be done based on SQL timestamps as well
- def unix2timestamp (self,unix):
- s = time.gmtime(unix)
- return "TIMESTAMP'%04d-%02d-%02d %02d:%02d:%02d'" % (s.tm_year,s.tm_mon,s.tm_mday,
- s.tm_hour,s.tm_min,s.tm_sec)
-
def sql(self, api, join_with = "AND"):
"""
Returns a SQL conditional that represents this filter.
'node_tag', 'conf_file_node', 'pcu_node', ]
fields = {
'node_id': Parameter(int, "Node identifier"),
+ 'node_type': Parameter(str,"Node type",max=20),
'hostname': Parameter(str, "Fully qualified hostname", max = 255),
'site_id': Parameter(int, "Site at which this node is located"),
'boot_state': Parameter(str, "Boot state", max = 20),
}
related_fields = {
'interfaces': [Mixed(Parameter(int, "Interface identifier"),
- Filter(Interface.fields))],
+ Filter(Interface.fields))],
'nodegroups': [Mixed(Parameter(int, "NodeGroup identifier"),
Parameter(str, "NodeGroup name"))],
'conf_files': [Parameter(int, "ConfFile identifier")],
'slices_whitelist': [Mixed(Parameter(int, "Slice identifier"),
Parameter(str, "Slice name"))]
}
+ view_name = "view_nodes"
+ view_tags_name = "view_node_tags"
+ tags = {
+ # regular
+ 'arch': Parameter(str, "node/config", ro=True),
+ 'deployment': Parameter(str, "node/operation"),
+ # dummynet
+ }
def validate_hostname(self, hostname):
if not valid_hostname(hostname):
def __init__(self, api, node_filter = None, columns = None):
Table.__init__(self, api, Node, columns)
- sql = "SELECT %s FROM view_nodes WHERE deleted IS False" % \
- ", ".join(self.columns)
+ # the view that we're selecting upon: start with view_nodes
+ view = "view_nodes"
+ # as many left joins as requested tags
+ for tagname in self.tag_columns:
+ view= "%s left join %s using (%s)"%(view,Node.tagvalue_view_name(tagname),Node.primary_key)
+
+ sql = "SELECT %s FROM %s WHERE deleted IS False" % \
+ (", ".join(self.columns.keys()+self.tag_columns.keys()),view)
if node_filter is not None:
if isinstance(node_filter, (list, tuple, set)):
"""
# Set this to the name of the table that stores the row.
+ # e.g. table_name = "nodes"
table_name = None
# Set this to the name of the primary key of the table. It is
# assumed that the this key is a sequence if it is not set when
# sync() is called.
+ # e.g. primary_key="node_id"
primary_key = None
# Set this to the names of tables that reference this table's
# sync().
fields = {}
+ # Set this to the name of the view that gathers the row and its relations
+ # e.g. view_name = "view_nodes"
+ view_name = None
+
+ # The name of the view that extends objects with tags
+ # e.g. view_tags_name = "view_node_tags"
+ view_tags_name = None
+
+ # Set this to the set of tags that can be returned by the Get function
+ tags = {}
+
def __init__(self, api, fields = {}):
dict.__init__(self, fields)
self.api = api
def associate(self, *args):
"""
- Provides a means for high lvl api calls to associate objects
- using low lvl calls.
+ Provides a means for high level api calls to associate objects
+ using low level calls.
"""
if len(args) < 3:
remove_object = classmethod(remove_object)
+ # convenience: check in dict (self.fields or self.tags) that a key is writable
+ @staticmethod
+ def is_writable (key,value,dict):
+ # if not mentioned, assume it's writable (e.g. deleted ...)
+ if key not in dict: return True
+ # if mentioned but not linked to a Parameter object, idem
+ if not isinstance(dict[key], Parameter): return True
+ # if not marked ro, it's writable
+ if not dict[key].ro: return True
+ return False
+
def db_fields(self, obj = None):
"""
Return only those fields that can be set or updated directly
for this object, and are not marked as a read-only Parameter.
"""
- if obj is None:
- obj = self
+ if obj is None: obj = self
db_fields = self.api.db.fields(self.table_name)
- return dict(filter(lambda (key, value): \
- key in db_fields and \
- (key not in self.fields or \
- not isinstance(self.fields[key], Parameter) or \
- not self.fields[key].ro),
- obj.items()))
+ return dict ( [ (key,value) for (key,value) in obj.items()
+ if key in db_fields and
+ Row.is_writable(key,value,self.fields) ] )
+
+ def tag_fields (self, obj=None):
+ """
+ Return the fields of obj that are mentioned in tags
+ """
+ if obj is None: obj=self
+
+ return dict ( [ (key,value) for (key,value) in obj.iteritems()
+ if key in self.tags and Row.is_writable(key,value,self.tags) ] )
+
+ # takes in input a list of columns, returns three lists
+ # fields, tags, rejected
+ @classmethod
+ def parse_columns (cls, columns):
+ (fields,tags,rejected)=({},{},{})
+ for column in columns:
+ if column in cls.fields: fields[column]=cls.fields[column]
+ elif column in cls.tags: tags[column]=cls.tags[column]
+ else: rejected.append(column)
+ return (fields,tags,rejected)
+
+ @classmethod
+ def tagvalue_view_name (cls, tagname):
+ return "tagvalue_view_%s_%s"%(cls.primary_key,tagname)
+
+ @classmethod
+ def tagvalue_view_create (cls,tagname):
+ """
+ returns an SQL sentence that creates a view named after the primary_key and tagname,
+ with 2 columns
+ (*) column 1: name=self.primary_key
+ (*) column 2: name=tagname value=tagvalue
+ """
+
+ if not cls.view_tags_name: return ""
+
+ table_name=cls.table_name
+ primary_key=cls.primary_key
+ view_tags_name=cls.view_tags_name
+ tagvalue_view_name=cls.tagvalue_view_name(tagname)
+ return 'CREATE OR REPLACE VIEW %(tagvalue_view_name)s ' \
+ 'as SELECT %(table_name)s.%(primary_key)s,%(view_tags_name)s.tagvalue as "%(tagname)s" ' \
+ 'from %(table_name)s right join %(view_tags_name)s using (%(primary_key)s) ' \
+ 'WHERE tagname = \'%(tagname)s\';'%locals()
+
+ @classmethod
+ def tagvalue_views_create (cls):
+ if not cls.tags: return
+ sql = []
+ for (type,type_dict) in cls.tags.iteritems():
+ for (tagname,details) in type_dict.iteritems():
+ sql.append(cls.tagvalue_view_create (tagname))
+ return sql
def __eq__(self, y):
"""
if columns is None:
columns = classobj.fields
+ tag_columns={}
else:
- columns = filter(lambda x: x in classobj.fields, columns)
+ (columns,tag_columns,rejected) = classobj.parse_columns(columns)
if not columns:
- raise PLCInvalidArgument, "No valid return fields specified"
+ raise PLCInvalidArgument, "No valid return fields specified for class %s"%classobj.__name__
+ if rejected:
+ raise PLCInvalidArgument, "unknown column(s) specified %r in %s"%(rejected,classobj.__name__)
self.columns = columns
+ self.tag_columns = tag_columns
def sync(self, commit = True):
"""
drop view mgn_all_views;
drop function mgn_drop_all_views ();
+----------------------------------------
+-- nodes
+----------------------------------------
+ALTER TABLE nodes ADD COLUMN node_type TEXT NOT NULL DEFAULT 'regular';
+
----------------------------------------
-- tag types
----------------------------------------
INSERT INTO boot_states (boot_state) VALUES ('install');
INSERT INTO boot_states (boot_state) VALUES ('reinstall');
+-- Known node types (Nodes.py expect max length to be 20)
+CREATE TABLE node_types (
+ node_type text PRIMARY KEY
+) WITH OIDS;
+INSERT INTO node_types (node_type) VALUES ('regular');
+INSERT INTO node_types (node_type) VALUES ('dummynet');
+
-- Nodes
CREATE TABLE nodes (
-- Mandatory
node_id serial PRIMARY KEY, -- Node identifier
+ node_type text REFERENCES node_types -- node type
+ DEFAULT 'regular',
+
hostname text NOT NULL, -- Node hostname
site_id integer REFERENCES sites NOT NULL, -- At which site
-
boot_state text REFERENCES boot_states NOT NULL -- Node boot state
DEFAULT 'install',
deleted boolean NOT NULL DEFAULT false, -- Is deleted
CREATE OR REPLACE VIEW view_nodes AS
SELECT
nodes.node_id,
+nodes.node_type,
nodes.hostname,
nodes.site_id,
nodes.boot_state,