From: Stephen Soltesz Date: Fri, 2 Jul 2010 23:56:48 +0000 (-0400) Subject: Add timestamps to Nodes, PCUs and Interfaces to make concrete X-Git-Tag: plcapi-5.0-13~6 X-Git-Url: http://git.onelab.eu/?p=plcapi.git;a=commitdiff_plain;h=5d8a25ab329d0c44d34645b0ddeacba2f02331eb Add timestamps to Nodes, PCUs and Interfaces to make concrete 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. --- diff --git a/PLC/Interfaces.py b/PLC/Interfaces.py index 46b6f40..2ea645f 100644 --- a/PLC/Interfaces.py +++ b/PLC/Interfaces.py @@ -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 diff --git a/PLC/Methods/BootUpdateNode.py b/PLC/Methods/BootUpdateNode.py index f89bf27..ca18886 100644 --- a/PLC/Methods/BootUpdateNode.py +++ b/PLC/Methods/BootUpdateNode.py @@ -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) diff --git a/PLC/Methods/GetBootMedium.py b/PLC/Methods/GetBootMedium.py index 848796d..53f68a1 100644 --- a/PLC/Methods/GetBootMedium.py +++ b/PLC/Methods/GetBootMedium.py @@ -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 diff --git a/PLC/Methods/RebootNodeWithPCU.py b/PLC/Methods/RebootNodeWithPCU.py index 2a31e2b..88c2225 100644 --- a/PLC/Methods/RebootNodeWithPCU.py +++ b/PLC/Methods/RebootNodeWithPCU.py @@ -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" diff --git a/PLC/Methods/UpdateInterface.py b/PLC/Methods/UpdateInterface.py index 966128a..ae32770 100644 --- a/PLC/Methods/UpdateInterface.py +++ b/PLC/Methods/UpdateInterface.py @@ -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(): diff --git a/PLC/Methods/UpdatePCU.py b/PLC/Methods/UpdatePCU.py index 2a1fe3b..039400b 100644 --- a/PLC/Methods/UpdatePCU.py +++ b/PLC/Methods/UpdatePCU.py @@ -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 diff --git a/PLC/Nodes.py b/PLC/Nodes.py index 9cf0edf..6b799c1 100644 --- a/PLC/Nodes.py +++ b/PLC/Nodes.py @@ -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 diff --git a/PLC/PCUs.py b/PLC/PCUs.py index 0bcaa67..98d5be3 100644 --- a/PLC/PCUs.py +++ b/PLC/PCUs.py @@ -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. diff --git a/planetlab5.sql b/planetlab5.sql index 522b8fd..25b3edd 100644 --- a/planetlab5.sql +++ b/planetlab5.sql @@ -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