Add timestamps to Nodes, PCUs and Interfaces to make concrete
authorStephen Soltesz <soltesz@cs.princeton.edu>
Fri, 2 Jul 2010 23:56:48 +0000 (19:56 -0400)
committerStephen Soltesz <soltesz@cs.princeton.edu>
Fri, 2 Jul 2010 23:56:48 +0000 (19:56 -0400)
statements about a node's configuration state.

Added:
    Node.last_boot             -- updated by BootUpdateNode()
    Node.last_download         -- updated by GetBootMedium()
    Node.last_pcu_reboot       -- updated by RebootNodeWithPCU()
    Node.last_pcu_confirmation -- updated by BootUpdateNode()
    Interface.last_updated     -- updated by UpdateInterface()
    PCU.last_updated           -- updated by UpdatePCU()

With these timestamps we can implement isValid() routines that indicate
whether the DB state is consistent with respect to the external deployment.
Previously, the DB state could be inconsistent, but there was no way to
identify this until runtime.  These checks should make it possible to validate
the DB state prior to runtime.

Also, isValid() is different from whether a node is online.  A node can be
offline and have a valid DB state.

For instance (in pseudocode):

    def Node.isValid():
        return Node.last_download < Node.last_boot &&
                Node.PCU.isValid(Node) &&
                Node.Interface.isValid(Node)

    def PCU.isValid(Node):
        return PCU.last_updated < Node.last_pcu_confirmation

    def Interface.isValid(Node):
        return Interface.last_updated < Node.last_download

In other words, if the primary interface for a node is updated more recently
than a bootimage is downloaded, then the DB configuration is out of sync with
the node deployment.

If the PCU is updated more recently than the last confirmed use of the PCU,
then the DB configuration is potentially out of sync with the deployment.

If the Node has downloaded a boot image prior to rebooting, then the DB config
is potentially out of sync with the deployment.

All of these states can be flagged in the GUI to help a technical contact
known the status of his configuration and deployment more easily.  The
recommended steps to take should be straight forward based on the conditions
above.  i.e. Create new boot image, reboot node, verify PCU, etc.

PLC/Interfaces.py
PLC/Methods/BootUpdateNode.py
PLC/Methods/GetBootMedium.py
PLC/Methods/RebootNodeWithPCU.py
PLC/Methods/UpdateInterface.py
PLC/Methods/UpdatePCU.py
PLC/Nodes.py
PLC/PCUs.py
planetlab5.sql

index 46b6f40..2ea645f 100644 (file)
@@ -67,6 +67,7 @@ class Interface(Row):
         'node_id': Parameter(int, "Node associated with this interface"),
         'is_primary': Parameter(bool, "Is the primary interface for this node"),
         'interface_tag_ids' : Parameter([int], "List of interface settings"),
+        'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
         }
 
     view_tags_name = "view_interface_tags"
@@ -209,6 +210,24 @@ class Interface(Row):
             if 'ip' not in self or not self['ip']:
                 raise PLCInvalidArgument, "For ipmi method, ip is required"
 
+    validate_last_updated = Row.validate_timestamp
+
+    def update_timestamp(self, col_name, commit = True):
+        """
+        Update col_name field with current time
+        """
+
+        assert 'interface_id' in self
+        assert self.table_name
+
+        self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
+                       " where interface_id = %d" % (self['interface_id']) )
+        self.sync(commit)
+
+    def update_last_updated(self, commit = True):
+        self.update_timestamp('last_updated', commit)
+
+
 class Interfaces(Table):
     """
     Representation of row(s) from the interfaces table in the
index f89bf27..ca18886 100644 (file)
@@ -1,11 +1,14 @@
 # $Id$
 # $URL$
+import time
+
 from PLC.Faults import *
 from PLC.Method import Method
 from PLC.Parameter import Parameter, Mixed
 from PLC.Auth import Auth, BootAuth, SessionAuth
 from PLC.Nodes import Node, Nodes
 from PLC.Interfaces import Interface, Interfaces
+from PLC.Timestamp import *
 
 can_update = lambda (field, value): field in \
              ['method', 'mac', 'gateway', 'network',
@@ -82,6 +85,12 @@ class BootUpdateNode(Method):
 
         # indicate that node has booted & contacted PLC.
         node.update_last_contact()
+        node.update_last_boot()
+
+        current_time = int(time.time())
+        # if last_pcu_reboot is within 20 minutes of current_time, accept that the PCU is responsible
+        if Timestamp.cast_long(node['last_pcu_reboot']) >= current_time - 60*20:
+            node.update_last_pcu_confirmation(commit=False)
 
         node.sync(commit = True)
 
index 848796d..53f68a1 100644 (file)
@@ -199,6 +199,7 @@ class GetBootMedium(Method):
         # renew the key and save it on the database
         if renew_key:
             node['key'] = compute_key()
+            node.update_last_download(commit=False)
             node.sync()
 
         # Generate node configuration file suitable for BootCD
index 2a31e2b..88c2225 100644 (file)
@@ -75,6 +75,8 @@ class RebootNodeWithPCU(Method):
         # p = pcu['ports'][i]
         ret = reboot.reboot_api(node, pcu, testrun)
 
+        node.update_last_pcu_reboot(commit=True) # commits new timestamp to node 
+
         self.event_objects = {'Node': [node['node_id']]}
         self.message = "RebootNodeWithPCU called"
 
index 966128a..ae32770 100644 (file)
@@ -73,6 +73,7 @@ class UpdateInterface(Method):
                 raise PLCPermissionDenied, "Not allowed to update interface"
 
         interface.update(native)
+        interface.update_last_updated(commit=False)
         interface.sync()
 
         for (tagname,value) in tags.iteritems():
index 2a1fe3b..039400b 100644 (file)
@@ -45,6 +45,7 @@ class UpdatePCU(Method):
                 raise PLCPermissionDenied, "Not allowed to update that PCU"
 
         pcu.update(pcu_fields)
+        pcu.update_last_updated(commit=False)
         pcu.sync()
 
         # Logging variables
index 9cf0edf..6b799c1 100644 (file)
@@ -57,6 +57,10 @@ class Node(Row):
         'date_created': Parameter(int, "Date and time when node entry was created", ro = True),
         'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
         'last_contact': Parameter(int, "Date and time when node last contacted plc", ro = True),
+        'last_boot': Parameter(int, "Date and time when node last booted", ro = True),
+        'last_download': Parameter(int, "Date and time when node boot image was created", ro = True),
+        'last_pcu_reboot': Parameter(int, "Date and time when PCU reboot was attempted", ro = True),
+        'last_pcu_confirmation': Parameter(int, "Date and time when PCU reboot was confirmed", ro = True),
         'verified': Parameter(bool, "Whether the node configuration is verified correct", ro=False),
         'key': Parameter(str, "(Admin only) Node key", max = 256),
         'session': Parameter(str, "(Admin only) Node session value", max = 256, ro = True),
@@ -113,31 +117,36 @@ class Node(Row):
     validate_date_created = Row.validate_timestamp
     validate_last_updated = Row.validate_timestamp
     validate_last_contact = Row.validate_timestamp
+    validate_last_boot = Row.validate_timestamp
+    validate_last_download = Row.validate_timestamp
+    validate_last_pcu_reboot = Row.validate_timestamp
+    validate_last_pcu_confirmation = Row.validate_timestamp
 
-    def update_last_contact(self, commit = True):
+    def update_timestamp(self, col_name, commit = True):
         """
-        Update last_contact field with current time
+        Update col_name field with current time
         """
 
         assert 'node_id' in self
         assert self.table_name
 
-        self.api.db.do("UPDATE %s SET last_contact = CURRENT_TIMESTAMP " % (self.table_name) + \
-                       " where node_id = %d" % ( self['node_id']) )
+        self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
+                       " where node_id = %d" % (self['node_id']) )
         self.sync(commit)
 
+    def update_last_boot(self, commit = True):
+        self.update_timestamp('last_boot', commit)
+    def update_last_download(self, commit = True):
+        self.update_timestamp('last_download', commit)
+    def update_last_pcu_reboot(self, commit = True):
+        self.update_timestamp('last_pcu_reboot', commit)
+    def update_last_pcu_confirmation(self, commit = True):
+        self.update_timestamp('last_pcu_confirmation', commit)
 
+    def update_last_contact(self, commit = True):
+        self.update_timestamp('last_contact', commit)
     def update_last_updated(self, commit = True):
-        """
-        Update last_updated field with current time
-        """
-
-        assert 'node_id' in self
-        assert self.table_name
-
-        self.api.db.do("UPDATE %s SET last_updated = CURRENT_TIMESTAMP " % (self.table_name) + \
-                       " where node_id = %d" % (self['node_id']) )
-        self.sync(commit)
+        self.update_timestamp('last_updated', commit)
 
     def update_tags(self, tags):
         from PLC.Shell import Shell
index 0bcaa67..98d5be3 100644 (file)
@@ -37,6 +37,7 @@ class PCU(Row):
         'model': Parameter(str, "PCU model string", max = 32, nullok = True),
         'node_ids': Parameter([int], "List of nodes that this PCU controls"),
         'ports': Parameter([int], "List of the port numbers that each node is connected to"),
+        'last_updated': Parameter(int, "Date and time when node entry was created", ro = True),
         }
 
     def validate_ip(self, ip):
@@ -44,6 +45,23 @@ class PCU(Row):
             raise PLCInvalidArgument, "Invalid IP address " + ip
         return ip
 
+    validate_last_updated = Row.validate_timestamp
+
+    def update_timestamp(self, col_name, commit = True):
+        """
+        Update col_name field with current time
+        """
+
+        assert 'pcu_id' in self
+        assert self.table_name
+
+        self.api.db.do("UPDATE %s SET %s = CURRENT_TIMESTAMP " % (self.table_name, col_name) + \
+                       " where pcu_id = %d" % (self['pcu_id']) )
+        self.sync(commit)
+
+    def update_last_updated(self, commit = True):
+        self.update_timestamp('last_updated', commit)
+
     def add_node(self, node, port, commit = True):
         """
         Add node to existing PCU.
index 522b8fd..25b3edd 100644 (file)
@@ -392,6 +392,9 @@ CREATE TABLE nodes (
     -- Timestamps
     date_created timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
     last_updated timestamp without time zone NOT NULL DEFAULT CURRENT_TIMESTAMP,
+    last_download timestamp without time zone,
+    last_pcu_reboot timestamp without time zone,
+    last_pcu_confirmation timestamp without time zone,
     last_contact timestamp without time zone   
 ) WITH OIDS;
 CREATE INDEX nodes_hostname_idx ON nodes (hostname);
@@ -457,7 +460,8 @@ CREATE TABLE interfaces (
     dns1 text,                                         -- Primary DNS server
     dns2 text,                                         -- Secondary DNS server
     bwlimit integer,                                   -- Bandwidth limit in bps
-    hostname text                                      -- Hostname of this interface
+    hostname text,                                     -- Hostname of this interface
+    last_updated timestamp without time zone -- When the interface was last updated
 ) WITH OIDS;
 CREATE INDEX interfaces_node_id_idx ON interfaces (node_id);
 
@@ -523,6 +527,7 @@ interfaces.dns1,
 interfaces.dns2,
 interfaces.bwlimit,
 interfaces.hostname,
+CAST(date_part('epoch', interfaces.last_updated) AS bigint) AS last_updated,
 COALESCE((SELECT interface_tag_ids FROM interface_tags WHERE interface_tags.interface_id = interfaces.interface_id), '{}') AS interface_tag_ids
 FROM interfaces;
 
@@ -662,6 +667,7 @@ CREATE TABLE pcus (
     username text,                                     -- Username, if applicable
     "password" text,                                   -- Password, if applicable
     model text,                                                -- Model, e.g. BayTech or iPal
+    last_updated timestamp without time zone,
     notes text                                         -- Random notes
 ) WITH OIDS;
 CREATE INDEX pcus_site_id_idx ON pcus (site_id);
@@ -1165,6 +1171,10 @@ 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,  
+CAST(date_part('epoch', nodes.last_boot) AS bigint) AS last_boot,  
+CAST(date_part('epoch', nodes.last_download) AS bigint) AS last_download,  
+CAST(date_part('epoch', nodes.last_pcu_reboot) AS bigint) AS last_pcu_reboot,  
+CAST(date_part('epoch', nodes.last_pcu_confirmation) AS bigint) AS last_pcu_confirmation,  
 peer_node.peer_id,
 peer_node.peer_node_id,
 COALESCE((SELECT interface_ids FROM node_interfaces 
@@ -1222,13 +1232,24 @@ AS nodegroup_ids
 FROM conf_files;
 
 --------------------------------------------------------------------------------
+DROP VIEW view_pcus;
 CREATE OR REPLACE VIEW view_pcus AS
 SELECT
-pcus.*,
+pcus.pcu_id,
+pcus.site_id,
+pcus.hostname,
+pcus.ip,
+pcus.protocol,
+pcus.username,
+pcus.password,
+pcus.model,
+pcus.notes,
+CAST(date_part('epoch', pcus.last_updated) AS bigint) AS last_updated,
 COALESCE((SELECT node_ids FROM pcu_nodes WHERE pcu_nodes.pcu_id = pcus.pcu_id), '{}') AS node_ids,
 COALESCE((SELECT ports FROM pcu_nodes WHERE pcu_nodes.pcu_id = pcus.pcu_id), '{}') AS ports
 FROM pcus;
 
+
 --------------------------------------------------------------------------------
 CREATE OR REPLACE VIEW view_sites AS
 SELECT