1   
  2   
  3   
  4   
  5   
  6   
  7   
  8   
  9   
 10   
 11   
 12   
 13   
 14   
 15   
 16  from buildbot.util import json 
 17  import sqlalchemy as sa 
 18  import sqlalchemy.exc 
 19  from buildbot.db import base 
 23   
 26   
 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   
 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           
 50          return self._getObjectId((name, class_name) 
 51                  ).addCallback(lambda objdict : objdict['id']) 
  52   
 53      @base.cached('objectids') 
 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               
 81               
 82               
 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   
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                   
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               
169               
170               
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  
181   
182          return self.db.pool.do(thd) 
183   
188