Package buildbot :: Package db :: Module state
[frames] | no frames]

Source Code for Module buildbot.db.state

  1  # This file is part of Buildbot.  Buildbot is free software: you can 
  2  # redistribute it and/or modify it under the terms of the GNU General Public 
  3  # License as published by the Free Software Foundation, version 2. 
  4  # 
  5  # This program is distributed in the hope that it will be useful, but WITHOUT 
  6  # ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS 
  7  # FOR A PARTICULAR PURPOSE.  See the GNU General Public License for more 
  8  # details. 
  9  # 
 10  # You should have received a copy of the GNU General Public License along with 
 11  # this program; if not, write to the Free Software Foundation, Inc., 51 
 12  # Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA. 
 13  # 
 14  # Copyright Buildbot Team Members 
 15   
 16  from buildbot.util import json 
 17  import sqlalchemy as sa 
 18  import sqlalchemy.exc 
 19  from buildbot.db import base 
20 21 -class _IdNotFoundError(Exception):
22 pass # used internally
23
24 -class ObjDict(dict):
25 pass
26
27 -class StateConnectorComponent(base.DBConnectorComponent):
28 """ 29 A DBConnectorComponent to handle maintaining arbitrary key/value state for 30 Buildbot objects. Objects are identified by their (user-visible) name and 31 their class. This allows e.g., a 'nightly_smoketest' object of class 32 NightlyScheduler to maintain its state even if it moves between masters, 33 but avoids cross-contaminating state between different classes. 34 35 Note that the class is not interpreted literally, and can be any string 36 that will uniquely identify the class for the object; if classes are 37 renamed, they can continue to use the old names. 38 """ 39
40 - def getObjectId(self, name, class_name):
41 """ 42 Get the object ID for this combination of a name and a class. This 43 will add a row to the 'objects' table if none exists already. 44 45 @param name: name of the object 46 @param class_name: object class name 47 @returns: the objectid, via a Deferred. 48 """ 49 # defer to a cached metho that only takes one parameter (a tuple) 50 return self._getObjectId((name, class_name) 51 ).addCallback(lambda objdict : objdict['id'])
52 53 @base.cached('objectids')
54 - def _getObjectId(self, name_class_name_tuple):
55 """ 56 Cache-compatible implementation of L{_getObjectId}, taking a single 57 parameter and returning a weakref-able value (a list). 58 """ 59 name, class_name = name_class_name_tuple 60 def thd(conn): 61 objects_tbl = self.db.model.objects 62 63 def select(): 64 q = sa.select([ objects_tbl.c.id ], 65 whereclause=((objects_tbl.c.name == name) 66 & (objects_tbl.c.class_name == class_name))) 67 res = conn.execute(q) 68 row = res.fetchone() 69 res.close() 70 if not row: 71 raise _IdNotFoundError 72 return row.id
73 74 def insert(): 75 res = conn.execute(objects_tbl.insert(), 76 name=name, 77 class_name=class_name) 78 return res.inserted_primary_key[0]
79 80 # we want to try selecting, then inserting, but if the insert fails 81 # then try selecting again. We include an invocation of a hook 82 # method to allow tests to exercise this particular behavior 83 try: 84 return ObjDict(id=select()) 85 except _IdNotFoundError: 86 pass 87 88 self._test_timing_hook(conn) 89 90 try: 91 return ObjDict(id=insert()) 92 except (sqlalchemy.exc.IntegrityError, 93 sqlalchemy.exc.ProgrammingError): 94 pass 95 96 return ObjDict(id=select()) 97 98 return self.db.pool.do(thd) 99
100 - class Thunk: pass
101 - def getState(self, objectid, name, default=Thunk):
102 """ 103 Get the state value for C{name} for the object with id C{objectid}. 104 105 @param objectid: objectid on which the state should be checked 106 @param name: name of the value to retrieve 107 @param default: (optional) value to return if C{name} is not present 108 @returns: state value via a Deferred 109 @raises KeyError: if C{name} is not present and no default is given 110 @raises TypeError: if JSON parsing fails 111 """ 112 113 def thd(conn): 114 object_state_tbl = self.db.model.object_state 115 q = sa.select([ object_state_tbl.c.value_json ], 116 whereclause=((object_state_tbl.c.objectid == objectid) 117 & (object_state_tbl.c.name == name))) 118 res = conn.execute(q) 119 row = res.fetchone() 120 res.close() 121 122 if not row: 123 if default is self.Thunk: 124 raise KeyError("no such state value '%s' for object %d" % 125 (name, objectid)) 126 return default 127 try: 128 return json.loads(row.value_json) 129 except: 130 raise TypeError("JSON error loading state value '%s' for %d" % 131 (name, objectid))
132 return self.db.pool.do(thd) 133
134 - def setState(self, objectid, name, value):
135 """ 136 Set the state value for C{name} for the object with id C{objectid}, 137 overwriting any existing value. 138 139 @param objectid: the objectid for which the state should be changed 140 @param name: the name of the value to change 141 @param value: the value to set - must be a JSONable object 142 @param returns: Deferred 143 @raises TypeError: if JSONification fails 144 """ 145 def thd(conn): 146 object_state_tbl = self.db.model.object_state 147 148 try: 149 value_json = json.dumps(value) 150 except: 151 raise TypeError("Error encoding JSON for %r" % (value,)) 152 153 def update(): 154 q = object_state_tbl.update( 155 whereclause=((object_state_tbl.c.objectid == objectid) 156 & (object_state_tbl.c.name == name))) 157 res = conn.execute(q, value_json=value_json) 158 159 # check whether that worked 160 return res.rowcount > 0
161 162 def insert(): 163 conn.execute(object_state_tbl.insert(), 164 objectid=objectid, 165 name=name, 166 value_json=value_json) 167 168 # try updating; if that fails, try inserting; if that fails, then 169 # we raced with another instance to insert, so let that instance 170 # win. 171 172 if update(): 173 return 174 175 self._test_timing_hook(conn) 176 177 try: 178 insert() 179 except (sqlalchemy.exc.IntegrityError, sqlalchemy.exc.ProgrammingError): 180 pass # someone beat us to it - oh well 181 182 return self.db.pool.do(thd) 183
184 - def _test_timing_hook(self, conn):
185 # called so tests can simulate another process inserting a database row 186 # at an inopportune moment 187 pass
188