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